From 197bb1eb18b0611dc7b012ec780934cb0f4534a3 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Thu, 12 Sep 2019 16:04:36 -0400 Subject: [PATCH 01/19] show overview tab with main tabs --- .../components/navigation_menu/main_tabs.tsx | 16 ++++++++-------- .../navigation_menu/navigation_menu.tsx | 2 +- .../public/components/navigation_menu/tabs.tsx | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/components/navigation_menu/main_tabs.tsx b/x-pack/legacy/plugins/ml/public/components/navigation_menu/main_tabs.tsx index 1b2b3e61a27c4..19e0b34ad0c9c 100644 --- a/x-pack/legacy/plugins/ml/public/components/navigation_menu/main_tabs.tsx +++ b/x-pack/legacy/plugins/ml/public/components/navigation_menu/main_tabs.tsx @@ -23,13 +23,13 @@ interface Props { function getTabs(disableLinks: boolean): Tab[] { return [ - // { - // id: 'overview', - // name: i18n.translate('xpack.ml.navMenu.overviewTabLinkText', { - // defaultMessage: 'Overview', - // }), - // disabled: disableLinks, - // }, + { + id: 'overview', + name: i18n.translate('xpack.ml.navMenu.overviewTabLinkText', { + defaultMessage: 'Overview', + }), + disabled: disableLinks, + }, { id: 'anomaly_detection', name: i18n.translate('xpack.ml.navMenu.anomalyDetectionTabLinkText', { @@ -66,7 +66,7 @@ interface TabData { } const TAB_DATA: Record = { - // overview: { testSubject: 'mlTabOverview', pathId: 'overview' }, + overview: { testSubject: 'mlTabOverview', pathId: 'overview' }, anomaly_detection: { testSubject: 'mlMainTab anomalyDetection', pathId: 'jobs' }, data_frames: { testSubject: 'mlMainTab dataFrames' }, data_frame_analytics: { testSubject: 'mlMainTab dataFrameAnalytics' }, diff --git a/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu.tsx b/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu.tsx index d61972bc0d850..5961bd5e71873 100644 --- a/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu.tsx +++ b/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu.tsx @@ -18,7 +18,7 @@ export type TabId = string; type TabSupport = Record; const tabSupport: TabSupport = { - // overview: null, + overview: null, jobs: 'anomaly_detection', settings: 'anomaly_detection', data_frames: null, diff --git a/x-pack/legacy/plugins/ml/public/components/navigation_menu/tabs.tsx b/x-pack/legacy/plugins/ml/public/components/navigation_menu/tabs.tsx index 7a428877f8fd8..89ada7453c7c5 100644 --- a/x-pack/legacy/plugins/ml/public/components/navigation_menu/tabs.tsx +++ b/x-pack/legacy/plugins/ml/public/components/navigation_menu/tabs.tsx @@ -19,7 +19,7 @@ interface Props { export function getTabs(tabId: TabId, disableLinks: boolean): Tab[] { const TAB_MAP: Partial> = { - // overview: [], + overview: [], datavisualizer: [], data_frames: [], data_frame_analytics: [], @@ -59,7 +59,7 @@ export function getTabs(tabId: TabId, disableLinks: boolean): Tab[] { } enum TAB_TEST_SUBJECT { - // overview = 'mlOverview', + overview = 'mlOverview', jobs = 'mlSubTab jobManagement', explorer = 'mlSubTab anomalyExplorer', timeseriesexplorer = 'mlSubTab singleMetricViewer', From e5b450b361c1539604c4ffc1e2299a98b6429871 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Thu, 12 Sep 2019 16:07:21 -0400 Subject: [PATCH 02/19] wip:add overview dir with initial components --- x-pack/legacy/plugins/ml/public/app.js | 3 +- .../plugins/ml/public/overview/breadcrumbs.ts | 23 ++++++ .../overview/components/analytics_panel.tsx | 42 ++++++++++ .../components/anomaly_detection_panel.tsx | 66 +++++++++++++++ .../ml/public/overview/components/content.tsx | 25 ++++++ .../ml/public/overview/components/sidebar.tsx | 80 +++++++++++++++++++ .../plugins/ml/public/overview/directive.tsx | 28 +++++++ .../plugins/ml/public/overview/index.ts | 8 ++ .../ml/public/overview/overview_page.tsx | 32 ++++++++ .../plugins/ml/public/overview/route.ts | 26 ++++++ 10 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 x-pack/legacy/plugins/ml/public/overview/breadcrumbs.ts create mode 100644 x-pack/legacy/plugins/ml/public/overview/components/analytics_panel.tsx create mode 100644 x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel.tsx create mode 100644 x-pack/legacy/plugins/ml/public/overview/components/content.tsx create mode 100644 x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx create mode 100644 x-pack/legacy/plugins/ml/public/overview/directive.tsx create mode 100644 x-pack/legacy/plugins/ml/public/overview/index.ts create mode 100644 x-pack/legacy/plugins/ml/public/overview/overview_page.tsx create mode 100644 x-pack/legacy/plugins/ml/public/overview/route.ts diff --git a/x-pack/legacy/plugins/ml/public/app.js b/x-pack/legacy/plugins/ml/public/app.js index ca0427544e83a..6297e5debcb21 100644 --- a/x-pack/legacy/plugins/ml/public/app.js +++ b/x-pack/legacy/plugins/ml/public/app.js @@ -19,6 +19,7 @@ import 'plugins/ml/components/transition/transition'; import 'plugins/ml/components/modal/modal'; import 'plugins/ml/access_denied'; import 'plugins/ml/jobs'; +import 'plugins/ml/overview'; import 'plugins/ml/services/calendar_service'; import 'plugins/ml/components/messagebar'; import 'plugins/ml/data_frame'; @@ -43,5 +44,5 @@ if (typeof uiRoutes.enable === 'function') { uiRoutes .otherwise({ - redirectTo: '/jobs' + redirectTo: '/jobs' // TODO change to overview once we can always see it }); diff --git a/x-pack/legacy/plugins/ml/public/overview/breadcrumbs.ts b/x-pack/legacy/plugins/ml/public/overview/breadcrumbs.ts new file mode 100644 index 0000000000000..893ae5de450ad --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/breadcrumbs.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +// @ts-ignore +import { ML_BREADCRUMB } from '../breadcrumbs'; + +export function getOverviewBreadcrumbs() { + // Whilst top level nav menu with tabs remains, + // use root ML breadcrumb. + return [ + ML_BREADCRUMB, + { + text: i18n.translate('xpack.ml.overviewBreadcrumbs.overviewLabel', { + defaultMessage: 'Overview', + }), + href: '', + }, + ]; +} diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel.tsx new file mode 100644 index 0000000000000..ec51b189a524f --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment, useState, useEffect } from 'react'; +import { EuiButton, EuiEmptyPrompt, EuiLoadingSpinner, EuiPanel } from '@elastic/eui'; +// import { FormattedMessage } from '@kbn/i18n/react'; + +// Fetch jobs and determine what to show +export const AnalyticsPanel: FC = () => { + const [isLoading, setIsLoading] = useState(false); + const [jobs, setJobs] = useState([]); // Load jobs just once + + return ( + + {isLoading && }      + {isLoading === false && jobs.length === 0 && ( + Create your first analytics job} + body={ + +

+ Data frame analytics enable you to perform different analyses of your data and + annotate it with the results. As part of its output, data frame analytics appends + the results of the analysis to the source data. +

+
+ } + actions={ + + Create job + + } + /> + )} + {isLoading === false && jobs.length > 0 &&
BOB
} +
+ ); +}; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel.tsx new file mode 100644 index 0000000000000..36c29a6ef9683 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment, useState, useEffect } from 'react'; +import { EuiButton, EuiEmptyPrompt, EuiLoadingSpinner, EuiPanel } from '@elastic/eui'; +// import { metaData } from 'ui/metadata'; +// import { ml } from '../../services/ml_api_service'; +// import { FormattedMessage } from '@kbn/i18n/react'; + +const createJobLink = '#/jobs/new_job/step/index_or_search'; + +export const AnomalyDetectionPanel: FC = () => { + const [isLoading, setIsLoading] = useState(false); + const [jobs, setJobs] = useState([]); // Load jobs just once + + // const loadJobs = async () => { + // try { + // const jobsResult = await ml.jobs.jobsSummary([]); + // const jobsSummaryList = jobsResult.map((job: any) => { + // job.latestTimestampSortValue = job.latestTimestampMs || 0; + // return job; + // }); + // setIsLoading(false); + // setJobs(jobsSummaryList); + // } catch (e) { + // // error toast + // setIsLoading(false); + // } + // }; + + useEffect(() => { + // setIsLoading(true); + // loadJobs(); + }, []); + + return ( + + {isLoading && }    + {isLoading === false && jobs.length === 0 && ( + Create your first anomaly detection job} + body={ + +

+ Machine learning makes it easy to detect anomalies in time series data stored in + Elasticsearch. Track one metric from a single machine or hundreds of metrics across + thousands of machines. Start automatically spotting the anomalies hiding in your + data and resolve issues faster.               +

+
+ } + actions={ + + Create job + + } + /> + )} + {isLoading === false && jobs.length > 0 &&
Jobs display
}   +
+ ); +}; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/content.tsx b/x-pack/legacy/plugins/ml/public/overview/components/content.tsx new file mode 100644 index 0000000000000..456410d05d698 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/components/content.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +// import { FormattedMessage } from '@kbn/i18n/react'; +import { AnomalyDetectionPanel } from './anomaly_detection_panel'; +import { AnalyticsPanel } from './analytics_panel'; + +// Fetch jobs and determine what to show +export const OverviewContent: FC = () => ( + + + + + + + + + + +); diff --git a/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx b/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx new file mode 100644 index 0000000000000..d22d476e6d7fb --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { EuiFlexItem, EuiLink, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { metadata } from 'ui/metadata'; + +const createJobLink = '#/jobs/new_job/step/index_or_search'; +// metadata.branch corresponds to the version used in documentation links. +const docsLink = `https://www.elastic.co/guide/en/kibana/${metadata.branch}/xpack-ml.html`; +const feedbackLink = 'https://www.elastic.co/community/'; + +export const OverviewSideBar: FC = () => ( + + +

+ +

+

+ + + + ), + createJob: ( + + + + ), + }} + /> +

+

+ +

+

+ + + + ), + createJob: ( + + + + ), + }} + /> +

+
+
+); diff --git a/x-pack/legacy/plugins/ml/public/overview/directive.tsx b/x-pack/legacy/plugins/ml/public/overview/directive.tsx new file mode 100644 index 0000000000000..ecf2b11579984 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/directive.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import ReactDOM from 'react-dom'; +import React from 'react'; +// @ts-ignore +import { uiModules } from 'ui/modules'; +const module = uiModules.get('apps/ml', ['react']); + +import { OverviewPage } from './overview_page'; + +module.directive('mlOverview', function() { + return { + scope: {}, + restrict: 'E', + link: async (scope: ng.IScope, element: ng.IAugmentedJQuery) => { + ReactDOM.render(, element[0]); + + element.on('$destroy', () => { + ReactDOM.unmountComponentAtNode(element[0]); + scope.$destroy(); + }); + }, + }; +}); diff --git a/x-pack/legacy/plugins/ml/public/overview/index.ts b/x-pack/legacy/plugins/ml/public/overview/index.ts new file mode 100644 index 0000000000000..ac00eab1f2cdb --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import './route'; +import './directive'; diff --git a/x-pack/legacy/plugins/ml/public/overview/overview_page.tsx b/x-pack/legacy/plugins/ml/public/overview/overview_page.tsx new file mode 100644 index 0000000000000..9de454e12786b --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/overview_page.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment, FC } from 'react'; +import { EuiFlexGroup, EuiHorizontalRule, EuiPage, EuiPageBody, EuiTitle } from '@elastic/eui'; +import { NavigationMenu } from '../components/navigation_menu/navigation_menu'; +import { OverviewSideBar } from './components/sidebar'; +import { OverviewContent } from './components/content'; +// import { i18n } from '@kbn/i18n'; + +export const OverviewPage: FC = () => { + return ( + + + + + +

Machine Learning

+
+ + + + + +
+
+
+ ); +}; diff --git a/x-pack/legacy/plugins/ml/public/overview/route.ts b/x-pack/legacy/plugins/ml/public/overview/route.ts new file mode 100644 index 0000000000000..46de3ab28f8a0 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/route.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import uiRoutes from 'ui/routes'; +// import React from 'react'; +// import { render, unmountComponentAtNode } from 'react-dom'; +// @ts-ignore no declaration module +// @ts-ignore no declaration module +import { checkFullLicense } from '../license/check_license'; +import { checkGetJobsPrivilege } from '../privilege/check_privilege'; +import { getOverviewBreadcrumbs } from './breadcrumbs'; +import './directive'; + +const template = ``; + +uiRoutes.when('/overview/?', { + template, + k7Breadcrumbs: getOverviewBreadcrumbs, + resolve: { + CheckLicense: checkFullLicense, + privileges: checkGetJobsPrivilege, + }, +}); From 8844b7aea564fb33caa9dba90da4005d6226d9b5 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Fri, 13 Sep 2019 14:16:00 -0400 Subject: [PATCH 03/19] convert and move AnalyticsTable and types to MlInMemoryTable components dir --- .../components/ml_in_memory_table/index.ts | 8 ++++ .../ml_in_memory_table.tsx} | 5 ++- .../components/ml_in_memory_table/types.ts} | 0 ...navigation_menu_react_wrapper_directive.js | 2 - .../source_index_preview.tsx | 2 +- .../components/step_define/pivot_preview.tsx | 2 +- .../components/transform_list/columns.tsx | 2 +- .../expanded_row_preview_pane.tsx | 2 +- .../transform_list/transform_list.tsx | 2 +- .../transform_list/transform_table.tsx | 2 +- .../components/exploration/exploration.tsx | 2 +- .../exploration/use_explore_data.ts | 2 +- .../analytics_list/analytics_list.tsx | 16 +++---- .../overview/components/analytics_panel.tsx | 42 ------------------- .../components/analytics_panel/index.ts | 7 ++++ .../ml/public/overview/components/content.tsx | 2 +- 16 files changed, 35 insertions(+), 63 deletions(-) create mode 100644 x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/index.ts rename x-pack/legacy/plugins/ml/public/{data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_table.tsx => components/ml_in_memory_table/ml_in_memory_table.tsx} (93%) rename x-pack/legacy/plugins/ml/{common/types/eui/in_memory_table.ts => public/components/ml_in_memory_table/types.ts} (100%) delete mode 100644 x-pack/legacy/plugins/ml/public/overview/components/analytics_panel.tsx create mode 100644 x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/index.ts diff --git a/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/index.ts b/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/index.ts new file mode 100644 index 0000000000000..91bf31ea1e7ab --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ProgressBar, MlInMemoryTable } from './ml_in_memory_table'; +export * from './types'; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_table.tsx b/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/ml_in_memory_table.tsx similarity index 93% rename from x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_table.tsx rename to x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/ml_in_memory_table.tsx index 006aef70e5528..a4335e64fafbe 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_table.tsx +++ b/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/ml_in_memory_table.tsx @@ -71,9 +71,10 @@ const getInitialSorting = (columns: any, sorting: any) => { }; }; -import { MlInMemoryTable } from '../../../../../../common/types/eui/in_memory_table'; +// import { MlInMemoryTable as InMemoryTable } from '../../../common/types/eui/in_memory_table'; +import { MlInMemoryTable as InMemoryTable } from './types'; -export class AnalyticsTable extends MlInMemoryTable { +export class MlInMemoryTable extends InMemoryTable { static getDerivedStateFromProps(nextProps: any, prevState: any) { const derivedState = { ...prevState.prevProps, diff --git a/x-pack/legacy/plugins/ml/common/types/eui/in_memory_table.ts b/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/types.ts similarity index 100% rename from x-pack/legacy/plugins/ml/common/types/eui/in_memory_table.ts rename to x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/types.ts diff --git a/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu_react_wrapper_directive.js b/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu_react_wrapper_directive.js index dcdaec159cc0a..543f04b6a4125 100644 --- a/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu_react_wrapper_directive.js +++ b/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu_react_wrapper_directive.js @@ -11,8 +11,6 @@ import ReactDOM from 'react-dom'; import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml'); -import 'ui/directives/kbn_href'; - import { NavigationMenu } from './navigation_menu'; module.directive('mlNavMenu', function () { diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/source_index_preview/source_index_preview.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/source_index_preview/source_index_preview.tsx index c26ce59343f45..7ceb97816b609 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/source_index_preview/source_index_preview.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/source_index_preview/source_index_preview.tsx @@ -33,7 +33,7 @@ import { MlInMemoryTable, SortingPropType, SORT_DIRECTION, -} from '../../../../../../common/types/eui/in_memory_table'; +} from '../../../../../components/ml_in_memory_table'; import { KBN_FIELD_TYPES } from '../../../../../../common/constants/field_types'; import { Dictionary } from '../../../../../../common/types/common'; diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_define/pivot_preview.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_define/pivot_preview.tsx index 09b97219ecdfa..2764f7a444832 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_define/pivot_preview.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_define/pivot_preview.tsx @@ -25,7 +25,7 @@ import { ColumnType, MlInMemoryTable, SORT_DIRECTION, -} from '../../../../../../common/types/eui/in_memory_table'; +} from '../../../../../components/ml_in_memory_table'; import { dictionaryToArray } from '../../../../../../common/types/common'; import { ES_FIELD_TYPES } from '../../../../../../common/constants/field_types'; import { formatHumanReadableDateTimeSeconds } from '../../../../../util/date_utils'; diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/columns.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/columns.tsx index f22cfc091a948..6d92386180439 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/columns.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/columns.tsx @@ -22,7 +22,7 @@ import { ComputedColumnType, ExpanderColumnType, FieldDataColumnType, -} from '../../../../../../common/types/eui/in_memory_table'; +} from '../../../../../components/ml_in_memory_table'; import { getTransformProgress, diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/expanded_row_preview_pane.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/expanded_row_preview_pane.tsx index dd8de60eb0fdb..d410a60e1c58c 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/expanded_row_preview_pane.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/expanded_row_preview_pane.tsx @@ -11,7 +11,7 @@ import { SortDirection, SORT_DIRECTION, FieldDataColumnType, -} from '../../../../../../common/types/eui/in_memory_table'; +} from '../../../../../components/ml_in_memory_table'; import { ml } from '../../../../../services/ml_api_service'; diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.tsx index 838b9678978cb..bf2894fe7dc24 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_list.tsx @@ -22,7 +22,7 @@ import { OnTableChangeArg, SortDirection, SORT_DIRECTION, -} from '../../../../../../common/types/eui/in_memory_table'; +} from '../../../../../components/ml_in_memory_table'; import { DataFrameTransformId, diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_table.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_table.tsx index bfb4e86abe8b5..43aac6825cce2 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_table.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/transform_management/components/transform_list/transform_table.tsx @@ -11,7 +11,7 @@ import React, { Fragment } from 'react'; import { EuiProgress } from '@elastic/eui'; -import { MlInMemoryTable } from '../../../../../../common/types/eui/in_memory_table'; +import { MlInMemoryTable } from '../../../../../components/ml_in_memory_table'; // The built in loading progress bar of EuiInMemoryTable causes a full DOM replacement // of the table and doesn't play well with auto-refreshing. That's why we're displaying diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx index fa9e68d0b25d3..9b497b430c9e4 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx @@ -36,7 +36,7 @@ import { OnTableChangeArg, SortingPropType, SORT_DIRECTION, -} from '../../../../../../common/types/eui/in_memory_table'; +} from '../../../../../components/ml_in_memory_table'; import { useUiChromeContext } from '../../../../../contexts/ui/use_ui_chrome_context'; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/use_explore_data.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/use_explore_data.ts index 800640f01daa9..ee86156c50d4d 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/use_explore_data.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/use_explore_data.ts @@ -8,7 +8,7 @@ import React, { useEffect, useState } from 'react'; import { SearchResponse } from 'elasticsearch'; -import { SortDirection, SORT_DIRECTION } from '../../../../../../common/types/eui/in_memory_table'; +import { SortDirection, SORT_DIRECTION } from '../../../../../components/ml_in_memory_table'; import { ml } from '../../../../../services/ml_api_service'; import { getNestedProperty } from '../../../../../util/object_utils'; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx index de87ca71e5181..45f53691eab8f 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx @@ -15,12 +15,6 @@ import { EuiEmptyPrompt, } from '@elastic/eui'; -import { - OnTableChangeArg, - SortDirection, - SORT_DIRECTION, -} from '../../../../../../common/types/eui/in_memory_table'; - import { DataFrameAnalyticsId, useRefreshAnalyticsList } from '../../../../common'; import { checkPermission } from '../../../../../privilege/check_privilege'; import { getTaskStateBadge } from './columns'; @@ -38,7 +32,13 @@ import { ActionDispatchers } from '../../hooks/use_create_analytics_form/actions import { getAnalyticsFactory } from '../../services/analytics_service'; import { getColumns } from './columns'; import { ExpandedRow } from './expanded_row'; -import { ProgressBar, AnalyticsTable } from './analytics_table'; +import { + ProgressBar, + MlInMemoryTable, + OnTableChangeArg, + SortDirection, + SORT_DIRECTION, +} from '../../../../../components/ml_in_memory_table'; function getItemIdToExpandedRowMap( itemIds: DataFrameAnalyticsId[], @@ -310,7 +310,7 @@ export const DataFrameAnalyticsList: FC = ({ return ( - { - const [isLoading, setIsLoading] = useState(false); - const [jobs, setJobs] = useState([]); // Load jobs just once - - return ( - - {isLoading && }      - {isLoading === false && jobs.length === 0 && ( - Create your first analytics job} - body={ - -

