[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
This commit is contained in:
parent
d00be3438c
commit
b9a43a1788
|
@ -8,7 +8,7 @@ import React, { FC } from 'react';
|
|||
|
||||
export interface StatsBarStat {
|
||||
label: string;
|
||||
value: string | number;
|
||||
value: number;
|
||||
show?: boolean;
|
||||
}
|
||||
interface StatProps {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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[],
|
||||
|
@ -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[]>([]);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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',
|
||||
})}
|
||||
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
|
@ -4,32 +4,35 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ml } from '../../../../../services/ml_api_service';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
GetDataFrameAnalyticsStatsResponse,
|
||||
GetDataFrameAnalyticsStatsResponseError,
|
||||
GetDataFrameAnalyticsStatsResponseOk,
|
||||
ml,
|
||||
} from '../../../../../services/ml_api_service';
|
||||
import {
|
||||
DataFrameAnalyticsConfig,
|
||||
refreshAnalyticsList$,
|
||||
REFRESH_ANALYTICS_LIST_STATE,
|
||||
refreshAnalyticsList$,
|
||||
} from '../../../../common';
|
||||
|
||||
import {
|
||||
DataFrameAnalyticsListRow,
|
||||
DataFrameAnalyticsStats,
|
||||
DATA_FRAME_MODE,
|
||||
DataFrameAnalyticsListRow,
|
||||
isDataFrameAnalyticsFailed,
|
||||
isDataFrameAnalyticsRunning,
|
||||
isDataFrameAnalyticsStats,
|
||||
isDataFrameAnalyticsStopped,
|
||||
} from '../../components/analytics_list/common';
|
||||
import { AnalyticStatsBarStats } from '../../../../../components/stats_bar';
|
||||
|
||||
interface GetDataFrameAnalyticsResponse {
|
||||
count: number;
|
||||
data_frame_analytics: DataFrameAnalyticsConfig[];
|
||||
}
|
||||
|
||||
interface GetDataFrameAnalyticsStatsResponseOk {
|
||||
node_failures?: object;
|
||||
count: number;
|
||||
data_frame_analytics: DataFrameAnalyticsStats[];
|
||||
}
|
||||
|
||||
const isGetDataFrameAnalyticsStatsResponseOk = (
|
||||
export const isGetDataFrameAnalyticsStatsResponseOk = (
|
||||
arg: any
|
||||
): arg is GetDataFrameAnalyticsStatsResponseOk => {
|
||||
return (
|
||||
|
@ -39,20 +42,71 @@ const isGetDataFrameAnalyticsStatsResponseOk = (
|
|||
);
|
||||
};
|
||||
|
||||
interface GetDataFrameAnalyticsStatsResponseError {
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message: string;
|
||||
export type GetAnalytics = (forceRefresh?: boolean) => void;
|
||||
|
||||
/**
|
||||
* Gets initial object for analytics stats.
|
||||
*/
|
||||
export function getInitialAnalyticsStats(): AnalyticStatsBarStats {
|
||||
return {
|
||||
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.runningAnalyticsLabel', {
|
||||
defaultMessage: 'Running',
|
||||
}),
|
||||
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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
type GetDataFrameAnalyticsStatsResponse =
|
||||
| GetDataFrameAnalyticsStatsResponseOk
|
||||
| GetDataFrameAnalyticsStatsResponseError;
|
||||
|
||||
export type GetAnalytics = (forceRefresh?: boolean) => void;
|
||||
/**
|
||||
* Gets analytics jobs stats formatted for the stats bar.
|
||||
*/
|
||||
export function getAnalyticsJobsStats(
|
||||
analyticsStats: GetDataFrameAnalyticsStatsResponseOk
|
||||
): AnalyticStatsBarStats {
|
||||
const resultStats: AnalyticStatsBarStats = analyticsStats.data_frame_analytics.reduce(
|
||||
(acc, { state }) => {
|
||||
if (isDataFrameAnalyticsFailed(state)) {
|
||||
acc.failed.value = ++acc.failed.value;
|
||||
} else if (isDataFrameAnalyticsRunning(state)) {
|
||||
acc.started.value = ++acc.started.value;
|
||||
} else if (isDataFrameAnalyticsStopped(state)) {
|
||||
acc.stopped.value = ++acc.stopped.value;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
getInitialAnalyticsStats()
|
||||
);
|
||||
resultStats.failed.show = resultStats.failed.value > 0;
|
||||
resultStats.total.value = analyticsStats.count;
|
||||
return resultStats;
|
||||
}
|
||||
|
||||
export const getAnalyticsFactory = (
|
||||
setAnalytics: React.Dispatch<React.SetStateAction<DataFrameAnalyticsListRow[]>>,
|
||||
setAnalyticsStats: React.Dispatch<React.SetStateAction<AnalyticStatsBarStats | undefined>>,
|
||||
setErrorMessage: React.Dispatch<
|
||||
React.SetStateAction<GetDataFrameAnalyticsStatsResponseError | undefined>
|
||||
>,
|
||||
|
@ -74,6 +128,10 @@ export const getAnalyticsFactory = (
|
|||
const analyticsConfigs: GetDataFrameAnalyticsResponse = await ml.dataFrameAnalytics.getDataFrameAnalytics();
|
||||
const analyticsStats: GetDataFrameAnalyticsStatsResponse = await ml.dataFrameAnalytics.getDataFrameAnalyticsStats();
|
||||
|
||||
const analyticsStatsResult = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats)
|
||||
? getAnalyticsJobsStats(analyticsStats)
|
||||
: undefined;
|
||||
|
||||
const tableRows = analyticsConfigs.data_frame_analytics.reduce(
|
||||
(reducedtableRows, config) => {
|
||||
const stats = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats)
|
||||
|
@ -100,6 +158,7 @@ export const getAnalyticsFactory = (
|
|||
);
|
||||
|
||||
setAnalytics(tableRows);
|
||||
setAnalyticsStats(analyticsStatsResult);
|
||||
setErrorMessage(undefined);
|
||||
setIsInitialized(true);
|
||||
refreshAnalyticsList$.next(REFRESH_ANALYTICS_LIST_STATE.IDLE);
|
||||
|
@ -109,6 +168,7 @@ export const getAnalyticsFactory = (
|
|||
refreshAnalyticsList$.next(REFRESH_ANALYTICS_LIST_STATE.ERROR);
|
||||
refreshAnalyticsList$.next(REFRESH_ANALYTICS_LIST_STATE.IDLE);
|
||||
setAnalytics([]);
|
||||
setAnalyticsStats(undefined);
|
||||
setErrorMessage(e);
|
||||
setIsInitialized(true);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
.job-management {
|
||||
padding: $euiSizeL;
|
||||
}
|
||||
|
||||
.new-job-button-container {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,11 +8,3 @@
|
|||
.job-management {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.job-buttons-container {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
import { loadFullJob, filterJobs, checkForAutoStartDatafeed } from '../utils';
|
||||
import { checkForAutoStartDatafeed, filterJobs, loadFullJob } from '../utils';
|
||||
import { JobsList } from '../jobs_list';
|
||||
import { JobDetails } from '../job_details';
|
||||
import { JobFilterBar } from '../job_filter_bar';
|
||||
|
@ -26,22 +28,11 @@ import { isEqual } from 'lodash';
|
|||
|
||||
import {
|
||||
DEFAULT_REFRESH_INTERVAL_MS,
|
||||
MINIMUM_REFRESH_INTERVAL_MS,
|
||||
DELETING_JOBS_REFRESH_INTERVAL_MS,
|
||||
MINIMUM_REFRESH_INTERVAL_MS,
|
||||
} from '../../../../../common/constants/jobs_list';
|
||||
|
||||
import React, {
|
||||
Component
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
||||
|
||||
let jobsRefreshInterval = null;
|
||||
let jobsRefreshInterval = null;
|
||||
let deletingJobsRefreshTimeout = null;
|
||||
|
||||
// 'isManagementTable' bool prop to determine when to configure table for use in Kibana management page
|
||||
|
@ -76,8 +67,8 @@ export class JobsListView extends Component {
|
|||
if (this.props.isManagementTable === true) {
|
||||
this.refreshJobSummaryList(true);
|
||||
} else {
|
||||
// The advanced job wizard is still angularjs based and triggers
|
||||
// broadcast events which it expects the jobs list to be subscribed to.
|
||||
// The advanced job wizard is still angularjs based and triggers
|
||||
// broadcast events which it expects the jobs list to be subscribed to.
|
||||
this.props.angularWrapperScope.$on('jobsUpdated', () => {
|
||||
this.refreshJobSummaryList(true);
|
||||
});
|
||||
|
@ -114,7 +105,7 @@ export class JobsListView extends Component {
|
|||
// so switch it on and set the interval to 30s
|
||||
timefilter.setRefreshInterval({
|
||||
pause: false,
|
||||
value: DEFAULT_REFRESH_INTERVAL_MS
|
||||
value: DEFAULT_REFRESH_INTERVAL_MS,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -124,7 +115,7 @@ export class JobsListView extends Component {
|
|||
initAutoRefreshUpdate() {
|
||||
// update the interval if it changes
|
||||
this.refreshIntervalSubscription = timefilter.getRefreshIntervalUpdate$().subscribe({
|
||||
next: () => this.setAutoRefresh()
|
||||
next: () => this.setAutoRefresh(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -143,7 +134,7 @@ export class JobsListView extends Component {
|
|||
this.clearRefreshInterval();
|
||||
if (interval >= MINIMUM_REFRESH_INTERVAL_MS) {
|
||||
this.blockRefresh = false;
|
||||
jobsRefreshInterval = setInterval(() => (this.refreshJobSummaryList()), interval);
|
||||
jobsRefreshInterval = setInterval(() => this.refreshJobSummaryList(), interval);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,13 +150,12 @@ export class JobsListView extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
toggleRow = (jobId) => {
|
||||
toggleRow = jobId => {
|
||||
if (this.state.itemIdToExpandedRowMap[jobId]) {
|
||||
const itemIdToExpandedRowMap = { ...this.state.itemIdToExpandedRowMap };
|
||||
delete itemIdToExpandedRowMap[jobId];
|
||||
this.setState({ itemIdToExpandedRowMap });
|
||||
} else {
|
||||
|
||||
let itemIdToExpandedRowMap = { ...this.state.itemIdToExpandedRowMap };
|
||||
|
||||
if (this.state.fullJobsList[jobId] !== undefined) {
|
||||
|
@ -191,7 +181,7 @@ export class JobsListView extends Component {
|
|||
|
||||
this.setState({ itemIdToExpandedRowMap }, () => {
|
||||
loadFullJob(jobId)
|
||||
.then((job) => {
|
||||
.then(job => {
|
||||
const fullJobsList = { ...this.state.fullJobsList };
|
||||
fullJobsList[jobId] = job;
|
||||
this.setState({ fullJobsList }, () => {
|
||||
|
@ -213,54 +203,54 @@ export class JobsListView extends Component {
|
|||
this.setState({ itemIdToExpandedRowMap });
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
addUpdateFunction = (id, f) => {
|
||||
this.updateFunctions[id] = f;
|
||||
}
|
||||
removeUpdateFunction = (id) => {
|
||||
};
|
||||
removeUpdateFunction = id => {
|
||||
delete this.updateFunctions[id];
|
||||
}
|
||||
};
|
||||
|
||||
setShowEditJobFlyoutFunction = (func) => {
|
||||
setShowEditJobFlyoutFunction = func => {
|
||||
this.showEditJobFlyout = func;
|
||||
}
|
||||
};
|
||||
unsetShowEditJobFlyoutFunction = () => {
|
||||
this.showEditJobFlyout = () => {};
|
||||
}
|
||||
};
|
||||
|
||||
setShowDeleteJobModalFunction = (func) => {
|
||||
setShowDeleteJobModalFunction = func => {
|
||||
this.showDeleteJobModal = func;
|
||||
}
|
||||
};
|
||||
unsetShowDeleteJobModalFunction = () => {
|
||||
this.showDeleteJobModal = () => {};
|
||||
}
|
||||
};
|
||||
|
||||
setShowStartDatafeedModalFunction = (func) => {
|
||||
setShowStartDatafeedModalFunction = func => {
|
||||
this.showStartDatafeedModal = func;
|
||||
}
|
||||
};
|
||||
unsetShowStartDatafeedModalFunction = () => {
|
||||
this.showStartDatafeedModal = () => {};
|
||||
}
|
||||
};
|
||||
|
||||
setShowCreateWatchFlyoutFunction = (func) => {
|
||||
setShowCreateWatchFlyoutFunction = func => {
|
||||
this.showCreateWatchFlyout = func;
|
||||
}
|
||||
};
|
||||
unsetShowCreateWatchFlyoutFunction = () => {
|
||||
this.showCreateWatchFlyout = () => {};
|
||||
}
|
||||
};
|
||||
getShowCreateWatchFlyoutFunction = () => {
|
||||
return this.showCreateWatchFlyout;
|
||||
}
|
||||
};
|
||||
|
||||
selectJobChange = (selectedJobs) => {
|
||||
selectJobChange = selectedJobs => {
|
||||
this.setState({ selectedJobs });
|
||||
}
|
||||
};
|
||||
|
||||
refreshSelectedJobs() {
|
||||
const selectedJobsIds = this.state.selectedJobs.map(j => j.id);
|
||||
|
@ -275,24 +265,23 @@ export class JobsListView extends Component {
|
|||
this.setState({ selectedJobs });
|
||||
}
|
||||
|
||||
setFilters = (filterClauses) => {
|
||||
setFilters = filterClauses => {
|
||||
const filteredJobsSummaryList = filterJobs(this.state.jobsSummaryList, filterClauses);
|
||||
this.setState({ filteredJobsSummaryList, filterClauses }, () => {
|
||||
this.refreshSelectedJobs();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onRefreshClick = () => {
|
||||
this.setState({ isRefreshing: true });
|
||||
this.refreshJobSummaryList(true);
|
||||
}
|
||||
};
|
||||
isDoneRefreshing = () => {
|
||||
this.setState({ isRefreshing: false });
|
||||
}
|
||||
};
|
||||
|
||||
async refreshJobSummaryList(forceRefresh = false) {
|
||||
if (forceRefresh === true || this.blockRefresh === false) {
|
||||
|
||||
// Set loading to true for jobs_list table for initial job loading
|
||||
if (this.state.loading === null) {
|
||||
this.setState({ loading: true });
|
||||
|
@ -302,24 +291,27 @@ export class JobsListView extends Component {
|
|||
try {
|
||||
const jobs = await ml.jobs.jobsSummary(expandedJobsIds);
|
||||
const fullJobsList = {};
|
||||
const jobsSummaryList = jobs.map((job) => {
|
||||
const jobsSummaryList = jobs.map(job => {
|
||||
if (job.fullJob !== undefined) {
|
||||
fullJobsList[job.id] = job.fullJob;
|
||||
delete job.fullJob;
|
||||
}
|
||||
job.latestTimestampSortValue = (job.latestTimestampMs || 0);
|
||||
job.latestTimestampSortValue = job.latestTimestampMs || 0;
|
||||
return job;
|
||||
});
|
||||
const filteredJobsSummaryList = filterJobs(jobsSummaryList, this.state.filterClauses);
|
||||
this.setState({ jobsSummaryList, filteredJobsSummaryList, fullJobsList, loading: false }, () => {
|
||||
this.refreshSelectedJobs();
|
||||
});
|
||||
this.setState(
|
||||
{ jobsSummaryList, filteredJobsSummaryList, fullJobsList, loading: false },
|
||||
() => {
|
||||
this.refreshSelectedJobs();
|
||||
}
|
||||
);
|
||||
|
||||
Object.keys(this.updateFunctions).forEach((j) => {
|
||||
Object.keys(this.updateFunctions).forEach(j => {
|
||||
this.updateFunctions[j].setState({ job: fullJobsList[j] });
|
||||
});
|
||||
|
||||
jobs.forEach((job) => {
|
||||
jobs.forEach(job => {
|
||||
if (job.deleting && this.state.itemIdToExpandedRowMap[job.id]) {
|
||||
this.toggleRow(job.id);
|
||||
}
|
||||
|
@ -342,7 +334,8 @@ export class JobsListView extends Component {
|
|||
async checkDeletingJobTasks(forceRefresh = false) {
|
||||
const { jobIds: taskJobIds } = await ml.jobs.deletingJobTasks();
|
||||
|
||||
const taskListHasChanged = (isEqual(taskJobIds.sort(), this.state.deletingJobIds.sort()) === false);
|
||||
const taskListHasChanged =
|
||||
isEqual(taskJobIds.sort(), this.state.deletingJobIds.sort()) === false;
|
||||
|
||||
this.setState({
|
||||
deletingJobIds: taskJobIds,
|
||||
|
@ -363,7 +356,13 @@ export class JobsListView extends Component {
|
|||
}
|
||||
|
||||
renderManagementJobsListComponents() {
|
||||
const { loading, itemIdToExpandedRowMap, filteredJobsSummaryList, fullJobsList, selectedJobs } = this.state;
|
||||
const {
|
||||
loading,
|
||||
itemIdToExpandedRowMap,
|
||||
filteredJobsSummaryList,
|
||||
fullJobsList,
|
||||
selectedJobs,
|
||||
} = this.state;
|
||||
return (
|
||||
<div className="managementJobsList">
|
||||
<div>
|
||||
|
@ -442,38 +441,51 @@ export class JobsListView extends Component {
|
|||
const { isManagementTable } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<JobStatsBar
|
||||
jobsSummaryList={jobsSummaryList}
|
||||
/>
|
||||
<div className="job-management" data-test-subj="ml-jobs-list">
|
||||
<NodeAvailableWarning />
|
||||
<UpgradeWarning />
|
||||
<header>
|
||||
<div className="job-buttons-container">
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<div className="job-management" data-test-subj="ml-jobs-list">
|
||||
{!isManagementTable && (
|
||||
<>
|
||||
<EuiTitle>
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.jobsList.title"
|
||||
defaultMessage="Anomaly detection jobs"
|
||||
/>
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<NodeAvailableWarning />
|
||||
|
||||
<UpgradeWarning />
|
||||
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<JobStatsBar jobsSummaryList={jobsSummaryList} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<RefreshJobsListButton
|
||||
onRefreshClick={this.onRefreshClick}
|
||||
isRefreshing={isRefreshing}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{!isManagementTable && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<RefreshJobsListButton
|
||||
onRefreshClick={this.onRefreshClick}
|
||||
isRefreshing={isRefreshing}
|
||||
/>
|
||||
<NewJobButton />
|
||||
</EuiFlexItem>
|
||||
{isManagementTable === undefined &&
|
||||
<EuiFlexItem grow={false}>
|
||||
<NewJobButton />
|
||||
</EuiFlexItem>}
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</header>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<div className="clear" />
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
{ !isManagementTable && this.renderJobsListComponents() }
|
||||
{ isManagementTable && this.renderManagementJobsListComponents() }
|
||||
</ div>
|
||||
</React.Fragment>
|
||||
{!isManagementTable && this.renderJobsListComponents()}
|
||||
{isManagementTable && this.renderManagementJobsListComponents()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { NavigationMenu } from '../../components/navigation_menu';
|
||||
|
||||
import { JobsListView } from './components/jobs_list_view';
|
||||
|
||||
export const JobsPage = (props) => (
|
||||
<Fragment>
|
||||
<>
|
||||
<NavigationMenu tabId="jobs" />
|
||||
<JobsListView {...props} />
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
|
||||
// Refresh button style
|
||||
|
||||
.job-buttons-container {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.managementJobsList{
|
||||
clear: both;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import { metadata } from 'ui/metadata';
|
|||
// @ts-ignore undeclared module
|
||||
import { JobsListView } from '../../../../jobs/jobs_list/components/jobs_list_view';
|
||||
import { DataFrameAnalyticsList } from '../../../../data_frame_analytics/pages/analytics_management/components/analytics_list';
|
||||
import { RefreshAnalyticsListButton } from '../../../../data_frame_analytics/pages/analytics_management/components/refresh_analytics_list_button';
|
||||
|
||||
interface Props {
|
||||
isMlEnabledInSpace: boolean;
|
||||
|
@ -56,10 +55,6 @@ function getTabs(isMlEnabledInSpace: boolean): Tab[] {
|
|||
content: (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m" />
|
||||
<span className="mlKibanaManagement__analyticsRefreshButton">
|
||||
<RefreshAnalyticsListButton />
|
||||
</span>
|
||||
<EuiSpacer size="s" className="mlKibanaManagement__analyticsSpacer" />
|
||||
<DataFrameAnalyticsList
|
||||
isManagementTable={true}
|
||||
isMlEnabledInSpace={isMlEnabledInSpace}
|
||||
|
|
|
@ -4,30 +4,44 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FC, Fragment, useState, useEffect } from 'react';
|
||||
import React, { FC, useEffect, useState } from 'react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiEmptyPrompt,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLoadingSpinner,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} 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';
|
||||
import { AnalyticStatsBarStats, StatsBar } from '../../../components/stats_bar';
|
||||
|
||||
interface Props {
|
||||
jobCreationDisabled: boolean;
|
||||
}
|
||||
export const AnalyticsPanel: FC<Props> = ({ jobCreationDisabled }) => {
|
||||
const [analytics, setAnalytics] = useState<DataFrameAnalyticsListRow[]>([]);
|
||||
const [analyticsStats, setAnalyticsStats] = useState<AnalyticStatsBarStats | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [errorMessage, setErrorMessage] = useState<any>(undefined);
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
const getAnalytics = getAnalyticsFactory(setAnalytics, setErrorMessage, setIsInitialized, false);
|
||||
const getAnalytics = getAnalyticsFactory(
|
||||
setAnalytics,
|
||||
setAnalyticsStats,
|
||||
setErrorMessage,
|
||||
setIsInitialized,
|
||||
false
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getAnalytics(true);
|
||||
|
@ -38,21 +52,19 @@ export const AnalyticsPanel: FC<Props> = ({ jobCreationDisabled }) => {
|
|||
};
|
||||
|
||||
const errorDisplay = (
|
||||
<Fragment>
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.ml.overview.analyticsList.errorPromptTitle', {
|
||||
defaultMessage: 'An error occurred getting the data frame analytics list.',
|
||||
})}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
>
|
||||
<pre>
|
||||
{errorMessage && errorMessage.message !== undefined
|
||||
? errorMessage.message
|
||||
: JSON.stringify(errorMessage)}
|
||||
</pre>
|
||||
</EuiCallOut>
|
||||
</Fragment>
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.ml.overview.analyticsList.errorPromptTitle', {
|
||||
defaultMessage: 'An error occurred getting the data frame analytics list.',
|
||||
})}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
>
|
||||
<pre>
|
||||
{errorMessage && errorMessage.message !== undefined
|
||||
? errorMessage.message
|
||||
: JSON.stringify(errorMessage)}
|
||||
</pre>
|
||||
</EuiCallOut>
|
||||
);
|
||||
|
||||
const panelClass = isInitialized === false ? 'mlOverviewPanel__isLoading' : 'mlOverviewPanel';
|
||||
|
@ -75,13 +87,11 @@ export const AnalyticsPanel: FC<Props> = ({ jobCreationDisabled }) => {
|
|||
</h2>
|
||||
}
|
||||
body={
|
||||
<Fragment>
|
||||
<p>
|
||||
{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. The analytics job stores the annotated data, as well as a copy of the source data, in a new index.`,
|
||||
})}
|
||||
</p>
|
||||
</Fragment>
|
||||
<p>
|
||||
{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. The analytics job stores the annotated data, as well as a copy of the source data, in a new index.`,
|
||||
})}
|
||||
</p>
|
||||
}
|
||||
actions={
|
||||
<EuiButton
|
||||
|
@ -99,7 +109,24 @@ export const AnalyticsPanel: FC<Props> = ({ jobCreationDisabled }) => {
|
|||
/>
|
||||
)}
|
||||
{isInitialized === true && analytics.length > 0 && (
|
||||
<Fragment>
|
||||
<>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="m">
|
||||
<h3>
|
||||
{i18n.translate('xpack.ml.overview.analyticsList.PanelTitle', {
|
||||
defaultMessage: 'Analytics',
|
||||
})}
|
||||
</h3>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
{analyticsStats !== undefined && (
|
||||
<EuiFlexItem grow={false} className="mlOverviewPanel__statsBar">
|
||||
<StatsBar stats={analyticsStats} dataTestSub={'mlOverviewAnalyticsStatsBar'} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<AnalyticsTable items={analytics} />
|
||||
<EuiSpacer size="m" />
|
||||
<div className="mlOverviewPanel__buttons">
|
||||
|
@ -114,7 +141,7 @@ export const AnalyticsPanel: FC<Props> = ({ jobCreationDisabled }) => {
|
|||
})}
|
||||
</EuiButton>
|
||||
</div>
|
||||
</Fragment>
|
||||
</>
|
||||
)}
|
||||
</EuiPanel>
|
||||
);
|
||||
|
|
|
@ -1,89 +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 } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { StatsBar, AnalyticStatsBarStats } from '../../../components/stats_bar';
|
||||
import {
|
||||
isDataFrameAnalyticsFailed,
|
||||
isDataFrameAnalyticsRunning,
|
||||
isDataFrameAnalyticsStopped,
|
||||
DataFrameAnalyticsListRow,
|
||||
} from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common';
|
||||
|
||||
function getAnalyticsStats(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.runningAnalyticsLabel', {
|
||||
defaultMessage: 'Running',
|
||||
}),
|
||||
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 (isDataFrameAnalyticsFailed(job.stats.state)) {
|
||||
failedJobs++;
|
||||
} else if (isDataFrameAnalyticsRunning(job.stats.state)) {
|
||||
startedJobs++;
|
||||
} else if (isDataFrameAnalyticsStopped(job.stats.state)) {
|
||||
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<Props> = ({ analyticsList }) => {
|
||||
const analyticsStats: AnalyticStatsBarStats = getAnalyticsStats(analyticsList);
|
||||
|
||||
return <StatsBar stats={analyticsStats} dataTestSub={'mlOverviewAnalyticsStatsBar'} />;
|
||||
};
|
|
@ -4,8 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { FC, Fragment, useState } from 'react';
|
||||
import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import React, { FC, useState } from 'react';
|
||||
import { EuiBadge } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
MlInMemoryTable,
|
||||
|
@ -25,7 +25,6 @@ import {
|
|||
} from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/columns';
|
||||
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';
|
||||
|
||||
interface Props {
|
||||
items: any[];
|
||||
|
@ -114,36 +113,19 @@ export const AnalyticsTable: FC<Props> = ({ items }) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="m">
|
||||
<h3>
|
||||
{i18n.translate('xpack.ml.overview.analyticsList.PanelTitle', {
|
||||
defaultMessage: 'Analytics',
|
||||
})}
|
||||
</h3>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} className="mlOverviewPanel__statsBar">
|
||||
<AnalyticsStatsBar analyticsList={items} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<MlInMemoryTable
|
||||
allowNeutralSort={false}
|
||||
className="mlAnalyticsTable"
|
||||
columns={columns}
|
||||
hasActions={false}
|
||||
isExpandable={false}
|
||||
isSelectable={false}
|
||||
items={items}
|
||||
itemId={DataFrameAnalyticsListColumn.id}
|
||||
onTableChange={onTableChange}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
data-test-subj="mlOverviewTableAnalytics"
|
||||
/>
|
||||
</Fragment>
|
||||
<MlInMemoryTable
|
||||
allowNeutralSort={false}
|
||||
className="mlAnalyticsTable"
|
||||
columns={columns}
|
||||
hasActions={false}
|
||||
isExpandable={false}
|
||||
isSelectable={false}
|
||||
items={items}
|
||||
itemId={DataFrameAnalyticsListColumn.id}
|
||||
onTableChange={onTableChange}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
data-test-subj="mlOverviewTableAnalytics"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ import { PrivilegesResponse } from '../../../common/types/privileges';
|
|||
import { MlSummaryJobs } from '../../../common/types/jobs';
|
||||
import { MlServerDefaults, MlServerLimits } from '../../jobs/new_job_new/utils/new_job_defaults';
|
||||
import { ES_AGGREGATION } from '../../../common/constants/aggregation_types';
|
||||
import { DataFrameAnalyticsStats } from '../../data_frame_analytics/pages/analytics_management/components/analytics_list/common';
|
||||
|
||||
// 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
|
||||
|
@ -65,7 +66,7 @@ declare interface Ml {
|
|||
|
||||
dataFrameAnalytics: {
|
||||
getDataFrameAnalytics(analyticsId?: string): Promise<any>;
|
||||
getDataFrameAnalyticsStats(analyticsId?: string): Promise<any>;
|
||||
getDataFrameAnalyticsStats(analyticsId?: string): Promise<GetDataFrameAnalyticsStatsResponse>;
|
||||
createDataFrameAnalytics(analyticsId: string, analyticsConfig: any): Promise<any>;
|
||||
evaluateDataFrameAnalytics(evaluateConfig: any): Promise<any>;
|
||||
deleteDataFrameAnalytics(analyticsId: string): Promise<any>;
|
||||
|
@ -155,3 +156,19 @@ declare interface Ml {
|
|||
}
|
||||
|
||||
declare const ml: Ml;
|
||||
|
||||
export interface GetDataFrameAnalyticsStatsResponseOk {
|
||||
node_failures?: object;
|
||||
count: number;
|
||||
data_frame_analytics: DataFrameAnalyticsStats[];
|
||||
}
|
||||
|
||||
export interface GetDataFrameAnalyticsStatsResponseError {
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type GetDataFrameAnalyticsStatsResponse =
|
||||
| GetDataFrameAnalyticsStatsResponseOk
|
||||
| GetDataFrameAnalyticsStatsResponseError;
|
||||
|
|
Loading…
Reference in a new issue