Skip to content

Commit

Permalink
[ML] Stats bar for data frame analytics (#49464)
Browse files Browse the repository at this point in the history
* [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
  • Loading branch information
darnautov authored Nov 12, 2019
1 parent d00be34 commit b9a43a1
Show file tree
Hide file tree
Showing 17 changed files with 422 additions and 328 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import React, { FC } from 'react';

export interface StatsBarStat {
label: string;
value: string | number;
value: number;
show?: boolean;
}
interface StatProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface AnalyticStatsBarStats extends Stats {
stopped: StatsBarStat;
}

type StatsBarStats = JobStatsBarStats | AnalyticStatsBarStats;
export type StatsBarStats = JobStatsBarStats | AnalyticStatsBarStats;
type StatsKey = keyof StatsBarStats;

interface StatsBarProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ import React, { Fragment, FC, useState } from 'react';

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

import { EuiButtonEmpty, EuiCallOut, EuiEmptyPrompt } from '@elastic/eui';
import {
EuiButtonEmpty,
EuiCallOut,
EuiEmptyPrompt,
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
} from '@elastic/eui';

import { DataFrameAnalyticsId, useRefreshAnalyticsList } from '../../../../common';
import { checkPermission } from '../../../../../privilege/check_privilege';
Expand All @@ -22,7 +29,6 @@ import {
Query,
Clause,
} from './common';
import { ActionDispatchers } from '../../hooks/use_create_analytics_form/actions';
import { getAnalyticsFactory } from '../../services/analytics_service';
import { getColumns } from './columns';
import { ExpandedRow } from './expanded_row';
Expand All @@ -33,6 +39,10 @@ import {
SortDirection,
SORT_DIRECTION,
} from '../../../../../components/ml_in_memory_table';
import { AnalyticStatsBarStats, StatsBar } from '../../../../../components/stats_bar';
import { RefreshAnalyticsListButton } from '../refresh_analytics_list_button';
import { CreateAnalyticsButton } from '../create_analytics_button';
import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form';

function getItemIdToExpandedRowMap(
itemIds: DataFrameAnalyticsId[],
Expand Down Expand Up @@ -62,20 +72,22 @@ interface Props {
isManagementTable?: boolean;
isMlEnabledInSpace?: boolean;
blockRefresh?: boolean;
openCreateJobModal?: ActionDispatchers['openModal'];
createAnalyticsForm?: CreateAnalyticsFormProps;
}
// isManagementTable - for use in Kibana managagement ML section
export const DataFrameAnalyticsList: FC<Props> = ({
isManagementTable = false,
isMlEnabledInSpace = true,
blockRefresh = false,
openCreateJobModal,
createAnalyticsForm,
}) => {
const [isInitialized, setIsInitialized] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [filterActive, setFilterActive] = useState(false);

const [analytics, setAnalytics] = useState<DataFrameAnalyticsListRow[]>([]);
const [analyticsStats, setAnalyticsStats] = useState<AnalyticStatsBarStats | undefined>(
undefined
);
const [filteredAnalytics, setFilteredAnalytics] = useState<DataFrameAnalyticsListRow[]>([]);
const [expandedRowItemIds, setExpandedRowItemIds] = useState<DataFrameAnalyticsId[]>([]);

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

const getAnalytics = getAnalyticsFactory(
setAnalytics,
setAnalyticsStats,
setErrorMessage,
setIsInitialized,
blockRefresh
);

// Subscribe to the refresh observable to trigger reloading the analytics list.
useRefreshAnalyticsList({
isLoading: setIsLoading,
Expand Down Expand Up @@ -213,9 +227,12 @@ export const DataFrameAnalyticsList: FC<Props> = ({
</h2>
}
actions={
!isManagementTable && openCreateJobModal !== undefined
!isManagementTable && createAnalyticsForm
? [
<EuiButtonEmpty onClick={openCreateJobModal} isDisabled={disabled}>
<EuiButtonEmpty
onClick={createAnalyticsForm.actions.openModal}
isDisabled={disabled}
>
{i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptButtonText', {
defaultMessage: 'Create your first data frame analytics job',
})}
Expand Down Expand Up @@ -310,7 +327,28 @@ export const DataFrameAnalyticsList: FC<Props> = ({

return (
<Fragment>
<ProgressBar isLoading={isLoading} />
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
{analyticsStats && (
<EuiFlexItem grow={false}>
<StatsBar stats={analyticsStats} dataTestSub={'mlAnalyticsStatsBar'} />
</EuiFlexItem>
)}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
<RefreshAnalyticsListButton />
</EuiFlexItem>
{!isManagementTable && createAnalyticsForm && (
<EuiFlexItem grow={false}>
<CreateAnalyticsButton {...createAnalyticsForm} />
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<MlInMemoryTable
allowNeutralSort={false}
className="mlAnalyticsTable"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function getErrorMessage(error: any) {
return JSON.stringify(error);
}

export const useCreateAnalyticsForm = () => {
export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
const kibanaContext = useKibanaContext();
const [state, dispatch] = useReducer(reducer, getInitialState());
const { refresh } = useRefreshAnalyticsList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,22 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { Fragment, FC, useState } from 'react';
import React, { FC, Fragment, useState } from 'react';

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

import {
EuiBetaBadge,
EuiFlexGroup,
EuiFlexItem,
EuiPage,
EuiPageBody,
EuiPageContentBody,
EuiPageContentHeader,
EuiPageContentHeaderSection,
EuiPanel,
EuiSpacer,
EuiTitle,
EuiPageHeader,
EuiPageHeaderSection,
} from '@elastic/eui';

import { NavigationMenu } from '../../../components/navigation_menu';
import { CreateAnalyticsButton } from './components/create_analytics_button';
import { DataFrameAnalyticsList } from './components/analytics_list';
import { RefreshAnalyticsListButton } from './components/refresh_analytics_list_button';
import { useRefreshInterval } from './components/analytics_list/use_refresh_interval';
import { useCreateAnalyticsForm } from './hooks/use_create_analytics_form';

Expand All @@ -42,8 +35,8 @@ export const Page: FC = () => {
<NavigationMenu tabId="data_frame_analytics" />
<EuiPage data-test-subj="mlPageDataFrameAnalytics">
<EuiPageBody>
<EuiPageContentHeader>
<EuiPageContentHeaderSection>
<EuiPageHeader>
<EuiPageHeaderSection>
<EuiTitle>
<h1>
<FormattedMessage
Expand All @@ -67,29 +60,12 @@ export const Page: FC = () => {
/>
</h1>
</EuiTitle>
</EuiPageContentHeaderSection>
<EuiPageContentHeaderSection>
<EuiFlexGroup alignItems="center">
{/* grow={false} fixes IE11 issue with nested flex */}
<EuiFlexItem grow={false}>
<RefreshAnalyticsListButton />
</EuiFlexItem>
{/* grow={false} fixes IE11 issue with nested flex */}
<EuiFlexItem grow={false}>
<CreateAnalyticsButton {...createAnalyticsForm} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageContentHeaderSection>
</EuiPageContentHeader>
<EuiPageContentBody>
<EuiSpacer size="l" />
<EuiPanel>
<DataFrameAnalyticsList
blockRefresh={blockRefresh}
openCreateJobModal={createAnalyticsForm.actions.openModal}
/>
</EuiPanel>
</EuiPageContentBody>
</EuiPageHeaderSection>
</EuiPageHeader>
<DataFrameAnalyticsList
blockRefresh={blockRefresh}
createAnalyticsForm={createAnalyticsForm}
/>
</EuiPageBody>
</EuiPage>
</Fragment>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* 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 { GetDataFrameAnalyticsStatsResponseOk } from '../../../../../services/ml_api_service';
import { getAnalyticsJobsStats } from './get_analytics';
import { DATA_FRAME_TASK_STATE } from '../../components/analytics_list/common';

jest.mock('ui/index_patterns', () => ({
validateIndexPattern: () => true,
}));

describe('get_analytics', () => {
test('should get analytics jobs stats', () => {
// arrange
const mockResponse: GetDataFrameAnalyticsStatsResponseOk = {
count: 2,
data_frame_analytics: [
{
id: 'outlier-cloudwatch',
state: DATA_FRAME_TASK_STATE.STOPPED,
progress: [
{
phase: 'reindexing',
progress_percent: 0,
},
{
phase: 'loading_data',
progress_percent: 0,
},
{
phase: 'analyzing',
progress_percent: 0,
},
{
phase: 'writing_results',
progress_percent: 0,
},
],
},
{
id: 'reg-gallery',
state: DATA_FRAME_TASK_STATE.FAILED,
progress: [
{
phase: 'reindexing',
progress_percent: 0,
},
{
phase: 'loading_data',
progress_percent: 0,
},
{
phase: 'analyzing',
progress_percent: 0,
},
{
phase: 'writing_results',
progress_percent: 0,
},
],
},
],
};

// act and assert
expect(getAnalyticsJobsStats(mockResponse)).toEqual({
total: {
label: 'Total analytics jobs',
value: 2,
show: true,
},
started: {
label: 'Running',
value: 0,
show: true,
},
stopped: {
label: 'Stopped',
value: 1,
show: true,
},
failed: {
label: 'Failed',
value: 1,
show: true,
},
});
});
});
Loading

0 comments on commit b9a43a1

Please sign in to comment.