- Data frame analytics enable you to perform different analyses of your data and - annotate it with the results. As part of its output, data frame analytics appends - the results of the analysis to the source data. -

-
- } - actions={ - - Create job - - } - /> - )} - {isLoading === false && jobs.length > 0 &&
BOB
} -
- ); -}; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/index.ts b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/index.ts new file mode 100644 index 0000000000000..24bfc63b3da3a --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AnalyticsPanel } from './analytics_panel'; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/content.tsx b/x-pack/legacy/plugins/ml/public/overview/components/content.tsx index 456410d05d698..9731868954d66 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/content.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/content.tsx @@ -8,7 +8,7 @@ import React, { FC } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; // import { FormattedMessage } from '@kbn/i18n/react'; import { AnomalyDetectionPanel } from './anomaly_detection_panel'; -import { AnalyticsPanel } from './analytics_panel'; +import { AnalyticsPanel } from './analytics_panel/'; // Fetch jobs and determine what to show export const OverviewContent: FC = () => ( From 570e71ef3e4c40372bcaf1ffae54295b3536703b Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Fri, 13 Sep 2019 14:16:43 -0400 Subject: [PATCH 04/19] create analytics table for overview --- .../components/analytics_list/actions.tsx | 44 +++--- .../components/analytics_list/columns.tsx | 102 +++++++------- .../components/analytics_list/common.ts | 1 + .../analytics_panel/analytics_panel.tsx | 72 ++++++++++ .../components/analytics_panel/table.tsx | 130 ++++++++++++++++++ .../ml/public/overview/overview_page.tsx | 5 - 6 files changed, 278 insertions(+), 76 deletions(-) create mode 100644 x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx create mode 100644 x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx index d1e95acef7157..ebc9b7c6a4c1e 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx @@ -21,31 +21,33 @@ import { stopAnalytics } from '../../services/analytics_service'; import { StartAction } from './action_start'; import { DeleteAction } from './action_delete'; +export const ANALYTICS_VIEW_ACTION = { + isPrimary: true, + render: (item: DataFrameAnalyticsListRow) => { + return ( + (window.location.href = getResultsUrl(item.id))} + size="xs" + color="text" + iconType="visTable" + aria-label={i18n.translate('xpack.ml.dataframe.analyticsList.viewAriaLabel', { + defaultMessage: 'View', + })} + > + {i18n.translate('xpack.ml.dataframe.analyticsList.viewActionName', { + defaultMessage: 'View', + })} + + ); + }, +}; + export const getActions = () => { const canStartStopDataFrameAnalytics: boolean = checkPermission('canStartStopDataFrameAnalytics'); return [ - { - isPrimary: true, - render: (item: DataFrameAnalyticsListRow) => { - return ( - (window.location.href = getResultsUrl(item.id))} - size="xs" - color="text" - iconType="visTable" - aria-label={i18n.translate('xpack.ml.dataframe.analyticsList.viewAriaLabel', { - defaultMessage: 'View', - })} - > - {i18n.translate('xpack.ml.dataframe.analyticsList.viewActionName', { - defaultMessage: 'View', - })} - - ); - }, - }, + ANALYTICS_VIEW_ACTION, { render: (item: DataFrameAnalyticsListRow) => { if (!isDataFrameAnalyticsRunning(item.stats)) { diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx index 6c6f53383dfb8..2f95ee94a794a 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx @@ -58,6 +58,57 @@ export const getTaskStateBadge = ( ); }; +export const PROGRESS_COLUMN = { + name: i18n.translate('xpack.ml.dataframe.analyticsList.progress', { + defaultMessage: 'Progress', + }), + sortable: (item: DataFrameAnalyticsListRow) => getDataFrameAnalyticsProgress(item.stats), + truncateText: true, + render(item: DataFrameAnalyticsListRow) { + const progress = getDataFrameAnalyticsProgress(item.stats); + + if (progress === undefined) { + return null; + } + + // For now all analytics jobs are batch jobs. + const isBatchTransform = true; + + return ( + + {isBatchTransform && ( + + + + {progress}% + + + + {`${progress}%`} + + + )} + {!isBatchTransform && ( + + + {item.stats.state === DATA_FRAME_TASK_STATE.STARTED && ( + + )} + {item.stats.state === DATA_FRAME_TASK_STATE.STOPPED && ( + + )} + + +   + + + )} + + ); + }, + width: '100px', +}; + export const getColumns = ( expandedRowItemIds: DataFrameAnalyticsId[], setExpandedRowItemIds: React.Dispatch>, @@ -166,56 +217,7 @@ export const getColumns = ( width: '100px', }, */ - { - name: i18n.translate('xpack.ml.dataframe.analyticsList.progress', { - defaultMessage: 'Progress', - }), - sortable: (item: DataFrameAnalyticsListRow) => getDataFrameAnalyticsProgress(item.stats), - truncateText: true, - render(item: DataFrameAnalyticsListRow) { - const progress = getDataFrameAnalyticsProgress(item.stats); - - if (progress === undefined) { - return null; - } - - // For now all analytics jobs are batch jobs. - const isBatchTransform = true; - - return ( - - {isBatchTransform && ( - - - - {progress}% - - - - {`${progress}%`} - - - )} - {!isBatchTransform && ( - - - {item.stats.state === DATA_FRAME_TASK_STATE.STARTED && ( - - )} - {item.stats.state === DATA_FRAME_TASK_STATE.STOPPED && ( - - )} - - -   - - - )} - - ); - }, - width: '100px', - }, + PROGRESS_COLUMN, ]; if (isManagementTable === true) { diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts index 3ad493c9e2b2d..c8dd101af8ab1 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts @@ -94,6 +94,7 @@ export interface DataFrameAnalyticsListRow { export enum DataFrameAnalyticsListColumn { configDestIndex = 'config.dest.index', configSourceIndex = 'config.source.index', + configCreateTime = 'config.create_time', // Description attribute is not supported yet by API // description = 'config.description', id = 'id', diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx new file mode 100644 index 0000000000000..55e9e2fd42d17 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment, useState, useEffect } from 'react'; +import { EuiButton, EuiCallOut, EuiEmptyPrompt, EuiLoadingSpinner, EuiPanel } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { AnalyticsTable } from './table'; +import { getAnalyticsFactory } from '../../../data_frame_analytics/pages/analytics_management/services/analytics_service'; +import { DataFrameAnalyticsListRow } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common'; + +export const AnalyticsPanel: FC = () => { + const [analytics, setAnalytics] = useState([]); + const [errorMessage, setErrorMessage] = useState(undefined); + const [isInitialized, setIsInitialized] = useState(false); + + const getAnalytics = getAnalyticsFactory(setAnalytics, setErrorMessage, setIsInitialized, false); + + useEffect(() => { + getAnalytics(true); + }, []); + + // TODO: add refresh button + // const onRefresh = () => { + // setIsInitialized(false); + // getAnalytics(true); + // }; + + const errorDisplay = ( + + +
{JSON.stringify(errorMessage)}
+
+
+ ); + + return ( + + {typeof errorMessage !== 'undefined' && errorDisplay} + {isInitialized === false && }      + {isInitialized === true && analytics.length === 0 && ( + Create your first analytics job} + body={ + +

+ Data frame analytics enable you to perform different analyses of your data and + annotate it with the results. As part of its output, data frame analytics appends + the results of the analysis to the source data. +

+
+ } + actions={ + + Create job + + } + /> + )} + {isInitialized === true && analytics.length > 0 && } +
+ ); +}; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx new file mode 100644 index 0000000000000..56afee4b00eb1 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx @@ -0,0 +1,130 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment, useState } from 'react'; +import { EuiBadge } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { + MlInMemoryTable, + SortDirection, + SORT_DIRECTION, + OnTableChangeArg, +} from '../../../components/ml_in_memory_table'; +import { getAnalysisType } from '../../../data_frame_analytics/common/analytics'; +import { + DataFrameAnalyticsListColumn, + DataFrameAnalyticsListRow, +} from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common'; +import { + getTaskStateBadge, + PROGRESS_COLUMN, +} from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/columns'; +import { ANALYTICS_VIEW_ACTION } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/actions'; +import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils'; + +interface Props { + items: any[]; +} +export const AnalyticsTable: FC = ({ items }) => { + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(10); + + const [sortField, setSortField] = useState(DataFrameAnalyticsListColumn.id); + const [sortDirection, setSortDirection] = useState(SORT_DIRECTION.ASC); + + // id, type, status, progress, created time, view icon + const columns: any[] = [ + { + field: DataFrameAnalyticsListColumn.id, + name: 'ID', + sortable: true, + truncateText: true, + }, + { + name: i18n.translate('xpack.ml.overview.analyticsList.type', { defaultMessage: 'Type' }), + sortable: (item: DataFrameAnalyticsListRow) => getAnalysisType(item.config.analysis), + truncateText: true, + render(item: DataFrameAnalyticsListRow) { + return {getAnalysisType(item.config.analysis)}; + }, + width: '150px', + }, + { + name: i18n.translate('xpack.ml.overview.analyticsList.status', { defaultMessage: 'Status' }), + sortable: (item: DataFrameAnalyticsListRow) => item.stats.state, + truncateText: true, + render(item: DataFrameAnalyticsListRow) { + return getTaskStateBadge(item.stats.state, item.stats.reason); + }, + width: '100px', + }, + PROGRESS_COLUMN, + { + field: DataFrameAnalyticsListColumn.configCreateTime, + name: i18n.translate('xpack.ml.overview.createdTimeColumnName', { + defaultMessage: 'Creation time', + }), + dataType: 'date', + render: (time: number) => formatHumanReadableDateTimeSeconds(time), + textOnly: true, + sortable: true, + }, + { + name: i18n.translate('xpack.ml.overview.analyticsList.tableActionLabel', { + defaultMessage: 'Actions', + }), + actions: [ANALYTICS_VIEW_ACTION], + width: '200px', + }, + ]; + + const onTableChange = ({ + page = { index: 0, size: 10 }, + sort = { field: DataFrameAnalyticsListColumn.id, direction: SORT_DIRECTION.ASC }, + }: OnTableChangeArg) => { + const { index, size } = page; + setPageIndex(index); + setPageSize(size); + + const { field, direction } = sort; + setSortField(field); + setSortDirection(direction); + }; + + const pagination = { + initialPageIndex: pageIndex, + initialPageSize: pageSize, + totalItemCount: items.length, + pageSizeOptions: [10, 20, 50], + hidePerPageOptions: false, + }; + + const sorting = { + sort: { + field: sortField, + direction: sortDirection, + }, + }; + + return ( + + + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/overview/overview_page.tsx b/x-pack/legacy/plugins/ml/public/overview/overview_page.tsx index 9de454e12786b..03cfbfbeea0ca 100644 --- a/x-pack/legacy/plugins/ml/public/overview/overview_page.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/overview_page.tsx @@ -9,7 +9,6 @@ import { EuiFlexGroup, EuiHorizontalRule, EuiPage, EuiPageBody, EuiTitle } from import { NavigationMenu } from '../components/navigation_menu/navigation_menu'; import { OverviewSideBar } from './components/sidebar'; import { OverviewContent } from './components/content'; -// import { i18n } from '@kbn/i18n'; export const OverviewPage: FC = () => { return ( @@ -17,10 +16,6 @@ export const OverviewPage: FC = () => { - -

Machine Learning

-
- From deed41c2814728c4678657ceb7a0b033b1913ed8 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Sat, 14 Sep 2019 09:27:35 -0400 Subject: [PATCH 05/19] add stats bar to analytics panel --- .../ml/public/components/stats_bar/index.ts | 2 +- .../public/components/stats_bar/stats_bar.tsx | 19 ++-- .../plugins/ml/public/overview/_index.scss | 1 + .../ml/public/overview/components/_index.scss | 1 + .../analytics_panel/_analytics_panel.scss | 3 + .../analytics_panel/analytics_panel.tsx | 4 +- .../analytics_panel/analytics_stats_bar.tsx | 87 +++++++++++++++++++ .../components/analytics_panel/table.tsx | 22 ++++- 8 files changed, 127 insertions(+), 12 deletions(-) create mode 100644 x-pack/legacy/plugins/ml/public/overview/_index.scss create mode 100644 x-pack/legacy/plugins/ml/public/overview/components/_index.scss create mode 100644 x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/_analytics_panel.scss create mode 100644 x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_stats_bar.tsx diff --git a/x-pack/legacy/plugins/ml/public/components/stats_bar/index.ts b/x-pack/legacy/plugins/ml/public/components/stats_bar/index.ts index 4c781afe0a64c..3994d77780c04 100644 --- a/x-pack/legacy/plugins/ml/public/components/stats_bar/index.ts +++ b/x-pack/legacy/plugins/ml/public/components/stats_bar/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { StatsBar, TransformStatsBarStats } from './stats_bar'; +export { StatsBar, TransformStatsBarStats, AnalyticStatsBarStats } from './stats_bar'; diff --git a/x-pack/legacy/plugins/ml/public/components/stats_bar/stats_bar.tsx b/x-pack/legacy/plugins/ml/public/components/stats_bar/stats_bar.tsx index 0995b1e70df5d..08d8d29b4fb13 100644 --- a/x-pack/legacy/plugins/ml/public/components/stats_bar/stats_bar.tsx +++ b/x-pack/legacy/plugins/ml/public/components/stats_bar/stats_bar.tsx @@ -7,24 +7,29 @@ import React, { FC } from 'react'; import { Stat, StatsBarStat } from './stat'; -interface JobStatsBarStats { - activeNodes: StatsBarStat; +interface Stats { total: StatsBarStat; - open: StatsBarStat; failed: StatsBarStat; +} +interface JobStatsBarStats extends Stats { + activeNodes: StatsBarStat; + open: StatsBarStat; closed: StatsBarStat; activeDatafeeds: StatsBarStat; } -export interface TransformStatsBarStats { - total: StatsBarStat; +export interface TransformStatsBarStats extends Stats { batch: StatsBarStat; continuous: StatsBarStat; - failed: StatsBarStat; started: StatsBarStat; } -type StatsBarStats = TransformStatsBarStats | JobStatsBarStats; +export interface AnalyticStatsBarStats extends Stats { + started: StatsBarStat; + stopped: StatsBarStat; +} + +type StatsBarStats = TransformStatsBarStats | JobStatsBarStats | AnalyticStatsBarStats; type StatsKey = keyof StatsBarStats; interface StatsBarProps { diff --git a/x-pack/legacy/plugins/ml/public/overview/_index.scss b/x-pack/legacy/plugins/ml/public/overview/_index.scss new file mode 100644 index 0000000000000..192091fb04e3c --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/_index.scss @@ -0,0 +1 @@ +@import './components/index'; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/_index.scss b/x-pack/legacy/plugins/ml/public/overview/components/_index.scss new file mode 100644 index 0000000000000..cdf89f6c49871 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/components/_index.scss @@ -0,0 +1 @@ +@import './analytics_panel/analytics_panel'; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/_analytics_panel.scss b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/_analytics_panel.scss new file mode 100644 index 0000000000000..201529223832f --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/_analytics_panel.scss @@ -0,0 +1,3 @@ +.mlOverviewAnalyticsPanel { + padding-top: 0; +} diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx index 55e9e2fd42d17..8a9b4d60350ad 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { AnalyticsTable } from './table'; import { getAnalyticsFactory } from '../../../data_frame_analytics/pages/analytics_management/services/analytics_service'; import { DataFrameAnalyticsListRow } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common'; - +// TODO: panels can be smaller when empty export const AnalyticsPanel: FC = () => { const [analytics, setAnalytics] = useState([]); const [errorMessage, setErrorMessage] = useState(undefined); @@ -43,7 +43,7 @@ export const AnalyticsPanel: FC = () => { ); return ( - + {typeof errorMessage !== 'undefined' && errorDisplay} {isInitialized === false && }      {isInitialized === true && analytics.length === 0 && ( diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_stats_bar.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_stats_bar.tsx new file mode 100644 index 0000000000000..3fda20c25531b --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_stats_bar.tsx @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { i18n } from '@kbn/i18n'; +import { StatsBar, AnalyticStatsBarStats } from '../../../components/stats_bar'; +import { + DataFrameAnalyticsListRow, + DATA_FRAME_TASK_STATE, +} from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common'; + +function createAnalyticsStats(analyticsList: any[]) { + const analyticsStats = { + total: { + label: i18n.translate('xpack.ml.overview.statsBar.totalAnalyticsLabel', { + defaultMessage: 'Total analytics jobs', + }), + value: 0, + show: true, + }, + started: { + label: i18n.translate('xpack.ml.overview.statsBar.startedAnalyticsLabel', { + defaultMessage: 'Started', + }), + value: 0, + show: true, + }, + stopped: { + label: i18n.translate('xpack.ml.overview.statsBar.stoppedAnalyticsLabel', { + defaultMessage: 'Stopped', + }), + value: 0, + show: true, + }, + failed: { + label: i18n.translate('xpack.ml.overview.statsBar.failedAnalyticsLabel', { + defaultMessage: 'Failed', + }), + value: 0, + show: false, + }, + }; + + if (analyticsList === undefined) { + return analyticsStats; + } + + let failedJobs = 0; + let startedJobs = 0; + let stoppedJobs = 0; + + analyticsList.forEach(job => { + if (job.stats.state === DATA_FRAME_TASK_STATE.FAILED) { + failedJobs++; + } else if (job.stats.state === DATA_FRAME_TASK_STATE.STARTED) { + startedJobs++; + } else if (job.stats.state === DATA_FRAME_TASK_STATE.STOPPED) { + stoppedJobs++; + } + }); + + analyticsStats.total.value = analyticsList.length; + analyticsStats.started.value = startedJobs; + analyticsStats.stopped.value = stoppedJobs; + + if (failedJobs !== 0) { + analyticsStats.failed.value = failedJobs; + analyticsStats.failed.show = true; + } else { + analyticsStats.failed.show = false; + } + + return analyticsStats; +} + +interface Props { + analyticsList: DataFrameAnalyticsListRow[]; +} + +export const AnalyticsStatsBar: FC = ({ analyticsList }) => { + const analyticsStats: AnalyticStatsBarStats = createAnalyticsStats(analyticsList); + + return ; +}; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx index 56afee4b00eb1..70e699e221064 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx @@ -5,7 +5,7 @@ */ import React, { FC, Fragment, useState } from 'react'; -import { EuiBadge } from '@elastic/eui'; +import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { MlInMemoryTable, @@ -24,6 +24,7 @@ import { } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/columns'; import { ANALYTICS_VIEW_ACTION } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/actions'; import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils'; +import { AnalyticsStatsBar } from './analytics_stats_bar'; interface Props { items: any[]; @@ -42,6 +43,7 @@ export const AnalyticsTable: FC = ({ items }) => { name: 'ID', sortable: true, truncateText: true, + width: '20%', }, { name: i18n.translate('xpack.ml.overview.analyticsList.type', { defaultMessage: 'Type' }), @@ -71,13 +73,14 @@ export const AnalyticsTable: FC = ({ items }) => { render: (time: number) => formatHumanReadableDateTimeSeconds(time), textOnly: true, sortable: true, + width: '20%', }, { name: i18n.translate('xpack.ml.overview.analyticsList.tableActionLabel', { defaultMessage: 'Actions', }), actions: [ANALYTICS_VIEW_ACTION], - width: '200px', + width: '100px', }, ]; @@ -111,6 +114,21 @@ export const AnalyticsTable: FC = ({ items }) => { return ( + + + +

+ {i18n.translate('xpack.ml.overview.analyticsPanelTitle', { + defaultMessage: 'Analytics', + })} +

+
+
+ + + +
+ Date: Sat, 14 Sep 2019 11:46:38 -0400 Subject: [PATCH 06/19] wip: adds anomaly detection table --- .../analytics_panel/analytics_panel.tsx | 2 +- .../components/analytics_panel/table.tsx | 2 +- .../components/anomaly_detection_panel.tsx | 66 ------- .../anomaly_detection_panel.tsx | 93 ++++++++++ .../anomaly_detection_panel/index.ts | 7 + .../anomaly_detection_panel/table.tsx | 168 ++++++++++++++++++ .../anomaly_detection_panel/utils.ts | 40 +++++ 7 files changed, 310 insertions(+), 68 deletions(-) delete mode 100644 x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel.tsx create mode 100644 x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx create mode 100644 x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/index.ts create mode 100644 x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx create mode 100644 x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx index 8a9b4d60350ad..8588a03f4a4a1 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx @@ -31,7 +31,7 @@ export const AnalyticsPanel: FC = () => { const errorDisplay = ( = ({ items }) => { const columns: any[] = [ { field: DataFrameAnalyticsListColumn.id, - name: 'ID', + name: i18n.translate('xpack.ml.overview.analyticsList.id', { defaultMessage: 'ID' }), sortable: true, truncateText: true, width: '20%', diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel.tsx deleted file mode 100644 index 36c29a6ef9683..0000000000000 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { FC, Fragment, useState, useEffect } from 'react'; -import { EuiButton, EuiEmptyPrompt, EuiLoadingSpinner, EuiPanel } from '@elastic/eui'; -// import { metaData } from 'ui/metadata'; -// import { ml } from '../../services/ml_api_service'; -// import { FormattedMessage } from '@kbn/i18n/react'; - -const createJobLink = '#/jobs/new_job/step/index_or_search'; - -export const AnomalyDetectionPanel: FC = () => { - const [isLoading, setIsLoading] = useState(false); - const [jobs, setJobs] = useState([]); // Load jobs just once - - // const loadJobs = async () => { - // try { - // const jobsResult = await ml.jobs.jobsSummary([]); - // const jobsSummaryList = jobsResult.map((job: any) => { - // job.latestTimestampSortValue = job.latestTimestampMs || 0; - // return job; - // }); - // setIsLoading(false); - // setJobs(jobsSummaryList); - // } catch (e) { - // // error toast - // setIsLoading(false); - // } - // }; - - useEffect(() => { - // setIsLoading(true); - // loadJobs(); - }, []); - - return ( - - {isLoading && }    - {isLoading === false && jobs.length === 0 && ( - Create your first anomaly detection job} - body={ - -

- Machine learning makes it easy to detect anomalies in time series data stored in - Elasticsearch. Track one metric from a single machine or hundreds of metrics across - thousands of machines. Start automatically spotting the anomalies hiding in your - data and resolve issues faster.               -

-
- } - actions={ - - Create job - - } - /> - )} - {isLoading === false && jobs.length > 0 &&
Jobs display
}   -
- ); -}; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx new file mode 100644 index 0000000000000..fbf8e1f9d9d7e --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment, useState, useEffect } from 'react'; +import { EuiButton, EuiCallOut, EuiEmptyPrompt, EuiLoadingSpinner, EuiPanel } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { AnomalyDetectionTable } from './table'; +// import { metaData } from 'ui/metadata'; +import { ml } from '../../../services/ml_api_service'; +import { getGroupsFromJobs, getStatsBarData } from './utils'; + +const createJobLink = '#/jobs/new_job/step/index_or_search'; + +// jobsSummary to get: stats bar info, jobs in group, latest timestamp, docs_processed +export const AnomalyDetectionPanel: FC = () => { + const [isLoading, setIsLoading] = useState(false); + const [groupsList, setGroupsList] = useState([]); + const [statsBarData, setStatsBarData] = useState(undefined); + const [errorMessage, setErrorMessage] = useState(undefined); + + const loadJobs = async () => { + try { + const jobsResult = await ml.jobs.jobsSummary([]); + const jobsSummaryList = jobsResult.map((job: any) => { + job.latestTimestampSortValue = job.latestTimestampMs || 0; + return job; + }); + const groups = getGroupsFromJobs(jobsSummaryList); + // const stats = getStatsBarData(jobsSummaryList); + setIsLoading(false); + setErrorMessage(undefined); + setGroupsList(groups); + // setStatsBarData(stats); + } catch (e) { + setErrorMessage(e); + setIsLoading(false); + } + }; + + useEffect(() => { + setIsLoading(true); + loadJobs(); + }, []); + + const errorDisplay = ( + + +
{JSON.stringify(errorMessage)}
+
+
+ ); + + return ( + + {typeof errorMessage !== 'undefined' && errorDisplay} + {isLoading && }    + {isLoading === false && typeof errorMessage === 'undefined' && groupsList.length === 0 && ( + Create your first anomaly detection job} + body={ + +

+ Machine learning makes it easy to detect anomalies in time series data stored in + Elasticsearch. Track one metric from a single machine or hundreds of metrics across + thousands of machines. Start automatically spotting the anomalies hiding in your + data and resolve issues faster.               +

+
+ } + actions={ + + Create job + + } + /> + )} + {isLoading === false && typeof errorMessage === 'undefined' && groupsList.length > 0 && ( + + )} +    +
+ ); +}; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/index.ts b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/index.ts new file mode 100644 index 0000000000000..1ccbd58b67295 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AnomalyDetectionPanel } from './anomaly_detection_panel'; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx new file mode 100644 index 0000000000000..95475638cc395 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx @@ -0,0 +1,168 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, Fragment, useState } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { + MlInMemoryTable, + SortDirection, + SORT_DIRECTION, + OnTableChangeArg, +} from '../../../components/ml_in_memory_table'; +import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils'; +// import { AnomalyDetectionStatsBar } from './anomaly_detection_stats_bar'; + +// interface AnomalyDetectionListColumns { +// id: string; +// max_anomaly_score: number; +// num_jobs: number; +// latest_timestamp: number; +// docs_processed: number; +// } + +// Used to pass on attribute names to table columns +export enum AnomalyDetectionListColumns { + id = 'id', + maxAnomalyScore = 'max_anomaly_score', + jobIds = 'jobIds', + latestTimestamp = 'latest_timestamp', + docsProcessed = 'docs_processed', +} + +interface Props { + items: AnomalyDetectionListColumns[]; + statsBarData: any; // TODO: pull in from statsbar types +} +export const AnomalyDetectionTable: FC = ({ items }) => { + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(10); + + const [sortField, setSortField] = useState(AnomalyDetectionListColumns.id); + const [sortDirection, setSortDirection] = useState(SORT_DIRECTION.ASC); + + // columns: group, max anomaly, jobs in group, latest timestamp, docs processed, action to explorer + // item: { id: group-id, max_anomaly_score: , num_jobs: num, latest_timestamp: 123456, docs_processed: num } + const columns: any[] = [ + { + field: AnomalyDetectionListColumns.id, + name: i18n.translate('xpack.ml.overview.anomalyDetectionList.id', { + defaultMessage: 'Group ID', + }), + sortable: true, + truncateText: true, + width: '20%', + }, + { + field: AnomalyDetectionListColumns.maxAnomalyScore, + name: i18n.translate('xpack.ml.overview.anomalyDetectionList.maxScore', { + defaultMessage: 'Max score', + }), + sortable: true, + truncateText: true, + width: '150px', + }, + { + field: AnomalyDetectionListColumns.jobIds, + name: i18n.translate('xpack.ml.overview.anomalyDetectionList.numJobs', { + defaultMessage: 'Jobs in group', + }), + render: jobIds => jobIds.length, + sortable: true, + truncateText: true, + width: '100px', + }, + { + field: AnomalyDetectionListColumns.latestTimestamp, + name: i18n.translate('xpack.ml.overview.anomalyDetectionList.latestTimestamp', { + defaultMessage: 'Latest timestamp', + }), + dataType: 'date', + render: (time: number) => formatHumanReadableDateTimeSeconds(time), + textOnly: true, + sortable: true, + width: '20%', + }, + { + field: AnomalyDetectionListColumns.docsProcessed, + name: i18n.translate('xpack.ml.overview.anomalyDetectionList.docsProcessed', { + defaultMessage: 'Docs processed', + }), + textOnly: true, + sortable: true, + width: '20%', + }, + // { + // name: i18n.translate('xpack.ml.overview.anomalyDetectionList.tableActionLabel', { + // defaultMessage: 'Actions', + // }), + // actions: [ANALYTICS_VIEW_ACTION], + // width: '100px', + // }, + ]; + + const onTableChange = ({ + page = { index: 0, size: 10 }, + sort = { field: AnomalyDetectionListColumns.id, direction: SORT_DIRECTION.ASC }, + }: OnTableChangeArg) => { + const { index, size } = page; + setPageIndex(index); + setPageSize(size); + + const { field, direction } = sort; + setSortField(field); + setSortDirection(direction); + }; + + const pagination = { + initialPageIndex: pageIndex, + initialPageSize: pageSize, + totalItemCount: items.length, + pageSizeOptions: [10, 20, 50], + hidePerPageOptions: false, + }; + + const sorting = { + sort: { + field: sortField, + direction: sortDirection, + }, + }; + + return ( + + + + +

