From 4cb42b4c7523a10e7c8aabce473c5efee4a5fec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Tue, 25 Aug 2020 10:26:25 +0200 Subject: [PATCH 01/17] Scaffold `` component --- .../infra/public/components/log_stream/index.tsx | 11 +++++++++++ x-pack/plugins/infra/public/index.ts | 3 +++ 2 files changed, 14 insertions(+) create mode 100644 x-pack/plugins/infra/public/components/log_stream/index.tsx diff --git a/x-pack/plugins/infra/public/components/log_stream/index.tsx b/x-pack/plugins/infra/public/components/log_stream/index.tsx new file mode 100644 index 00000000000000..1e521dcf56575f --- /dev/null +++ b/x-pack/plugins/infra/public/components/log_stream/index.tsx @@ -0,0 +1,11 @@ +/* + * 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'; + +export const LogStream: React.FC = () => { + return It works!; +}; diff --git a/x-pack/plugins/infra/public/index.ts b/x-pack/plugins/infra/public/index.ts index cadf9a48378665..73194cae00768e 100644 --- a/x-pack/plugins/infra/public/index.ts +++ b/x-pack/plugins/infra/public/index.ts @@ -26,3 +26,6 @@ export { FORMATTERS } from '../common/formatters'; export { InfraFormatterType } from './lib/lib'; export type InfraAppId = 'logs' | 'metrics'; + +// Shared components +export { LogStream } from './components/log_stream'; From 9e1b7b278e99a7ad0046900f82c20c640598d576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Tue, 25 Aug 2020 10:54:06 +0200 Subject: [PATCH 02/17] Scaffold `useLogStream()` hook --- .../public/components/log_stream/index.tsx | 14 +++++++-- .../containers/logs/log_stream/index.ts | 30 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/infra/public/containers/logs/log_stream/index.ts diff --git a/x-pack/plugins/infra/public/components/log_stream/index.tsx b/x-pack/plugins/infra/public/components/log_stream/index.tsx index 1e521dcf56575f..682dc58e431a61 100644 --- a/x-pack/plugins/infra/public/components/log_stream/index.tsx +++ b/x-pack/plugins/infra/public/components/log_stream/index.tsx @@ -4,8 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useEffect } from 'react'; +import { useLogStream } from '../../containers/logs/log_stream'; + +interface LogStreamProps { + startTimestamp: number; + endTimestamp: number; +} + +export const LogStream: React.FC = ({ startTimestamp, endTimestamp }) => { + const { entries, fetchEntries } = useLogStream({ startTimestamp, endTimestamp }); + + useEffect(() => fetchEntries(), [fetchEntries]); -export const LogStream: React.FC = () => { return It works!; }; diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts b/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts new file mode 100644 index 00000000000000..7bf906a67ebfa5 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts @@ -0,0 +1,30 @@ +/* + * 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 { useState, useCallback } from 'react'; + +interface LogStreamProps { + startTimestamp: number; + endTimestamp: number; +} + +interface LogStreamState { + entries: any[]; + fetchEntries: () => void; +} + +export function useLogStream({ startTimestamp, endTimestamp }: LogStreamProps): LogStreamState { + const [entries, setEntries] = useState([]); + + const fetchEntries = useCallback(() => { + setEntries([]); + }, []); + + return { + entries, + fetchEntries, + }; +} From ccfb7aa833eef2b484fc818565488185c4f226c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Tue, 25 Aug 2020 17:58:41 +0200 Subject: [PATCH 03/17] Render empty view --- .../public/components/log_stream/index.tsx | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/components/log_stream/index.tsx b/x-pack/plugins/infra/public/components/log_stream/index.tsx index 682dc58e431a61..acb9ef0cfa0ab3 100644 --- a/x-pack/plugins/infra/public/components/log_stream/index.tsx +++ b/x-pack/plugins/infra/public/components/log_stream/index.tsx @@ -5,7 +5,10 @@ */ import React, { useEffect } from 'react'; +import { noop } from 'lodash'; +import { EuiPanel } from '@elastic/eui'; import { useLogStream } from '../../containers/logs/log_stream'; +import { ScrollableLogTextStreamView } from '../logging/log_text_stream'; interface LogStreamProps { startTimestamp: number; @@ -17,5 +20,31 @@ export const LogStream: React.FC = ({ startTimestamp, endTimesta useEffect(() => fetchEntries(), [fetchEntries]); - return It works!; + return ( + + + + ); }; From 11210e1b782be7a1129d8cc747f720a0361059ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Tue, 25 Aug 2020 20:44:41 +0200 Subject: [PATCH 04/17] Add `seamless` option for seamless rendering --- .../public/components/log_stream/index.tsx | 119 +++++++++++++----- .../containers/logs/log_stream/index.ts | 47 ++++++- 2 files changed, 129 insertions(+), 37 deletions(-) diff --git a/x-pack/plugins/infra/public/components/log_stream/index.tsx b/x-pack/plugins/infra/public/components/log_stream/index.tsx index acb9ef0cfa0ab3..1ce13e0bc52127 100644 --- a/x-pack/plugins/infra/public/components/log_stream/index.tsx +++ b/x-pack/plugins/infra/public/components/log_stream/index.tsx @@ -4,47 +4,102 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect } from 'react'; +import React, { useMemo } from 'react'; import { noop } from 'lodash'; +import { useMount } from 'react-use'; import { EuiPanel } from '@elastic/eui'; + +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { useLogSource } from '../../containers/logs/log_source'; import { useLogStream } from '../../containers/logs/log_stream'; + import { ScrollableLogTextStreamView } from '../logging/log_text_stream'; interface LogStreamProps { + sourceId?: string; startTimestamp: number; endTimestamp: number; + seamless?: boolean; } -export const LogStream: React.FC = ({ startTimestamp, endTimestamp }) => { - const { entries, fetchEntries } = useLogStream({ startTimestamp, endTimestamp }); - - useEffect(() => fetchEntries(), [fetchEntries]); - - return ( - - - +export const LogStream: React.FC = ({ + sourceId = 'default', + startTimestamp, + endTimestamp, + seamless = false, +}) => { + // source boilerplate + const { services } = useKibana(); + const { + sourceConfiguration, + loadSourceConfiguration, + isLoadingSourceConfiguration, + } = useLogSource({ + sourceId, + fetch: services.http.fetch, + }); + + // Internal state + const { loadingState, entries, fetchEntries } = useLogStream({ + sourceId, + startTimestamp, + endTimestamp, + }); + + // Derived state + const isReloading = + isLoadingSourceConfiguration || loadingState === 'uninitialized' || loadingState === 'loading'; + + const columnConfigurations = useMemo(() => { + return sourceConfiguration ? sourceConfiguration.configuration.logColumns : []; + }, [sourceConfiguration]); + + const streamItems = useMemo( + () => + entries.map((entry) => ({ + kind: 'logEntry' as const, + logEntry: entry, + highlights: [], + })), + [entries] ); + + // Component lifetime + useMount(() => { + loadSourceConfiguration(); + fetchEntries(); + }); + + const streamView = ( + + ); + + // Rendering + if (seamless) { + return streamView; + } else { + return {streamView}; + } }; diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts b/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts index 7bf906a67ebfa5..69749fe28bfec9 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts @@ -4,9 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useState, useCallback } from 'react'; +import { useState, useMemo } from 'react'; +import { fetchLogEntries } from '../log_entries/api/fetch_log_entries'; +import { useTrackedPromise } from '../../../utils/use_tracked_promise'; interface LogStreamProps { + sourceId: string; startTimestamp: number; endTimestamp: number; } @@ -14,17 +17,51 @@ interface LogStreamProps { interface LogStreamState { entries: any[]; fetchEntries: () => void; + loadingState: 'uninitialized' | 'loading' | 'success' | 'error'; } -export function useLogStream({ startTimestamp, endTimestamp }: LogStreamProps): LogStreamState { +export function useLogStream({ + sourceId, + startTimestamp, + endTimestamp, +}: LogStreamProps): LogStreamState { const [entries, setEntries] = useState([]); + const [entriesPromise, fetchEntries] = useTrackedPromise( + { + cancelPreviousOn: 'creation', + createPromise: () => { + setEntries([]); + return fetchLogEntries({ sourceId, startTimestamp, endTimestamp }); + }, + onResolve: ({ data }) => { + setEntries(data.entries); + }, + }, + [sourceId, startTimestamp, endTimestamp] + ); - const fetchEntries = useCallback(() => { - setEntries([]); - }, []); + const loadingState = useMemo(() => convertPromiseStateToLoadingState(entriesPromise.state), [ + entriesPromise.state, + ]); return { entries, fetchEntries, + loadingState, }; } + +function convertPromiseStateToLoadingState( + state: 'uninitialized' | 'pending' | 'resolved' | 'rejected' +): LogStreamState['loadingState'] { + switch (state) { + case 'uninitialized': + return 'uninitialized'; + case 'pending': + return 'loading'; + case 'resolved': + return 'success'; + case 'rejected': + return 'error'; + } +} From 49026f785d9c8fba8dec2b7f7562c939cab46072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Wed, 26 Aug 2020 12:22:16 +0200 Subject: [PATCH 05/17] Allow for complex queries and UI manipulation --- .../public/components/log_stream/index.tsx | 14 ++++++++-- .../containers/logs/log_stream/index.ts | 28 +++++++++++++++++-- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/infra/public/components/log_stream/index.tsx b/x-pack/plugins/infra/public/components/log_stream/index.tsx index 1ce13e0bc52127..eb7fc1552b4e21 100644 --- a/x-pack/plugins/infra/public/components/log_stream/index.tsx +++ b/x-pack/plugins/infra/public/components/log_stream/index.tsx @@ -9,6 +9,8 @@ import { noop } from 'lodash'; import { useMount } from 'react-use'; import { EuiPanel } from '@elastic/eui'; +import { LogEntriesCursor } from '../../../common/http_api'; + import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; import { useLogSource } from '../../containers/logs/log_source'; import { useLogStream } from '../../containers/logs/log_stream'; @@ -20,6 +22,9 @@ interface LogStreamProps { startTimestamp: number; endTimestamp: number; seamless?: boolean; + query?: string; + center?: LogEntriesCursor; + highlight?: string; } export const LogStream: React.FC = ({ @@ -27,6 +32,9 @@ export const LogStream: React.FC = ({ startTimestamp, endTimestamp, seamless = false, + query, + center, + highlight, }) => { // source boilerplate const { services } = useKibana(); @@ -44,6 +52,8 @@ export const LogStream: React.FC = ({ sourceId, startTimestamp, endTimestamp, + query, + center, }); // Derived state @@ -72,7 +82,7 @@ export const LogStream: React.FC = ({ const streamView = ( = ({ reportVisibleInterval={noop} loadNewerItems={noop} reloadItems={fetchEntries} - highlightedItem={null} + highlightedItem={highlight ?? null} currentHighlightKey={null} startDateExpression={''} endDateExpression={''} diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts b/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts index 69749fe28bfec9..b414408512db29 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts @@ -5,17 +5,21 @@ */ import { useState, useMemo } from 'react'; +import { esKuery } from '../../../../../../../src/plugins/data/public'; import { fetchLogEntries } from '../log_entries/api/fetch_log_entries'; import { useTrackedPromise } from '../../../utils/use_tracked_promise'; +import { LogEntry, LogEntriesCursor } from '../../../../common/http_api'; interface LogStreamProps { sourceId: string; startTimestamp: number; endTimestamp: number; + query?: string; + center?: LogEntriesCursor; } interface LogStreamState { - entries: any[]; + entries: LogEntry[]; fetchEntries: () => void; loadingState: 'uninitialized' | 'loading' | 'success' | 'error'; } @@ -24,20 +28,38 @@ export function useLogStream({ sourceId, startTimestamp, endTimestamp, + query, + center, }: LogStreamProps): LogStreamState { const [entries, setEntries] = useState([]); + + const parsedQuery = useMemo(() => { + return query + ? JSON.stringify(esKuery.toElasticsearchQuery(esKuery.fromKueryExpression(query))) + : null; + }, [query]); + + // Callbacks const [entriesPromise, fetchEntries] = useTrackedPromise( { cancelPreviousOn: 'creation', createPromise: () => { setEntries([]); - return fetchLogEntries({ sourceId, startTimestamp, endTimestamp }); + const fetchPosition = center ? { center } : { before: 'last' }; + + return fetchLogEntries({ + sourceId, + startTimestamp, + endTimestamp, + query: parsedQuery, + ...fetchPosition, + }); }, onResolve: ({ data }) => { setEntries(data.entries); }, }, - [sourceId, startTimestamp, endTimestamp] + [sourceId, startTimestamp, endTimestamp, query] ); const loadingState = useMemo(() => convertPromiseStateToLoadingState(entriesPromise.state), [ From de15012d10c41b82b1abc82700ecd5281ffcb064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Tue, 1 Sep 2020 12:54:44 +0200 Subject: [PATCH 06/17] Tweak component layout The `` component misbehaves when its parent component doesn't have a fixed width. This is caused by an `AutoSizer` trying to determine the necessary width, and the `ScrollableLogTextStreamView` trying to hide the scrollbar by making the scrollable view 20 pixels wider than its container. To fix this we control the hiding behaviour through a prop, and disable it in our component. --- .../public/components/log_stream/index.tsx | 72 ++++++++++--------- .../scrollable_log_text_stream_view.tsx | 4 +- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/x-pack/plugins/infra/public/components/log_stream/index.tsx b/x-pack/plugins/infra/public/components/log_stream/index.tsx index eb7fc1552b4e21..0895a2f47d17a6 100644 --- a/x-pack/plugins/infra/public/components/log_stream/index.tsx +++ b/x-pack/plugins/infra/public/components/log_stream/index.tsx @@ -7,7 +7,7 @@ import React, { useMemo } from 'react'; import { noop } from 'lodash'; import { useMount } from 'react-use'; -import { EuiPanel } from '@elastic/eui'; +import { euiStyled } from '../../../../observability/public'; import { LogEntriesCursor } from '../../../common/http_api'; @@ -21,20 +21,20 @@ interface LogStreamProps { sourceId?: string; startTimestamp: number; endTimestamp: number; - seamless?: boolean; query?: string; center?: LogEntriesCursor; highlight?: string; + height?: string | number; } export const LogStream: React.FC = ({ sourceId = 'default', startTimestamp, endTimestamp, - seamless = false, query, center, highlight, + height = '400px', }) => { // source boilerplate const { services } = useKibana(); @@ -80,36 +80,40 @@ export const LogStream: React.FC = ({ fetchEntries(); }); - const streamView = ( - - ); + const parsedHeight = typeof height === 'number' ? `${height}px` : height; - // Rendering - if (seamless) { - return streamView; - } else { - return {streamView}; - } + return ( + + + + ); }; + +const LogStreamContent = euiStyled.div<{ height: string }>` + display: flex; + background-color: ${(props) => props.theme.eui.euiColorEmptyShade}; + height: ${(props) => props.height}; +`; diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx index fc0c50b9044dc1..ae375392b6b93e 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx @@ -60,6 +60,7 @@ interface ScrollableLogTextStreamViewProps { endDateExpression: string; updateDateRange: (range: { startDateExpression?: string; endDateExpression?: string }) => void; startLiveStreaming: () => void; + hideScrollbar?: boolean; } interface ScrollableLogTextStreamViewState { @@ -146,6 +147,7 @@ export class ScrollableLogTextStreamView extends React.PureComponent< setFlyoutVisibility, setContextEntry, } = this.props; + const hideScrollbar = this.props.hideScrollbar ?? true; const { targetId, items, isScrollLocked } = this.state; const hasItems = items.length > 0; @@ -196,7 +198,7 @@ export class ScrollableLogTextStreamView extends React.PureComponent< width={width} onVisibleChildrenChange={this.handleVisibleChildrenChange} target={targetId} - hideScrollbar={true} + hideScrollbar={hideScrollbar} data-test-subj={'logStream'} isLocked={isScrollLocked} entriesCount={items.length} From 46aaadb2952f8a5eaa72cfa9b78a12322eae71db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Wed, 26 Aug 2020 15:43:39 +0200 Subject: [PATCH 07/17] Use `` in "View in context" component --- .../view_log_in_context.ts | 50 ++----------- .../logs/stream/page_view_log_in_context.tsx | 74 +++++++------------ 2 files changed, 33 insertions(+), 91 deletions(-) diff --git a/x-pack/plugins/infra/public/containers/logs/view_log_in_context/view_log_in_context.ts b/x-pack/plugins/infra/public/containers/logs/view_log_in_context/view_log_in_context.ts index bc719cbd694e4f..61e1ea353880a0 100644 --- a/x-pack/plugins/infra/public/containers/logs/view_log_in_context/view_log_in_context.ts +++ b/x-pack/plugins/infra/public/containers/logs/view_log_in_context/view_log_in_context.ts @@ -3,24 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { useState, useEffect, useCallback } from 'react'; +import { useState } from 'react'; import createContainer from 'constate'; import { LogEntry } from '../../../../common/http_api'; -import { fetchLogEntries } from '../log_entries/api/fetch_log_entries'; -import { esKuery } from '../../../../../../../src/plugins/data/public'; - -function getQueryFromLogEntry(entry: LogEntry) { - const expression = Object.entries(entry.context).reduce((kuery, [key, value]) => { - const currentExpression = `${key} : "${value}"`; - if (kuery.length > 0) { - return `${kuery} AND ${currentExpression}`; - } else { - return currentExpression; - } - }, ''); - - return JSON.stringify(esKuery.toElasticsearchQuery(esKuery.fromKueryExpression(expression))); -} interface ViewLogInContextProps { sourceId: string; @@ -28,9 +13,7 @@ interface ViewLogInContextProps { endTimestamp: number; } -export interface ViewLogInContextState { - entries: LogEntry[]; - isLoading: boolean; +export interface ViewLogInContextState extends ViewLogInContextProps { contextEntry?: LogEntry; } @@ -42,37 +25,14 @@ export const useViewLogInContext = ( props: ViewLogInContextProps ): [ViewLogInContextState, ViewLogInContextCallbacks] => { const [contextEntry, setContextEntry] = useState(); - const [entries, setEntries] = useState([]); - const [isLoading, setIsLoading] = useState(false); const { startTimestamp, endTimestamp, sourceId } = props; - const maybeFetchLogs = useCallback(async () => { - if (contextEntry) { - setIsLoading(true); - const { data } = await fetchLogEntries({ - sourceId, - startTimestamp, - endTimestamp, - center: contextEntry.cursor, - query: getQueryFromLogEntry(contextEntry), - }); - setEntries(data.entries); - setIsLoading(false); - } else { - setEntries([]); - setIsLoading(false); - } - }, [contextEntry, startTimestamp, endTimestamp, sourceId]); - - useEffect(() => { - maybeFetchLogs(); - }, [maybeFetchLogs]); - return [ { + startTimestamp, + endTimestamp, + sourceId, contextEntry, - entries, - isLoading, }, { setContextEntry, diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx index 3ef32c920e2938..6a8b8adbeddc14 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx @@ -15,40 +15,36 @@ import { EuiSpacer, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { noop } from 'lodash'; +import { isEmpty } from 'lodash'; import React, { useCallback, useContext, useMemo } from 'react'; import { LogEntry } from '../../../../common/http_api'; -import { ScrollableLogTextStreamView } from '../../../components/logging/log_text_stream'; -import { useLogSourceContext } from '../../../containers/logs/log_source'; -import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration'; import { ViewLogInContext } from '../../../containers/logs/view_log_in_context'; import { useViewportDimensions } from '../../../utils/use_viewport_dimensions'; import { euiStyled } from '../../../../../observability/public'; +import { LogStream } from '../../../components/log_stream'; const MODAL_MARGIN = 25; export const PageViewLogInContext: React.FC = () => { - const { sourceConfiguration } = useLogSourceContext(); - const { textScale, textWrap } = useContext(LogViewConfiguration.Context); - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - const columnConfigurations = useMemo(() => sourceConfiguration?.configuration.logColumns ?? [], [ - sourceConfiguration, - ]); - const [{ contextEntry, entries, isLoading }, { setContextEntry }] = useContext( - ViewLogInContext.Context - ); + const [ + { contextEntry, startTimestamp, endTimestamp, sourceId }, + { setContextEntry }, + ] = useContext(ViewLogInContext.Context); const closeModal = useCallback(() => setContextEntry(undefined), [setContextEntry]); const { width: vw, height: vh } = useViewportDimensions(); - const streamItems = useMemo( - () => - entries.map((entry) => ({ - kind: 'logEntry' as const, - logEntry: entry, - highlights: [], - })), - [entries] - ); + const contextQuery = useMemo(() => { + if (contextEntry && !isEmpty(contextEntry.context)) { + return Object.entries(contextEntry.context).reduce((kuery, [key, value]) => { + const currentExpression = `${key} : "${value}"`; + if (kuery.length > 0) { + return `${kuery} AND ${currentExpression}`; + } else { + return currentExpression; + } + }, ''); + } + }, [contextEntry]); if (!contextEntry) { return null; @@ -64,31 +60,17 @@ export const PageViewLogInContext: React.FC = () => { wrap={false} style={{ height: '100%' }} > - + - - + + From 018d3f7f3c9dc5fb9ab9b31bc01aa6881fde0ca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Mon, 31 Aug 2020 10:48:32 +0200 Subject: [PATCH 08/17] Add README.md --- .../public/components/log_stream/README.md | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 x-pack/plugins/infra/public/components/log_stream/README.md diff --git a/x-pack/plugins/infra/public/components/log_stream/README.md b/x-pack/plugins/infra/public/components/log_stream/README.md new file mode 100644 index 00000000000000..3bb66cebde053d --- /dev/null +++ b/x-pack/plugins/infra/public/components/log_stream/README.md @@ -0,0 +1,69 @@ +# Embeddable `` component + +The purpose of this component is to allow you, the developer, to have your very own Log Stream in your plugin. + +The plugin is exposed through `infra/public`. Since Kibana uses relative paths is up to you to find how to import it (sorry). + +```tsx +import { LogStream } from '../../../../../../infra/public'; +``` + +## Prerequisites + +To use the component, there are several things you need to ensure in your plugin: + +- In your `kibana.json` plugin, you need to either add `"requiredBundles": ["infra"]` or `"requiredPlugins": ["infra"]`. +- Your plugin needs to be wrapped with a [`kibana-react` provider](https://github.com/elastic/kibana/blob/b2d0aa7b7fae1c89c8f9e8854ae73e71be64e765/src/plugins/kibana_react/README.md#L45). + +## Usage + +The simplest way to use the component is with a date range, passed with the `startTimestamp` and `endTimestamp` props. + +```tsx +const endTimestamp = Date.now(); +const startTimestamp = endTimestamp - 15 * 60 * 1000; // 15 minutes + +; +``` + +This will show a list of log entries between the time range, in ascending order (oldest first), but with the scroll position all the way to the bottom (showing the newest entries) + +### Filtering data + +You might want to show specific data for the purpose of your plugin. Maybe you want to show log lines from a specific host, or for an APM trace. You can pass a KQL expression via the `query` prop. + +```tsx + +``` + +### Modifying rendering + +By default the component will initially load at the bottom of the list, showing the newest entries. You can change what log line is shown in the center via the `center` prop. The prop takes a [`LogEntriesCursor`](https://github.com/elastic/kibana/blob/0a6c748cc837c016901f69ff05d81395aa2d41c8/x-pack/plugins/infra/common/http_api/log_entries/common.ts#L9-L13). + +```tsx + +``` + +If you want to highlight a specific log line, you can do so by passing its ID in the `highlight` prop. + +```tsx + +``` + +### Source configuration + +The infra plugin has the concept of "source configuration" to store settings for the logs UI. The component will use the source configuration to determine which indices to query or what columns to show. + +By default the `` uses the `"default"` source confiuration, but if your plugin uses a different one you can specify it via the `sourceId` prop. + +```tsx + +``` From 690130bdcfb1a23117121130a9c75359067e11f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Tue, 1 Sep 2020 17:00:27 +0200 Subject: [PATCH 09/17] POC: Integrate logs into APM --- x-pack/plugins/apm/kibana.json | 3 ++- .../WaterfallWithSummmary/TransactionTabs.tsx | 20 +++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json index 6cc3bb2a2c7e15..3a6c634ba5c61d 100644 --- a/x-pack/plugins/apm/kibana.json +++ b/x-pack/plugins/apm/kibana.json @@ -33,6 +33,7 @@ "kibanaReact", "kibanaUtils", "observability", - "home" + "home", + "infra" ] } diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx index 48413d6207ee3e..2151a1db668de2 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx @@ -15,6 +15,7 @@ import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; import { TransactionMetadata } from '../../../shared/MetadataTable/TransactionMetadata'; import { WaterfallContainer } from './WaterfallContainer'; import { IWaterfall } from './WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; +import { LogStream } from '../../../../../../infra/public'; const timelineTab = { key: 'timeline', @@ -30,6 +31,13 @@ const metadataTab = { }), }; +const logsTab = { + key: 'logs', + label: i18n.translate('xpack.apm.propertiesTable.tabs.logsLabel', { + defaultMessage: 'Logs', + }), +}; + interface Props { location: Location; transaction: Transaction; @@ -46,9 +54,9 @@ export function TransactionTabs({ exceedsMax, }: Props) { const history = useHistory(); - const tabs = [timelineTab, metadataTab]; + const tabs = [timelineTab, metadataTab, logsTab]; const currentTab = - urlParams.detailTab === metadataTab.key ? metadataTab : timelineTab; + tabs.find((tab) => tab.key === urlParams.detailTab) ?? timelineTab; return ( @@ -83,6 +91,14 @@ export function TransactionTabs({ waterfall={waterfall} exceedsMax={exceedsMax} /> + ) : currentTab.key === logsTab.key ? ( +
+ +
) : ( )} From 6a29684fc1f69806fb8424380d9f7e7f0cc79aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Tue, 1 Sep 2020 21:00:04 +0200 Subject: [PATCH 10/17] Fix types --- x-pack/plugins/infra/public/components/log_stream/index.tsx | 2 +- .../infra/public/pages/logs/stream/page_view_log_in_context.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/public/components/log_stream/index.tsx b/x-pack/plugins/infra/public/components/log_stream/index.tsx index 0895a2f47d17a6..845808fe9e86c6 100644 --- a/x-pack/plugins/infra/public/components/log_stream/index.tsx +++ b/x-pack/plugins/infra/public/components/log_stream/index.tsx @@ -44,7 +44,7 @@ export const LogStream: React.FC = ({ isLoadingSourceConfiguration, } = useLogSource({ sourceId, - fetch: services.http.fetch, + fetch: services?.http?.fetch!, }); // Internal state diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx index 6a8b8adbeddc14..30ccc0779e5719 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx @@ -12,7 +12,6 @@ import { EuiText, EuiTextColor, EuiToolTip, - EuiSpacer, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { isEmpty } from 'lodash'; From 1ed3dda36cc8ff677bfb1167631ac84bd4c3ebb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Wed, 2 Sep 2020 10:52:24 +0200 Subject: [PATCH 11/17] Crash and burn if core services are not available --- .../infra/public/components/log_stream/index.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/components/log_stream/index.tsx b/x-pack/plugins/infra/public/components/log_stream/index.tsx index 845808fe9e86c6..89e70b549e08cb 100644 --- a/x-pack/plugins/infra/public/components/log_stream/index.tsx +++ b/x-pack/plugins/infra/public/components/log_stream/index.tsx @@ -38,13 +38,23 @@ export const LogStream: React.FC = ({ }) => { // source boilerplate const { services } = useKibana(); + if (!services?.http?.fetch) { + throw new Error( + ` cannot access kibana core services. + +Ensure the component is mounted within kibana-react's hierarchy. +Read more at https://github.com/elastic/kibana/blob/master/src/plugins/kibana_react/README.md" +` + ); + } + const { sourceConfiguration, loadSourceConfiguration, isLoadingSourceConfiguration, } = useLogSource({ sourceId, - fetch: services?.http?.fetch!, + fetch: services.http.fetch, }); // Internal state From 77d53a802b4ead320ce1dcdb2bb4a61654f3682d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Wed, 2 Sep 2020 16:59:11 +0200 Subject: [PATCH 12/17] Add error handling recommendations --- x-pack/plugins/infra/public/components/log_stream/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/components/log_stream/README.md b/x-pack/plugins/infra/public/components/log_stream/README.md index 3bb66cebde053d..d3bd66ee97cb8e 100644 --- a/x-pack/plugins/infra/public/components/log_stream/README.md +++ b/x-pack/plugins/infra/public/components/log_stream/README.md @@ -13,7 +13,7 @@ import { LogStream } from '../../../../../../infra/public'; To use the component, there are several things you need to ensure in your plugin: - In your `kibana.json` plugin, you need to either add `"requiredBundles": ["infra"]` or `"requiredPlugins": ["infra"]`. -- Your plugin needs to be wrapped with a [`kibana-react` provider](https://github.com/elastic/kibana/blob/b2d0aa7b7fae1c89c8f9e8854ae73e71be64e765/src/plugins/kibana_react/README.md#L45). +- The component needs to be mounted inside the hiearchy of a [`kibana-react` provider](https://github.com/elastic/kibana/blob/b2d0aa7b7fae1c89c8f9e8854ae73e71be64e765/src/plugins/kibana_react/README.md#L45). ## Usage @@ -67,3 +67,7 @@ By default the `` uses the `"default"` source confiuration, but if ```tsx ``` + +### Considerations + +As mentioned in the prerequisites, the component relies on `kibana-react` to access kibana's core services. If this is not the case the component will throw an exception when rendering. We advise to use an `` in your component hierarchy to catch this error if necessary. From 9ce68b9305208c397a9e108b2ab862efb40c05b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Fri, 4 Sep 2020 15:00:15 +0200 Subject: [PATCH 13/17] Make the public export lazy --- x-pack/plugins/infra/public/components/log_stream/index.tsx | 4 ++++ x-pack/plugins/infra/public/index.ts | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/public/components/log_stream/index.tsx b/x-pack/plugins/infra/public/components/log_stream/index.tsx index 89e70b549e08cb..6bb4c9f3b8b794 100644 --- a/x-pack/plugins/infra/public/components/log_stream/index.tsx +++ b/x-pack/plugins/infra/public/components/log_stream/index.tsx @@ -127,3 +127,7 @@ const LogStreamContent = euiStyled.div<{ height: string }>` background-color: ${(props) => props.theme.eui.euiColorEmptyShade}; height: ${(props) => props.height}; `; + +// Allow for lazy loading +// eslint-disable-next-line import/no-default-export +export default LogStream; diff --git a/x-pack/plugins/infra/public/index.ts b/x-pack/plugins/infra/public/index.ts index 73194cae00768e..7605d043c2b74c 100644 --- a/x-pack/plugins/infra/public/index.ts +++ b/x-pack/plugins/infra/public/index.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import { lazy } from 'react'; import { PluginInitializer, PluginInitializerContext } from 'kibana/public'; import { Plugin } from './plugin'; import { @@ -28,4 +28,4 @@ export { InfraFormatterType } from './lib/lib'; export type InfraAppId = 'logs' | 'metrics'; // Shared components -export { LogStream } from './components/log_stream'; +export const LogStream = lazy(() => import('./components/log_stream')); From 4a63279d28a15aceede59863a6f9e3579a0cfab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Fri, 4 Sep 2020 15:54:59 +0200 Subject: [PATCH 14/17] Fix height for view log in context --- .../infra/public/pages/logs/stream/page_view_log_in_context.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx index 30ccc0779e5719..4ac3d15a822226 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx @@ -70,6 +70,7 @@ export const PageViewLogInContext: React.FC = () => { query={contextQuery} center={contextEntry.cursor} highlight={contextEntry.id} + height="100%" />
From 09b62e6faae13b68c32d56d58028817e31c4cefe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Fri, 4 Sep 2020 16:45:57 +0200 Subject: [PATCH 15/17] fixup! Make the public export lazy --- .../infra/public/components/log_stream/index.tsx | 2 +- .../log_stream/lazy_log_stream_wrapper.tsx | 16 ++++++++++++++++ x-pack/plugins/infra/public/index.ts | 3 +-- 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/infra/public/components/log_stream/lazy_log_stream_wrapper.tsx diff --git a/x-pack/plugins/infra/public/components/log_stream/index.tsx b/x-pack/plugins/infra/public/components/log_stream/index.tsx index 6bb4c9f3b8b794..f9bfbf95647980 100644 --- a/x-pack/plugins/infra/public/components/log_stream/index.tsx +++ b/x-pack/plugins/infra/public/components/log_stream/index.tsx @@ -17,7 +17,7 @@ import { useLogStream } from '../../containers/logs/log_stream'; import { ScrollableLogTextStreamView } from '../logging/log_text_stream'; -interface LogStreamProps { +export interface LogStreamProps { sourceId?: string; startTimestamp: number; endTimestamp: number; diff --git a/x-pack/plugins/infra/public/components/log_stream/lazy_log_stream_wrapper.tsx b/x-pack/plugins/infra/public/components/log_stream/lazy_log_stream_wrapper.tsx new file mode 100644 index 00000000000000..65433aab15716f --- /dev/null +++ b/x-pack/plugins/infra/public/components/log_stream/lazy_log_stream_wrapper.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 type { LogStreamProps } from './'; + +const LazyLogStream = React.lazy(() => import('./')); + +export const LazyLogStreamWrapper: React.FC = (props) => ( + }> + + +); diff --git a/x-pack/plugins/infra/public/index.ts b/x-pack/plugins/infra/public/index.ts index 7605d043c2b74c..873e3b1ce05837 100644 --- a/x-pack/plugins/infra/public/index.ts +++ b/x-pack/plugins/infra/public/index.ts @@ -3,7 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { lazy } from 'react'; import { PluginInitializer, PluginInitializerContext } from 'kibana/public'; import { Plugin } from './plugin'; import { @@ -28,4 +27,4 @@ export { InfraFormatterType } from './lib/lib'; export type InfraAppId = 'logs' | 'metrics'; // Shared components -export const LogStream = lazy(() => import('./components/log_stream')); +export { LazyLogStreamWrapper as LogStream } from './components/log_stream/lazy_log_stream_wrapper'; From 054781c18ef597a1dd87dd85d9a107668d40505d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Mon, 7 Sep 2020 11:13:28 +0200 Subject: [PATCH 16/17] Update docs --- x-pack/plugins/infra/public/components/log_stream/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/components/log_stream/README.md b/x-pack/plugins/infra/public/components/log_stream/README.md index d3bd66ee97cb8e..59b3edfab736cd 100644 --- a/x-pack/plugins/infra/public/components/log_stream/README.md +++ b/x-pack/plugins/infra/public/components/log_stream/README.md @@ -12,7 +12,7 @@ import { LogStream } from '../../../../../../infra/public'; To use the component, there are several things you need to ensure in your plugin: -- In your `kibana.json` plugin, you need to either add `"requiredBundles": ["infra"]` or `"requiredPlugins": ["infra"]`. +- In your plugin's `kibana.json` plugin, add `"infra"` to `requiredPlugins`. - The component needs to be mounted inside the hiearchy of a [`kibana-react` provider](https://github.com/elastic/kibana/blob/b2d0aa7b7fae1c89c8f9e8854ae73e71be64e765/src/plugins/kibana_react/README.md#L45). ## Usage From a53ae34bfd5b0b49715c2307fe6e486fce7324c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Mon, 7 Sep 2020 14:21:12 +0200 Subject: [PATCH 17/17] Revert "POC: Integrate logs into APM" This reverts commit 690130bdcfb1a23117121130a9c75359067e11f1. --- x-pack/plugins/apm/kibana.json | 3 +-- .../WaterfallWithSummmary/TransactionTabs.tsx | 20 ++----------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json index 3a6c634ba5c61d..6cc3bb2a2c7e15 100644 --- a/x-pack/plugins/apm/kibana.json +++ b/x-pack/plugins/apm/kibana.json @@ -33,7 +33,6 @@ "kibanaReact", "kibanaUtils", "observability", - "home", - "infra" + "home" ] } diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx index 2151a1db668de2..48413d6207ee3e 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx @@ -15,7 +15,6 @@ import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; import { TransactionMetadata } from '../../../shared/MetadataTable/TransactionMetadata'; import { WaterfallContainer } from './WaterfallContainer'; import { IWaterfall } from './WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; -import { LogStream } from '../../../../../../infra/public'; const timelineTab = { key: 'timeline', @@ -31,13 +30,6 @@ const metadataTab = { }), }; -const logsTab = { - key: 'logs', - label: i18n.translate('xpack.apm.propertiesTable.tabs.logsLabel', { - defaultMessage: 'Logs', - }), -}; - interface Props { location: Location; transaction: Transaction; @@ -54,9 +46,9 @@ export function TransactionTabs({ exceedsMax, }: Props) { const history = useHistory(); - const tabs = [timelineTab, metadataTab, logsTab]; + const tabs = [timelineTab, metadataTab]; const currentTab = - tabs.find((tab) => tab.key === urlParams.detailTab) ?? timelineTab; + urlParams.detailTab === metadataTab.key ? metadataTab : timelineTab; return ( @@ -91,14 +83,6 @@ export function TransactionTabs({ waterfall={waterfall} exceedsMax={exceedsMax} /> - ) : currentTab.key === logsTab.key ? ( -
- -
) : ( )}