Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Stats bar for data frame analytics #49464

Merged
merged 11 commits into from
Nov 12, 2019
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}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to move useCreateAnalyticsForm down into DataFrameAnalyticsList so we don't need to pass createAnalyticsForm down?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's the first thing I tried, but useCreateAnalyticsForm relied on useKibanaContext, which is not defined on the management page.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, makes sense, let's leave it as it is then.

/>
</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