[ML] Fixing cloud behaviour when ML nodes are lazily assigned (#90051)
* [ML] Fixing cloud behaviour when ML nodes are lazily assigned * fixing duplicate id * updating snapshot * removing comments * fixing tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
d00266b3ff
commit
501f763713
|
@ -32,6 +32,7 @@ export interface MlSummaryJob {
|
||||||
deleting?: boolean;
|
deleting?: boolean;
|
||||||
latestTimestampSortValue?: number;
|
latestTimestampSortValue?: number;
|
||||||
earliestStartTimestampMs?: number;
|
earliestStartTimestampMs?: number;
|
||||||
|
awaitingNodeAssignment: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuditMessage {
|
export interface AuditMessage {
|
||||||
|
|
|
@ -31,3 +31,8 @@ export interface MlInfoResponse {
|
||||||
upgrade_mode: boolean;
|
upgrade_mode: boolean;
|
||||||
cloudId?: string;
|
cloudId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MlNodeCount {
|
||||||
|
count: number;
|
||||||
|
lazyNodeCount: number;
|
||||||
|
}
|
||||||
|
|
|
@ -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 { JobsAwaitingNodeWarning } from './jobs_awaiting_node_warning';
|
||||||
|
export { NewJobAwaitingNodeWarning } from './new_job_awaiting_node';
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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 { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
|
import { isCloud } from '../../services/ml_server_info';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
jobCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const JobsAwaitingNodeWarning: FC<Props> = ({ jobCount }) => {
|
||||||
|
if (isCloud() === false || jobCount === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<EuiCallOut
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.ml.jobsAwaitingNodeWarning.title"
|
||||||
|
defaultMessage="Awaiting ML node provisioning"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
color="primary"
|
||||||
|
iconType="iInCircle"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.ml.jobsAwaitingNodeWarning.noMLNodesAvailableDescription"
|
||||||
|
defaultMessage="There {jobCount, plural, one {is} other {are}} {jobCount, plural, one {# job} other {# jobs}} waiting to be started while ML nodes are being provisioned."
|
||||||
|
values={{
|
||||||
|
jobCount,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</EuiCallOut>
|
||||||
|
<EuiSpacer size="m" />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { Fragment, FC } from 'react';
|
||||||
|
|
||||||
|
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
|
import { JobType } from '../../../../common/types/saved_objects';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
jobType: JobType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NewJobAwaitingNodeWarning: FC<Props> = () => {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<EuiCallOut
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.ml.jobsAwaitingNodeWarning.title"
|
||||||
|
defaultMessage="Awaiting ML node provisioning"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
color="primary"
|
||||||
|
iconType="iInCircle"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.ml.newJobAwaitingNodeWarning.noMLNodesAvailableDescription"
|
||||||
|
defaultMessage="Job cannot be started straight away, an ML node needs to be started. This will happen automatically."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</EuiCallOut>
|
||||||
|
<EuiSpacer size="m" />
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
};
|
|
@ -41,7 +41,7 @@ export const NodeAvailableWarning: FC = () => {
|
||||||
defaultMessage="You will not be able to create or run jobs."
|
defaultMessage="You will not be able to create or run jobs."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isCloud && id !== null && (
|
{isCloud() && id !== null && (
|
||||||
<div>
|
<div>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.ml.jobsList.nodeAvailableWarning.linkToCloudDescription"
|
id="xpack.ml.jobsList.nodeAvailableWarning.linkToCloudDescription"
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { BackToListPanel } from '../back_to_list_panel';
|
||||||
import { ViewResultsPanel } from '../view_results_panel';
|
import { ViewResultsPanel } from '../view_results_panel';
|
||||||
import { ProgressStats } from './progress_stats';
|
import { ProgressStats } from './progress_stats';
|
||||||
import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
|
import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
|
||||||
|
import { NewJobAwaitingNodeWarning } from '../../../../../components/jobs_awaiting_node_warning';
|
||||||
|
|
||||||
export const PROGRESS_REFRESH_INTERVAL_MS = 1000;
|
export const PROGRESS_REFRESH_INTERVAL_MS = 1000;
|
||||||
|
|
||||||
|
@ -41,6 +42,7 @@ export const CreateStepFooter: FC<Props> = ({ jobId, jobType, showProgress }) =>
|
||||||
const [currentProgress, setCurrentProgress] = useState<AnalyticsProgressStats | undefined>(
|
const [currentProgress, setCurrentProgress] = useState<AnalyticsProgressStats | undefined>(
|
||||||
undefined
|
undefined
|
||||||
);
|
);
|
||||||
|
const [showJobAssignWarning, setShowJobAssignWarning] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
services: { notifications },
|
services: { notifications },
|
||||||
|
@ -58,6 +60,10 @@ export const CreateStepFooter: FC<Props> = ({ jobId, jobType, showProgress }) =>
|
||||||
? analyticsStats.data_frame_analytics[0]
|
? analyticsStats.data_frame_analytics[0]
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
setShowJobAssignWarning(
|
||||||
|
jobStats?.state === DATA_FRAME_TASK_STATE.STARTING && jobStats?.node === undefined
|
||||||
|
);
|
||||||
|
|
||||||
if (jobStats !== undefined) {
|
if (jobStats !== undefined) {
|
||||||
const progressStats = getDataFrameAnalyticsProgressPhase(jobStats);
|
const progressStats = getDataFrameAnalyticsProgressPhase(jobStats);
|
||||||
|
|
||||||
|
@ -106,25 +112,28 @@ export const CreateStepFooter: FC<Props> = ({ jobId, jobType, showProgress }) =>
|
||||||
}, [initialized]);
|
}, [initialized]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiFlexGroup direction="column">
|
<>
|
||||||
<EuiFlexItem grow={false}>
|
{showJobAssignWarning && <NewJobAwaitingNodeWarning jobType="data-frame-analytics" />}
|
||||||
{showProgress && (
|
<EuiFlexGroup direction="column">
|
||||||
<ProgressStats currentProgress={currentProgress} failedJobMessage={failedJobMessage} />
|
<EuiFlexItem grow={false}>
|
||||||
)}
|
{showProgress && (
|
||||||
</EuiFlexItem>
|
<ProgressStats currentProgress={currentProgress} failedJobMessage={failedJobMessage} />
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<EuiHorizontalRule />
|
|
||||||
<EuiFlexGroup>
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<BackToListPanel />
|
|
||||||
</EuiFlexItem>
|
|
||||||
{jobFinished === true && (
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<ViewResultsPanel jobId={jobId} analysisType={jobType} />
|
|
||||||
</EuiFlexItem>
|
|
||||||
)}
|
)}
|
||||||
</EuiFlexGroup>
|
</EuiFlexItem>
|
||||||
</EuiFlexItem>
|
<EuiFlexItem grow={false}>
|
||||||
</EuiFlexGroup>
|
<EuiHorizontalRule />
|
||||||
|
<EuiFlexGroup>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<BackToListPanel />
|
||||||
|
</EuiFlexItem>
|
||||||
|
{jobFinished === true && (
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<ViewResultsPanel jobId={jobId} analysisType={jobType} />
|
||||||
|
</EuiFlexItem>
|
||||||
|
)}
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,6 +36,7 @@ import { AnalyticsEmptyPrompt } from './empty_prompt';
|
||||||
import { useTableSettings } from './use_table_settings';
|
import { useTableSettings } from './use_table_settings';
|
||||||
import { RefreshAnalyticsListButton } from '../refresh_analytics_list_button';
|
import { RefreshAnalyticsListButton } from '../refresh_analytics_list_button';
|
||||||
import { ListingPageUrlState } from '../../../../../../../common/types/common';
|
import { ListingPageUrlState } from '../../../../../../../common/types/common';
|
||||||
|
import { JobsAwaitingNodeWarning } from '../../../../../components/jobs_awaiting_node_warning';
|
||||||
|
|
||||||
const filters: EuiSearchBarProps['filters'] = [
|
const filters: EuiSearchBarProps['filters'] = [
|
||||||
{
|
{
|
||||||
|
@ -114,6 +115,7 @@ export const DataFrameAnalyticsList: FC<Props> = ({
|
||||||
);
|
);
|
||||||
const [expandedRowItemIds, setExpandedRowItemIds] = useState<DataFrameAnalyticsId[]>([]);
|
const [expandedRowItemIds, setExpandedRowItemIds] = useState<DataFrameAnalyticsId[]>([]);
|
||||||
const [errorMessage, setErrorMessage] = useState<any>(undefined);
|
const [errorMessage, setErrorMessage] = useState<any>(undefined);
|
||||||
|
const [jobsAwaitingNodeCount, setJobsAwaitingNodeCount] = useState(0);
|
||||||
|
|
||||||
const disabled =
|
const disabled =
|
||||||
!checkPermission('canCreateDataFrameAnalytics') ||
|
!checkPermission('canCreateDataFrameAnalytics') ||
|
||||||
|
@ -124,6 +126,7 @@ export const DataFrameAnalyticsList: FC<Props> = ({
|
||||||
setAnalyticsStats,
|
setAnalyticsStats,
|
||||||
setErrorMessage,
|
setErrorMessage,
|
||||||
setIsInitialized,
|
setIsInitialized,
|
||||||
|
setJobsAwaitingNodeCount,
|
||||||
blockRefresh,
|
blockRefresh,
|
||||||
isManagementTable
|
isManagementTable
|
||||||
);
|
);
|
||||||
|
@ -261,6 +264,7 @@ export const DataFrameAnalyticsList: FC<Props> = ({
|
||||||
<div data-test-subj="mlAnalyticsJobList">
|
<div data-test-subj="mlAnalyticsJobList">
|
||||||
{modals}
|
{modals}
|
||||||
{!isManagementTable && <EuiSpacer size="m" />}
|
{!isManagementTable && <EuiSpacer size="m" />}
|
||||||
|
<JobsAwaitingNodeWarning jobCount={jobsAwaitingNodeCount} />
|
||||||
<EuiFlexGroup justifyContent="spaceBetween">
|
<EuiFlexGroup justifyContent="spaceBetween">
|
||||||
{!isManagementTable && stats}
|
{!isManagementTable && stats}
|
||||||
{isManagementTable && managementStats}
|
{isManagementTable && managementStats}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
} from '../../components/analytics_list/common';
|
} from '../../components/analytics_list/common';
|
||||||
import { AnalyticStatsBarStats } from '../../../../../components/stats_bar';
|
import { AnalyticStatsBarStats } from '../../../../../components/stats_bar';
|
||||||
import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
|
import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics';
|
||||||
|
import { DATA_FRAME_TASK_STATE } from '../../../../../../../common/constants/data_frame_analytics';
|
||||||
|
|
||||||
export const isGetDataFrameAnalyticsStatsResponseOk = (
|
export const isGetDataFrameAnalyticsStatsResponseOk = (
|
||||||
arg: any
|
arg: any
|
||||||
|
@ -106,6 +107,7 @@ export const getAnalyticsFactory = (
|
||||||
React.SetStateAction<GetDataFrameAnalyticsStatsResponseError | undefined>
|
React.SetStateAction<GetDataFrameAnalyticsStatsResponseError | undefined>
|
||||||
>,
|
>,
|
||||||
setIsInitialized: React.Dispatch<React.SetStateAction<boolean>>,
|
setIsInitialized: React.Dispatch<React.SetStateAction<boolean>>,
|
||||||
|
setJobsAwaitingNodeCount: React.Dispatch<React.SetStateAction<number>>,
|
||||||
blockRefresh: boolean,
|
blockRefresh: boolean,
|
||||||
isManagementTable: boolean
|
isManagementTable: boolean
|
||||||
): GetAnalytics => {
|
): GetAnalytics => {
|
||||||
|
@ -134,6 +136,8 @@ export const getAnalyticsFactory = (
|
||||||
? getAnalyticsJobsStats(analyticsStats)
|
? getAnalyticsJobsStats(analyticsStats)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
let jobsAwaitingNodeCount = 0;
|
||||||
|
|
||||||
const tableRows = analyticsConfigs.data_frame_analytics.reduce(
|
const tableRows = analyticsConfigs.data_frame_analytics.reduce(
|
||||||
(reducedtableRows, config) => {
|
(reducedtableRows, config) => {
|
||||||
const stats = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats)
|
const stats = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats)
|
||||||
|
@ -146,6 +150,10 @@ export const getAnalyticsFactory = (
|
||||||
return reducedtableRows;
|
return reducedtableRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stats.state === DATA_FRAME_TASK_STATE.STARTING && stats.node === undefined) {
|
||||||
|
jobsAwaitingNodeCount++;
|
||||||
|
}
|
||||||
|
|
||||||
// Table with expandable rows requires `id` on the outer most level
|
// Table with expandable rows requires `id` on the outer most level
|
||||||
reducedtableRows.push({
|
reducedtableRows.push({
|
||||||
checkpointing: {},
|
checkpointing: {},
|
||||||
|
@ -166,6 +174,7 @@ export const getAnalyticsFactory = (
|
||||||
setAnalyticsStats(analyticsStatsResult);
|
setAnalyticsStats(analyticsStatsResult);
|
||||||
setErrorMessage(undefined);
|
setErrorMessage(undefined);
|
||||||
setIsInitialized(true);
|
setIsInitialized(true);
|
||||||
|
setJobsAwaitingNodeCount(jobsAwaitingNodeCount);
|
||||||
refreshAnalyticsList$.next(REFRESH_ANALYTICS_LIST_STATE.IDLE);
|
refreshAnalyticsList$.next(REFRESH_ANALYTICS_LIST_STATE.IDLE);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// An error is followed immediately by setting the state to idle.
|
// An error is followed immediately by setting the state to idle.
|
||||||
|
|
|
@ -32,6 +32,7 @@ import { MultiJobActions } from '../multi_job_actions';
|
||||||
import { NewJobButton } from '../new_job_button';
|
import { NewJobButton } from '../new_job_button';
|
||||||
import { JobStatsBar } from '../jobs_stats_bar';
|
import { JobStatsBar } from '../jobs_stats_bar';
|
||||||
import { NodeAvailableWarning } from '../../../../components/node_available_warning';
|
import { NodeAvailableWarning } from '../../../../components/node_available_warning';
|
||||||
|
import { JobsAwaitingNodeWarning } from '../../../../components/jobs_awaiting_node_warning';
|
||||||
import { SavedObjectsWarning } from '../../../../components/saved_objects_warning';
|
import { SavedObjectsWarning } from '../../../../components/saved_objects_warning';
|
||||||
import { DatePickerWrapper } from '../../../../components/navigation_menu/date_picker_wrapper';
|
import { DatePickerWrapper } from '../../../../components/navigation_menu/date_picker_wrapper';
|
||||||
import { UpgradeWarning } from '../../../../components/upgrade';
|
import { UpgradeWarning } from '../../../../components/upgrade';
|
||||||
|
@ -56,6 +57,7 @@ export class JobsListView extends Component {
|
||||||
itemIdToExpandedRowMap: {},
|
itemIdToExpandedRowMap: {},
|
||||||
filterClauses: [],
|
filterClauses: [],
|
||||||
deletingJobIds: [],
|
deletingJobIds: [],
|
||||||
|
jobsAwaitingNodeCount: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.spacesEnabled = props.spacesEnabled ?? false;
|
this.spacesEnabled = props.spacesEnabled ?? false;
|
||||||
|
@ -272,6 +274,7 @@ export class JobsListView extends Component {
|
||||||
spaces = allSpaces['anomaly-detector'];
|
spaces = allSpaces['anomaly-detector'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let jobsAwaitingNodeCount = 0;
|
||||||
const jobs = await ml.jobs.jobsSummary(expandedJobsIds);
|
const jobs = await ml.jobs.jobsSummary(expandedJobsIds);
|
||||||
const fullJobsList = {};
|
const fullJobsList = {};
|
||||||
const jobsSummaryList = jobs.map((job) => {
|
const jobsSummaryList = jobs.map((job) => {
|
||||||
|
@ -287,11 +290,21 @@ export class JobsListView extends Component {
|
||||||
spaces[job.id] !== undefined
|
spaces[job.id] !== undefined
|
||||||
? spaces[job.id]
|
? spaces[job.id]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
if (job.awaitingNodeAssignment === true) {
|
||||||
|
jobsAwaitingNodeCount++;
|
||||||
|
}
|
||||||
return job;
|
return job;
|
||||||
});
|
});
|
||||||
const filteredJobsSummaryList = filterJobs(jobsSummaryList, this.state.filterClauses);
|
const filteredJobsSummaryList = filterJobs(jobsSummaryList, this.state.filterClauses);
|
||||||
this.setState(
|
this.setState(
|
||||||
{ jobsSummaryList, filteredJobsSummaryList, fullJobsList, loading: false },
|
{
|
||||||
|
jobsSummaryList,
|
||||||
|
filteredJobsSummaryList,
|
||||||
|
fullJobsList,
|
||||||
|
loading: false,
|
||||||
|
jobsAwaitingNodeCount,
|
||||||
|
},
|
||||||
() => {
|
() => {
|
||||||
this.refreshSelectedJobs();
|
this.refreshSelectedJobs();
|
||||||
}
|
}
|
||||||
|
@ -407,7 +420,7 @@ export class JobsListView extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderJobsListComponents() {
|
renderJobsListComponents() {
|
||||||
const { isRefreshing, loading, jobsSummaryList } = this.state;
|
const { isRefreshing, loading, jobsSummaryList, jobsAwaitingNodeCount } = this.state;
|
||||||
const jobIds = jobsSummaryList.map((j) => j.id);
|
const jobIds = jobsSummaryList.map((j) => j.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -440,6 +453,7 @@ export class JobsListView extends Component {
|
||||||
</EuiPageHeader>
|
</EuiPageHeader>
|
||||||
|
|
||||||
<NodeAvailableWarning />
|
<NodeAvailableWarning />
|
||||||
|
<JobsAwaitingNodeWarning jobCount={jobsAwaitingNodeCount} />
|
||||||
<SavedObjectsWarning jobType="anomaly-detector" />
|
<SavedObjectsWarning jobType="anomaly-detector" />
|
||||||
|
|
||||||
<UpgradeWarning />
|
<UpgradeWarning />
|
||||||
|
|
|
@ -11,12 +11,14 @@ import { JobCreator } from '../job_creator';
|
||||||
import { DatafeedId, JobId } from '../../../../../../common/types/anomaly_detection_jobs';
|
import { DatafeedId, JobId } from '../../../../../../common/types/anomaly_detection_jobs';
|
||||||
import { DATAFEED_STATE } from '../../../../../../common/constants/states';
|
import { DATAFEED_STATE } from '../../../../../../common/constants/states';
|
||||||
|
|
||||||
const REFRESH_INTERVAL_MS = 100;
|
const REFRESH_INTERVAL_MS = 250;
|
||||||
|
const NODE_ASSIGNMENT_CHECK_REFRESH_INTERVAL_MS = 2000;
|
||||||
const TARGET_PROGRESS_DELTA = 2;
|
const TARGET_PROGRESS_DELTA = 2;
|
||||||
const REFRESH_RATE_ADJUSTMENT_DELAY_MS = 2000;
|
const REFRESH_RATE_ADJUSTMENT_DELAY_MS = 2000;
|
||||||
|
|
||||||
type Progress = number;
|
type Progress = number;
|
||||||
export type ProgressSubscriber = (progress: number) => void;
|
export type ProgressSubscriber = (progress: number) => void;
|
||||||
|
export type JobAssignmentSubscriber = (assigned: boolean) => void;
|
||||||
|
|
||||||
export class JobRunner {
|
export class JobRunner {
|
||||||
private _jobId: JobId;
|
private _jobId: JobId;
|
||||||
|
@ -35,6 +37,8 @@ export class JobRunner {
|
||||||
|
|
||||||
private _datafeedStartTime: number = 0;
|
private _datafeedStartTime: number = 0;
|
||||||
private _performRefreshRateAdjustment: boolean = false;
|
private _performRefreshRateAdjustment: boolean = false;
|
||||||
|
private _jobAssignedToNode: boolean = false;
|
||||||
|
private _jobAssignedToNode$: BehaviorSubject<boolean>;
|
||||||
|
|
||||||
constructor(jobCreator: JobCreator) {
|
constructor(jobCreator: JobCreator) {
|
||||||
this._jobId = jobCreator.jobId;
|
this._jobId = jobCreator.jobId;
|
||||||
|
@ -45,6 +49,7 @@ export class JobRunner {
|
||||||
this._stopRefreshPoll = jobCreator.stopAllRefreshPolls;
|
this._stopRefreshPoll = jobCreator.stopAllRefreshPolls;
|
||||||
|
|
||||||
this._progress$ = new BehaviorSubject(this._percentageComplete);
|
this._progress$ = new BehaviorSubject(this._percentageComplete);
|
||||||
|
this._jobAssignedToNode$ = new BehaviorSubject(this._jobAssignedToNode);
|
||||||
this._subscribers = jobCreator.subscribers;
|
this._subscribers = jobCreator.subscribers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +67,9 @@ export class JobRunner {
|
||||||
|
|
||||||
private async openJob(): Promise<void> {
|
private async openJob(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await mlJobService.openJob(this._jobId);
|
const { node }: { node?: string } = await mlJobService.openJob(this._jobId);
|
||||||
|
this._jobAssignedToNode = node !== undefined && node.length > 0;
|
||||||
|
this._jobAssignedToNode$.next(this._jobAssignedToNode);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -94,7 +101,7 @@ export class JobRunner {
|
||||||
this._datafeedState = DATAFEED_STATE.STARTED;
|
this._datafeedState = DATAFEED_STATE.STARTED;
|
||||||
this._percentageComplete = 0;
|
this._percentageComplete = 0;
|
||||||
|
|
||||||
const check = async () => {
|
const checkProgress = async () => {
|
||||||
const { isRunning, progress: prog, isJobClosed } = await this.getProgress();
|
const { isRunning, progress: prog, isJobClosed } = await this.getProgress();
|
||||||
|
|
||||||
// if the progress has reached 100% but the job is still running,
|
// if the progress has reached 100% but the job is still running,
|
||||||
|
@ -109,7 +116,9 @@ export class JobRunner {
|
||||||
|
|
||||||
if ((isRunning === true || isJobClosed === false) && this._stopRefreshPoll.stop === false) {
|
if ((isRunning === true || isJobClosed === false) && this._stopRefreshPoll.stop === false) {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
await check();
|
if (this._stopRefreshPoll.stop === false) {
|
||||||
|
await checkProgress();
|
||||||
|
}
|
||||||
}, this._refreshInterval);
|
}, this._refreshInterval);
|
||||||
} else {
|
} else {
|
||||||
// job has finished running, set progress to 100%
|
// job has finished running, set progress to 100%
|
||||||
|
@ -121,11 +130,30 @@ export class JobRunner {
|
||||||
subscriptions.forEach((s) => s.unsubscribe());
|
subscriptions.forEach((s) => s.unsubscribe());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkJobIsAssigned = async () => {
|
||||||
|
this._jobAssignedToNode = await this._isJobAssigned();
|
||||||
|
this._jobAssignedToNode$.next(this._jobAssignedToNode);
|
||||||
|
if (this._jobAssignedToNode === true) {
|
||||||
|
await checkProgress();
|
||||||
|
} else {
|
||||||
|
setTimeout(async () => {
|
||||||
|
if (this._stopRefreshPoll.stop === false) {
|
||||||
|
await checkJobIsAssigned();
|
||||||
|
}
|
||||||
|
}, NODE_ASSIGNMENT_CHECK_REFRESH_INTERVAL_MS);
|
||||||
|
}
|
||||||
|
};
|
||||||
// wait for the first check to run and then return success.
|
// wait for the first check to run and then return success.
|
||||||
// all subsequent checks will update the observable
|
// all subsequent checks will update the observable
|
||||||
if (pollProgress === true) {
|
if (pollProgress === true) {
|
||||||
await check();
|
if (this._jobAssignedToNode === true) {
|
||||||
|
await checkProgress();
|
||||||
|
} else {
|
||||||
|
await checkJobIsAssigned();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return started;
|
return started;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -159,6 +187,11 @@ export class JobRunner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _isJobAssigned(): Promise<boolean> {
|
||||||
|
const { jobs } = await ml.getJobStats({ jobId: this._jobId });
|
||||||
|
return jobs.length > 0 && jobs[0].node !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
public async startDatafeed() {
|
public async startDatafeed() {
|
||||||
return await this._startDatafeed(this._start, this._end, true);
|
return await this._startDatafeed(this._start, this._end, true);
|
||||||
}
|
}
|
||||||
|
@ -185,4 +218,12 @@ export class JobRunner {
|
||||||
const { isRunning } = await this.getProgress();
|
const { isRunning } = await this.getProgress();
|
||||||
return isRunning;
|
return isRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isJobAssignedToNode() {
|
||||||
|
return this._jobAssignedToNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public subscribeToJobAssignment(func: JobAssignmentSubscriber) {
|
||||||
|
return this._jobAssignedToNode$.subscribe(func);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { Fragment, FC, useContext, useState, useEffect } from 'react';
|
import React, { Fragment, FC, useContext, useState, useEffect } from 'react';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
EuiButton,
|
EuiButton,
|
||||||
EuiButtonEmpty,
|
EuiButtonEmpty,
|
||||||
|
@ -29,6 +30,7 @@ import { DetectorChart } from './components/detector_chart';
|
||||||
import { JobProgress } from './components/job_progress';
|
import { JobProgress } from './components/job_progress';
|
||||||
import { PostSaveOptions } from './components/post_save_options';
|
import { PostSaveOptions } from './components/post_save_options';
|
||||||
import { StartDatafeedSwitch } from './components/start_datafeed_switch';
|
import { StartDatafeedSwitch } from './components/start_datafeed_switch';
|
||||||
|
import { NewJobAwaitingNodeWarning } from '../../../../../components/jobs_awaiting_node_warning';
|
||||||
import { toastNotificationServiceProvider } from '../../../../../services/toast_notification_service';
|
import { toastNotificationServiceProvider } from '../../../../../services/toast_notification_service';
|
||||||
import {
|
import {
|
||||||
convertToAdvancedJob,
|
convertToAdvancedJob,
|
||||||
|
@ -55,6 +57,7 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
|
||||||
const [isValid, setIsValid] = useState(jobValidator.validationSummary.basic);
|
const [isValid, setIsValid] = useState(jobValidator.validationSummary.basic);
|
||||||
const [jobRunner, setJobRunner] = useState<JobRunner | null>(null);
|
const [jobRunner, setJobRunner] = useState<JobRunner | null>(null);
|
||||||
const [startDatafeed, setStartDatafeed] = useState(true);
|
const [startDatafeed, setStartDatafeed] = useState(true);
|
||||||
|
const [showJobAssignWarning, setShowJobAssignWarning] = useState(false);
|
||||||
|
|
||||||
const isAdvanced = isAdvancedJobCreator(jobCreator);
|
const isAdvanced = isAdvancedJobCreator(jobCreator);
|
||||||
const jsonEditorMode = isAdvanced ? EDITOR_MODE.EDITABLE : EDITOR_MODE.READONLY;
|
const jsonEditorMode = isAdvanced ? EDITOR_MODE.EDITABLE : EDITOR_MODE.READONLY;
|
||||||
|
@ -63,6 +66,20 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
|
||||||
jobCreator.subscribeToProgress(setProgress);
|
jobCreator.subscribeToProgress(setProgress);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let s: Subscription | null = null;
|
||||||
|
if (jobRunner !== null) {
|
||||||
|
s = jobRunner.subscribeToJobAssignment((assigned: boolean) =>
|
||||||
|
setShowJobAssignWarning(!assigned)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
if (s !== null) {
|
||||||
|
s?.unsubscribe();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [jobRunner]);
|
||||||
|
|
||||||
async function start() {
|
async function start() {
|
||||||
setCreatingJob(true);
|
setCreatingJob(true);
|
||||||
if (isAdvanced) {
|
if (isAdvanced) {
|
||||||
|
@ -155,6 +172,7 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<EuiHorizontalRule />
|
<EuiHorizontalRule />
|
||||||
|
{showJobAssignWarning && <NewJobAwaitingNodeWarning jobType="anomaly-detector" />}
|
||||||
<EuiFlexGroup>
|
<EuiFlexGroup>
|
||||||
{progress < 100 && (
|
{progress < 100 && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
|
|
@ -5,14 +5,16 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ml } from '../services/ml_api_service';
|
import { ml } from '../services/ml_api_service';
|
||||||
|
import { MlNodeCount } from '../../../common/types/ml_server_info';
|
||||||
|
|
||||||
let mlNodeCount: number = 0;
|
let mlNodeCount: number = 0;
|
||||||
|
let lazyMlNodeCount: number = 0;
|
||||||
let userHasPermissionToViewMlNodeCount: boolean = false;
|
let userHasPermissionToViewMlNodeCount: boolean = false;
|
||||||
|
|
||||||
export async function checkMlNodesAvailable(redirectToJobsManagementPage: () => Promise<void>) {
|
export async function checkMlNodesAvailable(redirectToJobsManagementPage: () => Promise<void>) {
|
||||||
try {
|
try {
|
||||||
const nodes = await getMlNodeCount();
|
const { count, lazyNodeCount } = await getMlNodeCount();
|
||||||
if (nodes.count !== undefined && nodes.count > 0) {
|
if (count > 0 || lazyNodeCount > 0) {
|
||||||
Promise.resolve();
|
Promise.resolve();
|
||||||
} else {
|
} else {
|
||||||
throw Error('Cannot load count of ML nodes');
|
throw Error('Cannot load count of ML nodes');
|
||||||
|
@ -25,23 +27,24 @@ export async function checkMlNodesAvailable(redirectToJobsManagementPage: () =>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMlNodeCount() {
|
export async function getMlNodeCount(): Promise<MlNodeCount> {
|
||||||
try {
|
try {
|
||||||
const nodes = await ml.mlNodeCount();
|
const nodes = await ml.mlNodeCount();
|
||||||
mlNodeCount = nodes.count;
|
mlNodeCount = nodes.count;
|
||||||
|
lazyMlNodeCount = nodes.lazyNodeCount;
|
||||||
userHasPermissionToViewMlNodeCount = true;
|
userHasPermissionToViewMlNodeCount = true;
|
||||||
return Promise.resolve(nodes);
|
return nodes;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
mlNodeCount = 0;
|
mlNodeCount = 0;
|
||||||
if (error.statusCode === 403) {
|
if (error.statusCode === 403) {
|
||||||
userHasPermissionToViewMlNodeCount = false;
|
userHasPermissionToViewMlNodeCount = false;
|
||||||
}
|
}
|
||||||
return Promise.resolve({ count: 0 });
|
return { count: 0, lazyNodeCount: 0 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mlNodesAvailable() {
|
export function mlNodesAvailable() {
|
||||||
return mlNodeCount !== 0;
|
return mlNodeCount !== 0 || lazyMlNodeCount !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function permissionToViewMlNodeCount() {
|
export function permissionToViewMlNodeCount() {
|
||||||
|
|
|
@ -28,8 +28,9 @@ import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
jobCreationDisabled: boolean;
|
jobCreationDisabled: boolean;
|
||||||
|
setLazyJobCount: React.Dispatch<React.SetStateAction<number>>;
|
||||||
}
|
}
|
||||||
export const AnalyticsPanel: FC<Props> = ({ jobCreationDisabled }) => {
|
export const AnalyticsPanel: FC<Props> = ({ jobCreationDisabled, setLazyJobCount }) => {
|
||||||
const [analytics, setAnalytics] = useState<DataFrameAnalyticsListRow[]>([]);
|
const [analytics, setAnalytics] = useState<DataFrameAnalyticsListRow[]>([]);
|
||||||
const [analyticsStats, setAnalyticsStats] = useState<AnalyticStatsBarStats | undefined>(
|
const [analyticsStats, setAnalyticsStats] = useState<AnalyticStatsBarStats | undefined>(
|
||||||
undefined
|
undefined
|
||||||
|
@ -52,6 +53,7 @@ export const AnalyticsPanel: FC<Props> = ({ jobCreationDisabled }) => {
|
||||||
setAnalyticsStats,
|
setAnalyticsStats,
|
||||||
setErrorMessage,
|
setErrorMessage,
|
||||||
setIsInitialized,
|
setIsInitialized,
|
||||||
|
setLazyJobCount,
|
||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
|
@ -51,9 +51,10 @@ function getDefaultAnomalyScores(groups: Group[]): MaxScoresByGroup {
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
jobCreationDisabled: boolean;
|
jobCreationDisabled: boolean;
|
||||||
|
setLazyJobCount: React.Dispatch<React.SetStateAction<number>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AnomalyDetectionPanel: FC<Props> = ({ jobCreationDisabled }) => {
|
export const AnomalyDetectionPanel: FC<Props> = ({ jobCreationDisabled, setLazyJobCount }) => {
|
||||||
const {
|
const {
|
||||||
services: { notifications },
|
services: { notifications },
|
||||||
} = useMlKibana();
|
} = useMlKibana();
|
||||||
|
@ -84,10 +85,14 @@ export const AnomalyDetectionPanel: FC<Props> = ({ jobCreationDisabled }) => {
|
||||||
const loadJobs = async () => {
|
const loadJobs = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
|
let lazyJobCount = 0;
|
||||||
try {
|
try {
|
||||||
const jobsResult: MlSummaryJobs = await ml.jobs.jobsSummary([]);
|
const jobsResult: MlSummaryJobs = await ml.jobs.jobsSummary([]);
|
||||||
const jobsSummaryList = jobsResult.map((job: MlSummaryJob) => {
|
const jobsSummaryList = jobsResult.map((job: MlSummaryJob) => {
|
||||||
job.latestTimestampSortValue = job.latestTimestampMs || 0;
|
job.latestTimestampSortValue = job.latestTimestampMs || 0;
|
||||||
|
if (job.awaitingNodeAssignment) {
|
||||||
|
lazyJobCount++;
|
||||||
|
}
|
||||||
return job;
|
return job;
|
||||||
});
|
});
|
||||||
const { groups: jobsGroups, count } = getGroupsFromJobs(jobsSummaryList);
|
const { groups: jobsGroups, count } = getGroupsFromJobs(jobsSummaryList);
|
||||||
|
@ -100,6 +105,7 @@ export const AnomalyDetectionPanel: FC<Props> = ({ jobCreationDisabled }) => {
|
||||||
setGroups(jobsGroups);
|
setGroups(jobsGroups);
|
||||||
setJobsList(jobsWithTimerange);
|
setJobsList(jobsWithTimerange);
|
||||||
loadMaxAnomalyScores(jobsGroups);
|
loadMaxAnomalyScores(jobsGroups);
|
||||||
|
setLazyJobCount(lazyJobCount);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setErrorMessage(e.message !== undefined ? e.message : JSON.stringify(e));
|
setErrorMessage(e.message !== undefined ? e.message : JSON.stringify(e));
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|
|
@ -12,20 +12,30 @@ import { AnalyticsPanel } from './analytics_panel';
|
||||||
interface Props {
|
interface Props {
|
||||||
createAnomalyDetectionJobDisabled: boolean;
|
createAnomalyDetectionJobDisabled: boolean;
|
||||||
createAnalyticsJobDisabled: boolean;
|
createAnalyticsJobDisabled: boolean;
|
||||||
|
setAdLazyJobCount: React.Dispatch<React.SetStateAction<number>>;
|
||||||
|
setDfaLazyJobCount: React.Dispatch<React.SetStateAction<number>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch jobs and determine what to show
|
// Fetch jobs and determine what to show
|
||||||
export const OverviewContent: FC<Props> = ({
|
export const OverviewContent: FC<Props> = ({
|
||||||
createAnomalyDetectionJobDisabled,
|
createAnomalyDetectionJobDisabled,
|
||||||
createAnalyticsJobDisabled,
|
createAnalyticsJobDisabled,
|
||||||
|
setAdLazyJobCount,
|
||||||
|
setDfaLazyJobCount,
|
||||||
}) => (
|
}) => (
|
||||||
<EuiFlexItem grow={3}>
|
<EuiFlexItem grow={3}>
|
||||||
<EuiFlexGroup direction="column">
|
<EuiFlexGroup direction="column">
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<AnomalyDetectionPanel jobCreationDisabled={createAnomalyDetectionJobDisabled} />
|
<AnomalyDetectionPanel
|
||||||
|
jobCreationDisabled={createAnomalyDetectionJobDisabled}
|
||||||
|
setLazyJobCount={setAdLazyJobCount}
|
||||||
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<AnalyticsPanel jobCreationDisabled={createAnalyticsJobDisabled} />
|
<AnalyticsPanel
|
||||||
|
jobCreationDisabled={createAnalyticsJobDisabled}
|
||||||
|
setLazyJobCount={setDfaLazyJobCount}
|
||||||
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { Fragment, FC } from 'react';
|
import React, { Fragment, FC, useState } from 'react';
|
||||||
import { EuiFlexGroup, EuiPage, EuiPageBody } from '@elastic/eui';
|
import { EuiFlexGroup, EuiPage, EuiPageBody } from '@elastic/eui';
|
||||||
import { checkPermission } from '../capabilities/check_capabilities';
|
import { checkPermission } from '../capabilities/check_capabilities';
|
||||||
import { mlNodesAvailable } from '../ml_nodes_check/check_ml_nodes';
|
import { mlNodesAvailable } from '../ml_nodes_check/check_ml_nodes';
|
||||||
|
@ -12,6 +12,7 @@ import { NavigationMenu } from '../components/navigation_menu';
|
||||||
import { OverviewSideBar } from './components/sidebar';
|
import { OverviewSideBar } from './components/sidebar';
|
||||||
import { OverviewContent } from './components/content';
|
import { OverviewContent } from './components/content';
|
||||||
import { NodeAvailableWarning } from '../components/node_available_warning';
|
import { NodeAvailableWarning } from '../components/node_available_warning';
|
||||||
|
import { JobsAwaitingNodeWarning } from '../components/jobs_awaiting_node_warning';
|
||||||
import { SavedObjectsWarning } from '../components/saved_objects_warning';
|
import { SavedObjectsWarning } from '../components/saved_objects_warning';
|
||||||
import { UpgradeWarning } from '../components/upgrade';
|
import { UpgradeWarning } from '../components/upgrade';
|
||||||
import { HelpMenu } from '../components/help_menu';
|
import { HelpMenu } from '../components/help_menu';
|
||||||
|
@ -27,12 +28,17 @@ export const OverviewPage: FC = () => {
|
||||||
services: { docLinks },
|
services: { docLinks },
|
||||||
} = useMlKibana();
|
} = useMlKibana();
|
||||||
const helpLink = docLinks.links.ml.guide;
|
const helpLink = docLinks.links.ml.guide;
|
||||||
|
|
||||||
|
const [adLazyJobCount, setAdLazyJobCount] = useState(0);
|
||||||
|
const [dfaLazyJobCount, setDfaLazyJobCount] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<NavigationMenu tabId="overview" />
|
<NavigationMenu tabId="overview" />
|
||||||
<EuiPage data-test-subj="mlPageOverview">
|
<EuiPage data-test-subj="mlPageOverview">
|
||||||
<EuiPageBody>
|
<EuiPageBody>
|
||||||
<NodeAvailableWarning />
|
<NodeAvailableWarning />
|
||||||
|
<JobsAwaitingNodeWarning jobCount={adLazyJobCount + dfaLazyJobCount} />
|
||||||
<SavedObjectsWarning />
|
<SavedObjectsWarning />
|
||||||
<UpgradeWarning />
|
<UpgradeWarning />
|
||||||
|
|
||||||
|
@ -41,6 +47,8 @@ export const OverviewPage: FC = () => {
|
||||||
<OverviewContent
|
<OverviewContent
|
||||||
createAnomalyDetectionJobDisabled={disableCreateAnomalyDetectionJob}
|
createAnomalyDetectionJobDisabled={disableCreateAnomalyDetectionJob}
|
||||||
createAnalyticsJobDisabled={disableCreateAnalyticsButton}
|
createAnalyticsJobDisabled={disableCreateAnalyticsButton}
|
||||||
|
setAdLazyJobCount={setAdLazyJobCount}
|
||||||
|
setDfaLazyJobCount={setDfaLazyJobCount}
|
||||||
/>
|
/>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</EuiPageBody>
|
</EuiPageBody>
|
||||||
|
|
|
@ -15,12 +15,17 @@ import { resultsApiProvider } from './results';
|
||||||
import { jobsApiProvider } from './jobs';
|
import { jobsApiProvider } from './jobs';
|
||||||
import { fileDatavisualizer } from './datavisualizer';
|
import { fileDatavisualizer } from './datavisualizer';
|
||||||
import { savedObjectsApiProvider } from './saved_objects';
|
import { savedObjectsApiProvider } from './saved_objects';
|
||||||
import { MlServerDefaults, MlServerLimits } from '../../../../common/types/ml_server_info';
|
import {
|
||||||
|
MlServerDefaults,
|
||||||
|
MlServerLimits,
|
||||||
|
MlNodeCount,
|
||||||
|
} from '../../../../common/types/ml_server_info';
|
||||||
|
|
||||||
import { MlCapabilitiesResponse } from '../../../../common/types/capabilities';
|
import { MlCapabilitiesResponse } from '../../../../common/types/capabilities';
|
||||||
import { Calendar, CalendarId, UpdateCalendar } from '../../../../common/types/calendars';
|
import { Calendar, CalendarId, UpdateCalendar } from '../../../../common/types/calendars';
|
||||||
import {
|
import {
|
||||||
Job,
|
Job,
|
||||||
|
JobStats,
|
||||||
Datafeed,
|
Datafeed,
|
||||||
CombinedJob,
|
CombinedJob,
|
||||||
Detector,
|
Detector,
|
||||||
|
@ -116,14 +121,14 @@ export function mlApiServicesProvider(httpService: HttpService) {
|
||||||
return {
|
return {
|
||||||
getJobs(obj?: { jobId?: string }) {
|
getJobs(obj?: { jobId?: string }) {
|
||||||
const jobId = obj && obj.jobId ? `/${obj.jobId}` : '';
|
const jobId = obj && obj.jobId ? `/${obj.jobId}` : '';
|
||||||
return httpService.http<any>({
|
return httpService.http<{ jobs: Job[]; count: number }>({
|
||||||
path: `${basePath()}/anomaly_detectors${jobId}`,
|
path: `${basePath()}/anomaly_detectors${jobId}`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getJobStats(obj: { jobId?: string }) {
|
getJobStats(obj: { jobId?: string }) {
|
||||||
const jobId = obj && obj.jobId ? `/${obj.jobId}` : '';
|
const jobId = obj && obj.jobId ? `/${obj.jobId}` : '';
|
||||||
return httpService.http<any>({
|
return httpService.http<{ jobs: JobStats[]; count: number }>({
|
||||||
path: `${basePath()}/anomaly_detectors${jobId}/_stats`,
|
path: `${basePath()}/anomaly_detectors${jobId}/_stats`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -614,7 +619,7 @@ export function mlApiServicesProvider(httpService: HttpService) {
|
||||||
},
|
},
|
||||||
|
|
||||||
mlNodeCount() {
|
mlNodeCount() {
|
||||||
return httpService.http<{ count: number }>({
|
return httpService.http<MlNodeCount>({
|
||||||
path: `${basePath()}/ml_node_count`,
|
path: `${basePath()}/ml_node_count`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
|
|
79
x-pack/plugins/ml/server/lib/node_utils.ts
Normal file
79
x-pack/plugins/ml/server/lib/node_utils.ts
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* 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 { IScopedClusterClient } from 'kibana/server';
|
||||||
|
import { MlNodeCount } from '../../common/types/ml_server_info';
|
||||||
|
|
||||||
|
export async function getMlNodeCount(client: IScopedClusterClient): Promise<MlNodeCount> {
|
||||||
|
const { body } = await client.asInternalUser.nodes.info({
|
||||||
|
filter_path: 'nodes.*.attributes',
|
||||||
|
});
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
if (typeof body.nodes === 'object') {
|
||||||
|
Object.keys(body.nodes).forEach((k) => {
|
||||||
|
if (body.nodes[k].attributes !== undefined) {
|
||||||
|
const maxOpenJobs = body.nodes[k].attributes['ml.max_open_jobs'];
|
||||||
|
if (maxOpenJobs !== null && maxOpenJobs > 0) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const lazyNodeCount = await getLazyMlNodeCount(client);
|
||||||
|
|
||||||
|
return { count, lazyNodeCount };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getLazyMlNodeCount(client: IScopedClusterClient) {
|
||||||
|
const { body } = await client.asInternalUser.cluster.getSettings({
|
||||||
|
include_defaults: true,
|
||||||
|
filter_path: '**.xpack.ml.max_lazy_ml_nodes',
|
||||||
|
});
|
||||||
|
|
||||||
|
const lazyMlNodesString: string | undefined = (
|
||||||
|
body.defaults ||
|
||||||
|
body.persistent ||
|
||||||
|
body.transient ||
|
||||||
|
{}
|
||||||
|
).xpack?.ml.max_lazy_ml_nodes;
|
||||||
|
|
||||||
|
const count = lazyMlNodesString === undefined ? 0 : +lazyMlNodesString;
|
||||||
|
|
||||||
|
if (count === 0 || isNaN(count)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function countJobsLazyStarting(
|
||||||
|
client: IScopedClusterClient,
|
||||||
|
startingJobsCount: number
|
||||||
|
) {
|
||||||
|
const lazyMlNodesCount = await getLazyMlNodeCount(client);
|
||||||
|
const { count: currentMlNodeCount } = await getMlNodeCount(client);
|
||||||
|
|
||||||
|
const availableLazyMlNodes = lazyMlNodesCount ?? lazyMlNodesCount - currentMlNodeCount;
|
||||||
|
|
||||||
|
let lazilyStartingJobsCount = startingJobsCount;
|
||||||
|
|
||||||
|
if (startingJobsCount > availableLazyMlNodes) {
|
||||||
|
if (lazyMlNodesCount > currentMlNodeCount) {
|
||||||
|
lazilyStartingJobsCount = availableLazyMlNodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = {
|
||||||
|
availableLazyMlNodes,
|
||||||
|
currentMlNodeCount,
|
||||||
|
lazilyStartingJobsCount,
|
||||||
|
totalStartingJobs: startingJobsCount,
|
||||||
|
};
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
|
@ -35,12 +35,12 @@ import {
|
||||||
import type { MlClient } from '../../lib/ml_client';
|
import type { MlClient } from '../../lib/ml_client';
|
||||||
|
|
||||||
export class AnalyticsManager {
|
export class AnalyticsManager {
|
||||||
private _client: IScopedClusterClient['asInternalUser'];
|
private _client: IScopedClusterClient;
|
||||||
private _mlClient: MlClient;
|
private _mlClient: MlClient;
|
||||||
private _inferenceModels: TrainedModelConfigResponse[];
|
private _inferenceModels: TrainedModelConfigResponse[];
|
||||||
private _jobStats: DataFrameAnalyticsStats[];
|
private _jobStats: DataFrameAnalyticsStats[];
|
||||||
|
|
||||||
constructor(mlClient: MlClient, client: IScopedClusterClient['asInternalUser']) {
|
constructor(mlClient: MlClient, client: IScopedClusterClient) {
|
||||||
this._client = client;
|
this._client = client;
|
||||||
this._mlClient = mlClient;
|
this._mlClient = mlClient;
|
||||||
this._inferenceModels = [];
|
this._inferenceModels = [];
|
||||||
|
@ -112,7 +112,9 @@ export class AnalyticsManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getAnalyticsStats() {
|
private async getAnalyticsStats() {
|
||||||
const resp = await this._mlClient.getDataFrameAnalyticsStats({ size: 1000 });
|
const resp = await this._mlClient.getDataFrameAnalyticsStats<{
|
||||||
|
data_frame_analytics: DataFrameAnalyticsStats[];
|
||||||
|
}>({ size: 1000 });
|
||||||
const stats = resp?.body?.data_frame_analytics;
|
const stats = resp?.body?.data_frame_analytics;
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
@ -142,15 +144,14 @@ export class AnalyticsManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getIndexData(index: string) {
|
private async getIndexData(index: string) {
|
||||||
const indexData = await this._client.indices.get({
|
const indexData = await this._client.asInternalUser.indices.get({
|
||||||
index,
|
index,
|
||||||
});
|
});
|
||||||
|
|
||||||
return indexData?.body;
|
return indexData?.body;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getTransformData(transformId: string) {
|
private async getTransformData(transformId: string) {
|
||||||
const transform = await this._client.transform.getTransform({
|
const transform = await this._client.asInternalUser.transform.getTransform({
|
||||||
transform_id: transformId,
|
transform_id: transformId,
|
||||||
});
|
});
|
||||||
const transformData = transform?.body?.transforms[0];
|
const transformData = transform?.body?.transforms[0];
|
||||||
|
|
|
@ -204,6 +204,7 @@ export function jobsProvider(client: IScopedClusterClient, mlClient: MlClient) {
|
||||||
isNotSingleMetricViewerJobMessage: errorMessage,
|
isNotSingleMetricViewerJobMessage: errorMessage,
|
||||||
nodeName: job.node ? job.node.name : undefined,
|
nodeName: job.node ? job.node.name : undefined,
|
||||||
deleting: job.deleting || undefined,
|
deleting: job.deleting || undefined,
|
||||||
|
awaitingNodeAssignment: isJobAwaitingNodeAssignment(job),
|
||||||
};
|
};
|
||||||
if (jobIds.find((j) => j === tempJob.id)) {
|
if (jobIds.find((j) => j === tempJob.id)) {
|
||||||
tempJob.fullJob = job;
|
tempJob.fullJob = job;
|
||||||
|
@ -519,6 +520,10 @@ export function jobsProvider(client: IScopedClusterClient, mlClient: MlClient) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isJobAwaitingNodeAssignment(job: CombinedJobWithStats) {
|
||||||
|
return job.node === undefined && job.state === JOB_STATE.OPENING;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
forceDeleteJob,
|
forceDeleteJob,
|
||||||
deleteJobs,
|
deleteJobs,
|
||||||
|
|
|
@ -44,7 +44,7 @@ function getAnalyticsMap(
|
||||||
client: IScopedClusterClient,
|
client: IScopedClusterClient,
|
||||||
idOptions: GetAnalyticsMapArgs
|
idOptions: GetAnalyticsMapArgs
|
||||||
) {
|
) {
|
||||||
const analytics = new AnalyticsManager(mlClient, client.asInternalUser);
|
const analytics = new AnalyticsManager(mlClient, client);
|
||||||
return analytics.getAnalyticsMap(idOptions);
|
return analytics.getAnalyticsMap(idOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ function getExtendedMap(
|
||||||
client: IScopedClusterClient,
|
client: IScopedClusterClient,
|
||||||
idOptions: ExtendAnalyticsMapArgs
|
idOptions: ExtendAnalyticsMapArgs
|
||||||
) {
|
) {
|
||||||
const analytics = new AnalyticsManager(mlClient, client.asInternalUser);
|
const analytics = new AnalyticsManager(mlClient, client);
|
||||||
return analytics.extendAnalyticsMapForAnalyticsJob(idOptions);
|
return analytics.extendAnalyticsMapForAnalyticsJob(idOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { schema } from '@kbn/config-schema';
|
import { schema } from '@kbn/config-schema';
|
||||||
import { IScopedClusterClient } from 'kibana/server';
|
|
||||||
import { wrapError } from '../client/error_wrapper';
|
import { wrapError } from '../client/error_wrapper';
|
||||||
import { mlLog } from '../lib/log';
|
import { mlLog } from '../lib/log';
|
||||||
import { capabilitiesProvider } from '../lib/capabilities';
|
import { capabilitiesProvider } from '../lib/capabilities';
|
||||||
import { spacesUtilsProvider } from '../lib/spaces_utils';
|
import { spacesUtilsProvider } from '../lib/spaces_utils';
|
||||||
import { RouteInitialization, SystemRouteDeps } from '../types';
|
import { RouteInitialization, SystemRouteDeps } from '../types';
|
||||||
|
import { getMlNodeCount } from '../lib/node_utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* System routes
|
* System routes
|
||||||
|
@ -19,25 +20,6 @@ export function systemRoutes(
|
||||||
{ router, mlLicense, routeGuard }: RouteInitialization,
|
{ router, mlLicense, routeGuard }: RouteInitialization,
|
||||||
{ getSpaces, cloud, resolveMlCapabilities }: SystemRouteDeps
|
{ getSpaces, cloud, resolveMlCapabilities }: SystemRouteDeps
|
||||||
) {
|
) {
|
||||||
async function getNodeCount(client: IScopedClusterClient) {
|
|
||||||
const { body } = await client.asInternalUser.nodes.info({
|
|
||||||
filter_path: 'nodes.*.attributes',
|
|
||||||
});
|
|
||||||
|
|
||||||
let count = 0;
|
|
||||||
if (typeof body.nodes === 'object') {
|
|
||||||
Object.keys(body.nodes).forEach((k) => {
|
|
||||||
if (body.nodes[k].attributes !== undefined) {
|
|
||||||
const maxOpenJobs = body.nodes[k].attributes['ml.max_open_jobs'];
|
|
||||||
if (maxOpenJobs !== null && maxOpenJobs > 0) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return { count };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @apiGroup SystemRoutes
|
* @apiGroup SystemRoutes
|
||||||
*
|
*
|
||||||
|
@ -156,7 +138,7 @@ export function systemRoutes(
|
||||||
routeGuard.basicLicenseAPIGuard(async ({ client, response }) => {
|
routeGuard.basicLicenseAPIGuard(async ({ client, response }) => {
|
||||||
try {
|
try {
|
||||||
return response.ok({
|
return response.ok({
|
||||||
body: await getNodeCount(client),
|
body: await getMlNodeCount(client),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return response.customError(wrapError(e));
|
return response.customError(wrapError(e));
|
||||||
|
|
|
@ -43,6 +43,7 @@ describe('useInstalledSecurityJobs', () => {
|
||||||
expect(result.current.jobs).toEqual(
|
expect(result.current.jobs).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
{
|
{
|
||||||
|
awaitingNodeAssignment: false,
|
||||||
datafeedId: 'datafeed-siem-api-rare_process_linux_ecs',
|
datafeedId: 'datafeed-siem-api-rare_process_linux_ecs',
|
||||||
datafeedIndices: ['auditbeat-*'],
|
datafeedIndices: ['auditbeat-*'],
|
||||||
datafeedState: 'stopped',
|
datafeedState: 'stopped',
|
||||||
|
|
|
@ -46,6 +46,7 @@ export const mockOpenedJob: MlSummaryJob = {
|
||||||
memory_status: 'hard_limit',
|
memory_status: 'hard_limit',
|
||||||
nodeName: 'siem-es',
|
nodeName: 'siem-es',
|
||||||
processed_record_count: 3425264,
|
processed_record_count: 3425264,
|
||||||
|
awaitingNodeAssignment: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockJobsSummaryResponse: MlSummaryJob[] = [
|
export const mockJobsSummaryResponse: MlSummaryJob[] = [
|
||||||
|
@ -64,6 +65,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [
|
||||||
latestTimestampMs: 1561402325194,
|
latestTimestampMs: 1561402325194,
|
||||||
earliestTimestampMs: 1554327458406,
|
earliestTimestampMs: 1554327458406,
|
||||||
isSingleMetricViewerJob: true,
|
isSingleMetricViewerJob: true,
|
||||||
|
awaitingNodeAssignment: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'siem-api-rare_process_linux_ecs',
|
id: 'siem-api-rare_process_linux_ecs',
|
||||||
|
@ -79,6 +81,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [
|
||||||
latestTimestampMs: 1557434782207,
|
latestTimestampMs: 1557434782207,
|
||||||
earliestTimestampMs: 1557353420495,
|
earliestTimestampMs: 1557353420495,
|
||||||
isSingleMetricViewerJob: true,
|
isSingleMetricViewerJob: true,
|
||||||
|
awaitingNodeAssignment: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'siem-api-rare_process_windows_ecs',
|
id: 'siem-api-rare_process_windows_ecs',
|
||||||
|
@ -92,6 +95,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [
|
||||||
datafeedIndices: ['winlogbeat-*'],
|
datafeedIndices: ['winlogbeat-*'],
|
||||||
datafeedState: 'stopped',
|
datafeedState: 'stopped',
|
||||||
isSingleMetricViewerJob: true,
|
isSingleMetricViewerJob: true,
|
||||||
|
awaitingNodeAssignment: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'siem-api-suspicious_login_activity_ecs',
|
id: 'siem-api-suspicious_login_activity_ecs',
|
||||||
|
@ -105,6 +109,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [
|
||||||
datafeedIndices: ['auditbeat-*'],
|
datafeedIndices: ['auditbeat-*'],
|
||||||
datafeedState: 'stopped',
|
datafeedState: 'stopped',
|
||||||
isSingleMetricViewerJob: true,
|
isSingleMetricViewerJob: true,
|
||||||
|
awaitingNodeAssignment: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -513,6 +518,7 @@ export const mockSecurityJobs: SecurityJob[] = [
|
||||||
isCompatible: true,
|
isCompatible: true,
|
||||||
isInstalled: true,
|
isInstalled: true,
|
||||||
isElasticJob: true,
|
isElasticJob: true,
|
||||||
|
awaitingNodeAssignment: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'rare_process_by_host_linux_ecs',
|
id: 'rare_process_by_host_linux_ecs',
|
||||||
|
@ -531,6 +537,7 @@ export const mockSecurityJobs: SecurityJob[] = [
|
||||||
isCompatible: true,
|
isCompatible: true,
|
||||||
isInstalled: true,
|
isInstalled: true,
|
||||||
isElasticJob: true,
|
isElasticJob: true,
|
||||||
|
awaitingNodeAssignment: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
datafeedId: '',
|
datafeedId: '',
|
||||||
|
@ -549,5 +556,6 @@ export const mockSecurityJobs: SecurityJob[] = [
|
||||||
isCompatible: false,
|
isCompatible: false,
|
||||||
isInstalled: false,
|
isInstalled: false,
|
||||||
isElasticJob: true,
|
isElasticJob: true,
|
||||||
|
awaitingNodeAssignment: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -65,6 +65,7 @@ describe('useSecurityJobs', () => {
|
||||||
memory_status: 'hard_limit',
|
memory_status: 'hard_limit',
|
||||||
moduleId: '',
|
moduleId: '',
|
||||||
processed_record_count: 582251,
|
processed_record_count: 582251,
|
||||||
|
awaitingNodeAssignment: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { result, waitForNextUpdate } = renderHook(() => useSecurityJobs(false));
|
const { result, waitForNextUpdate } = renderHook(() => useSecurityJobs(false));
|
||||||
|
|
|
@ -29,6 +29,7 @@ describe('useSecurityJobsHelpers', () => {
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
expect(securityJob).toEqual({
|
expect(securityJob).toEqual({
|
||||||
|
awaitingNodeAssignment: false,
|
||||||
datafeedId: '',
|
datafeedId: '',
|
||||||
datafeedIndices: [],
|
datafeedIndices: [],
|
||||||
datafeedState: '',
|
datafeedState: '',
|
||||||
|
|
|
@ -42,6 +42,7 @@ export const moduleToSecurityJob = (
|
||||||
isCompatible,
|
isCompatible,
|
||||||
isInstalled: false,
|
isInstalled: false,
|
||||||
isElasticJob: true,
|
isElasticJob: true,
|
||||||
|
awaitingNodeAssignment: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ exports[`JobsTableComponent renders correctly against snapshot 1`] = `
|
||||||
items={
|
items={
|
||||||
Array [
|
Array [
|
||||||
Object {
|
Object {
|
||||||
|
"awaitingNodeAssignment": false,
|
||||||
"datafeedId": "datafeed-linux_anomalous_network_activity_ecs",
|
"datafeedId": "datafeed-linux_anomalous_network_activity_ecs",
|
||||||
"datafeedIndices": Array [
|
"datafeedIndices": Array [
|
||||||
"auditbeat-*",
|
"auditbeat-*",
|
||||||
|
@ -52,6 +53,7 @@ exports[`JobsTableComponent renders correctly against snapshot 1`] = `
|
||||||
"processed_record_count": 32010,
|
"processed_record_count": 32010,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
|
"awaitingNodeAssignment": false,
|
||||||
"datafeedId": "datafeed-rare_process_by_host_linux_ecs",
|
"datafeedId": "datafeed-rare_process_by_host_linux_ecs",
|
||||||
"datafeedIndices": Array [
|
"datafeedIndices": Array [
|
||||||
"auditbeat-*",
|
"auditbeat-*",
|
||||||
|
@ -76,6 +78,7 @@ exports[`JobsTableComponent renders correctly against snapshot 1`] = `
|
||||||
"processed_record_count": 0,
|
"processed_record_count": 0,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
|
"awaitingNodeAssignment": false,
|
||||||
"datafeedId": "",
|
"datafeedId": "",
|
||||||
"datafeedIndices": Array [],
|
"datafeedIndices": Array [],
|
||||||
"datafeedState": "",
|
"datafeedState": "",
|
||||||
|
|
|
@ -28,6 +28,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = `
|
||||||
securityJobs={
|
securityJobs={
|
||||||
Array [
|
Array [
|
||||||
Object {
|
Object {
|
||||||
|
"awaitingNodeAssignment": false,
|
||||||
"datafeedId": "datafeed-linux_anomalous_network_activity_ecs",
|
"datafeedId": "datafeed-linux_anomalous_network_activity_ecs",
|
||||||
"datafeedIndices": Array [
|
"datafeedIndices": Array [
|
||||||
"auditbeat-*",
|
"auditbeat-*",
|
||||||
|
@ -55,6 +56,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = `
|
||||||
"processed_record_count": 32010,
|
"processed_record_count": 32010,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
|
"awaitingNodeAssignment": false,
|
||||||
"datafeedId": "datafeed-rare_process_by_host_linux_ecs",
|
"datafeedId": "datafeed-rare_process_by_host_linux_ecs",
|
||||||
"datafeedIndices": Array [
|
"datafeedIndices": Array [
|
||||||
"auditbeat-*",
|
"auditbeat-*",
|
||||||
|
@ -79,6 +81,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = `
|
||||||
"processed_record_count": 0,
|
"processed_record_count": 0,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
|
"awaitingNodeAssignment": false,
|
||||||
"datafeedId": "",
|
"datafeedId": "",
|
||||||
"datafeedIndices": Array [],
|
"datafeedIndices": Array [],
|
||||||
"datafeedState": "",
|
"datafeedState": "",
|
||||||
|
|
Loading…
Reference in a new issue