Skip to content

Commit b9a43a1

Browse files
authored
[ML] Stats bar for data frame analytics (#49464)
* [ML] stats for analytics jobs * [ML] alight stats position * [ML] refactor getAnalyticFactory, remove analytics stats bar component * [ML] align layout for anomaly detection * [ML] align layout * [ML] show failed jobs count * [ML] Anomaly detection jobs header * [ML] test * [ML] fix action columns * [ML] add type for createAnalyticsForm * [ML] move page title, prettier formatting
1 parent d00be34 commit b9a43a1

File tree

17 files changed

+422
-328
lines changed

17 files changed

+422
-328
lines changed

x-pack/legacy/plugins/ml/public/components/stats_bar/stat.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import React, { FC } from 'react';
88

99
export interface StatsBarStat {
1010
label: string;
11-
value: string | number;
11+
value: number;
1212
show?: boolean;
1313
}
1414
interface StatProps {

x-pack/legacy/plugins/ml/public/components/stats_bar/stats_bar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export interface AnalyticStatsBarStats extends Stats {
2323
stopped: StatsBarStat;
2424
}
2525

26-
type StatsBarStats = JobStatsBarStats | AnalyticStatsBarStats;
26+
export type StatsBarStats = JobStatsBarStats | AnalyticStatsBarStats;
2727
type StatsKey = keyof StatsBarStats;
2828

2929
interface StatsBarProps {

x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ import React, { Fragment, FC, useState } from 'react';
88

99
import { i18n } from '@kbn/i18n';
1010

11-
import { EuiButtonEmpty, EuiCallOut, EuiEmptyPrompt } from '@elastic/eui';
11+
import {
12+
EuiButtonEmpty,
13+
EuiCallOut,
14+
EuiEmptyPrompt,
15+
EuiFlexGroup,
16+
EuiFlexItem,
17+
EuiSpacer,
18+
} from '@elastic/eui';
1219

1320
import { DataFrameAnalyticsId, useRefreshAnalyticsList } from '../../../../common';
1421
import { checkPermission } from '../../../../../privilege/check_privilege';
@@ -22,7 +29,6 @@ import {
2229
Query,
2330
Clause,
2431
} from './common';
25-
import { ActionDispatchers } from '../../hooks/use_create_analytics_form/actions';
2632
import { getAnalyticsFactory } from '../../services/analytics_service';
2733
import { getColumns } from './columns';
2834
import { ExpandedRow } from './expanded_row';
@@ -33,6 +39,10 @@ import {
3339
SortDirection,
3440
SORT_DIRECTION,
3541
} from '../../../../../components/ml_in_memory_table';
42+
import { AnalyticStatsBarStats, StatsBar } from '../../../../../components/stats_bar';
43+
import { RefreshAnalyticsListButton } from '../refresh_analytics_list_button';
44+
import { CreateAnalyticsButton } from '../create_analytics_button';
45+
import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form';
3646

3747
function getItemIdToExpandedRowMap(
3848
itemIds: DataFrameAnalyticsId[],
@@ -62,20 +72,22 @@ interface Props {
6272
isManagementTable?: boolean;
6373
isMlEnabledInSpace?: boolean;
6474
blockRefresh?: boolean;
65-
openCreateJobModal?: ActionDispatchers['openModal'];
75+
createAnalyticsForm?: CreateAnalyticsFormProps;
6676
}
67-
// isManagementTable - for use in Kibana managagement ML section
6877
export const DataFrameAnalyticsList: FC<Props> = ({
6978
isManagementTable = false,
7079
isMlEnabledInSpace = true,
7180
blockRefresh = false,
72-
openCreateJobModal,
81+
createAnalyticsForm,
7382
}) => {
7483
const [isInitialized, setIsInitialized] = useState(false);
7584
const [isLoading, setIsLoading] = useState(false);
7685
const [filterActive, setFilterActive] = useState(false);
7786

7887
const [analytics, setAnalytics] = useState<DataFrameAnalyticsListRow[]>([]);
88+
const [analyticsStats, setAnalyticsStats] = useState<AnalyticStatsBarStats | undefined>(
89+
undefined
90+
);
7991
const [filteredAnalytics, setFilteredAnalytics] = useState<DataFrameAnalyticsListRow[]>([]);
8092
const [expandedRowItemIds, setExpandedRowItemIds] = useState<DataFrameAnalyticsId[]>([]);
8193

@@ -94,10 +106,12 @@ export const DataFrameAnalyticsList: FC<Props> = ({
94106

95107
const getAnalytics = getAnalyticsFactory(
96108
setAnalytics,
109+
setAnalyticsStats,
97110
setErrorMessage,
98111
setIsInitialized,
99112
blockRefresh
100113
);
114+
101115
// Subscribe to the refresh observable to trigger reloading the analytics list.
102116
useRefreshAnalyticsList({
103117
isLoading: setIsLoading,
@@ -213,9 +227,12 @@ export const DataFrameAnalyticsList: FC<Props> = ({
213227
</h2>
214228
}
215229
actions={
216-
!isManagementTable && openCreateJobModal !== undefined
230+
!isManagementTable && createAnalyticsForm
217231
? [
218-
<EuiButtonEmpty onClick={openCreateJobModal} isDisabled={disabled}>
232+
<EuiButtonEmpty
233+
onClick={createAnalyticsForm.actions.openModal}
234+
isDisabled={disabled}
235+
>
219236
{i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptButtonText', {
220237
defaultMessage: 'Create your first data frame analytics job',
221238
})}
@@ -310,7 +327,28 @@ export const DataFrameAnalyticsList: FC<Props> = ({
310327

311328
return (
312329
<Fragment>
313-
<ProgressBar isLoading={isLoading} />
330+
<EuiFlexGroup justifyContent="spaceBetween">
331+
<EuiFlexItem grow={false}>
332+
{analyticsStats && (
333+
<EuiFlexItem grow={false}>
334+
<StatsBar stats={analyticsStats} dataTestSub={'mlAnalyticsStatsBar'} />
335+
</EuiFlexItem>
336+
)}
337+
</EuiFlexItem>
338+
<EuiFlexItem grow={false}>
339+
<EuiFlexGroup alignItems="center" gutterSize="s">
340+
<EuiFlexItem grow={false}>
341+
<RefreshAnalyticsListButton />
342+
</EuiFlexItem>
343+
{!isManagementTable && createAnalyticsForm && (
344+
<EuiFlexItem grow={false}>
345+
<CreateAnalyticsButton {...createAnalyticsForm} />
346+
</EuiFlexItem>
347+
)}
348+
</EuiFlexGroup>
349+
</EuiFlexItem>
350+
</EuiFlexGroup>
351+
<EuiSpacer size="s" />
314352
<MlInMemoryTable
315353
allowNeutralSort={false}
316354
className="mlAnalyticsTable"

x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export function getErrorMessage(error: any) {
4343
return JSON.stringify(error);
4444
}
4545

46-
export const useCreateAnalyticsForm = () => {
46+
export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
4747
const kibanaContext = useKibanaContext();
4848
const [state, dispatch] = useReducer(reducer, getInitialState());
4949
const { refresh } = useRefreshAnalyticsList();

x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/page.tsx

Lines changed: 11 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,22 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
import React, { Fragment, FC, useState } from 'react';
7+
import React, { FC, Fragment, useState } from 'react';
88

99
import { FormattedMessage } from '@kbn/i18n/react';
1010
import { i18n } from '@kbn/i18n';
1111

1212
import {
1313
EuiBetaBadge,
14-
EuiFlexGroup,
15-
EuiFlexItem,
1614
EuiPage,
1715
EuiPageBody,
18-
EuiPageContentBody,
19-
EuiPageContentHeader,
20-
EuiPageContentHeaderSection,
21-
EuiPanel,
22-
EuiSpacer,
2316
EuiTitle,
17+
EuiPageHeader,
18+
EuiPageHeaderSection,
2419
} from '@elastic/eui';
2520

2621
import { NavigationMenu } from '../../../components/navigation_menu';
27-
import { CreateAnalyticsButton } from './components/create_analytics_button';
2822
import { DataFrameAnalyticsList } from './components/analytics_list';
29-
import { RefreshAnalyticsListButton } from './components/refresh_analytics_list_button';
3023
import { useRefreshInterval } from './components/analytics_list/use_refresh_interval';
3124
import { useCreateAnalyticsForm } from './hooks/use_create_analytics_form';
3225

@@ -42,8 +35,8 @@ export const Page: FC = () => {
4235
<NavigationMenu tabId="data_frame_analytics" />
4336
<EuiPage data-test-subj="mlPageDataFrameAnalytics">
4437
<EuiPageBody>
45-
<EuiPageContentHeader>
46-
<EuiPageContentHeaderSection>
38+
<EuiPageHeader>
39+
<EuiPageHeaderSection>
4740
<EuiTitle>
4841
<h1>
4942
<FormattedMessage
@@ -67,29 +60,12 @@ export const Page: FC = () => {
6760
/>
6861
</h1>
6962
</EuiTitle>
70-
</EuiPageContentHeaderSection>
71-
<EuiPageContentHeaderSection>
72-
<EuiFlexGroup alignItems="center">
73-
{/* grow={false} fixes IE11 issue with nested flex */}
74-
<EuiFlexItem grow={false}>
75-
<RefreshAnalyticsListButton />
76-
</EuiFlexItem>
77-
{/* grow={false} fixes IE11 issue with nested flex */}
78-
<EuiFlexItem grow={false}>
79-
<CreateAnalyticsButton {...createAnalyticsForm} />
80-
</EuiFlexItem>
81-
</EuiFlexGroup>
82-
</EuiPageContentHeaderSection>
83-
</EuiPageContentHeader>
84-
<EuiPageContentBody>
85-
<EuiSpacer size="l" />
86-
<EuiPanel>
87-
<DataFrameAnalyticsList
88-
blockRefresh={blockRefresh}
89-
openCreateJobModal={createAnalyticsForm.actions.openModal}
90-
/>
91-
</EuiPanel>
92-
</EuiPageContentBody>
63+
</EuiPageHeaderSection>
64+
</EuiPageHeader>
65+
<DataFrameAnalyticsList
66+
blockRefresh={blockRefresh}
67+
createAnalyticsForm={createAnalyticsForm}
68+
/>
9369
</EuiPageBody>
9470
</EuiPage>
9571
</Fragment>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { GetDataFrameAnalyticsStatsResponseOk } from '../../../../../services/ml_api_service';
8+
import { getAnalyticsJobsStats } from './get_analytics';
9+
import { DATA_FRAME_TASK_STATE } from '../../components/analytics_list/common';
10+
11+
jest.mock('ui/index_patterns', () => ({
12+
validateIndexPattern: () => true,
13+
}));
14+
15+
describe('get_analytics', () => {
16+
test('should get analytics jobs stats', () => {
17+
// arrange
18+
const mockResponse: GetDataFrameAnalyticsStatsResponseOk = {
19+
count: 2,
20+
data_frame_analytics: [
21+
{
22+
id: 'outlier-cloudwatch',
23+
state: DATA_FRAME_TASK_STATE.STOPPED,
24+
progress: [
25+
{
26+
phase: 'reindexing',
27+
progress_percent: 0,
28+
},
29+
{
30+
phase: 'loading_data',
31+
progress_percent: 0,
32+
},
33+
{
34+
phase: 'analyzing',
35+
progress_percent: 0,
36+
},
37+
{
38+
phase: 'writing_results',
39+
progress_percent: 0,
40+
},
41+
],
42+
},
43+
{
44+
id: 'reg-gallery',
45+
state: DATA_FRAME_TASK_STATE.FAILED,
46+
progress: [
47+
{
48+
phase: 'reindexing',
49+
progress_percent: 0,
50+
},
51+
{
52+
phase: 'loading_data',
53+
progress_percent: 0,
54+
},
55+
{
56+
phase: 'analyzing',
57+
progress_percent: 0,
58+
},
59+
{
60+
phase: 'writing_results',
61+
progress_percent: 0,
62+
},
63+
],
64+
},
65+
],
66+
};
67+
68+
// act and assert
69+
expect(getAnalyticsJobsStats(mockResponse)).toEqual({
70+
total: {
71+
label: 'Total analytics jobs',
72+
value: 2,
73+
show: true,
74+
},
75+
started: {
76+
label: 'Running',
77+
value: 0,
78+
show: true,
79+
},
80+
stopped: {
81+
label: 'Stopped',
82+
value: 1,
83+
show: true,
84+
},
85+
failed: {
86+
label: 'Failed',
87+
value: 1,
88+
show: true,
89+
},
90+
});
91+
});
92+
});

0 commit comments

Comments
 (0)