From 197a63b0f68a657879f864ce357ca5807febf23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Wed, 16 Oct 2019 00:33:37 +0200 Subject: [PATCH] [Logs UI] Add ML job status callouts to results page (#47642) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Amend outer page / panel structure * Remove unused imports * Add anomalies section and overall chart * Add legend * Rename dataSet to partition * Add max bucket anomaly score * Move job and setup status types to common * Move initial job status fetching into page content * Poll for job status on results page * Add table / expanded row charts * Add bucket span text * Add stat section to expanded rows * Amend annotations on overall graph * Add rule to account for EuiFlexItem edge case * Move functions that handle derivations of data to a new file * Tweak data points fetched * Style bars in grey for anomalies charts * Add severity scoring to annotations * Fix default * Remove decimal places from anomaly score representations * Show all partitions and overall anomaly score in annotation tooltip for overall chart * Handle 'unknown' to workaround lack of '' suuport in tables * Add stats section to overall anomalies section * Base x-domain off the series so that certain buckets aren't omitted * Tweak colours and DRY up annotation rendering * Add sorting to table * Add "number of logs" to API results and render in UI stats * Track and render out-of-sync job configurations * Adjust translation labels * Add stopped state callout * Add more callout icons * Fix api integration tests * Use "pretty" numbers for "Number of logs" stats * Improve status message wording * Change recreate job button color back to default * Add toolbar text * Format all y axis values to 3 digits * Remove "Overall anomaly score" and change all wording / calculations to "Max anomaly scores" * Sort anomaly maximum scores for the overall chart tooltip * Remove unused translations * Use white text with badge in toolbar * Factor out a job recreation callout * Replace `filter()[0]` with `find()` call * Amend key * Use Math.round and introduce a formatAnomalyScore helper function * Update x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx Co-Authored-By: Felix Stürmer * Format y-axis of log entry rate chart the same as anomalies charts * Update x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx Co-Authored-By: Felix Stürmer * Remove grow prop * Update x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/log_entry_rate.ts Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/log_entry_rate.ts Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/helpers/data_formatters.tsx Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/helpers/data_formatters.tsx Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/helpers/data_formatters.tsx Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/helpers/data_formatters.tsx Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/helpers/data_formatters.tsx Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/server/lib/log_analysis/log_analysis.ts Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/server/lib/log_analysis/log_analysis.ts Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts Co-Authored-By: Felix Stürmer * Update x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts Co-Authored-By: Felix Stürmer * Change path * Amend property name * Use proper default value argument * Use Kibana dateFormat setting for toolbar formatting * Change logic for calculating severity score categories * Add missing translations * Add interface to top of file * Add no-wrap to tooltip * Use more idomatic code * Use static value for series styles * Move the callouts into the right location * Fix linter warning * Add non-functional ML link button * Fix merge mistake --- .../infra/common/log_analysis/log_analysis.ts | 47 +++- .../logging/log_analysis_job_status/index.ts | 7 + .../job_configuration_outdated_callout.tsx | 29 +++ .../job_definition_outdated_callout.tsx | 29 +++ .../job_stopped_callout.tsx | 24 ++ .../log_analysis_job_problem_indicator.tsx | 29 +++ .../recreate_job_callout.tsx | 24 ++ .../logs/log_analysis/api/ml_api_types.ts | 16 ++ .../api/ml_get_jobs_summary_api.ts | 9 +- .../logs/log_analysis/api/ml_get_module.ts | 42 ++++ .../log_analysis/api/ml_setup_module_api.ts | 45 ++-- .../logs/log_analysis/log_analysis_jobs.tsx | 87 +++++-- .../log_analysis/log_analysis_setup_state.tsx | 25 +- .../log_analysis_status_state.tsx | 233 +++++++++++++++--- .../pages/logs/analysis/page_content.tsx | 13 +- .../logs/analysis/page_results_content.tsx | 26 +- .../logs/analysis/page_setup_content.tsx | 18 +- .../analysis/sections/anomalies/index.tsx | 85 ++++--- .../setup/recreate_ml_jobs_button.tsx | 29 +++ .../pages/logs/analysis/setup/steps/index.tsx | 36 ++- .../analysis/setup/steps/setup_process.tsx | 18 +- 21 files changed, 721 insertions(+), 150 deletions(-) create mode 100644 x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/index.ts create mode 100644 x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/job_configuration_outdated_callout.tsx create mode 100644 x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/job_definition_outdated_callout.tsx create mode 100644 x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/job_stopped_callout.tsx create mode 100644 x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/log_analysis_job_problem_indicator.tsx create mode 100644 x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/recreate_job_callout.tsx create mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_api_types.ts create mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_module.ts create mode 100644 x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/recreate_ml_jobs_button.tsx diff --git a/x-pack/legacy/plugins/infra/common/log_analysis/log_analysis.ts b/x-pack/legacy/plugins/infra/common/log_analysis/log_analysis.ts index 870d9f50d44de8..89e42f69c9daf1 100644 --- a/x-pack/legacy/plugins/infra/common/log_analysis/log_analysis.ts +++ b/x-pack/legacy/plugins/infra/common/log_analysis/log_analysis.ts @@ -12,10 +12,45 @@ export const jobTypeRT = rt.keyof({ export type JobType = rt.TypeOf; -export const jobStatusRT = rt.keyof({ - created: null, - missing: null, - running: null, -}); +// combines and abstracts job and datafeed status +export type JobStatus = + | 'unknown' + | 'missing' + | 'initializing' + | 'stopped' + | 'started' + | 'finished' + | 'failed'; + +export type SetupStatus = + | 'initializing' // acquiring job statuses to determine setup status + | 'unknown' // job status could not be acquired (failed request etc) + | 'required' // jobs are missing + | 'requiredForReconfiguration' // the configurations don't match the source configurations + | 'requiredForUpdate' // the definitions don't match the module definitions + | 'pending' // In the process of setting up the module for the first time or retrying, waiting for response + | 'succeeded' // setup succeeded, notifying user + | 'failed' // setup failed, notifying user + | 'hiddenAfterSuccess' // hide the setup screen and we show the results for the first time + | 'skipped' // setup hidden because the module is in a correct state already + | 'skippedButReconfigurable' // setup hidden even though the job configurations are outdated + | 'skippedButUpdatable'; // setup hidden even though the job definitions are outdated + +/** + * Maps a job status to the possibility that results have already been produced + * before this state was reached. + */ +export const isJobStatusWithResults = (jobStatus: JobStatus) => + ['started', 'finished', 'stopped', 'failed'].includes(jobStatus); + +export const isHealthyJobStatus = (jobStatus: JobStatus) => + ['started', 'finished'].includes(jobStatus); -export type JobStatus = rt.TypeOf; +/** + * Maps a setup status to the possibility that results have already been + * produced before this state was reached. + */ +export const isSetupStatusWithResults = (setupStatus: SetupStatus) => + ['skipped', 'hiddenAfterSuccess', 'skippedButReconfigurable', 'skippedButUpdatable'].includes( + setupStatus + ); diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/index.ts b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/index.ts new file mode 100644 index 00000000000000..06229a26afd192 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/index.ts @@ -0,0 +1,7 @@ +/* + * 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 * from './log_analysis_job_problem_indicator'; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/job_configuration_outdated_callout.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/job_configuration_outdated_callout.tsx new file mode 100644 index 00000000000000..13b7d1927f6761 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/job_configuration_outdated_callout.tsx @@ -0,0 +1,29 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; + +import { RecreateJobCallout } from './recreate_job_callout'; + +export const JobConfigurationOutdatedCallout: React.FC<{ + onRecreateMlJob: () => void; +}> = ({ onRecreateMlJob }) => ( + + + +); + +const jobConfigurationOutdatedTitle = i18n.translate( + 'xpack.infra.logs.analysis.jobConfigurationOutdatedCalloutTitle', + { + defaultMessage: 'ML job configuration outdated', + } +); diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/job_definition_outdated_callout.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/job_definition_outdated_callout.tsx new file mode 100644 index 00000000000000..5072fb09cdceb4 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/job_definition_outdated_callout.tsx @@ -0,0 +1,29 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; + +import { RecreateJobCallout } from './recreate_job_callout'; + +export const JobDefinitionOutdatedCallout: React.FC<{ + onRecreateMlJob: () => void; +}> = ({ onRecreateMlJob }) => ( + + + +); + +const jobDefinitionOutdatedTitle = i18n.translate( + 'xpack.infra.logs.analysis.jobDefinitionOutdatedCalloutTitle', + { + defaultMessage: 'ML job definition outdated', + } +); diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/job_stopped_callout.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/job_stopped_callout.tsx new file mode 100644 index 00000000000000..33f0a5b1399a15 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/job_stopped_callout.tsx @@ -0,0 +1,24 @@ +/* + * 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 from 'react'; +import { EuiCallOut } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +export const JobStoppedCallout: React.FC = () => ( + + + +); + +const jobStoppedTitle = i18n.translate('xpack.infra.logs.analysis.jobStoppedCalloutTitle', { + defaultMessage: 'ML job stopped', +}); diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/log_analysis_job_problem_indicator.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/log_analysis_job_problem_indicator.tsx new file mode 100644 index 00000000000000..018c5f5e0570da --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/log_analysis_job_problem_indicator.tsx @@ -0,0 +1,29 @@ +/* + * 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 from 'react'; + +import { JobStatus, SetupStatus } from '../../../../common/log_analysis'; +import { JobConfigurationOutdatedCallout } from './job_configuration_outdated_callout'; +import { JobDefinitionOutdatedCallout } from './job_definition_outdated_callout'; +import { JobStoppedCallout } from './job_stopped_callout'; + +export const LogAnalysisJobProblemIndicator: React.FC<{ + jobStatus: JobStatus; + setupStatus: SetupStatus; + onRecreateMlJobForReconfiguration: () => void; + onRecreateMlJobForUpdate: () => void; +}> = ({ jobStatus, setupStatus, onRecreateMlJobForReconfiguration, onRecreateMlJobForUpdate }) => { + if (jobStatus === 'stopped') { + return ; + } else if (setupStatus === 'skippedButUpdatable') { + return ; + } else if (setupStatus === 'skippedButReconfigurable') { + return ; + } + + return null; // no problem to indicate +}; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/recreate_job_callout.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/recreate_job_callout.tsx new file mode 100644 index 00000000000000..b95054bbd6a9b0 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/recreate_job_callout.tsx @@ -0,0 +1,24 @@ +/* + * 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 from 'react'; +import { EuiCallOut, EuiButton } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +export const RecreateJobCallout: React.FC<{ + onRecreateMlJob: () => void; + title?: React.ReactNode; +}> = ({ children, onRecreateMlJob, title }) => ( + +

{children}

+ + + +
+); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_api_types.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_api_types.ts new file mode 100644 index 00000000000000..deb3d528e42c22 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_api_types.ts @@ -0,0 +1,16 @@ +/* + * 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 * as rt from 'io-ts'; + +export const jobCustomSettingsRT = rt.partial({ + job_revision: rt.number, + logs_source_config: rt.partial({ + indexPattern: rt.string, + timestampField: rt.string, + bucketSpan: rt.number, + }), +}); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts index 0b6e0981c7f9f6..477e75fc3b90c9 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts @@ -4,11 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as rt from 'io-ts'; -import { kfetch } from 'ui/kfetch'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; +import * as rt from 'io-ts'; +import { kfetch } from 'ui/kfetch'; + +import { jobCustomSettingsRT } from './ml_api_types'; import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; import { getAllModuleJobIds } from '../../../../../common/log_analysis'; @@ -56,11 +58,14 @@ export const jobSummaryRT = rt.intersection([ datafeedIndices: rt.array(rt.string), datafeedState: datafeedStateRT, fullJob: rt.partial({ + custom_settings: jobCustomSettingsRT, finished_time: rt.number, }), }), ]); +export type JobSummary = rt.TypeOf; + export const fetchJobStatusResponsePayloadRT = rt.array(jobSummaryRT); export type FetchJobStatusResponsePayload = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_module.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_module.ts new file mode 100644 index 00000000000000..b58677ffa844e3 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_module.ts @@ -0,0 +1,42 @@ +/* + * 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 { fold } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { identity } from 'fp-ts/lib/function'; +import * as rt from 'io-ts'; +import { kfetch } from 'ui/kfetch'; + +import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; +import { jobCustomSettingsRT } from './ml_api_types'; + +export const callGetMlModuleAPI = async (moduleId: string) => { + const response = await kfetch({ + method: 'GET', + pathname: `/api/ml/modules/get_module/${moduleId}`, + }); + + return pipe( + getMlModuleResponsePayloadRT.decode(response), + fold(throwErrors(createPlainError), identity) + ); +}; + +const jobDefinitionRT = rt.type({ + id: rt.string, + config: rt.type({ + custom_settings: jobCustomSettingsRT, + }), +}); + +export type JobDefinition = rt.TypeOf; + +const getMlModuleResponsePayloadRT = rt.type({ + id: rt.string, + jobs: rt.array(jobDefinitionRT), +}); + +export type GetMlModuleResponsePayload = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts index 722d19d99bd23d..92078b23085c3c 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_setup_module_api.ts @@ -4,27 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as rt from 'io-ts'; -import { kfetch } from 'ui/kfetch'; - import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import { identity } from 'fp-ts/lib/function'; +import * as rt from 'io-ts'; +import { kfetch } from 'ui/kfetch'; + import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; import { getJobIdPrefix } from '../../../../../common/log_analysis'; - -const MODULE_ID = 'logs_ui_analysis'; - -// This is needed due to: https://github.com/elastic/kibana/issues/43671 -const removeSampleDataIndex = (indexPattern: string) => { - const SAMPLE_DATA_INDEX = 'kibana_sample_data_logs*'; - return indexPattern - .split(',') - .filter(index => index !== SAMPLE_DATA_INDEX) - .join(','); -}; +import { jobCustomSettingsRT } from './ml_api_types'; export const callSetupMlModuleAPI = async ( + moduleId: string, start: number | undefined, end: number | undefined, spaceId: string, @@ -35,23 +26,30 @@ export const callSetupMlModuleAPI = async ( ) => { const response = await kfetch({ method: 'POST', - pathname: `/api/ml/modules/setup/${MODULE_ID}`, + pathname: `/api/ml/modules/setup/${moduleId}`, body: JSON.stringify( setupMlModuleRequestPayloadRT.encode({ start, end, - indexPatternName: removeSampleDataIndex(indexPattern), + indexPatternName: indexPattern, prefix: getJobIdPrefix(spaceId, sourceId), startDatafeed: true, jobOverrides: [ { - job_id: 'log-entry-rate', + job_id: 'log-entry-rate' as const, analysis_config: { bucket_span: `${bucketSpan}ms`, }, data_description: { time_field: timeField, }, + custom_settings: { + logs_source_config: { + indexPattern, + timestampField: timeField, + bucketSpan, + }, + }, }, ], datafeedOverrides: [], @@ -70,11 +68,22 @@ const setupMlModuleTimeParamsRT = rt.partial({ end: rt.number, }); +const setupMlModuleLogEntryRateJobOverridesRT = rt.type({ + job_id: rt.literal('log-entry-rate'), + analysis_config: rt.type({ + bucket_span: rt.string, + }), + data_description: rt.type({ + time_field: rt.string, + }), + custom_settings: jobCustomSettingsRT, +}); + const setupMlModuleRequestParamsRT = rt.type({ indexPatternName: rt.string, prefix: rt.string, startDatafeed: rt.boolean, - jobOverrides: rt.array(rt.object), + jobOverrides: rt.array(setupMlModuleLogEntryRateJobOverridesRT), datafeedOverrides: rt.array(rt.object), }); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx index 2c9e16de6a06a1..83d4760259d9bb 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx @@ -5,7 +5,9 @@ */ import createContainer from 'constate-latest'; -import { useEffect, useMemo, useCallback } from 'react'; +import { useMemo, useCallback, useEffect } from 'react'; + +import { callGetMlModuleAPI } from './api/ml_get_module'; import { bucketSpan } from '../../../../common/log_analysis'; import { useTrackedPromise } from '../../../utils/use_tracked_promise'; import { callJobsSummaryAPI } from './api/ml_get_jobs_summary_api'; @@ -13,6 +15,9 @@ import { callSetupMlModuleAPI, SetupMlModuleResponsePayload } from './api/ml_set import { useLogAnalysisCleanup } from './log_analysis_cleanup'; import { useStatusState } from './log_analysis_status_state'; +const MODULE_ID = 'logs_ui_analysis'; +const SAMPLE_DATA_INDEX = 'kibana_sample_data_logs*'; + export const useLogAnalysisJobs = ({ indexPattern, sourceId, @@ -24,8 +29,35 @@ export const useLogAnalysisJobs = ({ spaceId: string; timeField: string; }) => { + const filteredIndexPattern = useMemo(() => removeSampleDataIndex(indexPattern), [indexPattern]); const { cleanupMLResources } = useLogAnalysisCleanup({ sourceId, spaceId }); - const [statusState, dispatch] = useStatusState(); + const [statusState, dispatch] = useStatusState({ + bucketSpan, + indexPattern: filteredIndexPattern, + timestampField: timeField, + }); + + const [fetchModuleDefinitionRequest, fetchModuleDefinition] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + dispatch({ type: 'fetchingModuleDefinition' }); + return await callGetMlModuleAPI(MODULE_ID); + }, + onResolve: response => { + dispatch({ + type: 'fetchedModuleDefinition', + spaceId, + sourceId, + moduleDefinition: response, + }); + }, + onReject: () => { + dispatch({ type: 'failedFetchingModuleDefinition' }); + }, + }, + [] + ); const [setupMlModuleRequest, setupMlModule] = useTrackedPromise( { @@ -33,11 +65,12 @@ export const useLogAnalysisJobs = ({ createPromise: async (start, end) => { dispatch({ type: 'startedSetup' }); return await callSetupMlModuleAPI( + MODULE_ID, start, end, spaceId, sourceId, - indexPattern, + filteredIndexPattern, timeField, bucketSpan ); @@ -49,7 +82,7 @@ export const useLogAnalysisJobs = ({ dispatch({ type: 'failedSetup' }); }, }, - [indexPattern, spaceId, sourceId, timeField, bucketSpan] + [filteredIndexPattern, spaceId, sourceId, timeField, bucketSpan] ); const [fetchJobStatusRequest, fetchJobStatus] = useTrackedPromise( @@ -66,22 +99,20 @@ export const useLogAnalysisJobs = ({ dispatch({ type: 'failedFetchingJobStatuses' }); }, }, - [indexPattern, spaceId, sourceId] + [filteredIndexPattern, spaceId, sourceId] ); - useEffect(() => { - fetchJobStatus(); - }, []); - - const isLoadingSetupStatus = useMemo(() => fetchJobStatusRequest.state === 'pending', [ - fetchJobStatusRequest.state, - ]); + const isLoadingSetupStatus = useMemo( + () => + fetchJobStatusRequest.state === 'pending' || fetchModuleDefinitionRequest.state === 'pending', + [fetchJobStatusRequest.state, fetchModuleDefinitionRequest.state] + ); const viewResults = useCallback(() => { dispatch({ type: 'viewedResults' }); }, []); - const retry = useCallback( + const cleanupAndSetup = useCallback( (start, end) => { dispatch({ type: 'startedSetup' }); cleanupMLResources() @@ -95,16 +126,38 @@ export const useLogAnalysisJobs = ({ [cleanupMLResources, setupMlModule] ); + const viewSetupForReconfiguration = useCallback(() => { + dispatch({ type: 'requestedJobConfigurationUpdate' }); + }, []); + + const viewSetupForUpdate = useCallback(() => { + dispatch({ type: 'requestedJobDefinitionUpdate' }); + }, []); + + useEffect(() => { + fetchModuleDefinition(); + }, [fetchModuleDefinition]); + return { - setupMlModuleRequest, - jobStatus: statusState.jobStatus, + fetchJobStatus, isLoadingSetupStatus, + jobStatus: statusState.jobStatus, + cleanupAndSetup, setup: setupMlModule, - retry, + setupMlModuleRequest, setupStatus: statusState.setupStatus, + viewSetupForReconfiguration, + viewSetupForUpdate, viewResults, - fetchJobStatus, }; }; export const LogAnalysisJobs = createContainer(useLogAnalysisJobs); +// +// This is needed due to: https://github.com/elastic/kibana/issues/43671 +const removeSampleDataIndex = (indexPattern: string) => { + return indexPattern + .split(',') + .filter(index => index !== SAMPLE_DATA_INDEX) + .join(','); +}; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.tsx index db0af883a046ed..9f108b0c50f536 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_setup_state.tsx @@ -5,28 +5,33 @@ */ import { useState, useCallback } from 'react'; +type SetupHandler = (startTime?: number | undefined, endTime?: number | undefined) => void; + interface Props { - setupModule: (startTime?: number | undefined, endTime?: number | undefined) => void; - retrySetup: (startTime?: number | undefined, endTime?: number | undefined) => void; + cleanupAndSetupModule: SetupHandler; + setupModule: SetupHandler; } const fourWeeksInMs = 86400000 * 7 * 4; -export const useAnalysisSetupState = ({ setupModule, retrySetup }: Props) => { +export const useAnalysisSetupState = ({ setupModule, cleanupAndSetupModule }: Props) => { const [startTime, setStartTime] = useState(Date.now() - fourWeeksInMs); const [endTime, setEndTime] = useState(undefined); + const setup = useCallback(() => { return setupModule(startTime, endTime); }, [setupModule, startTime, endTime]); - const retry = useCallback(() => { - return retrySetup(startTime, endTime); - }, [retrySetup, startTime, endTime]); + + const cleanupAndSetup = useCallback(() => { + return cleanupAndSetupModule(startTime, endTime); + }, [cleanupAndSetupModule, startTime, endTime]); + return { - setup, - retry, - setStartTime, + cleanupAndSetup, + endTime, setEndTime, + setStartTime, + setup, startTime, - endTime, }; }; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_status_state.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_status_state.tsx index efe5b245517abf..02606c223d86d8 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_status_state.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_status_state.tsx @@ -5,33 +5,26 @@ */ import { useReducer } from 'react'; -import { getDatafeedId, getJobId, JobType } from '../../../../common/log_analysis'; -import { FetchJobStatusResponsePayload } from './api/ml_get_jobs_summary_api'; -import { SetupMlModuleResponsePayload } from './api/ml_setup_module_api'; -// combines and abstracts job and datafeed status -type JobStatus = - | 'unknown' - | 'missing' - | 'initializing' - | 'stopped' - | 'started' - | 'finished' - | 'failed'; - -export type SetupStatus = - | 'initializing' // acquiring job statuses to determine setup status - | 'unknown' // job status could not be acquired (failed request etc) - | 'required' // jobs are missing - | 'pending' // In the process of setting up the module for the first time or retrying, waiting for response - | 'succeeded' // setup succeeded, notifying user - | 'failed' // setup failed, notifying user - | 'hiddenAfterSuccess' // hide the setup screen and we show the results for the first time - | 'skipped'; // setup hidden because the module is in a correct state already +import { + getDatafeedId, + getJobId, + isJobStatusWithResults, + JobStatus, + JobType, + jobTypeRT, + SetupStatus, +} from '../../../../common/log_analysis'; +import { FetchJobStatusResponsePayload, JobSummary } from './api/ml_get_jobs_summary_api'; +import { GetMlModuleResponsePayload, JobDefinition } from './api/ml_get_module'; +import { SetupMlModuleResponsePayload } from './api/ml_setup_module_api'; interface StatusReducerState { + jobDefinitions: JobDefinition[]; jobStatus: Record; + jobSummaries: JobSummary[]; setupStatus: SetupStatus; + sourceConfiguration: JobSourceConfiguration; } type StatusReducerAction = @@ -52,19 +45,39 @@ type StatusReducerAction = payload: FetchJobStatusResponsePayload; } | { type: 'failedFetchingJobStatuses' } + | { type: 'fetchingModuleDefinition' } + | { + type: 'fetchedModuleDefinition'; + spaceId: string; + sourceId: string; + moduleDefinition: GetMlModuleResponsePayload; + } + | { type: 'failedFetchingModuleDefinition' } + | { + type: 'updatedSourceConfiguration'; + spaceId: string; + sourceId: string; + sourceConfiguration: JobSourceConfiguration; + } + | { type: 'requestedJobConfigurationUpdate' } + | { type: 'requestedJobDefinitionUpdate' } | { type: 'viewedResults' }; -const initialState: StatusReducerState = { +const createInitialState = (sourceConfiguration: JobSourceConfiguration): StatusReducerState => ({ + jobDefinitions: [], jobStatus: { 'log-entry-rate': 'unknown', }, + jobSummaries: [], setupStatus: 'initializing', -}; + sourceConfiguration, +}); function statusReducer(state: StatusReducerState, action: StatusReducerAction): StatusReducerState { switch (action.type) { case 'startedSetup': { return { + ...state, jobStatus: { 'log-entry-rate': 'initializing', }, @@ -89,12 +102,14 @@ function statusReducer(state: StatusReducerState, action: StatusReducerAction): ? 'succeeded' : 'failed'; return { + ...state, jobStatus: nextJobStatus, setupStatus: nextSetupStatus, }; } case 'failedSetup': { return { + ...state, jobStatus: { ...state.jobStatus, 'log-entry-rate': 'failed', @@ -102,30 +117,40 @@ function statusReducer(state: StatusReducerState, action: StatusReducerAction): setupStatus: 'failed', }; } + case 'fetchingModuleDefinition': case 'fetchingJobStatuses': { return { ...state, - setupStatus: 'initializing', + setupStatus: state.setupStatus === 'unknown' ? 'initializing' : state.setupStatus, }; } case 'fetchedJobStatuses': { - const { payload, spaceId, sourceId } = action; + const { payload: jobSummaries, spaceId, sourceId } = action; + const { jobDefinitions, setupStatus, sourceConfiguration } = state; + const nextJobStatus = { ...state.jobStatus, - 'log-entry-rate': getJobStatus(getJobId(spaceId, sourceId, 'log-entry-rate'))(payload), + 'log-entry-rate': getJobStatus(getJobId(spaceId, sourceId, 'log-entry-rate'))(jobSummaries), }; - const nextSetupStatus = Object.values(nextJobStatus).every(jobState => - ['started', 'finished'].includes(jobState) - ) - ? 'skipped' - : 'required'; + const nextSetupStatus = getSetupStatus( + spaceId, + sourceId, + sourceConfiguration, + nextJobStatus, + jobDefinitions, + jobSummaries + )(setupStatus); + return { + ...state, + jobSummaries, jobStatus: nextJobStatus, setupStatus: nextSetupStatus, }; } case 'failedFetchingJobStatuses': { return { + ...state, setupStatus: 'unknown', jobStatus: { ...state.jobStatus, @@ -133,6 +158,56 @@ function statusReducer(state: StatusReducerState, action: StatusReducerAction): }, }; } + case 'fetchedModuleDefinition': { + const { spaceId, sourceId, moduleDefinition } = action; + const { jobStatus, jobSummaries, setupStatus, sourceConfiguration } = state; + + const nextSetupStatus = getSetupStatus( + spaceId, + sourceId, + sourceConfiguration, + jobStatus, + moduleDefinition.jobs, + jobSummaries + )(setupStatus); + + return { + ...state, + jobDefinitions: moduleDefinition.jobs, + setupStatus: nextSetupStatus, + }; + } + case 'updatedSourceConfiguration': { + const { spaceId, sourceId, sourceConfiguration } = action; + const { jobDefinitions, jobStatus, jobSummaries, setupStatus } = state; + + const nextSetupStatus = getSetupStatus( + spaceId, + sourceId, + sourceConfiguration, + jobStatus, + jobDefinitions, + jobSummaries + )(setupStatus); + + return { + ...state, + setupStatus: nextSetupStatus, + sourceConfiguration, + }; + } + case 'requestedJobConfigurationUpdate': { + return { + ...state, + setupStatus: 'requiredForReconfiguration', + }; + } + case 'requestedJobDefinitionUpdate': { + return { + ...state, + setupStatus: 'requiredForUpdate', + }; + } case 'viewedResults': { return { ...state, @@ -194,6 +269,96 @@ const getJobStatus = (jobId: string) => (jobSummaries: FetchJobStatusResponsePay } )[0] || 'missing'; -export const useStatusState = () => { - return useReducer(statusReducer, initialState); +const getSetupStatus = ( + spaceId: string, + sourceId: string, + sourceConfiguration: JobSourceConfiguration, + everyJobStatus: Record, + jobDefinitions: JobDefinition[], + jobSummaries: JobSummary[] +) => (previousSetupStatus: SetupStatus) => + Object.entries(everyJobStatus).reduce((setupStatus, [jobType, jobStatus]) => { + if (!jobTypeRT.is(jobType)) { + return setupStatus; + } + + const jobId = getJobId(spaceId, sourceId, jobType); + const jobDefinition = jobDefinitions.find(({ id }) => id === jobType); + + if (jobStatus === 'missing') { + return 'required'; + } else if ( + setupStatus === 'required' || + setupStatus === 'requiredForUpdate' || + setupStatus === 'requiredForReconfiguration' + ) { + return setupStatus; + } else if ( + setupStatus === 'skippedButUpdatable' || + (jobDefinition && + !isJobRevisionCurrent(jobId, jobDefinition.config.custom_settings.job_revision || 0)( + jobSummaries + )) + ) { + return 'skippedButUpdatable'; + } else if ( + setupStatus === 'skippedButReconfigurable' || + !isJobConfigurationConsistent(jobId, sourceConfiguration)(jobSummaries) + ) { + return 'skippedButReconfigurable'; + } else if (setupStatus === 'hiddenAfterSuccess') { + return setupStatus; + } else if (setupStatus === 'skipped' || isJobStatusWithResults(jobStatus)) { + return 'skipped'; + } + + return setupStatus; + }, previousSetupStatus); + +const isJobRevisionCurrent = (jobId: string, currentRevision: number) => ( + jobSummaries: FetchJobStatusResponsePayload +): boolean => + jobSummaries + .filter(jobSummary => jobSummary.id === jobId) + .every( + jobSummary => + jobSummary.fullJob && + jobSummary.fullJob.custom_settings && + jobSummary.fullJob.custom_settings.job_revision && + jobSummary.fullJob.custom_settings.job_revision >= currentRevision + ); + +const isJobConfigurationConsistent = ( + jobId: string, + sourceConfiguration: { + bucketSpan: number; + indexPattern: string; + timestampField: string; + } +) => (jobSummaries: FetchJobStatusResponsePayload): boolean => + jobSummaries + .filter(jobSummary => jobSummary.id === jobId) + .every(jobSummary => { + if (!jobSummary.fullJob || !jobSummary.fullJob.custom_settings) { + return false; + } + + const jobConfiguration = jobSummary.fullJob.custom_settings.logs_source_config; + + return ( + jobConfiguration && + jobConfiguration.bucketSpan === sourceConfiguration.bucketSpan && + jobConfiguration.indexPattern === sourceConfiguration.indexPattern && + jobConfiguration.timestampField === sourceConfiguration.timestampField + ); + }); + +export const useStatusState = (sourceConfiguration: JobSourceConfiguration) => { + return useReducer(statusReducer, sourceConfiguration, createInitialState); }; + +interface JobSourceConfiguration { + bucketSpan: number; + indexPattern: string; + timestampField: string; +} diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_content.tsx index bf3ac99a92bf7c..2b83007e2ab996 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_content.tsx @@ -5,8 +5,9 @@ */ import { i18n } from '@kbn/i18n'; -import React, { useContext } from 'react'; +import React, { useContext, useEffect } from 'react'; +import { isSetupStatusWithResults } from '../../../../common/log_analysis'; import { LoadingPage } from '../../../components/loading_page'; import { LogAnalysisCapabilities, LogAnalysisJobs } from '../../../containers/logs/log_analysis'; import { Source } from '../../../containers/source'; @@ -19,10 +20,14 @@ export const AnalysisPageContent = () => { const { sourceId, source } = useContext(Source.Context); const { hasLogAnalysisCapabilites } = useContext(LogAnalysisCapabilities.Context); - const { setup, retry, setupStatus, viewResults, fetchJobStatus } = useContext( + const { setup, cleanupAndSetup, setupStatus, viewResults, fetchJobStatus } = useContext( LogAnalysisJobs.Context ); + useEffect(() => { + fetchJobStatus(); + }, []); + if (!hasLogAnalysisCapabilites) { return ; } else if (setupStatus === 'initializing') { @@ -35,7 +40,7 @@ export const AnalysisPageContent = () => { ); } else if (setupStatus === 'unknown') { return ; - } else if (setupStatus === 'skipped' || setupStatus === 'hiddenAfterSuccess') { + } else if (isSetupStatusWithResults(setupStatus)) { return ( { return ( { + fetchJobStatus(); + }, JOB_STATUS_POLLING_INTERVAL); + return ( <> {isLoading && !logEntryRate ? ( @@ -191,8 +207,12 @@ export const AnalysisResultsContent = ({ diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_setup_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_setup_content.tsx index 120ae11b69f91c..16da61bd1d9c1c 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_setup_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_setup_content.tsx @@ -18,24 +18,26 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import euiStyled from '../../../../../../common/eui_styled_components'; +import { SetupStatus } from '../../../../common/log_analysis'; import { useTrackPageview } from '../../../hooks/use_track_metric'; import { AnalysisSetupSteps } from './setup/steps'; -import { SetupStatus } from '../../../containers/logs/log_analysis'; + +type SetupHandler = (startTime?: number | undefined, endTime?: number | undefined) => void; interface AnalysisSetupContentProps { - setup: (startTime?: number | undefined, endTime?: number | undefined) => void; - retry: (startTime?: number | undefined, endTime?: number | undefined) => void; + cleanupAndSetup: SetupHandler; indexPattern: string; - viewResults: () => void; + setup: SetupHandler; setupStatus: SetupStatus; + viewResults: () => void; } export const AnalysisSetupContent: React.FunctionComponent = ({ - setup, + cleanupAndSetup, indexPattern, - viewResults, - retry, + setup, setupStatus, + viewResults, }) => { useTrackPageview({ app: 'infra_logs', path: 'analysis_setup' }); useTrackPageview({ app: 'infra_logs', path: 'analysis_setup', delay: 15000 }); @@ -70,7 +72,7 @@ export const AnalysisSetupContent: React.FunctionComponent void; + setupStatus: SetupStatus; timeRange: TimeRange; + viewSetupForReconfiguration: () => void; + viewSetupForUpdate: () => void; +}> = ({ + isLoading, + jobStatus, + results, + setTimeRange, + setupStatus, + timeRange, + viewSetupForReconfiguration, + viewSetupForUpdate, }) => { const title = i18n.translate('xpack.infra.logs.analysis.anomaliesSectionTitle', { defaultMessage: 'Anomalies', @@ -86,10 +98,29 @@ export const AnomaliesResults = ({ return ( <> - -

