From de0499e45ddb84215a9d903d9dc9bc845559aa63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Thu, 8 Aug 2019 14:37:44 +0200 Subject: [PATCH 01/38] Add empty analysis tab --- .../infra/public/pages/logs/analysis/index.ts | 7 ++++++ .../infra/public/pages/logs/analysis/page.tsx | 24 +++++++++++++++++++ .../pages/logs/analysis/page_providers.tsx | 17 +++++++++++++ .../logs/analysis/page_results_content.tsx | 16 +++++++++++++ .../logs/analysis/page_setup_content.tsx | 16 +++++++++++++ .../plugins/infra/public/pages/logs/index.tsx | 9 +++++++ 6 files changed, 89 insertions(+) create mode 100644 x-pack/legacy/plugins/infra/public/pages/logs/analysis/index.ts create mode 100644 x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx create mode 100644 x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_providers.tsx create mode 100644 x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx create mode 100644 x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_setup_content.tsx diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/index.ts b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/index.ts new file mode 100644 index 0000000000000..224217e860e94 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/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 './page'; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx new file mode 100644 index 0000000000000..4e54cef7e5738 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.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 { ColumnarPage } from '../../../components/page'; +import { AnalysisPageProviders } from './page_providers'; +import { AnalysisResultsContent } from './page_results_content'; +import { AnalysisSetupContent } from './page_setup_content'; + +export const AnalysisPage = () => { + const isSetupRequired = true; + + return ( + + + {isSetupRequired ? : } + + + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_providers.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_providers.tsx new file mode 100644 index 0000000000000..95d6278164326 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_providers.tsx @@ -0,0 +1,17 @@ +/* + * 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 { Source, useSource } from '../../../containers/source'; +import { useSourceId } from '../../../containers/source_id'; + +export const AnalysisPageProviders: React.FunctionComponent = ({ children }) => { + const [sourceId] = useSourceId(); + const source = useSource({ sourceId }); + + return {children}; +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx new file mode 100644 index 0000000000000..a570f9e37309f --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -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 React from 'react'; + +import { useTrackPageview } from '../../../hooks/use_track_metric'; + +export const AnalysisResultsContent = () => { + useTrackPageview({ app: 'infra_logs', path: 'analysis_results' }); + useTrackPageview({ app: 'infra_logs', path: 'analysis_results', delay: 15000 }); + + return
Results
; +}; 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 new file mode 100644 index 0000000000000..a4e1402e9ce60 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_setup_content.tsx @@ -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 React from 'react'; + +import { useTrackPageview } from '../../../hooks/use_track_metric'; + +export const AnalysisSetupContent = () => { + useTrackPageview({ app: 'infra_logs', path: 'analysis_setup' }); + useTrackPageview({ app: 'infra_logs', path: 'analysis_setup', delay: 15000 }); + + return
Setup
; +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx index dbcd046549c96..06e67a9d3e1f5 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx @@ -20,6 +20,7 @@ import { Source } from '../../containers/source'; import { StreamPage } from './stream'; import { SettingsPage } from '../shared/settings'; import { AppNavigation } from '../../components/navigation/app_navigation'; +import { AnalysisPage } from './analysis'; interface LogsPageProps extends RouteComponentProps { intl: InjectedIntl; @@ -66,6 +67,13 @@ export const LogsPage = injectUICapabilities( }), path: `${match.path}/stream`, }, + { + title: intl.formatMessage({ + id: 'xpack.infra.logs.index.analysisTabTitle', + defaultMessage: 'Analysis', + }), + path: `${match.path}/analysis`, + }, { title: intl.formatMessage({ id: 'xpack.infra.logs.index.settingsTabTitle', @@ -79,6 +87,7 @@ export const LogsPage = injectUICapabilities( + From 0ba9cca7471b736864adf4e0c8c3187bea355229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Thu, 8 Aug 2019 16:31:10 +0200 Subject: [PATCH 02/38] Add ml capabilities check --- .../common/log_analysis/job_parameters.ts | 5 +- .../containers/logs/log_analysis/index.ts | 2 + .../log_analysis_capabilities.tsx | 85 +++++++++++ .../logs/log_analysis/log_analysis_jobs.tsx | 70 +++++++++ .../log_analysis/log_analysis_results.tsx | 2 +- .../logs/log_analysis/ml_api_types.ts | 28 ++++ .../plugins/infra/public/pages/logs/index.tsx | 138 ++++++++++-------- .../public/pages/logs/stream/page_content.tsx | 24 +-- .../pages/logs/stream/page_providers.tsx | 24 ++- 9 files changed, 283 insertions(+), 95 deletions(-) create mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx create mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx create mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts diff --git a/x-pack/legacy/plugins/infra/common/log_analysis/job_parameters.ts b/x-pack/legacy/plugins/infra/common/log_analysis/job_parameters.ts index ff4490d2c700a..82cfb0f83ed69 100644 --- a/x-pack/legacy/plugins/infra/common/log_analysis/job_parameters.ts +++ b/x-pack/legacy/plugins/infra/common/log_analysis/job_parameters.ts @@ -6,5 +6,8 @@ import { JobType } from './log_analysis'; +export const getJobIdPrefix = (spaceId: string, sourceId: string) => + `kibana-logs-ui-${spaceId}-${sourceId}-`; + export const getJobId = (spaceId: string, sourceId: string, jobType: JobType) => - `kibana-logs-ui-${spaceId}-${sourceId}-${jobType}`; + `${getJobIdPrefix(spaceId, sourceId)}${jobType}`; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/index.ts index 784c02f89615f..294a78cc85206 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/index.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/index.ts @@ -4,4 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './log_analysis_capabilities'; +export * from './log_analysis_jobs'; export * from './log_analysis_results'; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx new file mode 100644 index 0000000000000..f364e37462f39 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx @@ -0,0 +1,85 @@ +/* + * 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 { useMemo, useState, useEffect } from 'react'; +import { kfetch } from 'ui/kfetch'; + +import { useTrackedPromise } from '../../../utils/use_tracked_promise'; +import { + getMlCapabilitiesResponsePayloadRT, + GetMlCapabilitiesResponsePayload, +} from './ml_api_types'; +import { throwErrors, createPlainError } from '../../../../common/runtime_types'; + +export const useLogAnalysisCapabilities = () => { + const [mlCapabilities, setMlCapabilities] = useState( + initialMlCapabilities + ); + + const [fetchMlCapabilitiesRequest, fetchMlCapabilities] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + const rawResponse = await kfetch({ + method: 'GET', + pathname: '/api/ml/ml_capabilities', + }); + + return getMlCapabilitiesResponsePayloadRT + .decode(rawResponse) + .getOrElseL(throwErrors(createPlainError)); + }, + onResolve: response => { + setMlCapabilities(response); + }, + }, + [] + ); + + useEffect(() => { + fetchMlCapabilities(); + }, []); + + const isLoading = useMemo(() => fetchMlCapabilitiesRequest.state === 'pending', [ + fetchMlCapabilitiesRequest.state, + ]); + + return { + hasLogAnalysisCapabilites: mlCapabilities.capabilities.canCreateJob, + isLoading, + }; +}; + +const initialMlCapabilities = { + capabilities: { + canGetJobs: false, + canCreateJob: false, + canDeleteJob: false, + canOpenJob: false, + canCloseJob: false, + canForecastJob: false, + canGetDatafeeds: false, + canStartStopDatafeed: false, + canUpdateJob: false, + canUpdateDatafeed: false, + canPreviewDatafeed: false, + canGetCalendars: false, + canCreateCalendar: false, + canDeleteCalendar: false, + canGetFilters: false, + canCreateFilter: false, + canDeleteFilter: false, + canFindFileStructure: false, + canGetDataFrameJobs: false, + canDeleteDataFrameJob: false, + canPreviewDataFrameJob: false, + canCreateDataFrameJob: false, + canStartStopDataFrameJob: false, + }, + isPlatinumOrTrialLicense: false, + mlFeatureEnabledInSpace: false, + upgradeInProgress: false, +}; 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 new file mode 100644 index 0000000000000..dcecfaea3467b --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx @@ -0,0 +1,70 @@ +/* + * 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 createContainer from 'constate-latest'; +import { useMemo, useState } from 'react'; +import { kfetch } from 'ui/kfetch'; + +import { getJobIdPrefix } from '../../../../common/log_analysis'; +import { useTrackedPromise } from '../../../utils/use_tracked_promise'; +import { setupMlModuleRequestPayloadRT } from './ml_api_types'; + +type JobStatus = 'unknown' | 'querying' | 'missing' | 'creating' | 'running' | 'inconsistent'; + +export const useLogAnalysisJobs = ({ + indexPattern, + sourceId, + spaceId, +}: { + indexPattern: string; + sourceId: string; + spaceId: string; +}) => { + const [jobStatus, setJobStatus] = useState<{ + logEntryRate: JobStatus; + }>({ + logEntryRate: 'unknown', + }); + + // const [setupMlModuleRequest, setupMlModule] = useTrackedPromise( + // { + // cancelPreviousOn: 'resolution', + // createPromise: async () => { + // kfetch({ + // method: 'POST', + // pathname: '/api/ml/modules/setup', + // body: JSON.stringify( + // setupMlModuleRequestPayloadRT.encode({ + // indexPatternName: indexPattern, + // prefix: getJobIdPrefix(spaceId, sourceId), + // startDatafeed: true, + // }) + // ), + // }); + // }, + // }, + // [indexPattern, spaceId, sourceId] + // ); + + const [fetchJobStatusRequest, fetchJobStatus] = useTrackedPromise( + { + cancelPreviousOn: 'resolution', + createPromise: async () => { + kfetch({ + method: 'POST', + pathname: '/api/ml/jobs/jobs_summary', + }); + }, + }, + [indexPattern, spaceId, sourceId] + ); + + return { + jobStatus, + }; +}; + +export const LogAnalysisJobs = createContainer(useLogAnalysisJobs); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx index 1903441037822..861c07c3ad5fa 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import createContainer from 'constate-latest/dist/ts/src'; +import createContainer from 'constate-latest'; import { useMemo } from 'react'; import { useLogEntryRate } from './log_entry_rate'; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts new file mode 100644 index 0000000000000..ee70edc31d49b --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts @@ -0,0 +1,28 @@ +/* + * 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 getMlCapabilitiesResponsePayloadRT = rt.type({ + capabilities: rt.type({ + canGetJobs: rt.boolean, + canCreateJob: rt.boolean, + canDeleteJob: rt.boolean, + canOpenJob: rt.boolean, + canCloseJob: rt.boolean, + canForecastJob: rt.boolean, + canGetDatafeeds: rt.boolean, + canStartStopDatafeed: rt.boolean, + canUpdateJob: rt.boolean, + canUpdateDatafeed: rt.boolean, + canPreviewDatafeed: rt.boolean, + }), + isPlatinumOrTrialLicense: rt.boolean, + mlFeatureEnabledInSpace: rt.boolean, + upgradeInProgress: rt.boolean, +}); + +export type GetMlCapabilitiesResponsePayload = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx index 06e67a9d3e1f5..7fb060b45c63a 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx @@ -16,11 +16,15 @@ import { HelpCenterContent } from '../../components/help_center_content'; import { Header } from '../../components/header'; import { RoutedTabs } from '../../components/navigation/routed_tabs'; import { ColumnarPage } from '../../components/page'; -import { Source } from '../../containers/source'; +import { SourceLoadingPage } from '../../components/source_loading_page'; +import { SourceErrorPage } from '../../components/source_error_page'; +import { Source, useSource } from '../../containers/source'; import { StreamPage } from './stream'; import { SettingsPage } from '../shared/settings'; import { AppNavigation } from '../../components/navigation/app_navigation'; import { AnalysisPage } from './analysis'; +import { useLogAnalysisCapabilities } from '../../containers/logs/log_analysis'; +import { useSourceId } from '../../containers/source_id'; interface LogsPageProps extends RouteComponentProps { intl: InjectedIntl; @@ -28,69 +32,89 @@ interface LogsPageProps extends RouteComponentProps { } export const LogsPage = injectUICapabilities( - injectI18n(({ match, intl, uiCapabilities }: LogsPageProps) => ( - - - + injectI18n(({ match, intl, uiCapabilities }: LogsPageProps) => { + const [sourceId] = useSourceId(); + const source = useSource({ sourceId }); + const { hasLogAnalysisCapabilites } = useLogAnalysisCapabilities(); - + return ( + + + -
+ - - - + {source.isLoadingSource ? ( + + ) : source.hasFailedLoadingSource ? ( + + ) : ( + <> + + + - - - - - - - - )) + + + + + + + )} + + + ); + }) ); diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_content.tsx index abcc881dd250e..3188b3e03b015 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_content.tsx @@ -6,32 +6,12 @@ import React, { useContext } from 'react'; -import { SourceErrorPage } from '../../../components/source_error_page'; -import { SourceLoadingPage } from '../../../components/source_loading_page'; import { Source } from '../../../containers/source'; import { LogsPageLogsContent } from './page_logs_content'; import { LogsPageNoIndicesContent } from './page_no_indices_content'; export const StreamPageContent: React.FunctionComponent = () => { - const { - hasFailedLoadingSource, - isLoadingSource, - logIndicesExist, - loadSource, - loadSourceFailureMessage, - } = useContext(Source.Context); + const { logIndicesExist } = useContext(Source.Context); - return ( - <> - {isLoadingSource ? ( - - ) : logIndicesExist ? ( - - ) : hasFailedLoadingSource ? ( - - ) : ( - - )} - - ); + return <>{logIndicesExist ? : }; }; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_providers.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_providers.tsx index b93cf48bde5e7..55ffc807f7ba8 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_providers.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_providers.tsx @@ -4,27 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useContext } from 'react'; import { LogFlyout } from '../../../containers/logs/log_flyout'; import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration'; import { LogHighlightsState } from '../../../containers/logs/log_highlights/log_highlights'; -import { Source, useSource } from '../../../containers/source'; -import { useSourceId } from '../../../containers/source_id'; +import { Source } from '../../../containers/source'; export const LogsPageProviders: React.FunctionComponent = ({ children }) => { - const [sourceId] = useSourceId(); - const source = useSource({ sourceId }); + const { sourceId, version } = useContext(Source.Context); return ( - - - - - {children} - - - - + + + + {children} + + + ); }; From 84248c2f894f3365478b690b2bce190c39cc9c28 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Fri, 9 Aug 2019 00:37:19 +0100 Subject: [PATCH 03/38] Add job status checking functionality --- .../logs/log_analysis/log_analysis_jobs.tsx | 54 +++++++++++++++++-- .../logs/log_analysis/ml_api_types.ts | 29 ++++++++++ .../infra/public/pages/logs/analysis/page.tsx | 25 +++++++-- .../plugins/infra/public/pages/logs/index.tsx | 6 ++- 4 files changed, 103 insertions(+), 11 deletions(-) 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 dcecfaea3467b..cc97a3b9acebb 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,12 +5,17 @@ */ import createContainer from 'constate-latest'; -import { useMemo, useState } from 'react'; +import { useMemo, useEffect, useState } from 'react'; import { kfetch } from 'ui/kfetch'; - -import { getJobIdPrefix } from '../../../../common/log_analysis'; +import { values } from 'lodash'; +import { getJobId, getJobIdPrefix } from '../../../../common/log_analysis'; +import { throwErrors, createPlainError } from '../../../../common/runtime_types'; import { useTrackedPromise } from '../../../utils/use_tracked_promise'; -import { setupMlModuleRequestPayloadRT } from './ml_api_types'; +import { + setupMlModuleRequestPayloadRT, + fetchJobStatusRequestPayloadRT, + fetchJobStatusResponsePayloadRT, +} from './ml_api_types'; type JobStatus = 'unknown' | 'querying' | 'missing' | 'creating' | 'running' | 'inconsistent'; @@ -53,17 +58,56 @@ export const useLogAnalysisJobs = ({ { cancelPreviousOn: 'resolution', createPromise: async () => { - kfetch({ + const response = await kfetch({ method: 'POST', pathname: '/api/ml/jobs/jobs_summary', + body: JSON.stringify( + fetchJobStatusRequestPayloadRT.encode({ + jobIds: [getJobId(spaceId, sourceId, 'log-entry-rate')], + }) + ), }); + return fetchJobStatusResponsePayloadRT + .decode(response) + .getOrElseL(throwErrors(createPlainError)); + }, + onResolve: response => { + if (response && response.length) { + const logEntryRate = response.find( + job => job.id === getJobId(spaceId, sourceId, 'log-entry-rate') + ); + setJobStatus({ + logEntryRate: logEntryRate ? (logEntryRate.jobState as JobStatus) : 'unknown', + }); + } + }, + onReject: error => { + // TODO: Handle errors }, }, [indexPattern, spaceId, sourceId] ); + useEffect(() => { + fetchJobStatus(); + }, []); + + const isSetupRequired = useMemo(() => { + const jobStates = values(jobStatus); + const badStates = jobStates.filter(state => { + return state === 'unknown' || state === 'missing' || state === 'inconsistent'; + }); + return badStates.length > 0; + }, [jobStatus]); + + const isLoadingSetupStatus = useMemo(() => fetchJobStatusRequest.state === 'pending', [ + fetchJobStatusRequest.state, + ]); + return { jobStatus, + isSetupRequired, + isLoadingSetupStatus, }; }; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts index ee70edc31d49b..74f450d0a7637 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts @@ -26,3 +26,32 @@ export const getMlCapabilitiesResponsePayloadRT = rt.type({ }); export type GetMlCapabilitiesResponsePayload = rt.TypeOf; + +export const fetchJobStatusRequestPayloadRT = rt.type({ + jobIds: rt.array(rt.string), +}); + +export type FetchJobStatusRequestPayload = rt.TypeOf; + +// TODO: Get types aligned properly with response +// export const fetchJobStatusResponsePayloadRT = rt.array(rt.type({ +// datafeedId: rt.string, +// datafeedIndices: rt.array(rt.string), +// datafeedState: rt.string, +// description: rt.string, +// earliestTimestampMs: rt.number, +// groups: rt.array(rt.string), +// hasDatafeed: rt.boolean, +// id: rt.string, +// isSingleMetricViewerJob: rt.boolean, +// jobState: rt.string, +// latestResultsTimestampMs: rt.number, +// latestTimestampMs: rt.number, +// memory_status: rt.string, +// nodeName: rt.string, +// processed_record_count: rt.number +// })); + +export const fetchJobStatusResponsePayloadRT = rt.any; + +export type FetchJobStatusResponsePayload = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx index 4e54cef7e5738..fa1f3195dc1bc 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx @@ -4,20 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; - +import React, { useContext } from 'react'; +import { last } from 'lodash'; +import chrome from 'ui/chrome'; import { ColumnarPage } from '../../../components/page'; import { AnalysisPageProviders } from './page_providers'; import { AnalysisResultsContent } from './page_results_content'; import { AnalysisSetupContent } from './page_setup_content'; +import { useLogAnalysisJobs } from '../../../containers/logs/log_analysis/log_analysis_jobs'; +import { Source } from '../../../containers/source'; export const AnalysisPage = () => { - const isSetupRequired = true; + const { sourceId, source } = useContext(Source.Context); + // TODO: Find out the proper Kibana way to derive the space ID client side + const basePath = chrome.getBasePath(); + const spaceId = basePath.includes('s/') ? last(basePath.split('/')) : 'default'; + const { isSetupRequired, isLoadingSetupStatus } = useLogAnalysisJobs({ + indexPattern: source.configuration.logAlias, + sourceId, + spaceId, + }); return ( - {isSetupRequired ? : } + {isLoadingSetupStatus ? ( +
Checking status...
+ ) : isSetupRequired ? ( + + ) : ( + + )}
); diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx index 7fb060b45c63a..d4c410cd291eb 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx @@ -36,7 +36,6 @@ export const LogsPage = injectUICapabilities( const [sourceId] = useSourceId(); const source = useSource({ sourceId }); const { hasLogAnalysisCapabilites } = useLogAnalysisCapabilities(); - return ( @@ -65,7 +64,10 @@ export const LogsPage = injectUICapabilities( ]} readOnlyBadge={!uiCapabilities.logs.save} /> - {source.isLoadingSource ? ( + {source.isLoadingSource || + (!source.isLoadingSource && + !source.hasFailedLoadingSource && + source.source === undefined) ? ( ) : source.hasFailedLoadingSource ? ( Date: Fri, 9 Aug 2019 13:27:50 +0100 Subject: [PATCH 04/38] Add a loading page for the job status check --- .../legacy/plugins/infra/public/pages/logs/analysis/page.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx index fa1f3195dc1bc..1809f8041e2ff 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx @@ -8,6 +8,7 @@ import React, { useContext } from 'react'; import { last } from 'lodash'; import chrome from 'ui/chrome'; import { ColumnarPage } from '../../../components/page'; +import { LoadingPage } from '../../../components/loading_page'; import { AnalysisPageProviders } from './page_providers'; import { AnalysisResultsContent } from './page_results_content'; import { AnalysisSetupContent } from './page_setup_content'; @@ -29,7 +30,7 @@ export const AnalysisPage = () => { {isLoadingSetupStatus ? ( -
Checking status...
+ ) : isSetupRequired ? ( ) : ( From 6c0bac5bf4bde682e8a1961ef9a8807e2322ebf2 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Fri, 9 Aug 2019 15:51:36 +0100 Subject: [PATCH 05/38] Change types / change method for deriving space ID / change setup requirement filtering check --- .../logs/log_analysis/log_analysis_jobs.tsx | 22 ++++++++----------- .../logs/log_analysis/ml_api_types.ts | 8 ++++--- .../infra/public/pages/logs/analysis/page.tsx | 7 ++---- 3 files changed, 16 insertions(+), 21 deletions(-) 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 cc97a3b9acebb..b9576474453db 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 @@ -8,16 +8,13 @@ import createContainer from 'constate-latest'; import { useMemo, useEffect, useState } from 'react'; import { kfetch } from 'ui/kfetch'; import { values } from 'lodash'; -import { getJobId, getJobIdPrefix } from '../../../../common/log_analysis'; +import { getJobId } from '../../../../common/log_analysis'; import { throwErrors, createPlainError } from '../../../../common/runtime_types'; import { useTrackedPromise } from '../../../utils/use_tracked_promise'; -import { - setupMlModuleRequestPayloadRT, - fetchJobStatusRequestPayloadRT, - fetchJobStatusResponsePayloadRT, -} from './ml_api_types'; +import { fetchJobStatusRequestPayloadRT, fetchJobStatusResponsePayloadRT } from './ml_api_types'; -type JobStatus = 'unknown' | 'querying' | 'missing' | 'creating' | 'running' | 'inconsistent'; +type JobStatus = 'unknown' | 'closed' | 'closing' | 'failed' | 'opened' | 'opening' | 'deleted'; +// type DatafeedStatus = 'unknown' | 'started' | 'starting' | 'stopped' | 'stopping' | 'deleted'; export const useLogAnalysisJobs = ({ indexPattern, @@ -74,10 +71,10 @@ export const useLogAnalysisJobs = ({ onResolve: response => { if (response && response.length) { const logEntryRate = response.find( - job => job.id === getJobId(spaceId, sourceId, 'log-entry-rate') + (job: any) => job.id === getJobId(spaceId, sourceId, 'log-entry-rate') ); setJobStatus({ - logEntryRate: logEntryRate ? (logEntryRate.jobState as JobStatus) : 'unknown', + logEntryRate: logEntryRate ? logEntryRate.jobState : 'unknown', }); } }, @@ -94,10 +91,9 @@ export const useLogAnalysisJobs = ({ const isSetupRequired = useMemo(() => { const jobStates = values(jobStatus); - const badStates = jobStates.filter(state => { - return state === 'unknown' || state === 'missing' || state === 'inconsistent'; - }); - return badStates.length > 0; + return ( + jobStates.filter(state => state === 'opened' || state === 'opening').length < jobStates.length + ); }, [jobStatus]); const isLoadingSetupStatus = useMemo(() => fetchJobStatusRequest.state === 'pending', [ diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts index 74f450d0a7637..fc9ff0d3834b0 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts @@ -33,7 +33,6 @@ export const fetchJobStatusRequestPayloadRT = rt.type({ export type FetchJobStatusRequestPayload = rt.TypeOf; -// TODO: Get types aligned properly with response // export const fetchJobStatusResponsePayloadRT = rt.array(rt.type({ // datafeedId: rt.string, // datafeedIndices: rt.array(rt.string), @@ -48,8 +47,11 @@ export type FetchJobStatusRequestPayload = rt.TypeOf { const { sourceId, source } = useContext(Source.Context); - // TODO: Find out the proper Kibana way to derive the space ID client side - const basePath = chrome.getBasePath(); - const spaceId = basePath.includes('s/') ? last(basePath.split('/')) : 'default'; + const spaceId = chrome.getInjected('activeSpace').space.id; const { isSetupRequired, isLoadingSetupStatus } = useLogAnalysisJobs({ - indexPattern: source.configuration.logAlias, + indexPattern: source ? source.configuration.logAlias : '', sourceId, spaceId, }); From 98a9fa9bd229d7c641357558d2bd00f0832c077a Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Fri, 9 Aug 2019 16:11:15 +0100 Subject: [PATCH 06/38] Use new structure --- .../api/ml_get_jobs_summary_api.ts | 55 +++++++++++++++++++ .../logs/log_analysis/log_analysis_jobs.tsx | 17 +----- .../logs/log_analysis/ml_api_types.ts | 31 ----------- 3 files changed, 57 insertions(+), 46 deletions(-) create mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts 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 new file mode 100644 index 0000000000000..e364e534a2d61 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts @@ -0,0 +1,55 @@ +/* + * 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'; +import { kfetch } from 'ui/kfetch'; +import { getJobId } from '../../../../../common/log_analysis'; +import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; + +export const callJobsSummaryAPI = async (spaceId: string, sourceId: string) => { + const response = await kfetch({ + method: 'POST', + pathname: '/api/ml/jobs/jobs_summary', + body: JSON.stringify( + fetchJobStatusRequestPayloadRT.encode({ + jobIds: [getJobId(spaceId, sourceId, 'log-entry-rate')], + }) + ), + }); + return fetchJobStatusResponsePayloadRT.decode(response).getOrElseL(throwErrors(createPlainError)); +}; + +export const fetchJobStatusRequestPayloadRT = rt.type({ + jobIds: rt.array(rt.string), +}); + +export type FetchJobStatusRequestPayload = rt.TypeOf; + +// TODO: Get this to align with the payload - something is tripping it up somewhere +// export const fetchJobStatusResponsePayloadRT = rt.array(rt.type({ +// datafeedId: rt.string, +// datafeedIndices: rt.array(rt.string), +// datafeedState: rt.string, +// description: rt.string, +// earliestTimestampMs: rt.number, +// groups: rt.array(rt.string), +// hasDatafeed: rt.boolean, +// id: rt.string, +// isSingleMetricViewerJob: rt.boolean, +// jobState: rt.string, +// latestResultsTimestampMs: rt.number, +// latestTimestampMs: rt.number, +// memory_status: rt.string, +// nodeName: rt.union([rt.string, rt.undefined]), +// processed_record_count: rt.number, +// fullJob: rt.any, +// auditMessage: rt.any, +// deleting: rt.union([rt.boolean, rt.undefined]), +// })); + +export const fetchJobStatusResponsePayloadRT = rt.any; + +export type FetchJobStatusResponsePayload = rt.TypeOf; 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 b9576474453db..391a4b190edd2 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 @@ -6,12 +6,10 @@ import createContainer from 'constate-latest'; import { useMemo, useEffect, useState } from 'react'; -import { kfetch } from 'ui/kfetch'; import { values } from 'lodash'; import { getJobId } from '../../../../common/log_analysis'; -import { throwErrors, createPlainError } from '../../../../common/runtime_types'; import { useTrackedPromise } from '../../../utils/use_tracked_promise'; -import { fetchJobStatusRequestPayloadRT, fetchJobStatusResponsePayloadRT } from './ml_api_types'; +import { callJobsSummaryAPI } from './api/ml_get_jobs_summary_api'; type JobStatus = 'unknown' | 'closed' | 'closing' | 'failed' | 'opened' | 'opening' | 'deleted'; // type DatafeedStatus = 'unknown' | 'started' | 'starting' | 'stopped' | 'stopping' | 'deleted'; @@ -55,18 +53,7 @@ export const useLogAnalysisJobs = ({ { cancelPreviousOn: 'resolution', createPromise: async () => { - const response = await kfetch({ - method: 'POST', - pathname: '/api/ml/jobs/jobs_summary', - body: JSON.stringify( - fetchJobStatusRequestPayloadRT.encode({ - jobIds: [getJobId(spaceId, sourceId, 'log-entry-rate')], - }) - ), - }); - return fetchJobStatusResponsePayloadRT - .decode(response) - .getOrElseL(throwErrors(createPlainError)); + return callJobsSummaryAPI(spaceId, sourceId); }, onResolve: response => { if (response && response.length) { diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts index fc9ff0d3834b0..ee70edc31d49b 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/ml_api_types.ts @@ -26,34 +26,3 @@ export const getMlCapabilitiesResponsePayloadRT = rt.type({ }); export type GetMlCapabilitiesResponsePayload = rt.TypeOf; - -export const fetchJobStatusRequestPayloadRT = rt.type({ - jobIds: rt.array(rt.string), -}); - -export type FetchJobStatusRequestPayload = rt.TypeOf; - -// export const fetchJobStatusResponsePayloadRT = rt.array(rt.type({ -// datafeedId: rt.string, -// datafeedIndices: rt.array(rt.string), -// datafeedState: rt.string, -// description: rt.string, -// earliestTimestampMs: rt.number, -// groups: rt.array(rt.string), -// hasDatafeed: rt.boolean, -// id: rt.string, -// isSingleMetricViewerJob: rt.boolean, -// jobState: rt.string, -// latestResultsTimestampMs: rt.number, -// latestTimestampMs: rt.number, -// memory_status: rt.string, -// nodeName: rt.union([rt.string, rt.undefined]), -// processed_record_count: rt.number, -// fullJob: rt.any, -// auditMessage: rt.any, -// deleting: rt.union([rt.boolean, rt.undefined]), -// })); - -export const fetchJobStatusResponsePayloadRT = rt.any; - -export type FetchJobStatusResponsePayload = rt.TypeOf; From 823e0b0f6034d84313343282f3ac1c1ec8a4070f Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Mon, 12 Aug 2019 12:50:14 +0100 Subject: [PATCH 07/38] Add a loading page --- .../log_analysis/log_analysis_results.tsx | 5 ++++- .../infra/public/pages/logs/analysis/page.tsx | 2 +- .../logs/analysis/page_results_content.tsx | 21 +++++++++++++++++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx index 861c07c3ad5fa..5eeabb77e2f0d 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx @@ -10,13 +10,16 @@ import { useMemo } from 'react'; import { useLogEntryRate } from './log_entry_rate'; export const useLogAnalysisResults = ({ sourceId }: { sourceId: string }) => { - const { isLoading: isLoadingLogEntryRate, logEntryRate } = useLogEntryRate({ sourceId }); + const { isLoading: isLoadingLogEntryRate, logEntryRate, getLogEntryRate } = useLogEntryRate({ + sourceId, + }); const isLoading = useMemo(() => isLoadingLogEntryRate, [isLoadingLogEntryRate]); return { isLoading, logEntryRate, + getLogEntryRate, }; }; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx index 6a39b76f07f6d..bdc8b2539964d 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx @@ -31,7 +31,7 @@ export const AnalysisPage = () => { ) : isSetupRequired ? ( ) : ( - + )}
diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index a570f9e37309f..7235e16a96569 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -6,11 +6,28 @@ import React from 'react'; +import { i18n } from '@kbn/i18n'; import { useTrackPageview } from '../../../hooks/use_track_metric'; +import { useLogAnalysisResults } from '../../../containers/logs/log_analysis'; +import { LoadingPage } from '../../../components/loading_page'; -export const AnalysisResultsContent = () => { +export const AnalysisResultsContent = ({ sourceId }: { sourceId: string }) => { useTrackPageview({ app: 'infra_logs', path: 'analysis_results' }); useTrackPageview({ app: 'infra_logs', path: 'analysis_results', delay: 15000 }); - return
Results
; + const { isLoading, logEntryRate, getLogEntryRate } = useLogAnalysisResults({ sourceId }); + + return ( + <> + {isLoading ? ( + + ) : ( +
Results
+ )} + + ); }; From 4547b128390e20a45e67eeea02764ef93cc607a9 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Mon, 12 Aug 2019 13:53:44 +0100 Subject: [PATCH 08/38] Initial timeRange URL state hookup --- .../containers/logs/log_analysis/index.ts | 1 + .../log_analysis_results_url_state.tsx | 36 +++++++++++++++++++ .../logs/analysis/page_results_content.tsx | 8 +++-- 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/index.ts index 294a78cc85206..418b1e7439633 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/index.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/index.ts @@ -7,3 +7,4 @@ export * from './log_analysis_capabilities'; export * from './log_analysis_jobs'; export * from './log_analysis_results'; +export * from './log_analysis_results_url_state'; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx new file mode 100644 index 0000000000000..0b0421a2ad436 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx @@ -0,0 +1,36 @@ +/* + * 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 moment from 'moment'; +import { useEffect } from 'react'; +import { useUrlState } from '../../../utils/use_url_state'; +import { timeRangeRT } from '../../../../common/http_api/shared/time_range'; + +const TIME_RANGE_URL_STATE_KEY = 'timeRange'; + +export const useLogAnalysisResultsUrlState = () => { + const [timeRange, setTimeRange] = useUrlState({ + defaultState: { + startTime: moment + .utc() + .subtract(2, 'weeks') + .valueOf(), + endTime: moment.utc().valueOf(), + }, + decodeUrlState: (value: unknown) => timeRangeRT.decode(value).getOrElse(undefined), + encodeUrlState: timeRangeRT.encode, + urlStateKey: TIME_RANGE_URL_STATE_KEY, + }); + + useEffect(() => { + setTimeRange(timeRange); + }, []); + + return { + timeRange, + setTimeRange, + }; +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index 7235e16a96569..6b22db77b97b9 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { useTrackPageview } from '../../../hooks/use_track_metric'; import { useLogAnalysisResults } from '../../../containers/logs/log_analysis'; +import { useLogAnalysisResultsUrlState } from '../../../containers/logs/log_analysis'; import { LoadingPage } from '../../../components/loading_page'; export const AnalysisResultsContent = ({ sourceId }: { sourceId: string }) => { @@ -16,7 +17,7 @@ export const AnalysisResultsContent = ({ sourceId }: { sourceId: string }) => { useTrackPageview({ app: 'infra_logs', path: 'analysis_results', delay: 15000 }); const { isLoading, logEntryRate, getLogEntryRate } = useLogAnalysisResults({ sourceId }); - + const { timeRange, setTimeRange } = useLogAnalysisResultsUrlState(); return ( <> {isLoading ? ( @@ -26,7 +27,10 @@ export const AnalysisResultsContent = ({ sourceId }: { sourceId: string }) => { })} /> ) : ( -
Results
+
+ {timeRange.startTime} + {timeRange.endTime} +
)} ); From 0854d52071d8edf41f218a3a07821de080a46ec3 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Mon, 12 Aug 2019 16:47:35 +0100 Subject: [PATCH 09/38] Hook up params to data fetching --- .../log_analysis/log_analysis_results.tsx | 22 ++++++-- .../logs/log_analysis/log_entry_rate.tsx | 22 +++++--- .../logs/analysis/page_results_content.tsx | 52 +++++++++++++++---- 3 files changed, 77 insertions(+), 19 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx index 5eeabb77e2f0d..d1c10a5391185 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx @@ -5,21 +5,37 @@ */ import createContainer from 'constate-latest'; -import { useMemo } from 'react'; +import { useMemo, useEffect } from 'react'; import { useLogEntryRate } from './log_entry_rate'; -export const useLogAnalysisResults = ({ sourceId }: { sourceId: string }) => { +export const useLogAnalysisResults = ({ + sourceId, + startTime, + endTime, + bucketDuration = 15 * 60 * 1000, +}: { + sourceId: string; + startTime: number; + endTime: number; + bucketDuration?: number; // TODO: Get from hook state +}) => { const { isLoading: isLoadingLogEntryRate, logEntryRate, getLogEntryRate } = useLogEntryRate({ sourceId, + startTime, + endTime, + bucketDuration, }); const isLoading = useMemo(() => isLoadingLogEntryRate, [isLoadingLogEntryRate]); + useEffect(() => { + getLogEntryRate(); + }, [sourceId, startTime, endTime, bucketDuration]); + return { isLoading, logEntryRate, - getLogEntryRate, }; }; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_entry_rate.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_entry_rate.tsx index aee953f6b0f3b..18e73c540c115 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_entry_rate.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_entry_rate.tsx @@ -18,7 +18,17 @@ import { useTrackedPromise } from '../../../utils/use_tracked_promise'; type LogEntryRateResults = GetLogEntryRateSuccessResponsePayload['data']; -export const useLogEntryRate = ({ sourceId }: { sourceId: string }) => { +export const useLogEntryRate = ({ + sourceId, + startTime, + endTime, + bucketDuration, +}: { + sourceId: string; + startTime: number; + endTime: number; + bucketDuration: number; +}) => { const [logEntryRate, setLogEntryRate] = useState(null); const [getLogEntryRateRequest, getLogEntryRate] = useTrackedPromise( @@ -31,12 +41,12 @@ export const useLogEntryRate = ({ sourceId }: { sourceId: string }) => { body: JSON.stringify( getLogEntryRateRequestPayloadRT.encode({ data: { - sourceId, // TODO: get from hook arguments + sourceId, timeRange: { - startTime: Date.now(), // TODO: get from hook arguments - endTime: Date.now() + 1000 * 60 * 60, // TODO: get from hook arguments + startTime, + endTime, }, - bucketDuration: 15 * 60 * 1000, // TODO: get from hook arguments + bucketDuration, }, }) ), @@ -50,7 +60,7 @@ export const useLogEntryRate = ({ sourceId }: { sourceId: string }) => { setLogEntryRate(data); }, }, - [sourceId] + [sourceId, startTime, endTime, bucketDuration] ); const isLoading = useMemo(() => getLogEntryRateRequest.state === 'pending', [ diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index 6b22db77b97b9..da97a2e26055f 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -4,32 +4,64 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { i18n } from '@kbn/i18n'; +import { EuiSuperDatePicker } from '@elastic/eui'; +import dateMath from '@elastic/datemath'; +import moment from 'moment'; import { useTrackPageview } from '../../../hooks/use_track_metric'; import { useLogAnalysisResults } from '../../../containers/logs/log_analysis'; import { useLogAnalysisResultsUrlState } from '../../../containers/logs/log_analysis'; import { LoadingPage } from '../../../components/loading_page'; +const DATE_PICKER_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; + +const getLoadingState = () => { + return ( + + ); +}; + export const AnalysisResultsContent = ({ sourceId }: { sourceId: string }) => { useTrackPageview({ app: 'infra_logs', path: 'analysis_results' }); useTrackPageview({ app: 'infra_logs', path: 'analysis_results', delay: 15000 }); - const { isLoading, logEntryRate, getLogEntryRate } = useLogAnalysisResults({ sourceId }); const { timeRange, setTimeRange } = useLogAnalysisResultsUrlState(); + const { isLoading, logEntryRate } = useLogAnalysisResults({ + sourceId, + startTime: timeRange.startTime, + endTime: timeRange.endTime, + }); + const handleTimeRangeChange = useCallback( + ({ start, end }: { start: string; end: string }) => { + const parsedStart = dateMath.parse(start); + const parsedEnd = dateMath.parse(end); + setTimeRange({ + startTime: + !parsedStart || !parsedStart.isValid() ? timeRange.startTime : parsedStart.valueOf(), + endTime: !parsedEnd || !parsedEnd.isValid() ? timeRange.endTime : parsedEnd.valueOf(), + }); + }, + [setTimeRange, timeRange] + ); + return ( <> - {isLoading ? ( - + {isLoading && !logEntryRate ? ( + <>{getLoadingState()} ) : (
- {timeRange.startTime} - {timeRange.endTime} + + {isLoading ? <>{getLoadingState()} :
Graph
}
)} From c0becdc674b95cf4149ebd0756719adc6e70e2b9 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Mon, 12 Aug 2019 18:35:30 +0100 Subject: [PATCH 10/38] Fleshing out EUI structure --- .../logs/analysis/page_results_content.tsx | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index da97a2e26055f..87f47c1eeb248 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -7,7 +7,19 @@ import React, { useCallback } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiSuperDatePicker } from '@elastic/eui'; +import { + EuiSuperDatePicker, + EuiFlexGroup, + EuiFlexItem, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiTitle, + EuiPanel, +} from '@elastic/eui'; import dateMath from '@elastic/datemath'; import moment from 'moment'; import { useTrackPageview } from '../../../hooks/use_track_metric'; @@ -55,14 +67,31 @@ export const AnalysisResultsContent = ({ sourceId }: { sourceId: string }) => { {isLoading && !logEntryRate ? ( <>{getLoadingState()} ) : ( -
- - {isLoading ? <>{getLoadingState()} :
Graph
} -
+ <> + + + + Analysed x of x + + + + + + + + + + + {isLoading ? <>{getLoadingState()} :
Graph
} +
+
+
+
+ )} ); From ac15cc04c1b088cfe694af16d72250c322f4a089 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Tue, 13 Aug 2019 10:30:07 +0100 Subject: [PATCH 11/38] Change tab syntax --- .../plugins/infra/public/pages/logs/index.tsx | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx index d4c410cd291eb..f2fcabe60e913 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx @@ -36,6 +36,27 @@ export const LogsPage = injectUICapabilities( const [sourceId] = useSourceId(); const source = useSource({ sourceId }); const { hasLogAnalysisCapabilites } = useLogAnalysisCapabilities(); + const streamTab = { + title: intl.formatMessage({ + id: 'xpack.infra.logs.index.streamTabTitle', + defaultMessage: 'Stream', + }), + path: `${match.path}/stream`, + }; + const analysisTab = { + title: intl.formatMessage({ + id: 'xpack.infra.logs.index.analysisTabTitle', + defaultMessage: 'Analysis', + }), + path: `${match.path}/analysis`, + }; + const settingsTab = { + title: intl.formatMessage({ + id: 'xpack.infra.logs.index.settingsTabTitle', + defaultMessage: 'Settings', + }), + path: `${match.path}/settings`, + }; return ( @@ -78,33 +99,11 @@ export const LogsPage = injectUICapabilities( <> From 7aff31c70870ea3ddd79d7ee135682b81c217c42 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Tue, 13 Aug 2019 10:33:54 +0100 Subject: [PATCH 12/38] i18n translate message prop --- .../plugins/infra/public/pages/logs/analysis/page.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx index 6a39b76f07f6d..3932e686ce146 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx @@ -6,6 +6,7 @@ import React, { useContext } from 'react'; import chrome from 'ui/chrome'; +import i18n from '@elastic/i18n'; import { ColumnarPage } from '../../../components/page'; import { LoadingPage } from '../../../components/loading_page'; import { AnalysisPageProviders } from './page_providers'; @@ -27,7 +28,11 @@ export const AnalysisPage = () => { {isLoadingSetupStatus ? ( - + ) : isSetupRequired ? ( ) : ( From 5cd5ba4666a46a0891e0fa80396a52537ce81d11 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Tue, 13 Aug 2019 10:50:25 +0100 Subject: [PATCH 13/38] Fix import --- x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx index 3932e686ce146..ef4a1ffe6735c 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page.tsx @@ -6,7 +6,7 @@ import React, { useContext } from 'react'; import chrome from 'ui/chrome'; -import i18n from '@elastic/i18n'; +import { i18n } from '@kbn/i18n'; import { ColumnarPage } from '../../../components/page'; import { LoadingPage } from '../../../components/loading_page'; import { AnalysisPageProviders } from './page_providers'; From f59b27fc2ac503c38bedcce88a2dc39744d22b7b Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Tue, 13 Aug 2019 11:05:58 +0100 Subject: [PATCH 14/38] Add structural visual components --- .../pages/logs/analysis/page_results_content.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index 87f47c1eeb248..a6b8534845efc 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -68,10 +68,14 @@ export const AnalysisResultsContent = ({ sourceId }: { sourceId: string }) => { <>{getLoadingState()} ) : ( <> - + - - Analysed x of x + + + + Analysed x of x + + { - + From 7b3e6a848ac22e94d86a72cbcc0997672544cd77 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Tue, 13 Aug 2019 11:32:00 +0100 Subject: [PATCH 15/38] Split section in to independent component --- .../infra/public/pages/logs/analysis/page_results_content.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index a6b8534845efc..3c4996d9333fb 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -26,6 +26,7 @@ import { useTrackPageview } from '../../../hooks/use_track_metric'; import { useLogAnalysisResults } from '../../../containers/logs/log_analysis'; import { useLogAnalysisResultsUrlState } from '../../../containers/logs/log_analysis'; import { LoadingPage } from '../../../components/loading_page'; +import { LogRateResults } from './sections/log_rate'; const DATE_PICKER_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; @@ -90,7 +91,7 @@ export const AnalysisResultsContent = ({ sourceId }: { sourceId: string }) => { - {isLoading ? <>{getLoadingState()} :
Graph
} +
From 1a31ae5798d7fe6da92ed0c3123fc78004755e1e Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Tue, 13 Aug 2019 13:03:08 +0100 Subject: [PATCH 16/38] Real loading and no data states --- .../logs/analysis/page_results_content.tsx | 3 - .../pages/logs/analysis/sections/log_rate.tsx | 70 +++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index 3c4996d9333fb..373ad468b1579 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -15,9 +15,6 @@ import { EuiPageBody, EuiPageContent, EuiPageContentBody, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiTitle, EuiPanel, } from '@elastic/eui'; import dateMath from '@elastic/datemath'; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx new file mode 100644 index 0000000000000..c891809ee1a2e --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx @@ -0,0 +1,70 @@ +/* + * 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, { useContext } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiTitle, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingChart, + EuiSpacer, + EuiEmptyPrompt, +} from '@elastic/eui'; +import { GetLogEntryRateSuccessResponsePayload } from '../../../../../common/http_api/log_analysis/results/log_entry_rate'; + +export const LogRateResults = ({ + isLoading, + results, +}: { + isLoading: boolean; + results: GetLogEntryRateSuccessResponsePayload['data'] | null; +}) => { + const title = i18n.translate('xpack.infra.logs.analysis.logRateSectionTitle', { + defaultMessage: 'Logs entries', + }); + + const loadingAriaLabel = i18n.translate( + 'xpack.infra.logs.analysis.logRateSectionLoadingAriaLabel', + { defaultMessage: 'Loading log rate results' } + ); + + return ( + <> + +

{title}

+
+ + {isLoading ? ( + + + + + + ) : !results || (results && results.histogramBuckets && !results.histogramBuckets.length) ? ( + + {i18n.translate('xpack.infra.logs.analysis.logRateSectionNoDataTitle', { + defaultMessage: 'There is no data to display.', + })} + + } + titleSize="m" + body={ +

+ {i18n.translate('xpack.infra.logs.analysis.logRateSectionNoDataBody', { + defaultMessage: 'Try adjusting your time range', + })} +

+ } + /> + ) : ( +
results
+ )} + + ); +}; From 163be1e687d13b2b4340180d718099e8c06eb771 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Tue, 13 Aug 2019 14:19:42 +0100 Subject: [PATCH 17/38] Add initial chart rendering (WIP) --- .../log_entry_rate.tsx | 43 +++++++++++++++++ .../pages/logs/analysis/sections/log_rate.tsx | 47 ++++++++++++++++++- 2 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate.tsx diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate.tsx new file mode 100644 index 0000000000000..3b77cd4361435 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate.tsx @@ -0,0 +1,43 @@ +/* + * 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 { useMemo } from 'react'; +import { GetLogEntryRateSuccessResponsePayload } from '../../../../../common/http_api/log_analysis'; + +export const useLogEntryRateGraphData = ({ + data, +}: { + data: GetLogEntryRateSuccessResponsePayload['data'] | null; +}) => { + const areaSeries = useMemo(() => { + if (!data || (data && data.histogramBuckets && !data.histogramBuckets.length)) { + return []; + } + return data.histogramBuckets.reduce((acc: any, bucket) => { + acc.push({ + x: bucket.startTime, + min: bucket.modelLowerBoundStats.min, + max: bucket.modelUpperBoundStats.max, + }); + return acc; + }, []); + }, [data]); + + const lineSeries = useMemo(() => { + if (!data || (data && data.histogramBuckets && !data.histogramBuckets.length)) { + return []; + } + return data.histogramBuckets.reduce((acc: any, bucket) => { + acc.push([bucket.startTime, bucket.logEntryRateStats.avg]); + return acc; + }, []); + }, [data]); + + return { + areaSeries, + lineSeries, + }; +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx index c891809ee1a2e..ca45d0aa5e20a 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; +import { Axis, Chart, getAxisId, getSpecId, AreaSeries, LineSeries } from '@elastic/charts'; import { EuiTitle, EuiFlexGroup, @@ -15,6 +16,7 @@ import { EuiEmptyPrompt, } from '@elastic/eui'; import { GetLogEntryRateSuccessResponsePayload } from '../../../../../common/http_api/log_analysis/results/log_entry_rate'; +import { useLogEntryRateGraphData } from '../../../../containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate'; export const LogRateResults = ({ isLoading, @@ -32,6 +34,10 @@ export const LogRateResults = ({ { defaultMessage: 'Loading log rate results' } ); + const { areaSeries, lineSeries } = useLogEntryRateGraphData({ + data: results, + }); + return ( <> @@ -63,7 +69,44 @@ export const LogRateResults = ({ } /> ) : ( -
results
+
+ + value} + /> + value} + /> + + + +
)} ); From 80ba0c377ab3d4d02ae7a5d2702643c88c7cdd09 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Wed, 14 Aug 2019 11:39:33 +0100 Subject: [PATCH 18/38] Tick formatting for x axis --- .../pages/logs/analysis/sections/log_rate.tsx | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx index ca45d0aa5e20a..8838bd1883b65 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx @@ -4,9 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; -import { Axis, Chart, getAxisId, getSpecId, AreaSeries, LineSeries } from '@elastic/charts'; +import { first, last } from 'lodash'; +import { + Axis, + Chart, + getAxisId, + getSpecId, + AreaSeries, + LineSeries, + niceTimeFormatter, +} from '@elastic/charts'; import { EuiTitle, EuiFlexGroup, @@ -38,6 +47,14 @@ export const LogRateResults = ({ data: results, }); + const dateFormatter = useMemo( + () => + lineSeries.length > 0 + ? niceTimeFormatter([first(lineSeries)[0], last(lineSeries)[0]]) + : (value: number) => `${value}`, + [lineSeries] + ); + return ( <> @@ -76,7 +93,7 @@ export const LogRateResults = ({ title="Time" position="bottom" showOverlappingTicks - tickFormat={value => value} + tickFormat={dateFormatter} /> Date: Wed, 14 Aug 2019 22:46:04 +0100 Subject: [PATCH 19/38] Add series styling, tickFormatter etc --- .../pages/logs/analysis/sections/log_rate.tsx | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx index 8838bd1883b65..1f71d4c8099c0 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx @@ -15,6 +15,7 @@ import { AreaSeries, LineSeries, niceTimeFormatter, + Settings, } from '@elastic/charts'; import { EuiTitle, @@ -27,6 +28,12 @@ import { import { GetLogEntryRateSuccessResponsePayload } from '../../../../../common/http_api/log_analysis/results/log_entry_rate'; import { useLogEntryRateGraphData } from '../../../../containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate'; +const getColorsMap = (color, specId) => { + const map = new Map(); + map.set({ colorValues: [], specId }, color); + return map; +}; + export const LogRateResults = ({ isLoading, results, @@ -55,6 +62,9 @@ export const LogRateResults = ({ [lineSeries] ); + const areaSpecId = getSpecId('modelBounds'); + const lineSpecId = getSpecId('averageValues'); + return ( <> @@ -99,10 +109,11 @@ export const LogRateResults = ({ id={getAxisId('values')} title="Log entries" position="left" - tickFormat={value => value} + tickFormat={value => Number(value).toFixed(0)} /> + {/* */} )} From 0fc4232a6eb2b9759680226b7f732a711c37791c Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Wed, 14 Aug 2019 23:40:53 +0100 Subject: [PATCH 20/38] Base bucketDuration on time range for a sensible number of data points (naieve version) --- .../pages/logs/analysis/page_results_content.tsx | 11 ++++++++++- .../public/pages/logs/analysis/sections/log_rate.tsx | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index 373ad468b1579..a12021f0d849f 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { @@ -42,10 +42,19 @@ export const AnalysisResultsContent = ({ sourceId }: { sourceId: string }) => { useTrackPageview({ app: 'infra_logs', path: 'analysis_results', delay: 15000 }); const { timeRange, setTimeRange } = useLogAnalysisResultsUrlState(); + const bucketDuration = useMemo(() => { + const msRange = timeRange.endTime - timeRange.startTime; + const bucketIntervalInMs = msRange / 200; + const bucketSpan = 900000; // TODO: Pull this from 'common' when setup hook PR is merged + const result = ((bucketIntervalInMs + bucketSpan / 2) / bucketSpan) * bucketSpan; + const roundedResult = parseInt(Number(result).toFixed(0), 10); + return roundedResult < bucketSpan ? bucketSpan : roundedResult; + }, [timeRange]); const { isLoading, logEntryRate } = useLogAnalysisResults({ sourceId, startTime: timeRange.startTime, endTime: timeRange.endTime, + bucketDuration, }); const handleTimeRangeChange = useCallback( ({ start, end }: { start: string; end: string }) => { diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx index 1f71d4c8099c0..f64e1c84cb978 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx @@ -42,7 +42,7 @@ export const LogRateResults = ({ results: GetLogEntryRateSuccessResponsePayload['data'] | null; }) => { const title = i18n.translate('xpack.infra.logs.analysis.logRateSectionTitle', { - defaultMessage: 'Logs entries', + defaultMessage: 'Log entries', }); const loadingAriaLabel = i18n.translate( From e24490384dd02783b0611dac2696e8b09e2a2feb Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Thu, 15 Aug 2019 00:37:40 +0100 Subject: [PATCH 21/38] Add auto refresh --- .../log_analysis_results_url_state.tsx | 17 +++++++++ .../logs/analysis/page_results_content.tsx | 36 +++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx index 0b0421a2ad436..18107a7fc57a6 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx @@ -6,10 +6,14 @@ import moment from 'moment'; import { useEffect } from 'react'; +import * as rt from 'io-ts'; import { useUrlState } from '../../../utils/use_url_state'; import { timeRangeRT } from '../../../../common/http_api/shared/time_range'; +const autoRefreshRT = rt.boolean; + const TIME_RANGE_URL_STATE_KEY = 'timeRange'; +const AUTOREFRESH_URL_STATE_KEY = 'autoRefresh'; export const useLogAnalysisResultsUrlState = () => { const [timeRange, setTimeRange] = useUrlState({ @@ -29,8 +33,21 @@ export const useLogAnalysisResultsUrlState = () => { setTimeRange(timeRange); }, []); + const [autoRefreshEnabled, setAutoRefresh] = useUrlState({ + defaultState: false, + decodeUrlState: (value: unknown) => autoRefreshRT.decode(value).getOrElse(undefined), + encodeUrlState: autoRefreshRT.encode, + urlStateKey: AUTOREFRESH_URL_STATE_KEY, + }); + + useEffect(() => { + setAutoRefresh(autoRefreshEnabled); + }, []); + return { timeRange, setTimeRange, + autoRefreshEnabled, + setAutoRefresh, }; }; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index a12021f0d849f..d60da19522eb2 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { @@ -20,6 +20,7 @@ import { import dateMath from '@elastic/datemath'; import moment from 'moment'; import { useTrackPageview } from '../../../hooks/use_track_metric'; +import { useInterval } from '../../../hooks/use_interval'; import { useLogAnalysisResults } from '../../../containers/logs/log_analysis'; import { useLogAnalysisResultsUrlState } from '../../../containers/logs/log_analysis'; import { LoadingPage } from '../../../components/loading_page'; @@ -41,7 +42,28 @@ export const AnalysisResultsContent = ({ sourceId }: { sourceId: string }) => { useTrackPageview({ app: 'infra_logs', path: 'analysis_results' }); useTrackPageview({ app: 'infra_logs', path: 'analysis_results', delay: 15000 }); - const { timeRange, setTimeRange } = useLogAnalysisResultsUrlState(); + const { + timeRange, + setTimeRange, + autoRefreshEnabled, + setAutoRefresh, + } = useLogAnalysisResultsUrlState(); + + const [refreshInterval, setRefreshInterval] = useState(300000); + + const setTimeRangeToNow = useCallback(() => { + const range = timeRange.endTime - timeRange.startTime; + const nowInMs = moment() + .utc() + .valueOf(); + setTimeRange({ + startTime: nowInMs - range, + endTime: nowInMs, + }); + }, [timeRange.startTime, timeRange.endTime, setTimeRange]); + + useInterval(setTimeRangeToNow, autoRefreshEnabled ? refreshInterval : null); + const bucketDuration = useMemo(() => { const msRange = timeRange.endTime - timeRange.startTime; const bucketIntervalInMs = msRange / 200; @@ -89,6 +111,16 @@ export const AnalysisResultsContent = ({ sourceId }: { sourceId: string }) => { start={moment.utc(timeRange.startTime).format(DATE_PICKER_FORMAT)} end={moment.utc(timeRange.endTime).format(DATE_PICKER_FORMAT)} onTimeChange={handleTimeRangeChange} + isPaused={!autoRefreshEnabled} + refreshInterval={refreshInterval} + onRefreshChange={({ isPaused, refreshInterval: interval }) => { + if (isPaused) { + setAutoRefresh(false); + } else { + setRefreshInterval(interval); + setAutoRefresh(true); + } + }} /> From 23e0cee0ab46178789ebe4f6bf939230e573572c Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Thu, 15 Aug 2019 15:08:18 +0100 Subject: [PATCH 22/38] Adjust bucketDuration algorithm --- .../logs/analysis/page_results_content.tsx | 2 +- .../pages/logs/analysis/sections/log_rate.tsx | 22 +++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index d60da19522eb2..17359189a0f88 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -68,7 +68,7 @@ export const AnalysisResultsContent = ({ sourceId }: { sourceId: string }) => { const msRange = timeRange.endTime - timeRange.startTime; const bucketIntervalInMs = msRange / 200; const bucketSpan = 900000; // TODO: Pull this from 'common' when setup hook PR is merged - const result = ((bucketIntervalInMs + bucketSpan / 2) / bucketSpan) * bucketSpan; + const result = bucketSpan * Math.round(bucketIntervalInMs / bucketSpan); const roundedResult = parseInt(Number(result).toFixed(0), 10); return roundedResult < bucketSpan ? bucketSpan : roundedResult; }, [timeRange]); diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx index f64e1c84cb978..2ad5db7508574 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo } from 'react'; +import React, { useMemo, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { first, last } from 'lodash'; +import moment from 'moment'; import { Axis, Chart, @@ -16,6 +17,8 @@ import { LineSeries, niceTimeFormatter, Settings, + SpecId, + TooltipValue, } from '@elastic/charts'; import { EuiTitle, @@ -27,8 +30,9 @@ import { } from '@elastic/eui'; import { GetLogEntryRateSuccessResponsePayload } from '../../../../../common/http_api/log_analysis/results/log_entry_rate'; import { useLogEntryRateGraphData } from '../../../../containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate'; +import { useKibanaUiSetting } from '../../../../utils/use_kibana_ui_setting'; -const getColorsMap = (color, specId) => { +const getColorsMap = (color: string, specId: SpecId) => { const map = new Map(); map.set({ colorValues: [], specId }, color); return map; @@ -64,6 +68,14 @@ export const LogRateResults = ({ const areaSpecId = getSpecId('modelBounds'); const lineSpecId = getSpecId('averageValues'); + const [dateFormat] = useKibanaUiSetting('dateFormat'); + + const tooltipProps = { + headerFormatter: useCallback( + (data: TooltipValue) => moment.utc(data.value).format(dateFormat || 'Y-MM-DD HH:mm:ss.SSS'), + [dateFormat] + ), + }; return ( <> @@ -144,11 +156,7 @@ export const LogRateResults = ({ }} customSeriesColors={getColorsMap('rgb(49, 133, 252)', lineSpecId)} /> - {/* */} + )} From 2fc8486aaca285003191d188c19a599d62baabae Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Thu, 15 Aug 2019 15:56:44 +0100 Subject: [PATCH 23/38] Add some dark theme support --- .../logs/analysis/page_results_content.tsx | 7 +++ .../pages/logs/analysis/sections/log_rate.tsx | 47 ++++++++++++++----- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index 17359189a0f88..15886c967fcaf 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -65,6 +65,13 @@ export const AnalysisResultsContent = ({ sourceId }: { sourceId: string }) => { useInterval(setTimeRangeToNow, autoRefreshEnabled ? refreshInterval : null); const bucketDuration = useMemo(() => { + // This function takes the current time range in ms, + // works out the bucket interval we'd need to always + // display 200 data points, and then takes that new + // value and works out the nearest multiple of + // 900000 (15 minutes) to it, so that we don't end up with + // jaggy bucket boundaries between the ML buckets and our + // aggregation buckets. const msRange = timeRange.endTime - timeRange.startTime; const bucketIntervalInMs = msRange / 200; const bucketSpan = 900000; // TODO: Pull this from 'common' when setup hook PR is merged diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx index 2ad5db7508574..263fcdadfc934 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx @@ -8,6 +8,7 @@ import React, { useMemo, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { first, last } from 'lodash'; import moment from 'moment'; +import chrome from 'ui/chrome'; import { Axis, Chart, @@ -19,6 +20,9 @@ import { Settings, SpecId, TooltipValue, + Theme, + LIGHT_THEME, + DARK_THEME, } from '@elastic/charts'; import { EuiTitle, @@ -38,6 +42,15 @@ const getColorsMap = (color: string, specId: SpecId) => { return map; }; +const isDarkMode = () => chrome.getUiSettingsClient().get('theme:darkMode'); + +const getChartTheme = (): Theme => { + return isDarkMode() ? DARK_THEME : LIGHT_THEME; +}; + +const areaSeriesColour = 'rgb(224, 237, 255)'; +const lineSeriesColour = 'rgb(49, 133, 252)'; + export const LogRateResults = ({ isLoading, results, @@ -134,11 +147,17 @@ export const LogRateResults = ({ data={areaSeries} yScaleToDataExtent curve={2} - areaSeriesStyle={{ - line: { stroke: 'rgb(224, 237, 255)' }, - area: { fill: 'rgb(224, 237, 255)', visible: true, opacity: 0.8 }, - }} - customSeriesColors={getColorsMap('rgb(224, 237, 255)', areaSpecId)} + areaSeriesStyle={ + !isDarkMode + ? { + line: { stroke: areaSeriesColour }, + area: { fill: areaSeriesColour, visible: true, opacity: 0.8 }, + } + : undefined + } + customSeriesColors={ + !isDarkMode ? getColorsMap(areaSeriesColour, areaSpecId) : undefined + } /> - + )} From 368d6fcd6c74fa17aa5be46dd57185b8669349d9 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Thu, 15 Aug 2019 16:00:01 +0100 Subject: [PATCH 24/38] Call the functions --- .../public/pages/logs/analysis/sections/log_rate.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx index 263fcdadfc934..4a6ad275f6d7a 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx @@ -148,7 +148,7 @@ export const LogRateResults = ({ yScaleToDataExtent curve={2} areaSeriesStyle={ - !isDarkMode + !isDarkMode() ? { line: { stroke: areaSeriesColour }, area: { fill: areaSeriesColour, visible: true, opacity: 0.8 }, @@ -156,7 +156,7 @@ export const LogRateResults = ({ : undefined } customSeriesColors={ - !isDarkMode ? getColorsMap(areaSeriesColour, areaSpecId) : undefined + !isDarkMode() ? getColorsMap(areaSeriesColour, areaSpecId) : undefined } /> From c197b0699d5e08bce10b8a35fba71296338794c6 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Thu, 15 Aug 2019 16:10:45 +0100 Subject: [PATCH 25/38] Extract chart helpers --- .../logs/analysis/chart_helpers/index.tsx | 20 +++++++++++++++++++ .../pages/logs/analysis/sections/log_rate.tsx | 18 +---------------- 2 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 x-pack/legacy/plugins/infra/public/pages/logs/analysis/chart_helpers/index.tsx diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/chart_helpers/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/chart_helpers/index.tsx new file mode 100644 index 0000000000000..df0eca449bb9f --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/chart_helpers/index.tsx @@ -0,0 +1,20 @@ +/* + * 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 chrome from 'ui/chrome'; +import { SpecId, Theme, LIGHT_THEME, DARK_THEME } from '@elastic/charts'; + +export const getColorsMap = (color: string, specId: SpecId) => { + const map = new Map(); + map.set({ colorValues: [], specId }, color); + return map; +}; + +export const isDarkMode = () => chrome.getUiSettingsClient().get('theme:darkMode'); + +export const getChartTheme = (): Theme => { + return isDarkMode() ? DARK_THEME : LIGHT_THEME; +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx index 4a6ad275f6d7a..9d08b80ebda73 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx @@ -8,7 +8,6 @@ import React, { useMemo, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { first, last } from 'lodash'; import moment from 'moment'; -import chrome from 'ui/chrome'; import { Axis, Chart, @@ -18,11 +17,7 @@ import { LineSeries, niceTimeFormatter, Settings, - SpecId, TooltipValue, - Theme, - LIGHT_THEME, - DARK_THEME, } from '@elastic/charts'; import { EuiTitle, @@ -35,18 +30,7 @@ import { import { GetLogEntryRateSuccessResponsePayload } from '../../../../../common/http_api/log_analysis/results/log_entry_rate'; import { useLogEntryRateGraphData } from '../../../../containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate'; import { useKibanaUiSetting } from '../../../../utils/use_kibana_ui_setting'; - -const getColorsMap = (color: string, specId: SpecId) => { - const map = new Map(); - map.set({ colorValues: [], specId }, color); - return map; -}; - -const isDarkMode = () => chrome.getUiSettingsClient().get('theme:darkMode'); - -const getChartTheme = (): Theme => { - return isDarkMode() ? DARK_THEME : LIGHT_THEME; -}; +import { getColorsMap, isDarkMode, getChartTheme } from '../chart_helpers'; const areaSeriesColour = 'rgb(224, 237, 255)'; const lineSeriesColour = 'rgb(49, 133, 252)'; From a6891f0788d6310cc0a4eaf4c8d92f5c11d87926 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Thu, 15 Aug 2019 16:26:28 +0100 Subject: [PATCH 26/38] Amend io-ts types --- .../logs/log_analysis/log_analysis_results_url_state.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx index 18107a7fc57a6..8ae644a497e19 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results_url_state.tsx @@ -10,7 +10,8 @@ import * as rt from 'io-ts'; import { useUrlState } from '../../../utils/use_url_state'; import { timeRangeRT } from '../../../../common/http_api/shared/time_range'; -const autoRefreshRT = rt.boolean; +const autoRefreshRT = rt.union([rt.boolean, rt.undefined]); +const urlTimeRangeRT = rt.union([timeRangeRT, rt.undefined]); const TIME_RANGE_URL_STATE_KEY = 'timeRange'; const AUTOREFRESH_URL_STATE_KEY = 'autoRefresh'; @@ -24,8 +25,8 @@ export const useLogAnalysisResultsUrlState = () => { .valueOf(), endTime: moment.utc().valueOf(), }, - decodeUrlState: (value: unknown) => timeRangeRT.decode(value).getOrElse(undefined), - encodeUrlState: timeRangeRT.encode, + decodeUrlState: (value: unknown) => urlTimeRangeRT.decode(value).getOrElse(undefined), + encodeUrlState: urlTimeRangeRT.encode, urlStateKey: TIME_RANGE_URL_STATE_KEY, }); From 48d237a6bd5f25b5432f4ca5f2c5218bee52f0b0 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Thu, 15 Aug 2019 16:30:50 +0100 Subject: [PATCH 27/38] i18n translations --- .../pages/logs/analysis/sections/log_rate.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx index 9d08b80ebda73..85bf4b496cba3 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx @@ -109,20 +109,26 @@ export const LogRateResults = ({ Number(value).toFixed(0)} /> Date: Thu, 15 Aug 2019 16:55:49 +0100 Subject: [PATCH 28/38] Add types for graph data --- .../log_analysis_graph_data/log_entry_rate.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate.tsx index 3b77cd4361435..7ece766dda5d9 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate.tsx @@ -7,12 +7,21 @@ import { useMemo } from 'react'; import { GetLogEntryRateSuccessResponsePayload } from '../../../../../common/http_api/log_analysis'; +interface LogRateAreaSeriesDataPoint { + x: number; + min: number | null; + max: number | null; +} +type LogRateAreaSeries = LogRateAreaSeriesDataPoint[]; +type LogRateLineSeriesDataPoint = [number, number | null]; +type LogRateLineSeries = LogRateLineSeriesDataPoint[]; + export const useLogEntryRateGraphData = ({ data, }: { data: GetLogEntryRateSuccessResponsePayload['data'] | null; }) => { - const areaSeries = useMemo(() => { + const areaSeries: LogRateAreaSeries = useMemo(() => { if (!data || (data && data.histogramBuckets && !data.histogramBuckets.length)) { return []; } @@ -26,7 +35,7 @@ export const useLogEntryRateGraphData = ({ }, []); }, [data]); - const lineSeries = useMemo(() => { + const lineSeries: LogRateLineSeries = useMemo(() => { if (!data || (data && data.histogramBuckets && !data.histogramBuckets.length)) { return []; } From a32fa03ed2a0a33f9d3d3c35a1205e9808bafb0f Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Fri, 16 Aug 2019 15:33:22 +0100 Subject: [PATCH 29/38] Allow ability to toggle model bounds --- .../pages/logs/analysis/sections/log_rate.tsx | 168 ++++++++++-------- 1 file changed, 96 insertions(+), 72 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx index 85bf4b496cba3..8bae4d444528c 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo, useCallback } from 'react'; +import React, { useMemo, useCallback, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { first, last } from 'lodash'; import moment from 'moment'; @@ -26,6 +26,7 @@ import { EuiLoadingChart, EuiSpacer, EuiEmptyPrompt, + EuiCheckbox, } from '@elastic/eui'; import { GetLogEntryRateSuccessResponsePayload } from '../../../../../common/http_api/log_analysis/results/log_entry_rate'; import { useLogEntryRateGraphData } from '../../../../containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate'; @@ -50,6 +51,10 @@ export const LogRateResults = ({ 'xpack.infra.logs.analysis.logRateSectionLoadingAriaLabel', { defaultMessage: 'Loading log rate results' } ); + const showModelBoundsLabel = i18n.translate( + 'xpack.infra.logs.analysis.logRateSectionModelBoundsCheckboxLabel', + { defaultMessage: 'Show model bounds' } + ); const { areaSeries, lineSeries } = useLogEntryRateGraphData({ data: results, @@ -74,6 +79,8 @@ export const LogRateResults = ({ ), }; + const [isShowingModelBounds, setIsShowingModelBounds] = useState(true); + return ( <> @@ -105,77 +112,94 @@ export const LogRateResults = ({ } /> ) : ( -
- - - Number(value).toFixed(0)} - /> - - - - -
+ <> + + + { + setIsShowingModelBounds(e.target.checked); + }} + /> + + +
+ + + Number(value).toFixed(0)} + /> + {isShowingModelBounds ? ( + + ) : null} + + + +
+ )} ); From 47876bfe32b1199d1b048567e7f8c60d20e54e11 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Fri, 16 Aug 2019 16:19:36 +0100 Subject: [PATCH 30/38] Add anomaly series --- .../log_entry_rate.tsx | 19 ++++++++++++++ .../pages/logs/analysis/sections/log_rate.tsx | 26 ++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate.tsx index 7ece766dda5d9..f54402a1a8707 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate.tsx @@ -15,6 +15,8 @@ interface LogRateAreaSeriesDataPoint { type LogRateAreaSeries = LogRateAreaSeriesDataPoint[]; type LogRateLineSeriesDataPoint = [number, number | null]; type LogRateLineSeries = LogRateLineSeriesDataPoint[]; +type LogRateAnomalySeriesDataPoint = [number, number]; +type LogRateAnomalySeries = LogRateAnomalySeriesDataPoint[]; export const useLogEntryRateGraphData = ({ data, @@ -45,8 +47,25 @@ export const useLogEntryRateGraphData = ({ }, []); }, [data]); + const anomalySeries: LogRateAnomalySeries = useMemo(() => { + if (!data || (data && data.histogramBuckets && !data.histogramBuckets.length)) { + return []; + } + return data.histogramBuckets.reduce((acc: any, bucket) => { + if (bucket.anomalies.length > 0) { + bucket.anomalies.forEach(anomaly => { + acc.push([anomaly.startTime, anomaly.actualLogEntryRate]); + }); + return acc; + } else { + return acc; + } + }, []); + }, [data]); + return { areaSeries, lineSeries, + anomalySeries, }; }; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx index 8bae4d444528c..8d32c50475fb6 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx @@ -56,7 +56,7 @@ export const LogRateResults = ({ { defaultMessage: 'Show model bounds' } ); - const { areaSeries, lineSeries } = useLogEntryRateGraphData({ + const { areaSeries, lineSeries, anomalySeries } = useLogEntryRateGraphData({ data: results, }); @@ -70,6 +70,8 @@ export const LogRateResults = ({ const areaSpecId = getSpecId('modelBounds'); const lineSpecId = getSpecId('averageValues'); + const anomalySpecId = getSpecId('anomalies'); + const [dateFormat] = useKibanaUiSetting('dateFormat'); const tooltipProps = { @@ -196,6 +198,28 @@ export const LogRateResults = ({ !isDarkMode() ? getColorsMap(lineSeriesColour, lineSpecId) : undefined } /> +
From 1f1aab63ab89f554dc0ace08c8abece134d4a189 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Fri, 16 Aug 2019 17:03:08 +0100 Subject: [PATCH 31/38] Format date correctly --- .../infra/public/pages/logs/analysis/sections/log_rate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx index 8d32c50475fb6..52d16028ca7e4 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx @@ -76,7 +76,7 @@ export const LogRateResults = ({ const tooltipProps = { headerFormatter: useCallback( - (data: TooltipValue) => moment.utc(data.value).format(dateFormat || 'Y-MM-DD HH:mm:ss.SSS'), + (data: TooltipValue) => moment(data.value).format(dateFormat || 'Y-MM-DD HH:mm:ss.SSS'), [dateFormat] ), }; From bb459ac5eec4155134d335f0171eba7c42bb1445 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Fri, 16 Aug 2019 17:47:16 +0100 Subject: [PATCH 32/38] Add anomalies detected text --- .../log_analysis/log_analysis_results.tsx | 2 +- .../logs/analysis/page_results_content.tsx | 53 ++++++++++++++++++- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx index d1c10a5391185..1ab7392b3a76d 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx @@ -18,7 +18,7 @@ export const useLogAnalysisResults = ({ sourceId: string; startTime: number; endTime: number; - bucketDuration?: number; // TODO: Get from hook state + bucketDuration?: number; }) => { const { isLoading: isLoadingLogEntryRate, logEntryRate, getLogEntryRate } = useLogEntryRate({ sourceId, diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index 15886c967fcaf..e3ddfbc74577e 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -7,6 +7,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { EuiSuperDatePicker, EuiFlexGroup, @@ -16,6 +17,7 @@ import { EuiPageContent, EuiPageContentBody, EuiPanel, + EuiBadge, } from '@elastic/eui'; import dateMath from '@elastic/datemath'; import moment from 'moment'; @@ -98,6 +100,29 @@ export const AnalysisResultsContent = ({ sourceId }: { sourceId: string }) => { [setTimeRange, timeRange] ); + const anomaliesDetected = useMemo(() => { + if (!logEntryRate) { + return null; + } else { + if (logEntryRate.histogramBuckets && logEntryRate.histogramBuckets.length) { + return logEntryRate.histogramBuckets.reduce((acc: any, bucket) => { + if (bucket.anomalies.length > 0) { + return ( + acc + + bucket.anomalies.reduce((anomalyAcc: any, anomaly) => { + return anomalyAcc + 1; + }, 0) + ); + } else { + return acc; + } + }, 0); + } else { + return null; + } + } + }, [logEntryRate]); + return ( <> {isLoading && !logEntryRate ? ( @@ -108,8 +133,32 @@ export const AnalysisResultsContent = ({ sourceId }: { sourceId: string }) => { - - Analysed x of x + + {anomaliesDetected !== null ? ( + <> + + {anomaliesDetected === 0 ? ( + 0, + }} + /> + ) : ( + {anomaliesDetected} + ), + }} + /> + )} + + + ) : null} From 752979237e4ed76632ba4219fa7faabe8e2c6e42 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Mon, 19 Aug 2019 11:10:22 +0100 Subject: [PATCH 33/38] Simplify syntax --- .../logs/analysis/page_results_content.tsx | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index e3ddfbc74577e..bfa215fc45a7b 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -137,25 +137,18 @@ export const AnalysisResultsContent = ({ sourceId }: { sourceId: string }) => { {anomaliesDetected !== null ? ( <> - {anomaliesDetected === 0 ? ( - 0, - }} - /> - ) : ( - 0 + ) : ( {anomaliesDetected} ), - }} - /> - )} + }} + /> ) : null} From 7429cffc33b13bdfb32ddf6a6817cd35f1315aeb Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Mon, 19 Aug 2019 16:44:31 +0100 Subject: [PATCH 34/38] Update title --- .../infra/public/pages/logs/analysis/sections/log_rate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx index 52d16028ca7e4..0ae61f4aea6e9 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx @@ -44,7 +44,7 @@ export const LogRateResults = ({ results: GetLogEntryRateSuccessResponsePayload['data'] | null; }) => { const title = i18n.translate('xpack.infra.logs.analysis.logRateSectionTitle', { - defaultMessage: 'Log entries', + defaultMessage: 'Log entry anomalies', }); const loadingAriaLabel = i18n.translate( From be69bd6c28caeb813dd2c878f8e85da67bce387d Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Mon, 19 Aug 2019 16:54:15 +0100 Subject: [PATCH 35/38] Render panel within a page --- .../logs/analysis/page_results_content.tsx | 92 ++++++++++--------- 1 file changed, 47 insertions(+), 45 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index bfa215fc45a7b..de8c4afda6b1d 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -129,51 +129,53 @@ export const AnalysisResultsContent = ({ sourceId }: { sourceId: string }) => { <>{getLoadingState()} ) : ( <> - - - - - - {anomaliesDetected !== null ? ( - <> - - 0 - ) : ( - {anomaliesDetected} - ), - }} - /> - - - ) : null} - - - - - { - if (isPaused) { - setAutoRefresh(false); - } else { - setRefreshInterval(interval); - setAutoRefresh(true); - } - }} - /> - - - + + + + + + + {anomaliesDetected !== null ? ( + <> + + 0 + ) : ( + {anomaliesDetected} + ), + }} + /> + + + ) : null} + + + + + { + if (isPaused) { + setAutoRefresh(false); + } else { + setRefreshInterval(interval); + setAutoRefresh(true); + } + }} + /> + + + + From a4c19d4f296829cfc1cfc5fc2c5f0779a6870148 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Tue, 20 Aug 2019 11:46:35 +0100 Subject: [PATCH 36/38] Add ability to switch between chart and table view --- .../pages/logs/analysis/sections/log_rate.tsx | 230 ------------------ .../logs/analysis/sections/log_rate/chart.tsx | 179 ++++++++++++++ .../logs/analysis/sections/log_rate/index.tsx | 92 +++++++ .../logs/analysis/sections/log_rate/table.tsx | 96 ++++++++ .../sections/log_rate/view_switcher.tsx | 52 ++++ 5 files changed, 419 insertions(+), 230 deletions(-) delete mode 100644 x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx create mode 100644 x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/chart.tsx create mode 100644 x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx create mode 100644 x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/table.tsx create mode 100644 x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/view_switcher.tsx diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx deleted file mode 100644 index 0ae61f4aea6e9..0000000000000 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate.tsx +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useMemo, useCallback, useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import { first, last } from 'lodash'; -import moment from 'moment'; -import { - Axis, - Chart, - getAxisId, - getSpecId, - AreaSeries, - LineSeries, - niceTimeFormatter, - Settings, - TooltipValue, -} from '@elastic/charts'; -import { - EuiTitle, - EuiFlexGroup, - EuiFlexItem, - EuiLoadingChart, - EuiSpacer, - EuiEmptyPrompt, - EuiCheckbox, -} from '@elastic/eui'; -import { GetLogEntryRateSuccessResponsePayload } from '../../../../../common/http_api/log_analysis/results/log_entry_rate'; -import { useLogEntryRateGraphData } from '../../../../containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate'; -import { useKibanaUiSetting } from '../../../../utils/use_kibana_ui_setting'; -import { getColorsMap, isDarkMode, getChartTheme } from '../chart_helpers'; - -const areaSeriesColour = 'rgb(224, 237, 255)'; -const lineSeriesColour = 'rgb(49, 133, 252)'; - -export const LogRateResults = ({ - isLoading, - results, -}: { - isLoading: boolean; - results: GetLogEntryRateSuccessResponsePayload['data'] | null; -}) => { - const title = i18n.translate('xpack.infra.logs.analysis.logRateSectionTitle', { - defaultMessage: 'Log entry anomalies', - }); - - const loadingAriaLabel = i18n.translate( - 'xpack.infra.logs.analysis.logRateSectionLoadingAriaLabel', - { defaultMessage: 'Loading log rate results' } - ); - const showModelBoundsLabel = i18n.translate( - 'xpack.infra.logs.analysis.logRateSectionModelBoundsCheckboxLabel', - { defaultMessage: 'Show model bounds' } - ); - - const { areaSeries, lineSeries, anomalySeries } = useLogEntryRateGraphData({ - data: results, - }); - - const dateFormatter = useMemo( - () => - lineSeries.length > 0 - ? niceTimeFormatter([first(lineSeries)[0], last(lineSeries)[0]]) - : (value: number) => `${value}`, - [lineSeries] - ); - - const areaSpecId = getSpecId('modelBounds'); - const lineSpecId = getSpecId('averageValues'); - const anomalySpecId = getSpecId('anomalies'); - - const [dateFormat] = useKibanaUiSetting('dateFormat'); - - const tooltipProps = { - headerFormatter: useCallback( - (data: TooltipValue) => moment(data.value).format(dateFormat || 'Y-MM-DD HH:mm:ss.SSS'), - [dateFormat] - ), - }; - - const [isShowingModelBounds, setIsShowingModelBounds] = useState(true); - - return ( - <> - -

{title}

-
- - {isLoading ? ( - - - - - - ) : !results || (results && results.histogramBuckets && !results.histogramBuckets.length) ? ( - - {i18n.translate('xpack.infra.logs.analysis.logRateSectionNoDataTitle', { - defaultMessage: 'There is no data to display.', - })} - - } - titleSize="m" - body={ -

- {i18n.translate('xpack.infra.logs.analysis.logRateSectionNoDataBody', { - defaultMessage: 'Try adjusting your time range', - })} -

- } - /> - ) : ( - <> - - - { - setIsShowingModelBounds(e.target.checked); - }} - /> - - -
- - - Number(value).toFixed(0)} - /> - {isShowingModelBounds ? ( - - ) : null} - - - - -
- - )} - - ); -}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/chart.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/chart.tsx new file mode 100644 index 0000000000000..e12a4d8513af2 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/chart.tsx @@ -0,0 +1,179 @@ +/* + * 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, { useMemo, useCallback, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { first, last } from 'lodash'; +import moment from 'moment'; +import { + Axis, + Chart, + getAxisId, + getSpecId, + AreaSeries, + LineSeries, + niceTimeFormatter, + Settings, + TooltipValue, +} from '@elastic/charts'; +import { EuiFlexGroup, EuiFlexItem, EuiCheckbox } from '@elastic/eui'; +import { getColorsMap, isDarkMode, getChartTheme } from '../../chart_helpers'; +import { GetLogEntryRateSuccessResponsePayload } from '../../../../../../common/http_api/log_analysis/results/log_entry_rate'; +import { useLogEntryRateGraphData } from '../../../../../containers/logs/log_analysis/log_analysis_graph_data/log_entry_rate'; +import { useKibanaUiSetting } from '../../../../../utils/use_kibana_ui_setting'; + +const areaSeriesColour = 'rgb(224, 237, 255)'; +const lineSeriesColour = 'rgb(49, 133, 252)'; + +interface Props { + data: GetLogEntryRateSuccessResponsePayload['data'] | null; +} + +export const ChartView = ({ data }: Props) => { + const showModelBoundsLabel = i18n.translate( + 'xpack.infra.logs.analysis.logRateSectionModelBoundsCheckboxLabel', + { defaultMessage: 'Show model bounds' } + ); + + const { areaSeries, lineSeries, anomalySeries } = useLogEntryRateGraphData({ data }); + + const dateFormatter = useMemo( + () => + lineSeries.length > 0 + ? niceTimeFormatter([first(lineSeries)[0], last(lineSeries)[0]]) + : (value: number) => `${value}`, + [lineSeries] + ); + + const areaSpecId = getSpecId('modelBounds'); + const lineSpecId = getSpecId('averageValues'); + const anomalySpecId = getSpecId('anomalies'); + + const [dateFormat] = useKibanaUiSetting('dateFormat'); + + const tooltipProps = { + headerFormatter: useCallback( + (tooltipData: TooltipValue) => + moment(tooltipData.value).format(dateFormat || 'Y-MM-DD HH:mm:ss.SSS'), + [dateFormat] + ), + }; + + const [isShowingModelBounds, setIsShowingModelBounds] = useState(true); + + return ( + <> + + + { + setIsShowingModelBounds(e.target.checked); + }} + /> + + +
+ + + Number(value).toFixed(0)} + /> + {isShowingModelBounds ? ( + + ) : null} + + + + +
+ + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx new file mode 100644 index 0000000000000..e98050f7e83e9 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiTitle, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingChart, + EuiSpacer, + EuiEmptyPrompt, +} from '@elastic/eui'; +import { GetLogEntryRateSuccessResponsePayload } from '../../../../../../common/http_api/log_analysis/results/log_entry_rate'; +import { ViewSwitcher } from './view_switcher'; +import { ChartView } from './chart'; +import { TableView } from './table'; + +export enum ViewMode { + chart = 'chart', + table = 'table', +} + +export const LogRateResults = ({ + isLoading, + results, +}: { + isLoading: boolean; + results: GetLogEntryRateSuccessResponsePayload['data'] | null; +}) => { + const title = i18n.translate('xpack.infra.logs.analysis.logRateSectionTitle', { + defaultMessage: 'Log entry anomalies', + }); + + const loadingAriaLabel = i18n.translate( + 'xpack.infra.logs.analysis.logRateSectionLoadingAriaLabel', + { defaultMessage: 'Loading log rate results' } + ); + + const [viewMode, setViewMode] = useState(ViewMode.chart); + + return ( + <> + +

{title}

+
+ + {isLoading ? ( + + + + + + ) : !results || (results && results.histogramBuckets && !results.histogramBuckets.length) ? ( + + {i18n.translate('xpack.infra.logs.analysis.logRateSectionNoDataTitle', { + defaultMessage: 'There is no data to display.', + })} + + } + titleSize="m" + body={ +

+ {i18n.translate('xpack.infra.logs.analysis.logRateSectionNoDataBody', { + defaultMessage: 'Try adjusting your time range', + })} +

+ } + /> + ) : ( + <> + + + setViewMode(id as ViewMode)} /> + + + + {viewMode === ViewMode.chart ? ( + + ) : ( + + )} + + )} + + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/table.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/table.tsx new file mode 100644 index 0000000000000..5397436eb6652 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/table.tsx @@ -0,0 +1,96 @@ +/* + * 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, { useMemo, useCallback, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import moment from 'moment'; +import { EuiInMemoryTable } from '@elastic/eui'; +import { GetLogEntryRateSuccessResponsePayload } from '../../../../../../common/http_api/log_analysis/results/log_entry_rate'; +import { useKibanaUiSetting } from '../../../../../utils/use_kibana_ui_setting'; + +interface Props { + data: GetLogEntryRateSuccessResponsePayload['data']; +} + +const startTimeLabel = i18n.translate( + 'xpack.infra.logs.analysis.logRateSection.table.startTimeLabel', + { defaultMessage: 'Start time' } +); +const anomalyScoreLabel = i18n.translate( + 'xpack.infra.logs.analysis.logRateSection.table.anomalyScoreLabel', + { defaultMessage: 'Anomaly score' } +); +const actualLogEntryRateLabel = i18n.translate( + 'xpack.infra.logs.analysis.logRateSection.table.actualLogEntryRateLabel', + { defaultMessage: 'Actual rate' } +); +const typicalLogEntryRateLabel = i18n.translate( + 'xpack.infra.logs.analysis.logRateSection.table.typicalLogEntryRateLabel', + { defaultMessage: 'Typical rate' } +); + +export const TableView = ({ data }: Props) => { + const [dateFormat] = useKibanaUiSetting('dateFormat'); + + const formattedAnomalies = useMemo(() => { + return data.histogramBuckets.reduce((acc: any, bucket) => { + if (bucket.anomalies.length > 0) { + bucket.anomalies.forEach(anomaly => { + const formattedAnomaly = { + startTime: moment(anomaly.startTime).format(dateFormat || 'Y-MM-DD HH:mm:ss.SSS'), + anomalyScore: Number(anomaly.anomalyScore).toFixed(3), + typicalLogEntryRate: Number(anomaly.typicalLogEntryRate).toFixed(3), + actualLogEntryRate: Number(anomaly.actualLogEntryRate).toFixed(3), + }; + acc.push(formattedAnomaly); + }); + return acc; + } else { + return acc; + } + }, []); + }, [data]); + + const columns = [ + { + field: 'startTime', + name: startTimeLabel, + sortable: true, + 'data-test-subj': 'startTimeCell', + }, + { + field: 'anomalyScore', + name: anomalyScoreLabel, + sortable: true, + 'data-test-subj': 'anomalyScoreCell', + }, + { + field: 'actualLogEntryRate', + name: actualLogEntryRateLabel, + sortable: true, + 'data-test-subj': 'actualLogEntryRateCell', + }, + { + field: 'typicalLogEntryRate', + name: typicalLogEntryRateLabel, + sortable: true, + 'data-test-subj': 'typicalLogEntryRateCell', + }, + ]; + + const initialSorting = { + sort: { + field: 'anomalyScore', + direction: 'desc', + }, + }; + + return ( + <> + + + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/view_switcher.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/view_switcher.tsx new file mode 100644 index 0000000000000..fad866822bb93 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/view_switcher.tsx @@ -0,0 +1,52 @@ +/* + * 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 { EuiButtonGroup, EuiButtonGroupProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { ViewMode } from './index'; + +interface Props { + selectedView: string; + onChange: EuiButtonGroupProps['onChange']; +} + +const chartLabel = i18n.translate( + 'xpack.infra.logs.analysis.logRateSection.viewSwitcher.chartLabel', + { defaultMessage: 'Chart view' } +); +const tableLabel = i18n.translate( + 'xpack.infra.logs.analysis.logRateSection.viewSwitcher.tableLabel', + { defaultMessage: 'Table view' } +); +const legendLabel = i18n.translate( + 'xpack.infra.logs.analysis.logRateSection.viewSwitcher.legendLabel', + { defaultMessage: 'Switch between chart and table view' } +); + +export const ViewSwitcher = ({ selectedView, onChange }: Props) => { + const buttons = [ + { + id: ViewMode.chart, + label: chartLabel, + iconType: 'apps', + }, + { + id: ViewMode.table, + label: tableLabel, + iconType: 'editorUnorderedList', + }, + ]; + return ( + + ); +}; From 8c9667561d719324f218fe4411d6422d3eafb957 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Wed, 21 Aug 2019 12:17:35 +0100 Subject: [PATCH 37/38] Fix typechecking errors --- .../public/pages/logs/analysis/sections/log_rate/table.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/table.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/table.tsx index 5397436eb6652..19bfaacf5d5df 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/table.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/table.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo, useCallback, useState } from 'react'; +import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import moment from 'moment'; import { EuiInMemoryTable } from '@elastic/eui'; From 2245d827398fb76988f11410d9d23f2102df1784 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Thu, 22 Aug 2019 11:24:58 +0100 Subject: [PATCH 38/38] Add a Beta badge to the analysis tab --- .../components/navigation/routed_tabs.tsx | 2 +- .../plugins/infra/public/pages/logs/index.tsx | 44 +++++++++++++++++-- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/components/navigation/routed_tabs.tsx b/x-pack/legacy/plugins/infra/public/components/navigation/routed_tabs.tsx index 329d1a2455a6f..a059bbd286112 100644 --- a/x-pack/legacy/plugins/infra/public/components/navigation/routed_tabs.tsx +++ b/x-pack/legacy/plugins/infra/public/components/navigation/routed_tabs.tsx @@ -10,7 +10,7 @@ import { Route } from 'react-router-dom'; import euiStyled from '../../../../../common/eui_styled_components'; interface TabConfiguration { - title: string; + title: string | React.ReactNode; path: string; } diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx index f2fcabe60e913..a20b1dc9641c9 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx @@ -6,6 +6,7 @@ import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { EuiBetaBadge } from '@elastic/eui'; import React from 'react'; import { Route, RouteComponentProps, Switch } from 'react-router-dom'; import { UICapabilities } from 'ui/capabilities'; @@ -43,11 +44,46 @@ export const LogsPage = injectUICapabilities( }), path: `${match.path}/stream`, }; + const analysisBetaBadgeTitle = i18n.translate('xpack.infra.logs.index.analysisBetaBadgeTitle', { + defaultMessage: 'Analysis', + }); + const analysisBetaBadgeLabel = i18n.translate('xpack.infra.logs.index.analysisBetaBadgeLabel', { + defaultMessage: 'Beta', + }); + const analysisBetaBadgeTooltipContent = i18n.translate( + 'xpack.infra.logs.index.analysisBetaBadgeTooltipContent', + { + defaultMessage: + 'This feature is under active development. Extra functionality is coming, and some functionality may change.', + } + ); + const analysisBetaBadge = ( + + ); const analysisTab = { - title: intl.formatMessage({ - id: 'xpack.infra.logs.index.analysisTabTitle', - defaultMessage: 'Analysis', - }), + title: ( + <> + + {intl.formatMessage({ + id: 'xpack.infra.logs.index.analysisTabTitle', + defaultMessage: 'Analysis', + })} + + {analysisBetaBadge} + + ), path: `${match.path}/analysis`, }; const settingsTab = {