+ {i18n.translate('xpack.ml.overview.anomalyDetectionPanelTitle', { + defaultMessage: 'Anomaly Detection', + })} +

+
+
+ {/* + + */} +
+ + +
+ ); +}; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts new file mode 100644 index 0000000000000..b802dc744dde5 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// { id: group-id, max_anomaly_score: , jobIds: num, latest_timestamp: 123456, docs_processed: num } +export function getGroupsFromJobs(jobs: any) { + const groups: any = {}; + + jobs.forEach((job: any) => { + // Organize job by group + if (job.groups !== undefined) { + job.groups.forEach((g: any) => { + if (groups[g] === undefined) { + groups[g] = { + id: g, + jobIds: [job.id], + docs_processed: job.processed_record_count, + latest_timestamp: 0, + max_anomaly_score: null, + }; + } else { + groups[g].jobIds.push(job.id); + groups[g].docs_processed += job.processed_record_count; + // if incoming job latest timestamp is greater than the last saved one, replace it + if (job.latestTimestampMs > groups[g].latest_timestamp) { + groups[g].latest_timestamp = job.latestTimestampMs; + } + } + }); + } + }); + + return Object.values(groups); +} + +export function getStatsBarData(jobs: any) { + return {}; +} From 26afc8857481176482c2f6cdaa3aec8cddb37677 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Mon, 16 Sep 2019 10:53:36 -0600 Subject: [PATCH 07/19] adds actions column to anomaly detection table --- .../anomaly_detection_panel/actions.tsx | 38 +++++++++++++++ .../anomaly_detection_panel.tsx | 27 ++++++++--- .../anomaly_detection_panel/table.tsx | 25 +++++----- .../anomaly_detection_panel/utils.ts | 47 +++++++++++++++++-- .../ml/public/services/job_service.d.ts | 1 + 5 files changed, 116 insertions(+), 22 deletions(-) create mode 100644 x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/actions.tsx diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/actions.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/actions.tsx new file mode 100644 index 0000000000000..01cda611b34ad --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/actions.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +// @ts-ignore no module file +import { getLink } from '../../../jobs/jobs_list/components/job_actions/results'; + +interface Props { + jobsList: any; +} + +export const ExplorerLink: FC = ({ jobsList }) => { + const openJobsInAnomalyExplorerText = i18n.translate( + 'xpack.ml.overviewAnomalyDetection.resultActions.openJobsInAnomalyExplorerText', + { + defaultMessage: 'Open {jobsCount, plural, one {{jobId}} other {# jobs}} in Anomaly Explorer', + values: { jobsCount: jobsList.length, jobId: jobsList[0] && jobsList[0].id }, + } + ); + + return ( + + + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx index fbf8e1f9d9d7e..db30698c93541 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx @@ -10,29 +10,42 @@ import { i18n } from '@kbn/i18n'; import { AnomalyDetectionTable } from './table'; // import { metaData } from 'ui/metadata'; import { ml } from '../../../services/ml_api_service'; -import { getGroupsFromJobs, getStatsBarData } from './utils'; +import { getGroupsFromJobs, getStatsBarData, getJobsWithTimerange } from './utils'; + +export interface Group { + id: string; + jobIds: string[]; + docs_processed: number; + latest_timestamp: number; + max_anomaly_score: number | null; +} const createJobLink = '#/jobs/new_job/step/index_or_search'; // jobsSummary to get: stats bar info, jobs in group, latest timestamp, docs_processed export const AnomalyDetectionPanel: FC = () => { const [isLoading, setIsLoading] = useState(false); - const [groupsList, setGroupsList] = useState([]); + const [groupsList, setGroupsList] = useState([]); + const [jobsList, setJobsList] = useState({}); const [statsBarData, setStatsBarData] = useState(undefined); const [errorMessage, setErrorMessage] = useState(undefined); const loadJobs = async () => { try { const jobsResult = await ml.jobs.jobsSummary([]); - const jobsSummaryList = jobsResult.map((job: any) => { - job.latestTimestampSortValue = job.latestTimestampMs || 0; - return job; - }); + const jobsSummaryList = + jobsResult instanceof Array && + jobsResult.map((job: any) => { + job.latestTimestampSortValue = job.latestTimestampMs || 0; + return job; + }); const groups = getGroupsFromJobs(jobsSummaryList); + const jobsWithTimerange = getJobsWithTimerange(jobsSummaryList); // const stats = getStatsBarData(jobsSummaryList); setIsLoading(false); setErrorMessage(undefined); setGroupsList(groups); + setJobsList(jobsWithTimerange); // setStatsBarData(stats); } catch (e) { setErrorMessage(e); @@ -85,7 +98,7 @@ export const AnomalyDetectionPanel: FC = () => { /> )} {isLoading === false && typeof errorMessage === 'undefined' && groupsList.length > 0 && ( - + )}   
diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx index 95475638cc395..3797074fa557b 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx @@ -14,6 +14,9 @@ import { OnTableChangeArg, } from '../../../components/ml_in_memory_table'; import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils'; +import { ExplorerLink } from './actions'; +import { getJobsFromGroup } from './utils'; +import { Group } from './anomaly_detection_panel'; // import { AnomalyDetectionStatsBar } from './anomaly_detection_stats_bar'; // interface AnomalyDetectionListColumns { @@ -34,10 +37,11 @@ export enum AnomalyDetectionListColumns { } interface Props { - items: AnomalyDetectionListColumns[]; + items: Group[]; statsBarData: any; // TODO: pull in from statsbar types + jobsList: any; } -export const AnomalyDetectionTable: FC = ({ items }) => { +export const AnomalyDetectionTable: FC = ({ items, jobsList }) => { const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); @@ -45,7 +49,6 @@ export const AnomalyDetectionTable: FC = ({ items }) => { const [sortDirection, setSortDirection] = useState(SORT_DIRECTION.ASC); // columns: group, max anomaly, jobs in group, latest timestamp, docs processed, action to explorer - // item: { id: group-id, max_anomaly_score: , num_jobs: num, latest_timestamp: 123456, docs_processed: num } const columns: any[] = [ { field: AnomalyDetectionListColumns.id, @@ -70,7 +73,7 @@ export const AnomalyDetectionTable: FC = ({ items }) => { name: i18n.translate('xpack.ml.overview.anomalyDetectionList.numJobs', { defaultMessage: 'Jobs in group', }), - render: jobIds => jobIds.length, + render: (jobIds: Group['jobIds']) => jobIds.length, sortable: true, truncateText: true, width: '100px', @@ -95,13 +98,13 @@ export const AnomalyDetectionTable: FC = ({ items }) => { sortable: true, width: '20%', }, - // { - // name: i18n.translate('xpack.ml.overview.anomalyDetectionList.tableActionLabel', { - // defaultMessage: 'Actions', - // }), - // actions: [ANALYTICS_VIEW_ACTION], - // width: '100px', - // }, + { + name: i18n.translate('xpack.ml.overview.anomalyDetectionList.tableActionLabel', { + defaultMessage: 'Actions', + }), + render: (group: Group) => , + width: '100px', + }, ]; const onTableChange = ({ diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts index b802dc744dde5..d5569e0e7fc59 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts @@ -4,13 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -// { id: group-id, max_anomaly_score: , jobIds: num, latest_timestamp: 123456, docs_processed: num } -export function getGroupsFromJobs(jobs: any) { - const groups: any = {}; +import { Group } from './anomaly_detection_panel'; + +export function getGroupsFromJobs(jobs: any): Group[] { + const groups: any = { + no_group: { + id: 'ungrouped', + jobIds: [], + docs_processed: 0, + latest_timestamp: 0, + max_anomaly_score: null, + }, + }; jobs.forEach((job: any) => { // Organize job by group - if (job.groups !== undefined) { + if (job.groups.length > 0) { job.groups.forEach((g: any) => { if (groups[g] === undefined) { groups[g] = { @@ -29,12 +38,42 @@ export function getGroupsFromJobs(jobs: any) { } } }); + } else { + groups.no_group.jobIds.push(job.id); + groups.no_group.docs_processed += job.processed_record_count; + // if incoming job latest timestamp is greater than the last saved one, replace it + if (job.latestTimestampMs > groups.no_group.latest_timestamp) { + groups.no_group.latest_timestamp = job.latestTimestampMs; + } } }); return Object.values(groups); } +export function getJobsFromGroup(group: Group, jobs: any) { + return group.jobIds.map(jobId => jobs[jobId]).filter(id => id !== undefined); +} + +export function getJobsWithTimerange(jobsList: any) { + const jobs: any = {}; + jobsList.forEach((job: any) => { + if (jobs[job.id] === undefined) { + // create the job in the object with the times you need + if (job.earliestTimestampMs !== undefined) { + const { earliestTimestampMs, latestResultsTimestampMs } = job; + jobs[job.id] = { + id: job.id, + earliestTimestampMs, + latestResultsTimestampMs, + }; + } + } + }); + + return jobs; +} + export function getStatsBarData(jobs: any) { return {}; } diff --git a/x-pack/legacy/plugins/ml/public/services/job_service.d.ts b/x-pack/legacy/plugins/ml/public/services/job_service.d.ts index f94c0e866451d..3b60a7af505d7 100644 --- a/x-pack/legacy/plugins/ml/public/services/job_service.d.ts +++ b/x-pack/legacy/plugins/ml/public/services/job_service.d.ts @@ -11,6 +11,7 @@ export interface ExistingJobsAndGroups { declare interface JobService { currentJob: any; + createResultsUrlForJobs: () => string; tempJobCloningObjects: { job: any; skipTimeRangeStep: boolean; From bc2800ce581f3bb8de9ac3d4f0d92ead2aa27082 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Mon, 16 Sep 2019 11:46:09 -0600 Subject: [PATCH 08/19] add stats bar to anomaly detection panel --- .../components/job_actions/results.js | 2 +- .../anomaly_detection_panel.tsx | 4 +- .../anomaly_detection_panel/table.tsx | 10 +- .../anomaly_detection_panel/utils.ts | 93 ++++++++++++++++++- 4 files changed, 97 insertions(+), 12 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_actions/results.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_actions/results.js index 172c198509d28..5ec407f7f054e 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_actions/results.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/job_actions/results.js @@ -18,7 +18,7 @@ import chrome from 'ui/chrome'; import { mlJobService } from '../../../../services/job_service'; import { injectI18n } from '@kbn/i18n/react'; -function getLink(location, jobs) { +export function getLink(location, jobs) { const resultsPageUrl = mlJobService.createResultsUrlForJobs(jobs, location); return `${chrome.getBasePath()}/app/${resultsPageUrl}`; } diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx index db30698c93541..9e4f5252903b5 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx @@ -41,12 +41,12 @@ export const AnomalyDetectionPanel: FC = () => { }); const groups = getGroupsFromJobs(jobsSummaryList); const jobsWithTimerange = getJobsWithTimerange(jobsSummaryList); - // const stats = getStatsBarData(jobsSummaryList); + const stats = getStatsBarData(jobsSummaryList); setIsLoading(false); setErrorMessage(undefined); + setStatsBarData(stats); setGroupsList(groups); setJobsList(jobsWithTimerange); - // setStatsBarData(stats); } catch (e) { setErrorMessage(e); setIsLoading(false); diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx index 3797074fa557b..6f1e2aa974ceb 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx @@ -17,7 +17,7 @@ import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils'; import { ExplorerLink } from './actions'; import { getJobsFromGroup } from './utils'; import { Group } from './anomaly_detection_panel'; -// import { AnomalyDetectionStatsBar } from './anomaly_detection_stats_bar'; +import { StatsBar } from '../../../components/stats_bar'; // interface AnomalyDetectionListColumns { // id: string; @@ -41,7 +41,7 @@ interface Props { statsBarData: any; // TODO: pull in from statsbar types jobsList: any; } -export const AnomalyDetectionTable: FC = ({ items, jobsList }) => { +export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData }) => { const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); @@ -147,9 +147,9 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList }) => { - {/* - - */} + + +
{ + if (job.jobState === JOB_STATE.OPENED) { + jobStats.open.value++; + } else if (job.jobState === JOB_STATE.CLOSED) { + jobStats.closed.value++; + } else if (job.jobState === JOB_STATE.FAILED) { + failedJobs++; + } + + if (job.hasDatafeed && job.datafeedState === DATAFEED_STATE.STARTED) { + jobStats.activeDatafeeds.value++; + } + + if (job.nodeName !== undefined) { + mlNodes[job.nodeName] = {}; + } + }); + + jobStats.total.value = jobsList.length; + + // // Only show failed jobs if it is non-zero + if (failedJobs) { + jobStats.failed.value = failedJobs; + jobStats.failed.show = true; + } else { + jobStats.failed.show = false; + } + + jobStats.activeNodes.value = Object.keys(mlNodes).length; + + return jobStats; +} + export function getJobsFromGroup(group: Group, jobs: any) { return group.jobIds.map(jobId => jobs[jobId]).filter(id => id !== undefined); } @@ -73,7 +162,3 @@ export function getJobsWithTimerange(jobsList: any) { return jobs; } - -export function getStatsBarData(jobs: any) { - return {}; -} From 8ebf1fa8568fc34c8b165c41226483d4a3b05194 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Mon, 16 Sep 2019 18:12:54 -0600 Subject: [PATCH 09/19] add max anomaly score to table --- .../public/components/stats_bar/stats_bar.tsx | 2 +- .../analytics_panel/analytics_panel.tsx | 29 ++-- .../components/analytics_panel/table.tsx | 6 +- .../anomaly_detection_panel/actions.tsx | 1 - .../anomaly_detection_panel.tsx | 133 +++++++++++++++--- .../anomaly_detection_panel/table.tsx | 45 +++--- .../anomaly_detection_panel/utils.ts | 36 +++-- .../ml/public/overview/components/content.tsx | 4 +- .../ml/public/overview/components/sidebar.tsx | 7 +- .../ml/public/overview/overview_page.tsx | 2 +- .../ml/public/services/results_service.d.ts | 8 +- 11 files changed, 201 insertions(+), 72 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/components/stats_bar/stats_bar.tsx b/x-pack/legacy/plugins/ml/public/components/stats_bar/stats_bar.tsx index 08d8d29b4fb13..0ebc4647e030f 100644 --- a/x-pack/legacy/plugins/ml/public/components/stats_bar/stats_bar.tsx +++ b/x-pack/legacy/plugins/ml/public/components/stats_bar/stats_bar.tsx @@ -11,7 +11,7 @@ interface Stats { total: StatsBarStat; failed: StatsBarStat; } -interface JobStatsBarStats extends Stats { +export interface JobStatsBarStats extends Stats { activeNodes: StatsBarStat; open: StatsBarStat; closed: StatsBarStat; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx index 8588a03f4a4a1..5a66dfcd67f72 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx @@ -5,7 +5,14 @@ */ import React, { FC, Fragment, useState, useEffect } from 'react'; -import { EuiButton, EuiCallOut, EuiEmptyPrompt, EuiLoadingSpinner, EuiPanel } from '@elastic/eui'; +import { + EuiButton, + EuiCallOut, + EuiEmptyPrompt, + EuiLoadingSpinner, + EuiPanel, + EuiSpacer, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { AnalyticsTable } from './table'; import { getAnalyticsFactory } from '../../../data_frame_analytics/pages/analytics_management/services/analytics_service'; @@ -22,12 +29,6 @@ export const AnalyticsPanel: FC = () => { getAnalytics(true); }, []); - // TODO: add refresh button - // const onRefresh = () => { - // setIsInitialized(false); - // getAnalytics(true); - // }; - const errorDisplay = ( { ); return ( - + {typeof errorMessage !== 'undefined' && errorDisplay} {isInitialized === false && }      {isInitialized === true && analytics.length === 0 && ( @@ -66,7 +67,17 @@ export const AnalyticsPanel: FC = () => { } /> )} - {isInitialized === true && analytics.length > 0 && } + {isInitialized === true && analytics.length > 0 && ( + + + + + {i18n.translate('xpack.ml.overview.analyticsList.manageJobsButtonText', { + defaultMessage: 'Manage jobs', + })} + + + )} ); }; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx index 50758fc5c8625..8adc2c822b6cb 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx @@ -124,7 +124,11 @@ export const AnalyticsTable: FC = ({ items }) => { - + diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/actions.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/actions.tsx index 01cda611b34ad..d93b6acc7a93e 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/actions.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/actions.tsx @@ -30,7 +30,6 @@ export const ExplorerLink: FC = ({ jobsList }) => { iconType="tableOfContents" aria-label={openJobsInAnomalyExplorerText} className="results-button" - // isDisabled={jobActionsDisabled === true} data-test-subj={`openOverviewJobsInAnomalyExplorer`} /> diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx index 9e4f5252903b5..c0e85472dc82a 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx @@ -5,56 +5,140 @@ */ import React, { FC, Fragment, useState, useEffect } from 'react'; -import { EuiButton, EuiCallOut, EuiEmptyPrompt, EuiLoadingSpinner, EuiPanel } from '@elastic/eui'; +import { + EuiButton, + EuiCallOut, + EuiEmptyPrompt, + EuiLoadingSpinner, + EuiPanel, + EuiSpacer, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { toastNotifications } from 'ui/notify'; import { AnomalyDetectionTable } from './table'; -// import { metaData } from 'ui/metadata'; import { ml } from '../../../services/ml_api_service'; +import { mlResultsService } from '../../../services/results_service'; import { getGroupsFromJobs, getStatsBarData, getJobsWithTimerange } from './utils'; +export interface Groups { + [key: string]: Group; +} + export interface Group { id: string; jobIds: string[]; docs_processed: number; + earliest_timestamp: number; latest_timestamp: number; max_anomaly_score: number | null; } +export interface Job { + [key: string]: any; +} + +export type Jobs = Job[]; + +interface MaxScoresByGroup { + [key: string]: { + maxScore: number; + index?: number; + }; +} + const createJobLink = '#/jobs/new_job/step/index_or_search'; -// jobsSummary to get: stats bar info, jobs in group, latest timestamp, docs_processed +function getDefaultAnomalyScores(groups: Group[]): MaxScoresByGroup { + const anomalyScores: MaxScoresByGroup = {}; + groups.forEach(group => { + anomalyScores[group.id] = { maxScore: 0 }; + }); + + return anomalyScores; +} + export const AnomalyDetectionPanel: FC = () => { const [isLoading, setIsLoading] = useState(false); - const [groupsList, setGroupsList] = useState([]); - const [jobsList, setJobsList] = useState({}); + const [groups, setGroups] = useState({}); + const [groupsCount, setGroupsCount] = useState(0); + const [jobsList, setJobsList] = useState([]); const [statsBarData, setStatsBarData] = useState(undefined); const [errorMessage, setErrorMessage] = useState(undefined); const loadJobs = async () => { + setIsLoading(true); + try { - const jobsResult = await ml.jobs.jobsSummary([]); - const jobsSummaryList = - jobsResult instanceof Array && - jobsResult.map((job: any) => { - job.latestTimestampSortValue = job.latestTimestampMs || 0; - return job; - }); - const groups = getGroupsFromJobs(jobsSummaryList); + const jobsResult: Jobs | any = await ml.jobs.jobsSummary([]); + const jobsSummaryList = jobsResult.map((job: any) => { + job.latestTimestampSortValue = job.latestTimestampMs || 0; + return job; + }); + const jobsGroups = getGroupsFromJobs(jobsSummaryList); const jobsWithTimerange = getJobsWithTimerange(jobsSummaryList); const stats = getStatsBarData(jobsSummaryList); setIsLoading(false); setErrorMessage(undefined); setStatsBarData(stats); - setGroupsList(groups); + setGroupsCount(Object.values(jobsGroups).length); + setGroups(jobsGroups); setJobsList(jobsWithTimerange); + loadMaxAnomalyScores(jobsGroups); } catch (e) { - setErrorMessage(e); + setErrorMessage(JSON.stringify(e)); setIsLoading(false); } }; + const loadMaxAnomalyScores = async (groupsObject: Groups) => { + const groupsList: Group[] = Object.values(groupsObject); + const scores = getDefaultAnomalyScores(groupsList); + + try { + const promises: any[] = []; + groupsList.forEach((group, i) => { + if (group.jobIds.length > 0) { + scores[group.id].index = i; + promises.push( + mlResultsService.getOverallBucketScores( + group.jobIds, + 1, + group.earliest_timestamp, + group.latest_timestamp + ) + ); + } + }); + + const results = await Promise.all(promises); + const tempGroups = Object.assign({}, { ...groupsObject }); + // Check results for each group's promise index and update state + Object.keys(scores).forEach(groupId => { + const resultsIndex = scores[groupId] && scores[groupId].index; + const promiseResult: { success: boolean; results: { [key: string]: number } } = + resultsIndex !== undefined && results[resultsIndex]; + if (promiseResult.success === true && promiseResult.results !== undefined) { + const maxScore = Math.max(...Object.values(promiseResult.results)); + scores[groupId] = { maxScore }; + tempGroups[groupId].max_anomaly_score = maxScore; + } + }); + + setGroups(tempGroups); + } catch (e) { + toastNotifications.addDanger( + i18n.translate( + 'xpack.ml.overview.analytics.errorWithFetchingAnomalyScoreNotificationErrorMessage', + { + defaultMessage: 'An error occurred fetching anomaly scores: {error}', + values: { error: JSON.stringify(e) }, + } + ) + ); + } + }; + useEffect(() => { - setIsLoading(true); loadJobs(); }, []); @@ -73,10 +157,10 @@ export const AnomalyDetectionPanel: FC = () => { ); return ( - + {typeof errorMessage !== 'undefined' && errorDisplay} {isLoading && }    - {isLoading === false && typeof errorMessage === 'undefined' && groupsList.length === 0 && ( + {isLoading === false && typeof errorMessage === 'undefined' && groupsCount === 0 && ( Create your first anomaly detection job} @@ -97,10 +181,17 @@ export const AnomalyDetectionPanel: FC = () => { } /> )} - {isLoading === false && typeof errorMessage === 'undefined' && groupsList.length > 0 && ( - + {isLoading === false && typeof errorMessage === 'undefined' && groupsCount > 0 && ( + + + + + {i18n.translate('xpack.ml.overview.anomalyDetection.manageJobsButtonText', { + defaultMessage: 'Manage jobs', + })} + + )} -    ); }; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx index 6f1e2aa974ceb..d8e724b30d1e6 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx @@ -5,7 +5,7 @@ */ import React, { FC, Fragment, useState } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { MlInMemoryTable, @@ -16,16 +16,13 @@ import { import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils'; import { ExplorerLink } from './actions'; import { getJobsFromGroup } from './utils'; -import { Group } from './anomaly_detection_panel'; -import { StatsBar } from '../../../components/stats_bar'; - -// interface AnomalyDetectionListColumns { -// id: string; -// max_anomaly_score: number; -// num_jobs: number; -// latest_timestamp: number; -// docs_processed: number; -// } +import { Groups, Group, Jobs } from './anomaly_detection_panel'; +// @ts-ignore +import { StatsBar, JobStatsBarStats } from '../../../components/stats_bar'; +// @ts-ignore +import { JobSelectorBadge } from '../../../components/job_selector/job_selector_badge'; +// @ts-ignore +import { toLocaleString } from '../../../util/string_utils'; // Used to pass on attribute names to table columns export enum AnomalyDetectionListColumns { @@ -37,11 +34,13 @@ export enum AnomalyDetectionListColumns { } interface Props { - items: Group[]; - statsBarData: any; // TODO: pull in from statsbar types - jobsList: any; + items: Groups; + statsBarData: JobStatsBarStats; + jobsList: Jobs; } + export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData }) => { + const groupsList = Object.values(items); const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); @@ -55,16 +54,23 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData name: i18n.translate('xpack.ml.overview.anomalyDetectionList.id', { defaultMessage: 'Group ID', }), + render: (id: Group['id']) => , sortable: true, truncateText: true, width: '20%', }, { - field: AnomalyDetectionListColumns.maxAnomalyScore, name: i18n.translate('xpack.ml.overview.anomalyDetectionList.maxScore', { - defaultMessage: 'Max score', + defaultMessage: 'Max anomaly score', }), sortable: true, + render: (group: Group) => { + if (group.max_anomaly_score === null) { + return ; + } else { + return group.max_anomaly_score; + } + }, truncateText: true, width: '150px', }, @@ -94,6 +100,7 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData name: i18n.translate('xpack.ml.overview.anomalyDetectionList.docsProcessed', { defaultMessage: 'Docs processed', }), + render: (docs: number) => toLocaleString(docs), textOnly: true, sortable: true, width: '20%', @@ -123,7 +130,7 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData const pagination = { initialPageIndex: pageIndex, initialPageSize: pageSize, - totalItemCount: items.length, + totalItemCount: groupsList.length, pageSizeOptions: [10, 20, 50], hidePerPageOptions: false, }; @@ -147,7 +154,7 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData - + @@ -159,7 +166,7 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData hasActions={false} isExpandable={false} isSelectable={false} - items={items} + items={groupsList} itemId={AnomalyDetectionListColumns.id} onTableChange={onTableChange} pagination={pagination} diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts index cae454dd433d0..aa68e275853ab 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts @@ -6,11 +6,11 @@ import { i18n } from '@kbn/i18n'; import { JOB_STATE, DATAFEED_STATE } from '../../../../common/constants/states'; -import { Group } from './anomaly_detection_panel'; +import { Group, Groups, Jobs, Job } from './anomaly_detection_panel'; -export function getGroupsFromJobs(jobs: any): Group[] { +export function getGroupsFromJobs(jobs: Jobs): Groups { const groups: any = { - no_group: { + ungrouped: { id: 'ungrouped', jobIds: [], docs_processed: 0, @@ -28,32 +28,42 @@ export function getGroupsFromJobs(jobs: any): Group[] { id: g, jobIds: [job.id], docs_processed: job.processed_record_count, - latest_timestamp: 0, + earliest_timestamp: job.earliestTimestampMs, + latest_timestamp: job.latestTimestampMs, max_anomaly_score: null, }; } else { groups[g].jobIds.push(job.id); groups[g].docs_processed += job.processed_record_count; // if incoming job latest timestamp is greater than the last saved one, replace it - if (job.latestTimestampMs > groups[g].latest_timestamp) { + if (groups[g].latest_timestamp === undefined) { groups[g].latest_timestamp = job.latestTimestampMs; + } else if (job.latestTimestampMs > groups[g].latest_timestamp) { + groups[g].latest_timestamp = job.latestTimestampMs; + } + // if existing group's earliest timestamp is not defined, set it to current job's + // otherwise compare to current job's and replace if current job's earliest is less + if (groups[g].earliest_timestamp === undefined) { + groups[g].earliest_timestamp = job.earliestTimestampMs; + } else if (job.earliestTimestampMs < groups[g].earliest_timestamp) { + groups[g].earliest_timestamp = job.earliestTimestampMs; } } }); } else { - groups.no_group.jobIds.push(job.id); - groups.no_group.docs_processed += job.processed_record_count; + groups.ungrouped.jobIds.push(job.id); + groups.ungrouped.docs_processed += job.processed_record_count; // if incoming job latest timestamp is greater than the last saved one, replace it - if (job.latestTimestampMs > groups.no_group.latest_timestamp) { - groups.no_group.latest_timestamp = job.latestTimestampMs; + if (job.latestTimestampMs > groups.ungrouped.latest_timestamp) { + groups.ungrouped.latest_timestamp = job.latestTimestampMs; } } }); - return Object.values(groups); + return groups; } -export function getStatsBarData(jobsList) { +export function getStatsBarData(jobsList: any) { const jobStats = { activeNodes: { label: i18n.translate('xpack.ml.overviewJobsList.statsBar.activeMLNodesLabel', { @@ -104,10 +114,10 @@ export function getStatsBarData(jobsList) { } // object to keep track of nodes being used by jobs - const mlNodes = {}; + const mlNodes: any = {}; let failedJobs = 0; - jobsList.forEach(job => { + jobsList.forEach((job: Job) => { if (job.jobState === JOB_STATE.OPENED) { jobStats.open.value++; } else if (job.jobState === JOB_STATE.CLOSED) { diff --git a/x-pack/legacy/plugins/ml/public/overview/components/content.tsx b/x-pack/legacy/plugins/ml/public/overview/components/content.tsx index 9731868954d66..21aedce862662 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/content.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/content.tsx @@ -14,10 +14,10 @@ import { AnalyticsPanel } from './analytics_panel/'; export const OverviewContent: FC = () => ( - + - + diff --git a/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx b/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx index d22d476e6d7fb..e4d5acbb6aa0a 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx @@ -16,7 +16,7 @@ const feedbackLink = 'https://www.elastic.co/community/'; export const OverviewSideBar: FC = () => ( - +

(

diff --git a/x-pack/legacy/plugins/ml/public/overview/overview_page.tsx b/x-pack/legacy/plugins/ml/public/overview/overview_page.tsx index 03cfbfbeea0ca..bb9e45ae84a41 100644 --- a/x-pack/legacy/plugins/ml/public/overview/overview_page.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/overview_page.tsx @@ -5,7 +5,7 @@ */ import React, { Fragment, FC } from 'react'; -import { EuiFlexGroup, EuiHorizontalRule, EuiPage, EuiPageBody, EuiTitle } from '@elastic/eui'; +import { EuiFlexGroup, EuiPage, EuiPageBody } from '@elastic/eui'; import { NavigationMenu } from '../components/navigation_menu/navigation_menu'; import { OverviewSideBar } from './components/sidebar'; import { OverviewContent } from './components/content'; diff --git a/x-pack/legacy/plugins/ml/public/services/results_service.d.ts b/x-pack/legacy/plugins/ml/public/services/results_service.d.ts index 4d9b346000ba4..2bbe37c3fc05d 100644 --- a/x-pack/legacy/plugins/ml/public/services/results_service.d.ts +++ b/x-pack/legacy/plugins/ml/public/services/results_service.d.ts @@ -20,7 +20,13 @@ declare interface MlResultsService { getScheduledEventsByBucket: () => Promise; getTopInfluencers: () => Promise; getTopInfluencerValues: () => Promise; - getOverallBucketScores: () => Promise; + getOverallBucketScores: ( + jobIds: any, + topN: any, + earliestMs: any, + latestMs: any, + interval?: any + ) => Promise; getInfluencerValueMaxScoreByTime: () => Promise; getRecordInfluencers: () => Promise; getRecordsForInfluencer: () => Promise; From 14da9e6c31922460e3338684cd6410bb8e0f3487 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Tue, 17 Sep 2019 10:39:49 -0600 Subject: [PATCH 10/19] update score display. --- .../analytics_panel/analytics_panel.tsx | 2 +- .../components/analytics_panel/table.tsx | 4 +- .../anomaly_detection_panel/actions.tsx | 14 +++++-- .../anomaly_detection_panel.tsx | 2 +- .../anomaly_detection_panel/table.tsx | 37 +++++++++++++------ .../ml/public/overview/components/sidebar.tsx | 11 +----- .../plugins/ml/public/overview/directive.tsx | 12 +++++- 7 files changed, 52 insertions(+), 30 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx index 5a66dfcd67f72..b654303cdaceb 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx @@ -71,7 +71,7 @@ export const AnalyticsPanel: FC = () => { - + {i18n.translate('xpack.ml.overview.analyticsList.manageJobsButtonText', { defaultMessage: 'Manage jobs', })} diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx index 8adc2c822b6cb..8e6fd1a8f1bd1 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx @@ -66,7 +66,7 @@ export const AnalyticsTable: FC = ({ items }) => { PROGRESS_COLUMN, { field: DataFrameAnalyticsListColumn.configCreateTime, - name: i18n.translate('xpack.ml.overview.createdTimeColumnName', { + name: i18n.translate('xpack.ml.overview.analyticsList.reatedTimeColumnName', { defaultMessage: 'Creation time', }), dataType: 'date', @@ -118,7 +118,7 @@ export const AnalyticsTable: FC = ({ items }) => {

- {i18n.translate('xpack.ml.overview.analyticsPanelTitle', { + {i18n.translate('xpack.ml.overview.analyticsList.PanelTitle', { defaultMessage: 'Analytics', })}

diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/actions.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/actions.tsx index d93b6acc7a93e..6f14e26932df3 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/actions.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/actions.tsx @@ -5,7 +5,7 @@ */ import React, { FC } from 'react'; -import { EuiToolTip, EuiButtonIcon } from '@elastic/eui'; +import { EuiToolTip, EuiButtonEmpty } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; // @ts-ignore no module file import { getLink } from '../../../jobs/jobs_list/components/job_actions/results'; @@ -16,7 +16,7 @@ interface Props { export const ExplorerLink: FC = ({ jobsList }) => { const openJobsInAnomalyExplorerText = i18n.translate( - 'xpack.ml.overviewAnomalyDetection.resultActions.openJobsInAnomalyExplorerText', + 'xpack.ml.overview.anomalyDetection.resultActions.openJobsInAnomalyExplorerText', { defaultMessage: 'Open {jobsCount, plural, one {{jobId}} other {# jobs}} in Anomaly Explorer', values: { jobsCount: jobsList.length, jobId: jobsList[0] && jobsList[0].id }, @@ -25,13 +25,19 @@ export const ExplorerLink: FC = ({ jobsList }) => { return ( - + > + {i18n.translate('xpack.ml.overview.anomalyDetection.exploreActionName', { + defaultMessage: 'Explore', + })} + ); }; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx index c0e85472dc82a..bf4e652212769 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx @@ -128,7 +128,7 @@ export const AnomalyDetectionPanel: FC = () => { } catch (e) { toastNotifications.addDanger( i18n.translate( - 'xpack.ml.overview.analytics.errorWithFetchingAnomalyScoreNotificationErrorMessage', + 'xpack.ml.overview.anomalyDetection.errorWithFetchingAnomalyScoreNotificationErrorMessage', { defaultMessage: 'An error occurred fetching anomaly scores: {error}', values: { error: JSON.stringify(e) }, diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx index d8e724b30d1e6..bf43b82b9e70b 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx @@ -5,7 +5,14 @@ */ import React, { FC, Fragment, useState } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiSpacer, EuiText } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiHealth, + EuiLoadingSpinner, + EuiSpacer, + EuiText, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { MlInMemoryTable, @@ -23,6 +30,7 @@ import { StatsBar, JobStatsBarStats } from '../../../components/stats_bar'; import { JobSelectorBadge } from '../../../components/job_selector/job_selector_badge'; // @ts-ignore import { toLocaleString } from '../../../util/string_utils'; +import { getSeverityColor } from '../../../../common/util/anomaly_utils'; // Used to pass on attribute names to table columns export enum AnomalyDetectionListColumns { @@ -51,7 +59,7 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData const columns: any[] = [ { field: AnomalyDetectionListColumns.id, - name: i18n.translate('xpack.ml.overview.anomalyDetectionList.id', { + name: i18n.translate('xpack.ml.overview.anomalyDetection.tableId', { defaultMessage: 'Group ID', }), render: (id: Group['id']) => , @@ -60,15 +68,22 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData width: '20%', }, { - name: i18n.translate('xpack.ml.overview.anomalyDetectionList.maxScore', { + field: AnomalyDetectionListColumns.maxAnomalyScore, + name: i18n.translate('xpack.ml.overview.anomalyDetection.tableMaxScore', { defaultMessage: 'Max anomaly score', }), sortable: true, - render: (group: Group) => { - if (group.max_anomaly_score === null) { + render: (score: Group['max_anomaly_score']) => { + if (score === null) { return ; } else { - return group.max_anomaly_score; + const color: string = getSeverityColor(score); + return ( + // @ts-ignore + + {score >= 1 ? Math.floor(score) : '< 1'} + + ); } }, truncateText: true, @@ -76,7 +91,7 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData }, { field: AnomalyDetectionListColumns.jobIds, - name: i18n.translate('xpack.ml.overview.anomalyDetectionList.numJobs', { + name: i18n.translate('xpack.ml.overview.anomalyDetection.tableNumJobs', { defaultMessage: 'Jobs in group', }), render: (jobIds: Group['jobIds']) => jobIds.length, @@ -86,7 +101,7 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData }, { field: AnomalyDetectionListColumns.latestTimestamp, - name: i18n.translate('xpack.ml.overview.anomalyDetectionList.latestTimestamp', { + name: i18n.translate('xpack.ml.overview.anomalyDetection.tableLatestTimestamp', { defaultMessage: 'Latest timestamp', }), dataType: 'date', @@ -97,7 +112,7 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData }, { field: AnomalyDetectionListColumns.docsProcessed, - name: i18n.translate('xpack.ml.overview.anomalyDetectionList.docsProcessed', { + name: i18n.translate('xpack.ml.overview.anomalyDetection.tableDocsProcessed', { defaultMessage: 'Docs processed', }), render: (docs: number) => toLocaleString(docs), @@ -106,7 +121,7 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData width: '20%', }, { - name: i18n.translate('xpack.ml.overview.anomalyDetectionList.tableActionLabel', { + name: i18n.translate('xpack.ml.overview.anomalyDetection.tableActionLabel', { defaultMessage: 'Actions', }), render: (group: Group) => , @@ -148,7 +163,7 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData

- {i18n.translate('xpack.ml.overview.anomalyDetectionPanelTitle', { + {i18n.translate('xpack.ml.overview.anomalyDetection.panelTitle', { defaultMessage: 'Anomaly Detection', })}

diff --git a/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx b/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx index e4d5acbb6aa0a..02e1efe6588a2 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx @@ -26,8 +26,7 @@ export const OverviewSideBar: FC = () => (

( /> ), - createJob: ( - - - - ), }} />

diff --git a/x-pack/legacy/plugins/ml/public/overview/directive.tsx b/x-pack/legacy/plugins/ml/public/overview/directive.tsx index ecf2b11579984..bd3b653ccbb64 100644 --- a/x-pack/legacy/plugins/ml/public/overview/directive.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/directive.tsx @@ -6,6 +6,8 @@ import ReactDOM from 'react-dom'; import React from 'react'; +import { I18nContext } from 'ui/i18n'; +import { timefilter } from 'ui/timefilter'; // @ts-ignore import { uiModules } from 'ui/modules'; const module = uiModules.get('apps/ml', ['react']); @@ -17,7 +19,15 @@ module.directive('mlOverview', function() { scope: {}, restrict: 'E', link: async (scope: ng.IScope, element: ng.IAugmentedJQuery) => { - ReactDOM.render(, element[0]); + timefilter.disableTimeRangeSelector(); + timefilter.disableAutoRefreshSelector(); + + ReactDOM.render( + + + , + element[0] + ); element.on('$destroy', () => { ReactDOM.unmountComponentAtNode(element[0]); From dfc1e5bc7fd74f779b021cc772e4321e668e5007 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Tue, 17 Sep 2019 12:11:40 -0600 Subject: [PATCH 11/19] add refresh button to panels --- x-pack/legacy/plugins/ml/public/app.js | 2 +- .../analytics_panel/analytics_panel.tsx | 22 ++++++++++++++----- .../anomaly_detection_panel.tsx | 22 ++++++++++++++----- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/app.js b/x-pack/legacy/plugins/ml/public/app.js index 6297e5debcb21..7cea0edaa7df7 100644 --- a/x-pack/legacy/plugins/ml/public/app.js +++ b/x-pack/legacy/plugins/ml/public/app.js @@ -44,5 +44,5 @@ if (typeof uiRoutes.enable === 'function') { uiRoutes .otherwise({ - redirectTo: '/jobs' // TODO change to overview once we can always see it + redirectTo: '/overview' }); diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx index b654303cdaceb..a5c0ff1c506e4 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx @@ -7,6 +7,7 @@ import React, { FC, Fragment, useState, useEffect } from 'react'; import { EuiButton, + EuiButtonEmpty, EuiCallOut, EuiEmptyPrompt, EuiLoadingSpinner, @@ -29,6 +30,10 @@ export const AnalyticsPanel: FC = () => { getAnalytics(true); }, []); + const onRefresh = () => { + getAnalytics(true); + }; + const errorDisplay = ( { - - {i18n.translate('xpack.ml.overview.analyticsList.manageJobsButtonText', { - defaultMessage: 'Manage jobs', - })} - +
+ + {i18n.translate('xpack.ml.overview.analyticsList.refreshJobsButtonText', { + defaultMessage: 'Refresh', + })} + + + {i18n.translate('xpack.ml.overview.analyticsList.manageJobsButtonText', { + defaultMessage: 'Manage jobs', + })} + +
)} diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx index bf4e652212769..1655f90ee4b2e 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx @@ -7,6 +7,7 @@ import React, { FC, Fragment, useState, useEffect } from 'react'; import { EuiButton, + EuiButtonEmpty, EuiCallOut, EuiEmptyPrompt, EuiLoadingSpinner, @@ -142,6 +143,10 @@ export const AnomalyDetectionPanel: FC = () => { loadJobs(); }, []); + const onRefresh = () => { + loadJobs(); + }; + const errorDisplay = ( { - - {i18n.translate('xpack.ml.overview.anomalyDetection.manageJobsButtonText', { - defaultMessage: 'Manage jobs', - })} - +
+ + {i18n.translate('xpack.ml.overview.anomalyDetection.refreshJobsButtonText', { + defaultMessage: 'Refresh', + })} + + + {i18n.translate('xpack.ml.overview.anomalyDetection.manageJobsButtonText', { + defaultMessage: 'Manage jobs', + })} + +
)} From aa7b38e01894453a942a7936f8c701ffc3838eff Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Tue, 17 Sep 2019 13:14:05 -0600 Subject: [PATCH 12/19] create scss files for styles --- x-pack/legacy/plugins/ml/public/index.scss | 1 + .../ml/public/overview/components/_index.scss | 13 ++++++++++++- .../analytics_panel/_analytics_panel.scss | 3 --- .../components/analytics_panel/analytics_panel.tsx | 4 ++-- .../overview/components/analytics_panel/table.tsx | 6 +----- .../anomaly_detection_panel.tsx | 4 ++-- .../components/anomaly_detection_panel/table.tsx | 2 +- 7 files changed, 19 insertions(+), 14 deletions(-) delete mode 100644 x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/_analytics_panel.scss diff --git a/x-pack/legacy/plugins/ml/public/index.scss b/x-pack/legacy/plugins/ml/public/index.scss index 2a751aea1d831..4cab633d5fa56 100644 --- a/x-pack/legacy/plugins/ml/public/index.scss +++ b/x-pack/legacy/plugins/ml/public/index.scss @@ -26,6 +26,7 @@ @import 'datavisualizer/index'; @import 'explorer/index'; // SASSTODO: This file needs to be rewritten @import 'jobs/index'; // SASSTODO: This collection of sass files has multiple problems + @import 'overview/index'; @import 'settings/index'; @import 'timeseriesexplorer/index'; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/_index.scss b/x-pack/legacy/plugins/ml/public/overview/components/_index.scss index cdf89f6c49871..e7c39544da45b 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/_index.scss +++ b/x-pack/legacy/plugins/ml/public/overview/components/_index.scss @@ -1 +1,12 @@ -@import './analytics_panel/analytics_panel'; +.mlOverviewPanel { + padding-top: 0; +} + +.mlOverviewPanel__buttons { + float: right; +} + +.mlOverviewPanel__statsBar { + margin-top: 0; + margin-right: 0 +} diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/_analytics_panel.scss b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/_analytics_panel.scss deleted file mode 100644 index 201529223832f..0000000000000 --- a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/_analytics_panel.scss +++ /dev/null @@ -1,3 +0,0 @@ -.mlOverviewAnalyticsPanel { - padding-top: 0; -} diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx index a5c0ff1c506e4..91fffbc2c1b2f 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx @@ -49,7 +49,7 @@ export const AnalyticsPanel: FC = () => { ); return ( - + {typeof errorMessage !== 'undefined' && errorDisplay} {isInitialized === false && }      {isInitialized === true && analytics.length === 0 && ( @@ -76,7 +76,7 @@ export const AnalyticsPanel: FC = () => { -
+
{i18n.translate('xpack.ml.overview.analyticsList.refreshJobsButtonText', { defaultMessage: 'Refresh', diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx index 8e6fd1a8f1bd1..6095bf991d0cd 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx @@ -124,11 +124,7 @@ export const AnalyticsTable: FC = ({ items }) => {

- + diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx index 1655f90ee4b2e..223d6c668b61f 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx @@ -162,7 +162,7 @@ export const AnomalyDetectionPanel: FC = () => { ); return ( - + {typeof errorMessage !== 'undefined' && errorDisplay} {isLoading && }    {isLoading === false && typeof errorMessage === 'undefined' && groupsCount === 0 && ( @@ -190,7 +190,7 @@ export const AnomalyDetectionPanel: FC = () => { -
+
{i18n.translate('xpack.ml.overview.anomalyDetection.refreshJobsButtonText', { defaultMessage: 'Refresh', diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx index bf43b82b9e70b..324d14792c11e 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx @@ -169,7 +169,7 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData - + From 1e899ea807151600b3942f6ccc9b9b9219381205 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Tue, 17 Sep 2019 14:57:46 -0600 Subject: [PATCH 13/19] update functional nav tests --- .../components/ml_in_memory_table/ml_in_memory_table.tsx | 1 - .../legacy/plugins/ml/public/overview/components/content.tsx | 1 - x-pack/legacy/plugins/ml/public/overview/route.ts | 3 --- x-pack/test/functional/apps/machine_learning/pages.ts | 4 ++++ .../test/functional/services/machine_learning/navigation.ts | 4 ++++ 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/ml_in_memory_table.tsx b/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/ml_in_memory_table.tsx index a4335e64fafbe..c277d53a02ab0 100644 --- a/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/ml_in_memory_table.tsx +++ b/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/ml_in_memory_table.tsx @@ -71,7 +71,6 @@ const getInitialSorting = (columns: any, sorting: any) => { }; }; -// import { MlInMemoryTable as InMemoryTable } from '../../../common/types/eui/in_memory_table'; import { MlInMemoryTable as InMemoryTable } from './types'; export class MlInMemoryTable extends InMemoryTable { diff --git a/x-pack/legacy/plugins/ml/public/overview/components/content.tsx b/x-pack/legacy/plugins/ml/public/overview/components/content.tsx index 21aedce862662..98295fe0a1a49 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/content.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/content.tsx @@ -6,7 +6,6 @@ import React, { FC } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -// import { FormattedMessage } from '@kbn/i18n/react'; import { AnomalyDetectionPanel } from './anomaly_detection_panel'; import { AnalyticsPanel } from './analytics_panel/'; diff --git a/x-pack/legacy/plugins/ml/public/overview/route.ts b/x-pack/legacy/plugins/ml/public/overview/route.ts index 46de3ab28f8a0..4f5ce7e453f9a 100644 --- a/x-pack/legacy/plugins/ml/public/overview/route.ts +++ b/x-pack/legacy/plugins/ml/public/overview/route.ts @@ -5,9 +5,6 @@ */ import uiRoutes from 'ui/routes'; -// import React from 'react'; -// import { render, unmountComponentAtNode } from 'react-dom'; -// @ts-ignore no declaration module // @ts-ignore no declaration module import { checkFullLicense } from '../license/check_license'; import { checkGetJobsPrivilege } from '../privilege/check_privilege'; diff --git a/x-pack/test/functional/apps/machine_learning/pages.ts b/x-pack/test/functional/apps/machine_learning/pages.ts index f336ae0a570dc..0c997c7389317 100644 --- a/x-pack/test/functional/apps/machine_learning/pages.ts +++ b/x-pack/test/functional/apps/machine_learning/pages.ts @@ -24,6 +24,10 @@ export default function({ getService }: FtrProviderContext) { await ml.navigation.navigateToMl(); }); + it('loads the overview page', async () => { + await ml.navigation.navigateToOverview(); + }); + it('loads the anomaly detection area', async () => { await ml.navigation.navigateToAnomalyDetection(); }); diff --git a/x-pack/test/functional/services/machine_learning/navigation.ts b/x-pack/test/functional/services/machine_learning/navigation.ts index 3ddf7196c52bb..293e43f9ceaf4 100644 --- a/x-pack/test/functional/services/machine_learning/navigation.ts +++ b/x-pack/test/functional/services/machine_learning/navigation.ts @@ -50,6 +50,10 @@ export function MachineLearningNavigationProvider({ ]); }, + async navigateToOverview() { + await this.navigateToArea('mlTabOverview', 'mlPageOverview'); + }, + async navigateToAnomalyDetection() { await this.navigateToArea('mlMainTab anomalyDetection', 'mlPageJobManagement'); await this.assertTabsExist('mlSubTab', [ From e682a0c0601d77c1dfa846887bb583a2d13c0a18 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Wed, 18 Sep 2019 13:14:42 -0600 Subject: [PATCH 14/19] fix functional test failure --- .../apps/machine_learning/feature_controls/ml_spaces.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts index ea3c10f16ca60..b81656cd6c4b7 100644 --- a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts @@ -51,7 +51,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { basePath: '/s/custom_space', }); - await testSubjects.existOrFail('ml-jobs-list'); + await testSubjects.existOrFail('mlPageOverview'); }); }); From 59c980893ca72c667bda1921584b74d5907f35bf Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Wed, 18 Sep 2019 13:17:02 -0600 Subject: [PATCH 15/19] fix anomalyDetection for when there are no jobs --- .../anomaly_detection_panel/anomaly_detection_panel.tsx | 4 ++-- .../overview/components/anomaly_detection_panel/utils.ts | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx index 223d6c668b61f..9c7c9c9da8581 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx @@ -75,13 +75,13 @@ export const AnomalyDetectionPanel: FC = () => { job.latestTimestampSortValue = job.latestTimestampMs || 0; return job; }); - const jobsGroups = getGroupsFromJobs(jobsSummaryList); + const { groups: jobsGroups, count } = getGroupsFromJobs(jobsSummaryList); const jobsWithTimerange = getJobsWithTimerange(jobsSummaryList); const stats = getStatsBarData(jobsSummaryList); setIsLoading(false); setErrorMessage(undefined); setStatsBarData(stats); - setGroupsCount(Object.values(jobsGroups).length); + setGroupsCount(count); setGroups(jobsGroups); setJobsList(jobsWithTimerange); loadMaxAnomalyScores(jobsGroups); diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts index aa68e275853ab..9410eb4264456 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts @@ -60,7 +60,13 @@ export function getGroupsFromJobs(jobs: Jobs): Groups { } }); - return groups; + if (groups.ungrouped.jobIds.length === 0) { + delete groups.ungrouped; + } + + const count = Object.values(groups).length; + + return { groups, count }; } export function getStatsBarData(jobsList: any) { From ce6b3fa485554c37baa628f9315713628bba8152 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Wed, 18 Sep 2019 13:56:59 -0600 Subject: [PATCH 16/19] add translations and update jobList types --- x-pack/legacy/plugins/ml/common/types/jobs.ts | 24 ++++++ .../components/ml_in_memory_table/types.ts | 1 + .../ml/public/components/stats_bar/index.ts | 7 +- .../components/analytics_list/actions.tsx | 4 +- .../components/analytics_list/columns.tsx | 4 +- .../analytics_panel/analytics_panel.tsx | 26 ++++-- .../analytics_panel/analytics_stats_bar.tsx | 4 +- .../components/analytics_panel/table.tsx | 11 +-- .../anomaly_detection_panel/actions.tsx | 3 +- .../anomaly_detection_panel.tsx | 80 +++++++++---------- .../anomaly_detection_panel/table.tsx | 11 +-- .../anomaly_detection_panel/utils.ts | 32 ++++++-- .../ml/public/overview/components/sidebar.tsx | 2 +- .../public/services/ml_api_service/index.d.ts | 3 +- 14 files changed, 136 insertions(+), 76 deletions(-) diff --git a/x-pack/legacy/plugins/ml/common/types/jobs.ts b/x-pack/legacy/plugins/ml/common/types/jobs.ts index 7b18ebccd5244..07c2be3e7f0b4 100644 --- a/x-pack/legacy/plugins/ml/common/types/jobs.ts +++ b/x-pack/legacy/plugins/ml/common/types/jobs.ts @@ -39,6 +39,30 @@ export interface MlJob { state: string; } +export interface MlSummaryJob { + id: string; + description: string; + groups: string[]; + processed_record_count: number; + memory_status?: string; + jobState: string; + hasDatafeed: boolean; + datafeedId?: string; + datafeedIndices: any[]; + datafeedState?: string; + latestTimestampMs: number; + earliestTimestampMs?: number; + latestResultsTimestampMs: number; + isSingleMetricViewerJob: boolean; + nodeName?: string; + deleting?: boolean; + fullJob?: any; + auditMessage?: any; + latestTimestampSortValue?: number; +} + +export type MlSummaryJobs = MlSummaryJob[]; + export function isMlJob(arg: any): arg is MlJob { return typeof arg.job_id === 'string'; } diff --git a/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/types.ts b/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/types.ts index d5a0a50fe6a54..434384cefa336 100644 --- a/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/types.ts +++ b/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/types.ts @@ -30,6 +30,7 @@ export interface FieldDataColumnType { truncateText?: boolean; render?: RenderFunc; footer?: string | ReactElement | FooterFunc; + textOnly?: boolean; } export interface ComputedColumnType { diff --git a/x-pack/legacy/plugins/ml/public/components/stats_bar/index.ts b/x-pack/legacy/plugins/ml/public/components/stats_bar/index.ts index 3994d77780c04..003378cfc14a5 100644 --- a/x-pack/legacy/plugins/ml/public/components/stats_bar/index.ts +++ b/x-pack/legacy/plugins/ml/public/components/stats_bar/index.ts @@ -4,4 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export { StatsBar, TransformStatsBarStats, AnalyticStatsBarStats } from './stats_bar'; +export { + StatsBar, + TransformStatsBarStats, + AnalyticStatsBarStats, + JobStatsBarStats, +} from './stats_bar'; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx index ebc9b7c6a4c1e..f2d19027e3430 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx @@ -21,7 +21,7 @@ import { stopAnalytics } from '../../services/analytics_service'; import { StartAction } from './action_start'; import { DeleteAction } from './action_delete'; -export const ANALYTICS_VIEW_ACTION = { +export const AnalyticsViewAction = { isPrimary: true, render: (item: DataFrameAnalyticsListRow) => { return ( @@ -47,7 +47,7 @@ export const getActions = () => { const canStartStopDataFrameAnalytics: boolean = checkPermission('canStartStopDataFrameAnalytics'); return [ - ANALYTICS_VIEW_ACTION, + AnalyticsViewAction, { render: (item: DataFrameAnalyticsListRow) => { if (!isDataFrameAnalyticsRunning(item.stats)) { diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx index 2f95ee94a794a..07adff6c3e415 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx @@ -58,7 +58,7 @@ export const getTaskStateBadge = ( ); }; -export const PROGRESS_COLUMN = { +export const progressColumn = { name: i18n.translate('xpack.ml.dataframe.analyticsList.progress', { defaultMessage: 'Progress', }), @@ -217,7 +217,7 @@ export const getColumns = ( width: '100px', }, */ - PROGRESS_COLUMN, + progressColumn, ]; if (isManagementTable === true) { diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx index 91fffbc2c1b2f..6f573e2e454b9 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx @@ -18,7 +18,7 @@ import { i18n } from '@kbn/i18n'; import { AnalyticsTable } from './table'; import { getAnalyticsFactory } from '../../../data_frame_analytics/pages/analytics_management/services/analytics_service'; import { DataFrameAnalyticsListRow } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common'; -// TODO: panels can be smaller when empty + export const AnalyticsPanel: FC = () => { const [analytics, setAnalytics] = useState([]); const [errorMessage, setErrorMessage] = useState(undefined); @@ -43,7 +43,11 @@ export const AnalyticsPanel: FC = () => { color="danger" iconType="alert" > -
{JSON.stringify(errorMessage)}
+
+          {errorMessage && errorMessage.message !== undefined
+            ? errorMessage.message
+            : JSON.stringify(errorMessage)}
+        
); @@ -55,19 +59,27 @@ export const AnalyticsPanel: FC = () => { {isInitialized === true && analytics.length === 0 && ( Create your first analytics job} + title={ +

+ {i18n.translate('xpack.ml.overview.analyticsList.createFirstJobMessage', { + defaultMessage: 'Create your first analytics job.', + })} +

+ } body={

- Data frame analytics enable you to perform different analyses of your data and - annotate it with the results. As part of its output, data frame analytics appends - the results of the analysis to the source data. + {i18n.translate('xpack.ml.overview.analyticsList.emptyPromptText', { + defaultMessage: `Data frame analytics enable you to perform different analyses of your data and annotate it with the results. As part of its output, data frame analytics appends the results of the analysis to the source data.`, + })}

} actions={ - Create job + {i18n.translate('xpack.ml.overview.analyticsList.createJobButtonText', { + defaultMessage: 'Create job.', + })} } /> diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_stats_bar.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_stats_bar.tsx index 3fda20c25531b..fb841afd8f449 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_stats_bar.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_stats_bar.tsx @@ -12,7 +12,7 @@ import { DATA_FRAME_TASK_STATE, } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common'; -function createAnalyticsStats(analyticsList: any[]) { +function getAnalyticsStats(analyticsList: any[]) { const analyticsStats = { total: { label: i18n.translate('xpack.ml.overview.statsBar.totalAnalyticsLabel', { @@ -81,7 +81,7 @@ interface Props { } export const AnalyticsStatsBar: FC = ({ analyticsList }) => { - const analyticsStats: AnalyticStatsBarStats = createAnalyticsStats(analyticsList); + const analyticsStats: AnalyticStatsBarStats = getAnalyticsStats(analyticsList); return ; }; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx index 6095bf991d0cd..1ac767ab97700 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/table.tsx @@ -12,6 +12,7 @@ import { SortDirection, SORT_DIRECTION, OnTableChangeArg, + ColumnType, } from '../../../components/ml_in_memory_table'; import { getAnalysisType } from '../../../data_frame_analytics/common/analytics'; import { @@ -20,9 +21,9 @@ import { } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common'; import { getTaskStateBadge, - PROGRESS_COLUMN, + progressColumn, } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/columns'; -import { ANALYTICS_VIEW_ACTION } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/actions'; +import { AnalyticsViewAction } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/actions'; import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils'; import { AnalyticsStatsBar } from './analytics_stats_bar'; @@ -37,7 +38,7 @@ export const AnalyticsTable: FC = ({ items }) => { const [sortDirection, setSortDirection] = useState(SORT_DIRECTION.ASC); // id, type, status, progress, created time, view icon - const columns: any[] = [ + const columns: ColumnType[] = [ { field: DataFrameAnalyticsListColumn.id, name: i18n.translate('xpack.ml.overview.analyticsList.id', { defaultMessage: 'ID' }), @@ -63,7 +64,7 @@ export const AnalyticsTable: FC = ({ items }) => { }, width: '100px', }, - PROGRESS_COLUMN, + progressColumn, { field: DataFrameAnalyticsListColumn.configCreateTime, name: i18n.translate('xpack.ml.overview.analyticsList.reatedTimeColumnName', { @@ -79,7 +80,7 @@ export const AnalyticsTable: FC = ({ items }) => { name: i18n.translate('xpack.ml.overview.analyticsList.tableActionLabel', { defaultMessage: 'Actions', }), - actions: [ANALYTICS_VIEW_ACTION], + actions: [AnalyticsViewAction], width: '100px', }, ]; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/actions.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/actions.tsx index 6f14e26932df3..e865fd44c2a19 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/actions.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/actions.tsx @@ -9,9 +9,10 @@ import { EuiToolTip, EuiButtonEmpty } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; // @ts-ignore no module file import { getLink } from '../../../jobs/jobs_list/components/job_actions/results'; +import { MlSummaryJobs } from '../../../../common/types/jobs'; interface Props { - jobsList: any; + jobsList: MlSummaryJobs; } export const ExplorerLink: FC = ({ jobsList }) => { diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx index 9c7c9c9da8581..8cdcdcd2659f0 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx @@ -20,10 +20,10 @@ import { AnomalyDetectionTable } from './table'; import { ml } from '../../../services/ml_api_service'; import { mlResultsService } from '../../../services/results_service'; import { getGroupsFromJobs, getStatsBarData, getJobsWithTimerange } from './utils'; +import { Dictionary } from '../../../../common/types/common'; +import { MlSummaryJobs, MlSummaryJob } from '../../../../common/types/jobs'; -export interface Groups { - [key: string]: Group; -} +export type GroupsDictionary = Dictionary; export interface Group { id: string; @@ -34,18 +34,10 @@ export interface Group { max_anomaly_score: number | null; } -export interface Job { - [key: string]: any; -} - -export type Jobs = Job[]; - -interface MaxScoresByGroup { - [key: string]: { - maxScore: number; - index?: number; - }; -} +type MaxScoresByGroup = Dictionary<{ + maxScore: number; + index?: number; +}>; const createJobLink = '#/jobs/new_job/step/index_or_search'; @@ -60,9 +52,9 @@ function getDefaultAnomalyScores(groups: Group[]): MaxScoresByGroup { export const AnomalyDetectionPanel: FC = () => { const [isLoading, setIsLoading] = useState(false); - const [groups, setGroups] = useState({}); + const [groups, setGroups] = useState({}); const [groupsCount, setGroupsCount] = useState(0); - const [jobsList, setJobsList] = useState([]); + const [jobsList, setJobsList] = useState([]); const [statsBarData, setStatsBarData] = useState(undefined); const [errorMessage, setErrorMessage] = useState(undefined); @@ -70,8 +62,8 @@ export const AnomalyDetectionPanel: FC = () => { setIsLoading(true); try { - const jobsResult: Jobs | any = await ml.jobs.jobsSummary([]); - const jobsSummaryList = jobsResult.map((job: any) => { + const jobsResult: MlSummaryJobs = await ml.jobs.jobsSummary([]); + const jobsSummaryList = jobsResult.map((job: MlSummaryJob) => { job.latestTimestampSortValue = job.latestTimestampMs || 0; return job; }); @@ -86,33 +78,30 @@ export const AnomalyDetectionPanel: FC = () => { setJobsList(jobsWithTimerange); loadMaxAnomalyScores(jobsGroups); } catch (e) { - setErrorMessage(JSON.stringify(e)); + setErrorMessage(e.message !== undefined ? e.message : JSON.stringify(e)); setIsLoading(false); } }; - const loadMaxAnomalyScores = async (groupsObject: Groups) => { + const loadMaxAnomalyScores = async (groupsObject: GroupsDictionary) => { const groupsList: Group[] = Object.values(groupsObject); const scores = getDefaultAnomalyScores(groupsList); try { - const promises: any[] = []; - groupsList.forEach((group, i) => { - if (group.jobIds.length > 0) { + const promises = groupsList + .filter(group => group.jobIds.length > 0) + .map((group, i) => { scores[group.id].index = i; - promises.push( - mlResultsService.getOverallBucketScores( - group.jobIds, - 1, - group.earliest_timestamp, - group.latest_timestamp - ) + return mlResultsService.getOverallBucketScores( + group.jobIds, + 1, + group.earliest_timestamp, + group.latest_timestamp ); - } - }); + }); const results = await Promise.all(promises); - const tempGroups = Object.assign({}, { ...groupsObject }); + const tempGroups = { ...groupsObject }; // Check results for each group's promise index and update state Object.keys(scores).forEach(groupId => { const resultsIndex = scores[groupId] && scores[groupId].index; @@ -132,7 +121,7 @@ export const AnomalyDetectionPanel: FC = () => { 'xpack.ml.overview.anomalyDetection.errorWithFetchingAnomalyScoreNotificationErrorMessage', { defaultMessage: 'An error occurred fetching anomaly scores: {error}', - values: { error: JSON.stringify(e) }, + values: { error: e.message !== undefined ? e.message : JSON.stringify(e) }, } ) ); @@ -156,7 +145,7 @@ export const AnomalyDetectionPanel: FC = () => { color="danger" iconType="alert" > -
{JSON.stringify(errorMessage)}
+
{errorMessage}
); @@ -168,20 +157,27 @@ export const AnomalyDetectionPanel: FC = () => { {isLoading === false && typeof errorMessage === 'undefined' && groupsCount === 0 && ( Create your first anomaly detection job} + title={ +

+ {i18n.translate('xpack.ml.overview.anomalyDetection.createFirstJobMessage', { + defaultMessage: 'Create your first anomaly detection job.', + })} +

+ } body={

- Machine learning makes it easy to detect anomalies in time series data stored in - Elasticsearch. Track one metric from a single machine or hundreds of metrics across - thousands of machines. Start automatically spotting the anomalies hiding in your - data and resolve issues faster.               + {i18n.translate('xpack.ml.overview.anomalyDetection.emptyPromptText', { + defaultMessage: `Machine learning makes it easy to detect anomalies in time series data stored in Elasticsearch. Track one metric from a single machine or hundreds of metrics across thousands of machines. Start automatically spotting the anomalies hiding in your data and resolve issues faster.`, + })}

} actions={ - Create job + {i18n.translate('xpack.ml.overview.anomalyDetection.createJobButtonText', { + defaultMessage: 'Create job.', + })} } /> diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx index 324d14792c11e..a9c5ae0e5f31b 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/table.tsx @@ -19,12 +19,13 @@ import { SortDirection, SORT_DIRECTION, OnTableChangeArg, + ColumnType, } from '../../../components/ml_in_memory_table'; import { formatHumanReadableDateTimeSeconds } from '../../../util/date_utils'; import { ExplorerLink } from './actions'; import { getJobsFromGroup } from './utils'; -import { Groups, Group, Jobs } from './anomaly_detection_panel'; -// @ts-ignore +import { GroupsDictionary, Group } from './anomaly_detection_panel'; +import { MlSummaryJobs } from '../../../../common/types/jobs'; import { StatsBar, JobStatsBarStats } from '../../../components/stats_bar'; // @ts-ignore import { JobSelectorBadge } from '../../../components/job_selector/job_selector_badge'; @@ -42,9 +43,9 @@ export enum AnomalyDetectionListColumns { } interface Props { - items: Groups; + items: GroupsDictionary; statsBarData: JobStatsBarStats; - jobsList: Jobs; + jobsList: MlSummaryJobs; } export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData }) => { @@ -56,7 +57,7 @@ export const AnomalyDetectionTable: FC = ({ items, jobsList, statsBarData const [sortDirection, setSortDirection] = useState(SORT_DIRECTION.ASC); // columns: group, max anomaly, jobs in group, latest timestamp, docs processed, action to explorer - const columns: any[] = [ + const columns: ColumnType[] = [ { field: AnomalyDetectionListColumns.id, name: i18n.translate('xpack.ml.overview.anomalyDetection.tableId', { diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts index 9410eb4264456..d346562eceed2 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts @@ -6,9 +6,12 @@ import { i18n } from '@kbn/i18n'; import { JOB_STATE, DATAFEED_STATE } from '../../../../common/constants/states'; -import { Group, Groups, Jobs, Job } from './anomaly_detection_panel'; +import { Group, GroupsDictionary } from './anomaly_detection_panel'; +import { MlSummaryJobs, MlSummaryJob } from '../../../../common/types/jobs'; -export function getGroupsFromJobs(jobs: Jobs): Groups { +export function getGroupsFromJobs( + jobs: MlSummaryJobs +): { groups: GroupsDictionary; count: number } { const groups: any = { ungrouped: { id: 'ungrouped', @@ -19,7 +22,7 @@ export function getGroupsFromJobs(jobs: Jobs): Groups { }, }; - jobs.forEach((job: any) => { + jobs.forEach((job: MlSummaryJob) => { // Organize job by group if (job.groups.length > 0) { job.groups.forEach((g: any) => { @@ -43,9 +46,12 @@ export function getGroupsFromJobs(jobs: Jobs): Groups { } // if existing group's earliest timestamp is not defined, set it to current job's // otherwise compare to current job's and replace if current job's earliest is less - if (groups[g].earliest_timestamp === undefined) { + if (groups[g].earliest_timestamp === undefined && job.earliestTimestampMs !== undefined) { groups[g].earliest_timestamp = job.earliestTimestampMs; - } else if (job.earliestTimestampMs < groups[g].earliest_timestamp) { + } else if ( + job.earliestTimestampMs !== undefined && + job.earliestTimestampMs < groups[g].earliest_timestamp + ) { groups[g].earliest_timestamp = job.earliestTimestampMs; } } @@ -57,6 +63,18 @@ export function getGroupsFromJobs(jobs: Jobs): Groups { if (job.latestTimestampMs > groups.ungrouped.latest_timestamp) { groups.ungrouped.latest_timestamp = job.latestTimestampMs; } + + if ( + groups.ungrouped.earliest_timestamp === undefined && + job.earliestTimestampMs !== undefined + ) { + groups.ungrouped.earliest_timestamp = job.earliestTimestampMs; + } else if ( + job.earliestTimestampMs !== undefined && + job.earliestTimestampMs < groups.ungrouped.earliest_timestamp + ) { + groups.ungrouped.earliest_timestamp = job.earliestTimestampMs; + } } }); @@ -123,7 +141,7 @@ export function getStatsBarData(jobsList: any) { const mlNodes: any = {}; let failedJobs = 0; - jobsList.forEach((job: Job) => { + jobsList.forEach((job: MlSummaryJob) => { if (job.jobState === JOB_STATE.OPENED) { jobStats.open.value++; } else if (job.jobState === JOB_STATE.CLOSED) { @@ -143,7 +161,7 @@ export function getStatsBarData(jobsList: any) { jobStats.total.value = jobsList.length; - // // Only show failed jobs if it is non-zero + // Only show failed jobs if it is non-zero if (failedJobs) { jobStats.failed.value = failedJobs; jobStats.failed.show = true; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx b/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx index 02e1efe6588a2..26b67c7774978 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx @@ -60,7 +60,7 @@ export const OverviewSideBar: FC = () => ( ), diff --git a/x-pack/legacy/plugins/ml/public/services/ml_api_service/index.d.ts b/x-pack/legacy/plugins/ml/public/services/ml_api_service/index.d.ts index c593da3f2c4ee..4cfa7a53f7e83 100644 --- a/x-pack/legacy/plugins/ml/public/services/ml_api_service/index.d.ts +++ b/x-pack/legacy/plugins/ml/public/services/ml_api_service/index.d.ts @@ -12,6 +12,7 @@ import { DataFrameTransformEndpointRequest, DataFrameTransformEndpointResult, } from '../../data_frame/pages/transform_management/components/transform_list/common'; +import { MlSummaryJobs } from '../../../common/types/jobs'; // TODO This is not a complete representation of all methods of `ml.*`. // It just satisfies needs for other parts of the code area which use @@ -88,7 +89,7 @@ declare interface Ml { getVisualizerOverallStats(obj: object): Promise; jobs: { - jobsSummary(jobIds: string[]): Promise; + jobsSummary(jobIds: string[]): Promise; jobs(jobIds: string[]): Promise; groups(): Promise; updateGroups(updatedJobs: string[]): Promise; From 640f802a3333c742f1b8cd95d47ff936f5d8c2db Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Mon, 23 Sep 2019 07:59:24 -0600 Subject: [PATCH 17/19] add maxAnomalyScore endpoint --- .../analytics_panel/analytics_panel.tsx | 2 +- .../anomaly_detection_panel.tsx | 21 ++---- .../anomaly_detection_panel/utils.ts | 23 ------- .../public/services/ml_api_service/index.d.ts | 4 ++ .../public/services/ml_api_service/results.js | 17 +++++ .../models/results_service/results_service.js | 68 +++++++++++++++++++ .../ml/server/routes/results_service.js | 26 +++++++ 7 files changed, 123 insertions(+), 38 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx index 6f573e2e454b9..d45a137d9e9a3 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx @@ -70,7 +70,7 @@ export const AnalyticsPanel: FC = () => {

{i18n.translate('xpack.ml.overview.analyticsList.emptyPromptText', { - defaultMessage: `Data frame analytics enable you to perform different analyses of your data and annotate it with the results. As part of its output, data frame analytics appends the results of the analysis to the source data.`, + defaultMessage: `Data frame analytics enable you to perform different analyses of your data and annotate it with the results. The analytics job stores the annotated data, as well as a copy of the source data, in a new index.`, })}

diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx index 8cdcdcd2659f0..126bc27f8bda9 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx @@ -15,10 +15,10 @@ import { EuiSpacer, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import moment from 'moment'; import { toastNotifications } from 'ui/notify'; import { AnomalyDetectionTable } from './table'; import { ml } from '../../../services/ml_api_service'; -import { mlResultsService } from '../../../services/results_service'; import { getGroupsFromJobs, getStatsBarData, getJobsWithTimerange } from './utils'; import { Dictionary } from '../../../../common/types/common'; import { MlSummaryJobs, MlSummaryJob } from '../../../../common/types/jobs'; @@ -92,12 +92,10 @@ export const AnomalyDetectionPanel: FC = () => { .filter(group => group.jobIds.length > 0) .map((group, i) => { scores[group.id].index = i; - return mlResultsService.getOverallBucketScores( - group.jobIds, - 1, - group.earliest_timestamp, - group.latest_timestamp - ); + const latestTimestamp = group.latest_timestamp; + const startMoment = moment(latestTimestamp); + const twentyFourHoursAgo = startMoment.subtract(24, 'hours').valueOf(); + return ml.results.getMaxAnomalyScore(group.jobIds, twentyFourHoursAgo, latestTimestamp); }); const results = await Promise.all(promises); @@ -105,13 +103,8 @@ export const AnomalyDetectionPanel: FC = () => { // Check results for each group's promise index and update state Object.keys(scores).forEach(groupId => { const resultsIndex = scores[groupId] && scores[groupId].index; - const promiseResult: { success: boolean; results: { [key: string]: number } } = - resultsIndex !== undefined && results[resultsIndex]; - if (promiseResult.success === true && promiseResult.results !== undefined) { - const maxScore = Math.max(...Object.values(promiseResult.results)); - scores[groupId] = { maxScore }; - tempGroups[groupId].max_anomaly_score = maxScore; - } + scores[groupId] = results[resultsIndex]; + tempGroups[groupId].max_anomaly_score = results[resultsIndex]; }); setGroups(tempGroups); diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts index d346562eceed2..f33b60853ea66 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/utils.ts @@ -31,7 +31,6 @@ export function getGroupsFromJobs( id: g, jobIds: [job.id], docs_processed: job.processed_record_count, - earliest_timestamp: job.earliestTimestampMs, latest_timestamp: job.latestTimestampMs, max_anomaly_score: null, }; @@ -44,16 +43,6 @@ export function getGroupsFromJobs( } else if (job.latestTimestampMs > groups[g].latest_timestamp) { groups[g].latest_timestamp = job.latestTimestampMs; } - // if existing group's earliest timestamp is not defined, set it to current job's - // otherwise compare to current job's and replace if current job's earliest is less - if (groups[g].earliest_timestamp === undefined && job.earliestTimestampMs !== undefined) { - groups[g].earliest_timestamp = job.earliestTimestampMs; - } else if ( - job.earliestTimestampMs !== undefined && - job.earliestTimestampMs < groups[g].earliest_timestamp - ) { - groups[g].earliest_timestamp = job.earliestTimestampMs; - } } }); } else { @@ -63,18 +52,6 @@ export function getGroupsFromJobs( if (job.latestTimestampMs > groups.ungrouped.latest_timestamp) { groups.ungrouped.latest_timestamp = job.latestTimestampMs; } - - if ( - groups.ungrouped.earliest_timestamp === undefined && - job.earliestTimestampMs !== undefined - ) { - groups.ungrouped.earliest_timestamp = job.earliestTimestampMs; - } else if ( - job.earliestTimestampMs !== undefined && - job.earliestTimestampMs < groups.ungrouped.earliest_timestamp - ) { - groups.ungrouped.earliest_timestamp = job.earliestTimestampMs; - } } }); diff --git a/x-pack/legacy/plugins/ml/public/services/ml_api_service/index.d.ts b/x-pack/legacy/plugins/ml/public/services/ml_api_service/index.d.ts index 4cfa7a53f7e83..475e723f9fbc4 100644 --- a/x-pack/legacy/plugins/ml/public/services/ml_api_service/index.d.ts +++ b/x-pack/legacy/plugins/ml/public/services/ml_api_service/index.d.ts @@ -88,6 +88,10 @@ declare interface Ml { getVisualizerFieldStats(obj: object): Promise; getVisualizerOverallStats(obj: object): Promise; + results: { + getMaxAnomalyScore: (jobIds: string[], earliestMs: number, latestMs: number) => Promise; // THIS ONE IS RIGHT + }; + jobs: { jobsSummary(jobIds: string[]): Promise; jobs(jobIds: string[]): Promise; diff --git a/x-pack/legacy/plugins/ml/public/services/ml_api_service/results.js b/x-pack/legacy/plugins/ml/public/services/ml_api_service/results.js index aab8e8bd70abc..7a776d61dca21 100644 --- a/x-pack/legacy/plugins/ml/public/services/ml_api_service/results.js +++ b/x-pack/legacy/plugins/ml/public/services/ml_api_service/results.js @@ -45,6 +45,23 @@ export const results = { }); }, + getMaxAnomalyScore( + jobIds, + earliestMs, + latestMs + ) { + + return http({ + url: `${basePath}/results/max_anomaly_score`, + method: 'POST', + data: { + jobIds, + earliestMs, + latestMs + } + }); + }, + getCategoryDefinition(jobId, categoryId) { return http({ url: `${basePath}/results/category_definition`, diff --git a/x-pack/legacy/plugins/ml/server/models/results_service/results_service.js b/x-pack/legacy/plugins/ml/server/models/results_service/results_service.js index 7c00c3540eca4..3fd20308b2f9b 100644 --- a/x-pack/legacy/plugins/ml/server/models/results_service/results_service.js +++ b/x-pack/legacy/plugins/ml/server/models/results_service/results_service.js @@ -200,6 +200,73 @@ export function resultsServiceProvider(callWithRequest) { } + // Returns the maximum anomaly_score for result_type:bucket over jobIds for the interval passed in + async function getMaxAnomalyScore(jobIds = [], earliestMs, latestMs) { + // Build the criteria to use in the bool filter part of the request. + // Adds criteria for the time range plus any specified job IDs. + const boolCriteria = [ + { + range: { + timestamp: { + gte: earliestMs, + lte: latestMs, + format: 'epoch_millis' + } + } + } + ]; + + if (jobIds.length > 0) { + let jobIdFilterStr = ''; + jobIds.forEach((jobId, i) => { + if (i > 0) { + jobIdFilterStr += ' OR '; + } + jobIdFilterStr += 'job_id:'; + jobIdFilterStr += jobId; + }); + boolCriteria.push({ + query_string: { + analyze_wildcard: false, + query: jobIdFilterStr + } + }); + } + + const query = { + size: 0, + index: ML_RESULTS_INDEX_PATTERN, + body: { + query: { + bool: { + filter: [{ + query_string: { + query: 'result_type:bucket', + analyze_wildcard: false + } + }, { + bool: { + must: boolCriteria + } + }] + } + }, + aggs: { + max_score: { + max: { + field: 'anomaly_score' + } + } + } + } + }; + + const resp = await callWithRequest('search', query); + const maxScore = _.get(resp, ['aggregations', 'max_score', 'value'], null); + + return maxScore; + } + // Obtains the latest bucket result timestamp by job ID. // Returns data over all jobs unless an optional list of job IDs of interest is supplied. // Returned response consists of latest bucket timestamps (ms since Jan 1 1970) against job ID @@ -342,6 +409,7 @@ export function resultsServiceProvider(callWithRequest) { getCategoryDefinition, getCategoryExamples, getLatestBucketTimestampByJob, + getMaxAnomalyScore }; } diff --git a/x-pack/legacy/plugins/ml/server/routes/results_service.js b/x-pack/legacy/plugins/ml/server/routes/results_service.js index 9f1869ed55007..82688eb7faf20 100644 --- a/x-pack/legacy/plugins/ml/server/routes/results_service.js +++ b/x-pack/legacy/plugins/ml/server/routes/results_service.js @@ -58,6 +58,19 @@ function getCategoryExamples(callWithRequest, payload) { maxExamples); } + +function getMaxAnomalyScore(callWithRequest, payload) { + const rs = resultsServiceProvider(callWithRequest); + const { + jobIds, + earliestMs, + latestMs } = payload; + return rs.getMaxAnomalyScore( + jobIds, + earliestMs, + latestMs); +} + export function resultsServiceRoutes({ commonRouteConfig, elasticsearchPlugin, route }) { route({ @@ -86,6 +99,19 @@ export function resultsServiceRoutes({ commonRouteConfig, elasticsearchPlugin, r } }); + route({ + method: 'POST', + path: '/api/ml/results/max_anomaly_score', + handler(request) { + const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request); + return getMaxAnomalyScore(callWithRequest, request.payload) + .catch(resp => wrapError(resp)); + }, + config: { + ...commonRouteConfig + } + }); + route({ method: 'POST', path: '/api/ml/results/category_examples', From a9dfe1cd94d68fb7d969acd927bdeb1fc4f3ee65 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Mon, 23 Sep 2019 08:32:21 -0600 Subject: [PATCH 18/19] fix types and update tab testSub --- .../ml/public/components/navigation_menu/main_tabs.tsx | 2 +- .../anomaly_detection_panel/anomaly_detection_panel.tsx | 4 ++-- .../test/functional/services/machine_learning/navigation.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/components/navigation_menu/main_tabs.tsx b/x-pack/legacy/plugins/ml/public/components/navigation_menu/main_tabs.tsx index 19e0b34ad0c9c..0ba38abb454e2 100644 --- a/x-pack/legacy/plugins/ml/public/components/navigation_menu/main_tabs.tsx +++ b/x-pack/legacy/plugins/ml/public/components/navigation_menu/main_tabs.tsx @@ -66,7 +66,7 @@ interface TabData { } const TAB_DATA: Record = { - overview: { testSubject: 'mlTabOverview', pathId: 'overview' }, + overview: { testSubject: 'mlMainTab overview', pathId: 'overview' }, anomaly_detection: { testSubject: 'mlMainTab anomalyDetection', pathId: 'jobs' }, data_frames: { testSubject: 'mlMainTab dataFrames' }, data_frame_analytics: { testSubject: 'mlMainTab dataFrameAnalytics' }, diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx index 126bc27f8bda9..bb1a2a6c68e92 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx @@ -103,8 +103,8 @@ export const AnomalyDetectionPanel: FC = () => { // Check results for each group's promise index and update state Object.keys(scores).forEach(groupId => { const resultsIndex = scores[groupId] && scores[groupId].index; - scores[groupId] = results[resultsIndex]; - tempGroups[groupId].max_anomaly_score = results[resultsIndex]; + scores[groupId] = resultsIndex !== undefined && results[resultsIndex]; + tempGroups[groupId].max_anomaly_score = resultsIndex !== undefined && results[resultsIndex]; }); setGroups(tempGroups); diff --git a/x-pack/test/functional/services/machine_learning/navigation.ts b/x-pack/test/functional/services/machine_learning/navigation.ts index 293e43f9ceaf4..b73cd9825b1bd 100644 --- a/x-pack/test/functional/services/machine_learning/navigation.ts +++ b/x-pack/test/functional/services/machine_learning/navigation.ts @@ -51,7 +51,7 @@ export function MachineLearningNavigationProvider({ }, async navigateToOverview() { - await this.navigateToArea('mlTabOverview', 'mlPageOverview'); + await this.navigateToArea('mlMainTab overview', 'mlPageOverview'); }, async navigateToAnomalyDetection() { From d981ba0a0f7c62b781cc099bff9ef1c2fa58d7c7 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Mon, 23 Sep 2019 10:08:50 -0600 Subject: [PATCH 19/19] fix transforms use of inMemoryTable --- .../components/ml_in_memory_table/ml_in_memory_table.tsx | 4 ++-- .../ml/public/components/ml_in_memory_table/types.ts | 2 +- .../source_index_preview/source_index_preview.tsx | 4 ++-- .../components/step_define/pivot_preview.tsx | 4 ++-- .../components/transform_list/transform_table.tsx | 4 ++-- .../components/exploration/exploration.tsx | 4 ++-- .../components/analytics_panel/analytics_stats_bar.tsx | 6 +++++- 7 files changed, 16 insertions(+), 12 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/ml_in_memory_table.tsx b/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/ml_in_memory_table.tsx index c277d53a02ab0..d5316b22a6a6f 100644 --- a/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/ml_in_memory_table.tsx +++ b/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/ml_in_memory_table.tsx @@ -71,9 +71,9 @@ const getInitialSorting = (columns: any, sorting: any) => { }; }; -import { MlInMemoryTable as InMemoryTable } from './types'; +import { MlInMemoryTableBasic } from './types'; -export class MlInMemoryTable extends InMemoryTable { +export class MlInMemoryTable extends MlInMemoryTableBasic { static getDerivedStateFromProps(nextProps: any, prevState: any) { const derivedState = { ...prevState.prevProps, diff --git a/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/types.ts b/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/types.ts index 434384cefa336..22c35fc453b88 100644 --- a/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/types.ts +++ b/x-pack/legacy/plugins/ml/public/components/ml_in_memory_table/types.ts @@ -192,6 +192,6 @@ interface ComponentWithConstructor extends Component { new (): Component; } -export const MlInMemoryTable = (EuiInMemoryTable as any) as ComponentWithConstructor< +export const MlInMemoryTableBasic = (EuiInMemoryTable as any) as ComponentWithConstructor< EuiInMemoryTableProps >; diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/source_index_preview/source_index_preview.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/source_index_preview/source_index_preview.tsx index 7ceb97816b609..7c7b211fe43db 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/source_index_preview/source_index_preview.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/source_index_preview/source_index_preview.tsx @@ -30,7 +30,7 @@ import { import { ColumnType, - MlInMemoryTable, + MlInMemoryTableBasic, SortingPropType, SORT_DIRECTION, } from '../../../../../components/ml_in_memory_table'; @@ -405,7 +405,7 @@ export const SourceIndexPreview: React.SFC = React.memo(({ cellClick, que )} {clearTable === false && columns.length > 0 && sorting !== false && ( - = React.memo(({ aggs, groupBy, )} {dataFramePreviewData.length > 0 && clearTable === false && columns.length > 0 && ( - { }; }; -export class TransformTable extends MlInMemoryTable { +export class TransformTable extends MlInMemoryTableBasic { static getDerivedStateFromProps(nextProps: any, prevState: any) { const derivedState = { ...prevState.prevProps, diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx index 9b497b430c9e4..95ec334667547 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx @@ -32,7 +32,7 @@ import euiThemeDark from '@elastic/eui/dist/eui_theme_dark.json'; import { ColumnType, - MlInMemoryTable, + MlInMemoryTableBasic, OnTableChangeArg, SortingPropType, SORT_DIRECTION, @@ -466,7 +466,7 @@ export const Exploration: FC = React.memo(({ jobId }) => { )} {clearTable === false && columns.length > 0 && sortField !== '' && ( - { if (job.stats.state === DATA_FRAME_TASK_STATE.FAILED) { failedJobs++; - } else if (job.stats.state === DATA_FRAME_TASK_STATE.STARTED) { + } else if ( + job.stats.state === DATA_FRAME_TASK_STATE.STARTED || + job.stats.state === DATA_FRAME_TASK_STATE.ANALYZING || + job.stats.state === DATA_FRAME_TASK_STATE.REINDEXING + ) { startedJobs++; } else if (job.stats.state === DATA_FRAME_TASK_STATE.STOPPED) { stoppedJobs++;