{title}

-
- + + + +

{title}

+
+
+ + + + + +
+ + + {isLoading ? ( @@ -187,18 +218,16 @@ const AnnotationTooltip: React.FunctionComponent<{ details: string }> = ({ detai {overallAnomalyScoreLabel}
    - {parsedDetails.anomalyScoresByPartition.map( - ({ partitionId, maximumAnomalyScore }, index) => { - return ( -
  • - - {`${partitionId}: `} - {maximumAnomalyScore} - -
  • - ); - } - )} + {parsedDetails.anomalyScoresByPartition.map(({ partitionId, maximumAnomalyScore }) => { + return ( +
  • + + {`${partitionId}: `} + {maximumAnomalyScore} + +
  • + ); + })}
); diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/recreate_ml_jobs_button.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/recreate_ml_jobs_button.tsx new file mode 100644 index 00000000000000..0232c1167f194a --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/recreate_ml_jobs_button.tsx @@ -0,0 +1,29 @@ +/* + * 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 from 'react'; +import { EuiButton } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +export const RecreateMLJobsButton: React.FunctionComponent<{ + onClick: () => void; +}> = ({ onClick }) => { + return ( + <> + + + + + + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/steps/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/steps/index.tsx index b33a609e191535..f755251def965c 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/steps/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/steps/index.tsx @@ -4,32 +4,42 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import { i18n } from '@kbn/i18n'; import { EuiSteps, EuiStepStatus } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +import { SetupStatus } from '../../../../../../common/log_analysis'; +import { useAnalysisSetupState } from '../../../../../containers/logs/log_analysis/log_analysis_setup_state'; import { InitialConfiguration } from './initial_configuration'; import { SetupProcess } from './setup_process'; -import { useAnalysisSetupState } from '../../../../../containers/logs/log_analysis/log_analysis_setup_state'; -import { SetupStatus } from '../../../../../containers/logs/log_analysis'; + +type SetupHandler = (startTime?: number | undefined, endTime?: number | undefined) => void; interface AnalysisSetupStepsProps { - setup: (startTime?: number | undefined, endTime?: number | undefined) => void; - retry: (startTime?: number | undefined, endTime?: number | undefined) => void; - viewResults: () => void; + cleanupAndSetup: SetupHandler; indexPattern: string; + setup: SetupHandler; setupStatus: SetupStatus; + viewResults: () => void; } export const AnalysisSetupSteps: React.FunctionComponent = ({ - setup: setupModule, - retry: retrySetup, - viewResults, + cleanupAndSetup: cleanupAndSetupModule, indexPattern, + setup: setupModule, setupStatus, + viewResults, }: AnalysisSetupStepsProps) => { - const { setup, retry, setStartTime, setEndTime, startTime, endTime } = useAnalysisSetupState({ + const { + setup, + cleanupAndSetup, + setStartTime, + setEndTime, + startTime, + endTime, + } = useAnalysisSetupState({ setupModule, - retrySetup, + cleanupAndSetupModule, }); const steps = [ @@ -55,7 +65,7 @@ export const AnalysisSetupSteps: React.FunctionComponent ), diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/steps/setup_process.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/steps/setup_process.tsx index 806a049eb5d1e4..7d3b602f906fb6 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/steps/setup_process.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/setup/steps/setup_process.tsx @@ -4,23 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import { - EuiLoadingSpinner, EuiButton, - EuiSpacer, EuiFlexGroup, EuiFlexItem, + EuiLoadingSpinner, + EuiSpacer, EuiText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; + +import { SetupStatus } from '../../../../../../common/log_analysis'; import { CreateMLJobsButton } from '../create_ml_jobs_button'; -import { SetupStatus } from '../../../../../containers/logs/log_analysis'; +import { RecreateMLJobsButton } from '../recreate_ml_jobs_button'; interface Props { viewResults: () => void; setup: () => void; - retry: () => void; + cleanupAndSetup: () => void; indexPattern: string; setupStatus: SetupStatus; } @@ -28,7 +30,7 @@ interface Props { export const SetupProcess: React.FunctionComponent = ({ viewResults, setup, - retry, + cleanupAndSetup, indexPattern, setupStatus, }: Props) => { @@ -57,7 +59,7 @@ export const SetupProcess: React.FunctionComponent = ({ }} /> - + = ({ /> + ) : setupStatus === 'requiredForUpdate' || setupStatus === 'requiredForReconfiguration' ? ( + ) : ( )}