[Metrics UI] Override anomaly detection partition field (#79214)
* Add ability to override datafeeds and job config for partition field * Remove debug * UX cleanup * Fix types, delete dead code * Fix types
This commit is contained in:
parent
e5d8d49164
commit
1841495322
|
@ -33,11 +33,11 @@ export interface ModuleDescriptor<JobType extends string> {
|
||||||
partitionField?: string
|
partitionField?: string
|
||||||
) => Promise<SetupMlModuleResponsePayload>;
|
) => Promise<SetupMlModuleResponsePayload>;
|
||||||
cleanUpModule: (spaceId: string, sourceId: string) => Promise<DeleteJobsResponsePayload>;
|
cleanUpModule: (spaceId: string, sourceId: string) => Promise<DeleteJobsResponsePayload>;
|
||||||
validateSetupIndices: (
|
validateSetupIndices?: (
|
||||||
indices: string[],
|
indices: string[],
|
||||||
timestampField: string
|
timestampField: string
|
||||||
) => Promise<ValidationIndicesResponsePayload>;
|
) => Promise<ValidationIndicesResponsePayload>;
|
||||||
validateSetupDatasets: (
|
validateSetupDatasets?: (
|
||||||
indices: string[],
|
indices: string[],
|
||||||
timestampField: string,
|
timestampField: string,
|
||||||
startTime: number,
|
startTime: number,
|
||||||
|
|
|
@ -1,289 +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 { isEqual } from 'lodash';
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
||||||
import { usePrevious } from 'react-use';
|
|
||||||
import {
|
|
||||||
combineDatasetFilters,
|
|
||||||
DatasetFilter,
|
|
||||||
filterDatasetFilter,
|
|
||||||
isExampleDataIndex,
|
|
||||||
} from '../../../common/infra_ml';
|
|
||||||
import {
|
|
||||||
AvailableIndex,
|
|
||||||
ValidationIndicesError,
|
|
||||||
ValidationUIError,
|
|
||||||
} from '../../components/logging/log_analysis_setup/initial_configuration_step';
|
|
||||||
import { useTrackedPromise } from '../../utils/use_tracked_promise';
|
|
||||||
import { ModuleDescriptor, ModuleSourceConfiguration } from './infra_ml_module_types';
|
|
||||||
|
|
||||||
type SetupHandler = (
|
|
||||||
indices: string[],
|
|
||||||
startTime: number | undefined,
|
|
||||||
endTime: number | undefined,
|
|
||||||
datasetFilter: DatasetFilter
|
|
||||||
) => void;
|
|
||||||
|
|
||||||
interface AnalysisSetupStateArguments<JobType extends string> {
|
|
||||||
cleanUpAndSetUpModule: SetupHandler;
|
|
||||||
moduleDescriptor: ModuleDescriptor<JobType>;
|
|
||||||
setUpModule: SetupHandler;
|
|
||||||
sourceConfiguration: ModuleSourceConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fourWeeksInMs = 86400000 * 7 * 4;
|
|
||||||
|
|
||||||
export const useAnalysisSetupState = <JobType extends string>({
|
|
||||||
cleanUpAndSetUpModule,
|
|
||||||
moduleDescriptor: { validateSetupDatasets, validateSetupIndices },
|
|
||||||
setUpModule,
|
|
||||||
sourceConfiguration,
|
|
||||||
}: AnalysisSetupStateArguments<JobType>) => {
|
|
||||||
const [startTime, setStartTime] = useState<number | undefined>(Date.now() - fourWeeksInMs);
|
|
||||||
const [endTime, setEndTime] = useState<number | undefined>(undefined);
|
|
||||||
|
|
||||||
const isTimeRangeValid = useMemo(
|
|
||||||
() => (startTime != null && endTime != null ? startTime < endTime : true),
|
|
||||||
[endTime, startTime]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [validatedIndices, setValidatedIndices] = useState<AvailableIndex[]>(
|
|
||||||
sourceConfiguration.indices.map((indexName) => ({
|
|
||||||
name: indexName,
|
|
||||||
validity: 'unknown' as const,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateIndicesWithValidationErrors = useCallback(
|
|
||||||
(validationErrors: ValidationIndicesError[]) =>
|
|
||||||
setValidatedIndices((availableIndices) =>
|
|
||||||
availableIndices.map((previousAvailableIndex) => {
|
|
||||||
const indexValiationErrors = validationErrors.filter(
|
|
||||||
({ index }) => index === previousAvailableIndex.name
|
|
||||||
);
|
|
||||||
|
|
||||||
if (indexValiationErrors.length > 0) {
|
|
||||||
return {
|
|
||||||
validity: 'invalid',
|
|
||||||
name: previousAvailableIndex.name,
|
|
||||||
errors: indexValiationErrors,
|
|
||||||
};
|
|
||||||
} else if (previousAvailableIndex.validity === 'valid') {
|
|
||||||
return {
|
|
||||||
...previousAvailableIndex,
|
|
||||||
validity: 'valid',
|
|
||||||
errors: [],
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
validity: 'valid',
|
|
||||||
name: previousAvailableIndex.name,
|
|
||||||
isSelected: !isExampleDataIndex(previousAvailableIndex.name),
|
|
||||||
availableDatasets: [],
|
|
||||||
datasetFilter: {
|
|
||||||
type: 'includeAll' as const,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
})
|
|
||||||
),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateIndicesWithAvailableDatasets = useCallback(
|
|
||||||
(availableDatasets: Array<{ indexName: string; datasets: string[] }>) =>
|
|
||||||
setValidatedIndices((availableIndices) =>
|
|
||||||
availableIndices.map((previousAvailableIndex) => {
|
|
||||||
if (previousAvailableIndex.validity !== 'valid') {
|
|
||||||
return previousAvailableIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
const availableDatasetsForIndex = availableDatasets.filter(
|
|
||||||
({ indexName }) => indexName === previousAvailableIndex.name
|
|
||||||
);
|
|
||||||
const newAvailableDatasets = availableDatasetsForIndex.flatMap(
|
|
||||||
({ datasets }) => datasets
|
|
||||||
);
|
|
||||||
|
|
||||||
// filter out datasets that have disappeared if this index' datasets were updated
|
|
||||||
const newDatasetFilter: DatasetFilter =
|
|
||||||
availableDatasetsForIndex.length > 0
|
|
||||||
? filterDatasetFilter(previousAvailableIndex.datasetFilter, (dataset) =>
|
|
||||||
newAvailableDatasets.includes(dataset)
|
|
||||||
)
|
|
||||||
: previousAvailableIndex.datasetFilter;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...previousAvailableIndex,
|
|
||||||
availableDatasets: newAvailableDatasets,
|
|
||||||
datasetFilter: newDatasetFilter,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const validIndexNames = useMemo(
|
|
||||||
() => validatedIndices.filter((index) => index.validity === 'valid').map((index) => index.name),
|
|
||||||
[validatedIndices]
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectedIndexNames = useMemo(
|
|
||||||
() =>
|
|
||||||
validatedIndices
|
|
||||||
.filter((index) => index.validity === 'valid' && index.isSelected)
|
|
||||||
.map((i) => i.name),
|
|
||||||
[validatedIndices]
|
|
||||||
);
|
|
||||||
|
|
||||||
const datasetFilter = useMemo(
|
|
||||||
() =>
|
|
||||||
validatedIndices
|
|
||||||
.flatMap((validatedIndex) =>
|
|
||||||
validatedIndex.validity === 'valid'
|
|
||||||
? validatedIndex.datasetFilter
|
|
||||||
: { type: 'includeAll' as const }
|
|
||||||
)
|
|
||||||
.reduce(combineDatasetFilters, { type: 'includeAll' as const }),
|
|
||||||
[validatedIndices]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [validateIndicesRequest, validateIndices] = useTrackedPromise(
|
|
||||||
{
|
|
||||||
cancelPreviousOn: 'resolution',
|
|
||||||
createPromise: async () => {
|
|
||||||
return await validateSetupIndices(
|
|
||||||
sourceConfiguration.indices,
|
|
||||||
sourceConfiguration.timestampField
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onResolve: ({ data: { errors } }) => {
|
|
||||||
updateIndicesWithValidationErrors(errors);
|
|
||||||
},
|
|
||||||
onReject: () => {
|
|
||||||
setValidatedIndices([]);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[sourceConfiguration.indices, sourceConfiguration.timestampField]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [validateDatasetsRequest, validateDatasets] = useTrackedPromise(
|
|
||||||
{
|
|
||||||
cancelPreviousOn: 'resolution',
|
|
||||||
createPromise: async () => {
|
|
||||||
if (validIndexNames.length === 0) {
|
|
||||||
return { data: { datasets: [] } };
|
|
||||||
}
|
|
||||||
|
|
||||||
return await validateSetupDatasets(
|
|
||||||
validIndexNames,
|
|
||||||
sourceConfiguration.timestampField,
|
|
||||||
startTime ?? 0,
|
|
||||||
endTime ?? Date.now()
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onResolve: ({ data: { datasets } }) => {
|
|
||||||
updateIndicesWithAvailableDatasets(datasets);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[validIndexNames, sourceConfiguration.timestampField, startTime, endTime]
|
|
||||||
);
|
|
||||||
|
|
||||||
const setUp = useCallback(() => {
|
|
||||||
return setUpModule(selectedIndexNames, startTime, endTime, datasetFilter);
|
|
||||||
}, [setUpModule, selectedIndexNames, startTime, endTime, datasetFilter]);
|
|
||||||
|
|
||||||
const cleanUpAndSetUp = useCallback(() => {
|
|
||||||
return cleanUpAndSetUpModule(selectedIndexNames, startTime, endTime, datasetFilter);
|
|
||||||
}, [cleanUpAndSetUpModule, selectedIndexNames, startTime, endTime, datasetFilter]);
|
|
||||||
|
|
||||||
const isValidating = useMemo(
|
|
||||||
() => validateIndicesRequest.state === 'pending' || validateDatasetsRequest.state === 'pending',
|
|
||||||
[validateDatasetsRequest.state, validateIndicesRequest.state]
|
|
||||||
);
|
|
||||||
|
|
||||||
const validationErrors = useMemo<ValidationUIError[]>(() => {
|
|
||||||
if (isValidating) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
// validate request status
|
|
||||||
...(validateIndicesRequest.state === 'rejected' ||
|
|
||||||
validateDatasetsRequest.state === 'rejected'
|
|
||||||
? [{ error: 'NETWORK_ERROR' as const }]
|
|
||||||
: []),
|
|
||||||
// validation request results
|
|
||||||
...validatedIndices.reduce<ValidationUIError[]>((errors, index) => {
|
|
||||||
return index.validity === 'invalid' && selectedIndexNames.includes(index.name)
|
|
||||||
? [...errors, ...index.errors]
|
|
||||||
: errors;
|
|
||||||
}, []),
|
|
||||||
// index count
|
|
||||||
...(selectedIndexNames.length === 0 ? [{ error: 'TOO_FEW_SELECTED_INDICES' as const }] : []),
|
|
||||||
// time range
|
|
||||||
...(!isTimeRangeValid ? [{ error: 'INVALID_TIME_RANGE' as const }] : []),
|
|
||||||
];
|
|
||||||
}, [
|
|
||||||
isValidating,
|
|
||||||
validateIndicesRequest.state,
|
|
||||||
validateDatasetsRequest.state,
|
|
||||||
validatedIndices,
|
|
||||||
selectedIndexNames,
|
|
||||||
isTimeRangeValid,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const prevStartTime = usePrevious(startTime);
|
|
||||||
const prevEndTime = usePrevious(endTime);
|
|
||||||
const prevValidIndexNames = usePrevious(validIndexNames);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isTimeRangeValid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
validateIndices();
|
|
||||||
}, [isTimeRangeValid, validateIndices]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isTimeRangeValid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
startTime !== prevStartTime ||
|
|
||||||
endTime !== prevEndTime ||
|
|
||||||
!isEqual(validIndexNames, prevValidIndexNames)
|
|
||||||
) {
|
|
||||||
validateDatasets();
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
endTime,
|
|
||||||
isTimeRangeValid,
|
|
||||||
prevEndTime,
|
|
||||||
prevStartTime,
|
|
||||||
prevValidIndexNames,
|
|
||||||
startTime,
|
|
||||||
validIndexNames,
|
|
||||||
validateDatasets,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
cleanUpAndSetUp,
|
|
||||||
datasetFilter,
|
|
||||||
endTime,
|
|
||||||
isValidating,
|
|
||||||
selectedIndexNames,
|
|
||||||
setEndTime,
|
|
||||||
setStartTime,
|
|
||||||
setUp,
|
|
||||||
startTime,
|
|
||||||
validatedIndices,
|
|
||||||
setValidatedIndices,
|
|
||||||
validationErrors,
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -10,17 +10,27 @@ import { cleanUpJobsAndDatafeeds } from '../../infra_ml_cleanup';
|
||||||
import { callJobsSummaryAPI } from '../../api/ml_get_jobs_summary_api';
|
import { callJobsSummaryAPI } from '../../api/ml_get_jobs_summary_api';
|
||||||
import { callGetMlModuleAPI } from '../../api/ml_get_module';
|
import { callGetMlModuleAPI } from '../../api/ml_get_module';
|
||||||
import { callSetupMlModuleAPI } from '../../api/ml_setup_module_api';
|
import { callSetupMlModuleAPI } from '../../api/ml_setup_module_api';
|
||||||
import { callValidateIndicesAPI } from '../../../logs/log_analysis/api/validate_indices';
|
|
||||||
import { callValidateDatasetsAPI } from '../../../logs/log_analysis/api/validate_datasets';
|
|
||||||
import {
|
import {
|
||||||
metricsHostsJobTypes,
|
metricsHostsJobTypes,
|
||||||
getJobId,
|
getJobId,
|
||||||
MetricsHostsJobType,
|
MetricsHostsJobType,
|
||||||
DatasetFilter,
|
DatasetFilter,
|
||||||
bucketSpan,
|
bucketSpan,
|
||||||
partitionField,
|
|
||||||
} from '../../../../../common/infra_ml';
|
} from '../../../../../common/infra_ml';
|
||||||
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
|
import MemoryJob from '../../../../../../ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/hosts_memory_usage.json';
|
||||||
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
|
import MemoryDatafeed from '../../../../../../ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/datafeed_hosts_memory_usage.json';
|
||||||
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
|
import NetworkInJob from '../../../../../../ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/hosts_network_in.json';
|
||||||
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
|
import NetworkInDatafeed from '../../../../../../ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/datafeed_hosts_network_in.json';
|
||||||
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
|
import NetworkOutJob from '../../../../../../ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/hosts_network_out.json';
|
||||||
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
|
import NetworkOutDatafeed from '../../../../../../ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/datafeed_hosts_network_out.json';
|
||||||
|
|
||||||
|
type JobType = 'hosts_memory_usage' | 'hosts_network_in' | 'hosts_network_out';
|
||||||
const moduleId = 'metrics_ui_hosts';
|
const moduleId = 'metrics_ui_hosts';
|
||||||
const moduleName = i18n.translate('xpack.infra.ml.metricsModuleName', {
|
const moduleName = i18n.translate('xpack.infra.ml.metricsModuleName', {
|
||||||
defaultMessage: 'Metrics anomanly detection',
|
defaultMessage: 'Metrics anomanly detection',
|
||||||
|
@ -54,23 +64,68 @@ const setUpModule = async (
|
||||||
end: number | undefined,
|
end: number | undefined,
|
||||||
datasetFilter: DatasetFilter,
|
datasetFilter: DatasetFilter,
|
||||||
{ spaceId, sourceId, indices, timestampField }: ModuleSourceConfiguration,
|
{ spaceId, sourceId, indices, timestampField }: ModuleSourceConfiguration,
|
||||||
pField?: string
|
partitionField?: string
|
||||||
) => {
|
) => {
|
||||||
const indexNamePattern = indices.join(',');
|
const indexNamePattern = indices.join(',');
|
||||||
const jobIds = ['hosts_memory_usage', 'hosts_network_in', 'hosts_network_out'];
|
const jobIds: JobType[] = ['hosts_memory_usage', 'hosts_network_in', 'hosts_network_out'];
|
||||||
const jobOverrides = jobIds.map((id) => ({
|
|
||||||
job_id: id,
|
const jobOverrides = jobIds.map((id) => {
|
||||||
data_description: {
|
const { job: defaultJobConfig } = getDefaultJobConfigs(id);
|
||||||
time_field: timestampField,
|
|
||||||
},
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
custom_settings: {
|
const analysis_config: any = {
|
||||||
metrics_source_config: {
|
...defaultJobConfig.analysis_config,
|
||||||
indexPattern: indexNamePattern,
|
};
|
||||||
timestampField,
|
|
||||||
bucketSpan,
|
if (partitionField) {
|
||||||
|
analysis_config.detectors[0].partition_field_name = partitionField;
|
||||||
|
if (analysis_config.influencers.indexOf(partitionField) === -1) {
|
||||||
|
analysis_config.influencers.push(partitionField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
job_id: id,
|
||||||
|
data_description: {
|
||||||
|
time_field: timestampField,
|
||||||
},
|
},
|
||||||
},
|
analysis_config,
|
||||||
}));
|
custom_settings: {
|
||||||
|
metrics_source_config: {
|
||||||
|
indexPattern: indexNamePattern,
|
||||||
|
timestampField,
|
||||||
|
bucketSpan,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const datafeedOverrides = jobIds.map((id) => {
|
||||||
|
const { datafeed: defaultDatafeedConfig } = getDefaultJobConfigs(id);
|
||||||
|
|
||||||
|
if (!partitionField || id === 'hosts_memory_usage') {
|
||||||
|
// Since the host memory usage doesn't have custom aggs, we don't need to do anything to add a partition field
|
||||||
|
return defaultDatafeedConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a partition field, we need to change the aggregation to do a terms agg at the top level
|
||||||
|
const aggregations = {
|
||||||
|
[partitionField]: {
|
||||||
|
terms: {
|
||||||
|
field: partitionField,
|
||||||
|
},
|
||||||
|
aggregations: {
|
||||||
|
...defaultDatafeedConfig.aggregations,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...defaultDatafeedConfig,
|
||||||
|
job_id: id,
|
||||||
|
aggregations,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return callSetupMlModuleAPI(
|
return callSetupMlModuleAPI(
|
||||||
moduleId,
|
moduleId,
|
||||||
|
@ -80,36 +135,34 @@ const setUpModule = async (
|
||||||
sourceId,
|
sourceId,
|
||||||
indexNamePattern,
|
indexNamePattern,
|
||||||
jobOverrides,
|
jobOverrides,
|
||||||
[]
|
datafeedOverrides
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDefaultJobConfigs = (jobId: JobType): { datafeed: any; job: any } => {
|
||||||
|
switch (jobId) {
|
||||||
|
case 'hosts_memory_usage':
|
||||||
|
return {
|
||||||
|
datafeed: MemoryDatafeed,
|
||||||
|
job: MemoryJob,
|
||||||
|
};
|
||||||
|
case 'hosts_network_in':
|
||||||
|
return {
|
||||||
|
datafeed: NetworkInDatafeed,
|
||||||
|
job: NetworkInJob,
|
||||||
|
};
|
||||||
|
case 'hosts_network_out':
|
||||||
|
return {
|
||||||
|
datafeed: NetworkOutDatafeed,
|
||||||
|
job: NetworkOutJob,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const cleanUpModule = async (spaceId: string, sourceId: string) => {
|
const cleanUpModule = async (spaceId: string, sourceId: string) => {
|
||||||
return await cleanUpJobsAndDatafeeds(spaceId, sourceId, metricsHostsJobTypes);
|
return await cleanUpJobsAndDatafeeds(spaceId, sourceId, metricsHostsJobTypes);
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateSetupIndices = async (indices: string[], timestampField: string) => {
|
|
||||||
return await callValidateIndicesAPI(indices, [
|
|
||||||
{
|
|
||||||
name: timestampField,
|
|
||||||
validTypes: ['date'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: partitionField,
|
|
||||||
validTypes: ['keyword'],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateSetupDatasets = async (
|
|
||||||
indices: string[],
|
|
||||||
timestampField: string,
|
|
||||||
startTime: number,
|
|
||||||
endTime: number
|
|
||||||
) => {
|
|
||||||
return await callValidateDatasetsAPI(indices, timestampField, startTime, endTime);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const metricHostsModule: ModuleDescriptor<MetricsHostsJobType> = {
|
export const metricHostsModule: ModuleDescriptor<MetricsHostsJobType> = {
|
||||||
moduleId,
|
moduleId,
|
||||||
moduleName,
|
moduleName,
|
||||||
|
@ -121,6 +174,4 @@ export const metricHostsModule: ModuleDescriptor<MetricsHostsJobType> = {
|
||||||
getModuleDefinition,
|
getModuleDefinition,
|
||||||
setUpModule,
|
setUpModule,
|
||||||
cleanUpModule,
|
cleanUpModule,
|
||||||
validateSetupDatasets,
|
|
||||||
validateSetupIndices,
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,17 +10,28 @@ import { cleanUpJobsAndDatafeeds } from '../../infra_ml_cleanup';
|
||||||
import { callJobsSummaryAPI } from '../../api/ml_get_jobs_summary_api';
|
import { callJobsSummaryAPI } from '../../api/ml_get_jobs_summary_api';
|
||||||
import { callGetMlModuleAPI } from '../../api/ml_get_module';
|
import { callGetMlModuleAPI } from '../../api/ml_get_module';
|
||||||
import { callSetupMlModuleAPI } from '../../api/ml_setup_module_api';
|
import { callSetupMlModuleAPI } from '../../api/ml_setup_module_api';
|
||||||
import { callValidateIndicesAPI } from '../../../logs/log_analysis/api/validate_indices';
|
|
||||||
import { callValidateDatasetsAPI } from '../../../logs/log_analysis/api/validate_datasets';
|
|
||||||
import {
|
import {
|
||||||
metricsK8SJobTypes,
|
metricsK8SJobTypes,
|
||||||
getJobId,
|
getJobId,
|
||||||
MetricK8sJobType,
|
MetricK8sJobType,
|
||||||
DatasetFilter,
|
DatasetFilter,
|
||||||
bucketSpan,
|
bucketSpan,
|
||||||
partitionField,
|
|
||||||
} from '../../../../../common/infra_ml';
|
} from '../../../../../common/infra_ml';
|
||||||
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
|
import MemoryJob from '../../../../../../ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/k8s_memory_usage.json';
|
||||||
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
|
import MemoryDatafeed from '../../../../../../ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/datafeed_k8s_memory_usage.json';
|
||||||
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
|
import NetworkInJob from '../../../../../../ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/k8s_network_in.json';
|
||||||
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
|
import NetworkInDatafeed from '../../../../../../ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/datafeed_k8s_network_in.json';
|
||||||
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
|
import NetworkOutJob from '../../../../../../ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/k8s_network_out.json';
|
||||||
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
|
import NetworkOutDatafeed from '../../../../../../ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/datafeed_k8s_network_out.json';
|
||||||
|
|
||||||
|
type JobType = 'k8s_memory_usage' | 'k8s_network_in' | 'k8s_network_out';
|
||||||
|
export const DEFAULT_K8S_PARTITION_FIELD = 'kubernetes.namespace';
|
||||||
const moduleId = 'metrics_ui_k8s';
|
const moduleId = 'metrics_ui_k8s';
|
||||||
const moduleName = i18n.translate('xpack.infra.ml.metricsModuleName', {
|
const moduleName = i18n.translate('xpack.infra.ml.metricsModuleName', {
|
||||||
defaultMessage: 'Metrics anomanly detection',
|
defaultMessage: 'Metrics anomanly detection',
|
||||||
|
@ -54,26 +65,72 @@ const setUpModule = async (
|
||||||
end: number | undefined,
|
end: number | undefined,
|
||||||
datasetFilter: DatasetFilter,
|
datasetFilter: DatasetFilter,
|
||||||
{ spaceId, sourceId, indices, timestampField }: ModuleSourceConfiguration,
|
{ spaceId, sourceId, indices, timestampField }: ModuleSourceConfiguration,
|
||||||
pField?: string
|
partitionField?: string
|
||||||
) => {
|
) => {
|
||||||
const indexNamePattern = indices.join(',');
|
const indexNamePattern = indices.join(',');
|
||||||
const jobIds = ['k8s_memory_usage', 'k8s_network_in', 'k8s_network_out'];
|
const jobIds: JobType[] = ['k8s_memory_usage', 'k8s_network_in', 'k8s_network_out'];
|
||||||
const jobOverrides = jobIds.map((id) => ({
|
const jobOverrides = jobIds.map((id) => {
|
||||||
job_id: id,
|
const { job: defaultJobConfig } = getDefaultJobConfigs(id);
|
||||||
analysis_config: {
|
|
||||||
bucket_span: `${bucketSpan}ms`,
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
},
|
const analysis_config: any = {
|
||||||
data_description: {
|
...defaultJobConfig.analysis_config,
|
||||||
time_field: timestampField,
|
};
|
||||||
},
|
|
||||||
custom_settings: {
|
if (partitionField) {
|
||||||
metrics_source_config: {
|
analysis_config.detectors[0].partition_field_name = partitionField;
|
||||||
indexPattern: indexNamePattern,
|
if (analysis_config.influencers.indexOf(partitionField) === -1) {
|
||||||
timestampField,
|
analysis_config.influencers.push(partitionField);
|
||||||
bucketSpan,
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
job_id: id,
|
||||||
|
data_description: {
|
||||||
|
time_field: timestampField,
|
||||||
},
|
},
|
||||||
},
|
analysis_config,
|
||||||
}));
|
custom_settings: {
|
||||||
|
metrics_source_config: {
|
||||||
|
indexPattern: indexNamePattern,
|
||||||
|
timestampField,
|
||||||
|
bucketSpan,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const datafeedOverrides = jobIds.map((id) => {
|
||||||
|
const { datafeed: defaultDatafeedConfig } = getDefaultJobConfigs(id);
|
||||||
|
|
||||||
|
if (!partitionField || id === 'k8s_memory_usage') {
|
||||||
|
// Since the host memory usage doesn't have custom aggs, we don't need to do anything to add a partition field
|
||||||
|
return defaultDatafeedConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because the ML K8s jobs ship with a default partition field of {kubernetes.namespace}, ignore that agg and wrap it in our own agg.
|
||||||
|
const innerAggregation =
|
||||||
|
defaultDatafeedConfig.aggregations[DEFAULT_K8S_PARTITION_FIELD].aggregations;
|
||||||
|
|
||||||
|
// If we have a partition field, we need to change the aggregation to do a terms agg to partition the data at the top level
|
||||||
|
const aggregations = {
|
||||||
|
[partitionField]: {
|
||||||
|
terms: {
|
||||||
|
field: partitionField,
|
||||||
|
size: 25, // 25 is arbitratry and only used to keep the number of buckets to a managable level in the event that the user choose a high cardinality partition field.
|
||||||
|
},
|
||||||
|
aggregations: {
|
||||||
|
...innerAggregation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...defaultDatafeedConfig,
|
||||||
|
job_id: id,
|
||||||
|
aggregations,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return callSetupMlModuleAPI(
|
return callSetupMlModuleAPI(
|
||||||
moduleId,
|
moduleId,
|
||||||
|
@ -83,36 +140,34 @@ const setUpModule = async (
|
||||||
sourceId,
|
sourceId,
|
||||||
indexNamePattern,
|
indexNamePattern,
|
||||||
jobOverrides,
|
jobOverrides,
|
||||||
[]
|
datafeedOverrides
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDefaultJobConfigs = (jobId: JobType): { datafeed: any; job: any } => {
|
||||||
|
switch (jobId) {
|
||||||
|
case 'k8s_memory_usage':
|
||||||
|
return {
|
||||||
|
datafeed: MemoryDatafeed,
|
||||||
|
job: MemoryJob,
|
||||||
|
};
|
||||||
|
case 'k8s_network_in':
|
||||||
|
return {
|
||||||
|
datafeed: NetworkInDatafeed,
|
||||||
|
job: NetworkInJob,
|
||||||
|
};
|
||||||
|
case 'k8s_network_out':
|
||||||
|
return {
|
||||||
|
datafeed: NetworkOutDatafeed,
|
||||||
|
job: NetworkOutJob,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const cleanUpModule = async (spaceId: string, sourceId: string) => {
|
const cleanUpModule = async (spaceId: string, sourceId: string) => {
|
||||||
return await cleanUpJobsAndDatafeeds(spaceId, sourceId, metricsK8SJobTypes);
|
return await cleanUpJobsAndDatafeeds(spaceId, sourceId, metricsK8SJobTypes);
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateSetupIndices = async (indices: string[], timestampField: string) => {
|
|
||||||
return await callValidateIndicesAPI(indices, [
|
|
||||||
{
|
|
||||||
name: timestampField,
|
|
||||||
validTypes: ['date'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: partitionField,
|
|
||||||
validTypes: ['keyword'],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateSetupDatasets = async (
|
|
||||||
indices: string[],
|
|
||||||
timestampField: string,
|
|
||||||
startTime: number,
|
|
||||||
endTime: number
|
|
||||||
) => {
|
|
||||||
return await callValidateDatasetsAPI(indices, timestampField, startTime, endTime);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const metricHostsModule: ModuleDescriptor<MetricK8sJobType> = {
|
export const metricHostsModule: ModuleDescriptor<MetricK8sJobType> = {
|
||||||
moduleId,
|
moduleId,
|
||||||
moduleName,
|
moduleName,
|
||||||
|
@ -124,6 +179,4 @@ export const metricHostsModule: ModuleDescriptor<MetricK8sJobType> = {
|
||||||
getModuleDefinition,
|
getModuleDefinition,
|
||||||
setUpModule,
|
setUpModule,
|
||||||
cleanUpModule,
|
cleanUpModule,
|
||||||
validateSetupDatasets,
|
|
||||||
validateSetupIndices,
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -50,10 +50,10 @@ export const AnomalyDetectionFlyout = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<EuiButtonEmpty iconSide={'right'} onClick={openFlyout}>
|
<EuiButtonEmpty iconSide={'left'} iconType={'inspect'} onClick={openFlyout}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.infra.ml.anomalyDetectionButton"
|
id="xpack.infra.ml.anomalyDetectionButton"
|
||||||
defaultMessage="Anomaly Detection"
|
defaultMessage="Anomaly detection"
|
||||||
/>
|
/>
|
||||||
</EuiButtonEmpty>
|
</EuiButtonEmpty>
|
||||||
{showFlyout && (
|
{showFlyout && (
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useCallback, useEffect } from 'react';
|
import React, { useState, useCallback, useEffect } from 'react';
|
||||||
import { EuiFlyoutHeader, EuiTitle, EuiFlyoutBody, EuiTabs, EuiTab, EuiSpacer } from '@elastic/eui';
|
import { EuiFlyoutHeader, EuiTitle, EuiFlyoutBody, EuiSpacer } from '@elastic/eui';
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
import { EuiText, EuiFlexGroup, EuiFlexItem, EuiCard, EuiIcon } from '@elastic/eui';
|
import { EuiText, EuiFlexGroup, EuiFlexItem, EuiCard, EuiIcon } from '@elastic/eui';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
@ -30,7 +30,7 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FlyoutHome = (props: Props) => {
|
export const FlyoutHome = (props: Props) => {
|
||||||
const [tab, setTab] = useState<'jobs' | 'anomalies'>('jobs');
|
const [tab] = useState<'jobs' | 'anomalies'>('jobs');
|
||||||
const { goToSetup } = props;
|
const { goToSetup } = props;
|
||||||
const {
|
const {
|
||||||
fetchJobStatus: fetchHostJobStatus,
|
fetchJobStatus: fetchHostJobStatus,
|
||||||
|
@ -56,18 +56,10 @@ export const FlyoutHome = (props: Props) => {
|
||||||
goToSetup('kubernetes');
|
goToSetup('kubernetes');
|
||||||
}, [goToSetup]);
|
}, [goToSetup]);
|
||||||
|
|
||||||
const goToJobs = useCallback(() => {
|
|
||||||
setTab('jobs');
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const jobIds = [
|
const jobIds = [
|
||||||
...(k8sJobSummaries || []).map((k) => k.id),
|
...(k8sJobSummaries || []).map((k) => k.id),
|
||||||
...(hostJobSummaries || []).map((h) => h.id),
|
...(hostJobSummaries || []).map((h) => h.id),
|
||||||
];
|
];
|
||||||
const anomaliesUrl = useLinkProps({
|
|
||||||
app: 'ml',
|
|
||||||
pathname: `/explorer?_g=${createResultsUrl(jobIds)}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasInfraMLReadCapabilities) {
|
if (hasInfraMLReadCapabilities) {
|
||||||
|
@ -105,30 +97,24 @@ export const FlyoutHome = (props: Props) => {
|
||||||
</EuiFlyoutHeader>
|
</EuiFlyoutHeader>
|
||||||
|
|
||||||
<EuiFlyoutBody>
|
<EuiFlyoutBody>
|
||||||
<EuiTabs>
|
<div>
|
||||||
<EuiTab isSelected={tab === 'jobs'} onClick={goToJobs}>
|
<EuiText>
|
||||||
<FormattedMessage
|
<p>
|
||||||
defaultMessage="Jobs"
|
<FormattedMessage
|
||||||
id="xpack.infra.ml.anomalyFlyout.jobsTabLabel"
|
defaultMessage="Anomaly detection is powered by machine learning. Machine learning jobs are available for the following resource types. Enable these jobs to begin detecting anomalies in your infrastructure metrics."
|
||||||
/>
|
id="xpack.infra.ml.anomalyFlyout.create.description"
|
||||||
</EuiTab>
|
/>
|
||||||
<EuiTab
|
</p>
|
||||||
disabled={jobIds.length === 0}
|
</EuiText>
|
||||||
isSelected={tab === 'anomalies'}
|
</div>
|
||||||
{...anomaliesUrl}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Anomalies"
|
|
||||||
id="xpack.infra.ml.anomalyFlyout.anomaliesTabLabel"
|
|
||||||
/>
|
|
||||||
</EuiTab>
|
|
||||||
</EuiTabs>
|
|
||||||
<EuiSpacer size="l" />
|
<EuiSpacer size="l" />
|
||||||
{hostJobSummaries.length > 0 && (
|
{hostJobSummaries.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<JobsEnabledCallout
|
<JobsEnabledCallout
|
||||||
hasHostJobs={hostJobSummaries.length > 0}
|
hasHostJobs={hostJobSummaries.length > 0}
|
||||||
hasK8sJobs={k8sJobSummaries.length > 0}
|
hasK8sJobs={k8sJobSummaries.length > 0}
|
||||||
|
jobIds={jobIds}
|
||||||
/>
|
/>
|
||||||
<EuiSpacer size="l" />
|
<EuiSpacer size="l" />
|
||||||
</>
|
</>
|
||||||
|
@ -151,6 +137,7 @@ export const FlyoutHome = (props: Props) => {
|
||||||
interface CalloutProps {
|
interface CalloutProps {
|
||||||
hasHostJobs: boolean;
|
hasHostJobs: boolean;
|
||||||
hasK8sJobs: boolean;
|
hasK8sJobs: boolean;
|
||||||
|
jobIds: string[];
|
||||||
}
|
}
|
||||||
const JobsEnabledCallout = (props: CalloutProps) => {
|
const JobsEnabledCallout = (props: CalloutProps) => {
|
||||||
let target = '';
|
let target = '';
|
||||||
|
@ -175,8 +162,34 @@ const JobsEnabledCallout = (props: CalloutProps) => {
|
||||||
pathname: '/jobs',
|
pathname: '/jobs',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const anomaliesUrl = useLinkProps({
|
||||||
|
app: 'ml',
|
||||||
|
pathname: `/explorer?_g=${createResultsUrl(props.jobIds)}`,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<EuiFlexGroup gutterSize={'s'}>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiButton {...manageJobsLinkProps} style={{ marginRight: 5 }}>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Manage jobs"
|
||||||
|
id="xpack.infra.ml.anomalyFlyout.manageJobs"
|
||||||
|
/>
|
||||||
|
</EuiButton>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiButton {...anomaliesUrl}>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="View anomalies"
|
||||||
|
id="xpack.infra.ml.anomalyFlyout.anomaliesTabLabel"
|
||||||
|
/>
|
||||||
|
</EuiButton>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
|
||||||
|
<EuiSpacer size="l" />
|
||||||
|
|
||||||
<EuiCallOut
|
<EuiCallOut
|
||||||
size="m"
|
size="m"
|
||||||
color="success"
|
color="success"
|
||||||
|
@ -189,13 +202,6 @@ const JobsEnabledCallout = (props: CalloutProps) => {
|
||||||
}
|
}
|
||||||
iconType="check"
|
iconType="check"
|
||||||
/>
|
/>
|
||||||
<EuiSpacer size="l" />
|
|
||||||
<EuiButton {...manageJobsLinkProps}>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Manage Jobs"
|
|
||||||
id="xpack.infra.ml.anomalyFlyout.manageJobs"
|
|
||||||
/>
|
|
||||||
</EuiButton>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -211,30 +217,11 @@ interface CreateJobTab {
|
||||||
const CreateJobTab = (props: CreateJobTab) => {
|
const CreateJobTab = (props: CreateJobTab) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
{/* <EuiSpacer size="l" /> */}
|
||||||
<EuiText>
|
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Create ML Jobs"
|
|
||||||
id="xpack.infra.ml.anomalyFlyout.create.jobsTitle"
|
|
||||||
/>
|
|
||||||
</h3>
|
|
||||||
</EuiText>
|
|
||||||
<EuiText>
|
|
||||||
<p>
|
|
||||||
<FormattedMessage
|
|
||||||
defaultMessage="Machine Learning jobs are available for the following resource types. Enable these jobs to begin detecting anomalies in your infrastructure metrics"
|
|
||||||
id="xpack.infra.ml.anomalyFlyout.create.description"
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</EuiText>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<EuiSpacer size="l" />
|
|
||||||
<EuiFlexGroup gutterSize={'m'}>
|
<EuiFlexGroup gutterSize={'m'}>
|
||||||
<EuiFlexItem>
|
<EuiFlexItem>
|
||||||
<EuiCard
|
<EuiCard
|
||||||
// isDisabled={props.hasSetupCapabilities}
|
isDisabled={!props.hasSetupCapabilities}
|
||||||
icon={<EuiIcon type={'storage'} />}
|
icon={<EuiIcon type={'storage'} />}
|
||||||
// title="Hosts"
|
// title="Hosts"
|
||||||
title={
|
title={
|
||||||
|
@ -245,7 +232,7 @@ const CreateJobTab = (props: CreateJobTab) => {
|
||||||
}
|
}
|
||||||
description={
|
description={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Detect anomalies for CPU usage, memory usage, network traffic, and load."
|
defaultMessage="Detect anomalies for memory usage and network traffic."
|
||||||
id="xpack.infra.ml.anomalyFlyout.create.hostDescription"
|
id="xpack.infra.ml.anomalyFlyout.create.hostDescription"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -254,7 +241,7 @@ const CreateJobTab = (props: CreateJobTab) => {
|
||||||
{props.hasHostJobs && (
|
{props.hasHostJobs && (
|
||||||
<EuiButtonEmpty onClick={props.createHosts}>
|
<EuiButtonEmpty onClick={props.createHosts}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Recreate Jobs"
|
defaultMessage="Recreate jobs"
|
||||||
id="xpack.infra.ml.anomalyFlyout.create.recreateButton"
|
id="xpack.infra.ml.anomalyFlyout.create.recreateButton"
|
||||||
/>
|
/>
|
||||||
</EuiButtonEmpty>
|
</EuiButtonEmpty>
|
||||||
|
@ -262,7 +249,7 @@ const CreateJobTab = (props: CreateJobTab) => {
|
||||||
{!props.hasHostJobs && (
|
{!props.hasHostJobs && (
|
||||||
<EuiButton onClick={props.createHosts}>
|
<EuiButton onClick={props.createHosts}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Create Jobs"
|
defaultMessage="Enable"
|
||||||
id="xpack.infra.ml.anomalyFlyout.create.createButton"
|
id="xpack.infra.ml.anomalyFlyout.create.createButton"
|
||||||
/>
|
/>
|
||||||
</EuiButton>
|
</EuiButton>
|
||||||
|
@ -273,7 +260,7 @@ const CreateJobTab = (props: CreateJobTab) => {
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem>
|
<EuiFlexItem>
|
||||||
<EuiCard
|
<EuiCard
|
||||||
// isDisabled={props.hasSetupCapabilities}
|
isDisabled={!props.hasSetupCapabilities}
|
||||||
icon={<EuiIcon type={'logoKubernetes'} />}
|
icon={<EuiIcon type={'logoKubernetes'} />}
|
||||||
title={
|
title={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -283,7 +270,7 @@ const CreateJobTab = (props: CreateJobTab) => {
|
||||||
}
|
}
|
||||||
description={
|
description={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Detect anomalies for CPU usage, memory usage, network traffic, and load."
|
defaultMessage="Detect anomalies for memory usage and network traffic."
|
||||||
id="xpack.infra.ml.anomalyFlyout.create.k8sDescription"
|
id="xpack.infra.ml.anomalyFlyout.create.k8sDescription"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -292,7 +279,7 @@ const CreateJobTab = (props: CreateJobTab) => {
|
||||||
{props.hasK8sJobs && (
|
{props.hasK8sJobs && (
|
||||||
<EuiButtonEmpty onClick={props.createK8s}>
|
<EuiButtonEmpty onClick={props.createK8s}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Recreate Jobs"
|
defaultMessage="Recreate jobs"
|
||||||
id="xpack.infra.ml.anomalyFlyout.create.recreateButton"
|
id="xpack.infra.ml.anomalyFlyout.create.recreateButton"
|
||||||
/>
|
/>
|
||||||
</EuiButtonEmpty>
|
</EuiButtonEmpty>
|
||||||
|
@ -300,7 +287,7 @@ const CreateJobTab = (props: CreateJobTab) => {
|
||||||
{!props.hasK8sJobs && (
|
{!props.hasK8sJobs && (
|
||||||
<EuiButton onClick={props.createK8s}>
|
<EuiButton onClick={props.createK8s}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Create Jobs"
|
defaultMessage="Enable"
|
||||||
id="xpack.infra.ml.anomalyFlyout.create.createButton"
|
id="xpack.infra.ml.anomalyFlyout.create.createButton"
|
||||||
/>
|
/>
|
||||||
</EuiButton>
|
</EuiButton>
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { useSourceViaHttp } from '../../../../../../containers/source/use_source
|
||||||
import { useMetricK8sModuleContext } from '../../../../../../containers/ml/modules/metrics_k8s/module';
|
import { useMetricK8sModuleContext } from '../../../../../../containers/ml/modules/metrics_k8s/module';
|
||||||
import { useMetricHostsModuleContext } from '../../../../../../containers/ml/modules/metrics_hosts/module';
|
import { useMetricHostsModuleContext } from '../../../../../../containers/ml/modules/metrics_hosts/module';
|
||||||
import { FixedDatePicker } from '../../../../../../components/fixed_datepicker';
|
import { FixedDatePicker } from '../../../../../../components/fixed_datepicker';
|
||||||
|
import { DEFAULT_K8S_PARTITION_FIELD } from '../../../../../../containers/ml/modules/metrics_k8s/module_descriptor';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
jobType: 'hosts' | 'kubernetes';
|
jobType: 'hosts' | 'kubernetes';
|
||||||
|
@ -107,7 +108,7 @@ export const JobSetupScreen = (props: Props) => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.jobType === 'kubernetes') {
|
if (props.jobType === 'kubernetes') {
|
||||||
setPartitionField(['kubernetes.namespace']);
|
setPartitionField([DEFAULT_K8S_PARTITION_FIELD]);
|
||||||
}
|
}
|
||||||
}, [props.jobType]);
|
}, [props.jobType]);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue