From f0c4393015e0ccac3e88d8ba551b4dddfb2f26be Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Thu, 5 Sep 2019 13:30:01 -0500 Subject: [PATCH 1/5] [Logs UI] Stop live streaming on scroll or minimap click --- .../plugins/infra/common/time/time_key.ts | 1 + .../scrollable_log_text_stream_view.tsx | 1 - .../containers/logs/with_stream_items.ts | 12 +++++-- .../store/local/log_position/actions.ts | 3 +- .../public/store/local/log_position/epic.ts | 36 ++++++++++++++++--- .../store/local/log_position/reducer.ts | 18 ++++++++++ .../store/local/log_position/selectors.ts | 2 ++ 7 files changed, 64 insertions(+), 9 deletions(-) diff --git a/x-pack/legacy/plugins/infra/common/time/time_key.ts b/x-pack/legacy/plugins/infra/common/time/time_key.ts index dca64dacfcb21..117cd38314de0 100644 --- a/x-pack/legacy/plugins/infra/common/time/time_key.ts +++ b/x-pack/legacy/plugins/infra/common/time/time_key.ts @@ -11,6 +11,7 @@ export interface TimeKey { time: number; tiebreaker: number; gid?: string; + fromAutoReload?: boolean; } export interface UniqueTimeKey extends TimeKey { diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx index 40a5d8c30d4bb..614b5699fd39c 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx @@ -113,7 +113,6 @@ class ScrollableLogTextStreamViewClass extends React.PureComponent< } = this.props; const { targetId } = this.state; const hasItems = items.length > 0; - return ( {isReloading && !hasItems ? ( diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts b/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts index 6a79d7b8e4ac9..12117d88f8283 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts @@ -21,6 +21,7 @@ export const withStreamItems = connect( isAutoReloading: logPositionSelectors.selectIsAutoReloading(state), isReloading: logEntriesSelectors.selectIsReloadingEntries(state), isLoadingMore: logEntriesSelectors.selectIsLoadingMoreEntries(state), + wasAutoReloadJustAborted: logPositionSelectors.selectAutoReloadJustAborted(state), hasMoreBeforeStart: logEntriesSelectors.selectHasMoreBeforeStart(state), hasMoreAfterEnd: logEntriesSelectors.selectHasMoreAfterEnd(state), lastLoadedTime: logEntriesSelectors.selectEntriesLastLoadedTime(state), @@ -54,12 +55,19 @@ export const WithStreamItems = withStreamItems( const { currentHighlightKey, logEntryHighlightsById } = useContext(LogHighlightsState.Context); const items = useMemo( () => - props.isReloading && !props.isAutoReloading + props.isReloading && !props.isAutoReloading && !props.wasAutoReloadJustAborted ? [] : props.entries.map(logEntry => createLogEntryStreamItem(logEntry, logEntryHighlightsById[logEntry.gid] || []) ), - [props.isReloading, props.isAutoReloading, props.entries, logEntryHighlightsById] + + [ + props.isReloading, + props.isAutoReloading, + props.wasAutoReloadJustAborted, + props.entries, + logEntryHighlightsById, + ] ); useEffect(() => { diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/actions.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/actions.ts index 86cd899b20b4d..a5a165cc29275 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/actions.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/log_position/actions.ts @@ -12,10 +12,11 @@ const actionCreator = actionCreatorFactory('x-pack/infra/local/log_position'); export const jumpToTargetPosition = actionCreator('JUMP_TO_TARGET_POSITION'); -export const jumpToTargetPositionTime = (time: number) => +export const jumpToTargetPositionTime = (time: number, fromAutoReload: boolean = false) => jumpToTargetPosition({ tiebreaker: 0, time, + fromAutoReload, }); export interface ReportVisiblePositionsPayload { diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/epic.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/epic.ts index 01b9b6eb0bb42..d8942e0c31ab1 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/epic.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/log_position/epic.ts @@ -5,19 +5,45 @@ */ import { Action } from 'redux'; -import { Epic } from 'redux-observable'; +import { Epic, combineEpics } from 'redux-observable'; import { timer } from 'rxjs'; -import { exhaustMap, filter, map, takeUntil } from 'rxjs/operators'; +import { exhaustMap, filter, map, takeUntil, mapTo, withLatestFrom } from 'rxjs/operators'; -import { jumpToTargetPositionTime, startAutoReload, stopAutoReload } from './actions'; +import { + jumpToTargetPosition, + jumpToTargetPositionTime, + startAutoReload, + stopAutoReload, + reportVisiblePositions, +} from './actions'; -export const createLogPositionEpic = (): Epic => action$ => +const createLiveStreamEpic = (): Epic => action$ => action$.pipe( filter(startAutoReload.match), exhaustMap(({ payload }) => timer(0, payload).pipe( - map(() => jumpToTargetPositionTime(Date.now())), + map(() => jumpToTargetPositionTime(Date.now(), true)), takeUntil(action$.pipe(filter(stopAutoReload.match))) ) ) ); + +const createLiveStreamScrollCancelEpic = (): Epic< + Action, + Action, + State, + { selectIsAutoReloadingLogEntries: (state: State) => boolean } +> => (action$, state$, { selectIsAutoReloadingLogEntries }) => + action$.pipe( + filter( + action => + (reportVisiblePositions.match(action) && action.payload.fromScroll) || + (jumpToTargetPosition.match(action) && !action.payload.fromAutoReload) + ), + withLatestFrom(state$), + filter(([, state]) => selectIsAutoReloadingLogEntries(state)), + mapTo(stopAutoReload()) + ); + +export const createLogPositionEpic = () => + combineEpics(createLiveStreamEpic(), createLiveStreamScrollCancelEpic()); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts index 878ecae686e40..552df52fcf0b8 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts @@ -15,6 +15,8 @@ import { stopAutoReload, } from './actions'; +import { loadEntriesActionCreators } from '../../remote/log_entries/operations/load'; + interface ManualTargetPositionUpdatePolicy { policy: 'manual'; } @@ -37,6 +39,7 @@ export interface LogPositionState { endKey: TimeKey | null; }; controlsShouldDisplayTargetPosition: boolean; + autoReloadJustAborted: boolean; } export const initialLogPositionState: LogPositionState = { @@ -50,6 +53,7 @@ export const initialLogPositionState: LogPositionState = { startKey: null, }, controlsShouldDisplayTargetPosition: false, + autoReloadJustAborted: false, }; const targetPositionReducer = reducerWithInitialState(initialLogPositionState.targetPosition).case( @@ -85,14 +89,28 @@ const controlsShouldDisplayTargetPositionReducer = reducerWithInitialState( initialLogPositionState.controlsShouldDisplayTargetPosition ) .case(jumpToTargetPosition, () => true) + .case(stopAutoReload, () => false) + .case(startAutoReload, () => true) .case(reportVisiblePositions, (state, { fromScroll }) => { if (fromScroll) return false; return state; }); +// If auto reload is aborted before a pending request finishes, this flag will +// prevent the UI from displaying the Loading Entries screen +const autoReloadJustAbortedReducer = reducerWithInitialState( + initialLogPositionState.autoReloadJustAborted +) + .case(stopAutoReload, () => true) + .case(startAutoReload, () => false) + .case(loadEntriesActionCreators.resolveDone, () => false) + .case(loadEntriesActionCreators.resolveFailed, () => false) + .case(loadEntriesActionCreators.resolve, () => false); + export const logPositionReducer = combineReducers({ targetPosition: targetPositionReducer, updatePolicy: targetPositionUpdatePolicyReducer, visiblePositions: visiblePositionReducer, controlsShouldDisplayTargetPosition: controlsShouldDisplayTargetPositionReducer, + autoReloadJustAborted: autoReloadJustAbortedReducer, }); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts index 7104883a585c1..a48571103eed1 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts @@ -13,6 +13,8 @@ export const selectTargetPosition = (state: LogPositionState) => state.targetPos export const selectIsAutoReloading = (state: LogPositionState) => state.updatePolicy.policy === 'interval'; +export const selectAutoReloadJustAborted = (state: LogPositionState) => state.autoReloadJustAborted; + export const selectFirstVisiblePosition = (state: LogPositionState) => state.visiblePositions.startKey ? state.visiblePositions.startKey : null; From 2e05cd6dace0ce19b36ef6f8197896d5b9aee357 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Fri, 6 Sep 2019 16:54:45 -0500 Subject: [PATCH 2/5] Silently pause streaming on scroll up --- .../logging/log_text_stream/jump_to_tail.tsx | 56 +++++++++++++++++++ .../scrollable_log_text_stream_view.tsx | 39 ++++++++++++- .../log_text_stream/vertical_scroll_panel.tsx | 12 +++- .../components/logging/log_time_controls.tsx | 5 +- .../containers/logs/with_log_position.tsx | 7 ++- .../pages/logs/stream/page_logs_content.tsx | 15 ++++- .../public/pages/logs/stream/page_toolbar.tsx | 4 +- .../store/local/log_position/actions.ts | 6 +- .../public/store/local/log_position/epic.ts | 22 +++++--- .../store/local/log_position/reducer.ts | 17 +++++- .../store/local/log_position/selectors.ts | 2 + .../plugins/infra/public/store/store.ts | 1 + 12 files changed, 161 insertions(+), 25 deletions(-) create mode 100644 x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/jump_to_tail.tsx diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/jump_to_tail.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/jump_to_tail.tsx new file mode 100644 index 0000000000000..92bcdbdf83ca2 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/jump_to_tail.tsx @@ -0,0 +1,56 @@ +/* + * 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. + */ + +/* eslint-disable max-classes-per-file */ + +import { EuiButtonEmpty, EuiIcon, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import * as React from 'react'; + +import euiStyled from '../../../../../../common/eui_styled_components'; + +interface LogTextStreamJumpToTailProps { + onClickJump?: () => void; + width: number; +} + +export class LogTextStreamJumpToTail extends React.PureComponent { + public render() { + const { onClickJump, width } = this.props; + return ( + + + + + + + + + + + ); + } +} + +const JumpToTailWrapper = euiStyled.div<{ width: number }>` + align-items: center; + display: flex; + min-height: ${props => props.theme.eui.euiSizeXXL}; + width: ${props => props.width}px; + position: fixed; + bottom: 0; + background-color: ${props => props.theme.eui.euiColorEmptyShade}; +`; + +const MessageWrapper = euiStyled.div` + padding: 8px 16px; +`; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx index 614b5699fd39c..6dfeee7bcf0f0 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx @@ -19,6 +19,7 @@ import { InfraLoadingPanel } from '../../loading'; import { getStreamItemBeforeTimeKey, getStreamItemId, parseStreamItemId, StreamItem } from './item'; import { LogColumnHeaders } from './column_headers'; import { LogTextStreamLoadingItemView } from './loading_item_view'; +import { LogTextStreamJumpToTail } from './jump_to_tail'; import { LogEntryRow } from './log_entry_row'; import { MeasurableItemView } from './measurable_item_view'; import { VerticalScrollPanel } from './vertical_scroll_panel'; @@ -52,11 +53,17 @@ interface ScrollableLogTextStreamViewProps { intl: InjectedIntl; highlightedItem: string | null; currentHighlightKey: UniqueTimeKey | null; + scrollLock: { + enable: () => void; + disable: () => void; + isEnabled: boolean; + }; } interface ScrollableLogTextStreamViewState { target: TimeKey | null; targetId: string | null; + items: StreamItem[]; } class ScrollableLogTextStreamViewClass extends React.PureComponent< @@ -70,20 +77,28 @@ class ScrollableLogTextStreamViewClass extends React.PureComponent< const hasNewTarget = nextProps.target && nextProps.target !== prevState.target; const hasItems = nextProps.items.length > 0; + // Prevent new entries from being appended and moving the stream forward when + // the user has scrolled up during live streaming + const nextItems = + hasItems && nextProps.scrollLock.isEnabled ? prevState.items : nextProps.items; + if (nextProps.isStreaming && hasItems) { return { target: nextProps.target, targetId: getStreamItemId(nextProps.items[nextProps.items.length - 1]), + items: nextItems, }; } else if (hasNewTarget && hasItems) { return { target: nextProps.target, targetId: getStreamItemId(getStreamItemBeforeTimeKey(nextProps.items, nextProps.target!)), + items: nextItems, }; } else if (!nextProps.target || !hasItems) { return { target: null, targetId: null, + items: [], }; } @@ -93,6 +108,7 @@ class ScrollableLogTextStreamViewClass extends React.PureComponent< public readonly state = { target: null, targetId: null, + items: [], }; public render() { @@ -106,12 +122,12 @@ class ScrollableLogTextStreamViewClass extends React.PureComponent< isLoadingMore, isReloading, isStreaming, - items, lastLoadedTime, scale, wrap, + scrollLock, } = this.props; - const { targetId } = this.state; + const { targetId, items } = this.state; const hasItems = items.length > 0; return ( @@ -162,6 +178,7 @@ class ScrollableLogTextStreamViewClass extends React.PureComponent< target={targetId} hideScrollbar={true} data-test-subj={'logStream'} + isLocked={scrollLock.isEnabled} > {registerChild => ( <> @@ -209,6 +226,12 @@ class ScrollableLogTextStreamViewClass extends React.PureComponent< lastStreamingUpdate={isStreaming ? lastLoadedTime : null} onLoadMore={this.handleLoadNewerItems} /> + {scrollLock.isEnabled && ( + + )} )} @@ -262,6 +285,9 @@ class ScrollableLogTextStreamViewClass extends React.PureComponent< pagesBelow: number; fromScroll: boolean; }) => { + if (fromScroll && this.props.isStreaming) { + this.props.scrollLock[pagesBelow === 0 ? 'disable' : 'enable'](); + } this.props.reportVisibleInterval({ endKey: parseStreamItemId(bottomChild), middleKey: parseStreamItemId(middleChild), @@ -272,6 +298,15 @@ class ScrollableLogTextStreamViewClass extends React.PureComponent< }); } ); + + private handleJumpToTail = () => { + const { items, scrollLock } = this.props; + scrollLock.disable(); + const lastItemTarget = getStreamItemId(items[items.length - 1]); + this.setState({ + targetId: lastItemTarget, + }); + }; } export const ScrollableLogTextStreamView = injectI18n(ScrollableLogTextStreamViewClass); diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/vertical_scroll_panel.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/vertical_scroll_panel.tsx index 8700239d84f62..8edec4d1777d0 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/vertical_scroll_panel.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/vertical_scroll_panel.tsx @@ -29,6 +29,7 @@ interface VerticalScrollPanelProps { width: number; hideScrollbar?: boolean; 'data-test-subj'?: string; + isLocked: boolean; } interface VerticalScrollPanelSnapshot { @@ -217,7 +218,16 @@ export class VerticalScrollPanel extends React.PureComponent< prevState: {}, snapshot: VerticalScrollPanelSnapshot ) { - this.handleUpdatedChildren(snapshot.scrollTarget, snapshot.scrollOffset); + if ( + prevProps.height !== this.props.height || + prevProps.target !== this.props.target || + React.Children.count(prevProps.children) !== React.Children.count(this.props.children) + ) { + this.handleUpdatedChildren(snapshot.scrollTarget, snapshot.scrollOffset); + } + if (prevProps.isLocked && !this.props.isLocked && this.scrollRef.current) { + this.scrollRef.current.scrollTop = this.scrollRef.current.scrollHeight; + } } public componentWillUnmount() { diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_time_controls.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_time_controls.tsx index 4472ec40dcfd2..c5bb905782c20 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_time_controls.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_time_controls.tsx @@ -13,7 +13,7 @@ const noop = () => undefined; interface LogTimeControlsProps { currentTime: number | null; - startLiveStreaming: (interval: number) => any; + startLiveStreaming: () => any; stopLiveStreaming: () => any; isLiveStreaming: boolean; jumpToTime: (time: number) => any; @@ -25,7 +25,6 @@ class LogTimeControlsUI extends React.PureComponent { const { currentTime, isLiveStreaming, intl } = this.props; const currentMoment = currentTime ? moment(currentTime) : null; - if (isLiveStreaming) { return ( @@ -89,7 +88,7 @@ class LogTimeControlsUI extends React.PureComponent { }; private startLiveStreaming = () => { - this.props.startLiveStreaming(5000); + this.props.startLiveStreaming(); }; private stopLiveStreaming = () => { diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/with_log_position.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/with_log_position.tsx index d1f1b363d44bf..bbcec36e17f9d 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/with_log_position.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/with_log_position.tsx @@ -18,6 +18,7 @@ export const withLogPosition = connect( (state: State) => ({ firstVisiblePosition: logPositionSelectors.selectFirstVisiblePosition(state), isAutoReloading: logPositionSelectors.selectIsAutoReloading(state), + isScrollLocked: logPositionSelectors.selectAutoReloadScrollLock(state), lastVisiblePosition: logPositionSelectors.selectFirstVisiblePosition(state), targetPosition: logPositionSelectors.selectTargetPosition(state), urlState: selectPositionUrlState(state), @@ -31,6 +32,8 @@ export const withLogPosition = connect( reportVisiblePositions: logPositionActions.reportVisiblePositions, startLiveStreaming: logPositionActions.startAutoReload, stopLiveStreaming: logPositionActions.stopAutoReload, + scrollLockLiveStreaming: logPositionActions.lockAutoReloadScroll, + scrollUnlockLiveStreaming: logPositionActions.unlockAutoReloadScroll, }) ); @@ -65,7 +68,7 @@ export const WithLogPositionUrlState = () => ( jumpToTargetPosition(newUrlState.position); } if (newUrlState && newUrlState.streamLive) { - startLiveStreaming(5000); + startLiveStreaming(); } else if ( newUrlState && typeof newUrlState.streamLive !== 'undefined' && @@ -81,7 +84,7 @@ export const WithLogPositionUrlState = () => ( jumpToTargetPositionTime(Date.now()); } if (initialUrlState && initialUrlState.streamLive) { - startLiveStreaming(5000); + startLiveStreaming(); } }} /> diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx index caae36aac8a65..7e351bfe78952 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx @@ -78,7 +78,15 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { - {({ isAutoReloading, jumpToTargetPosition, reportVisiblePositions, targetPosition }) => ( + {({ + isAutoReloading, + jumpToTargetPosition, + reportVisiblePositions, + targetPosition, + scrollLockLiveStreaming, + scrollUnlockLiveStreaming, + isScrollLocked, + }) => ( {({ currentHighlightKey, @@ -109,6 +117,11 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { setFlyoutVisibility={setFlyoutVisibility} highlightedItem={surroundingLogsId ? surroundingLogsId : null} currentHighlightKey={currentHighlightKey} + scrollLock={{ + enable: scrollLockLiveStreaming, + disable: scrollUnlockLiveStreaming, + isEnabled: isScrollLocked, + }} /> )} diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_toolbar.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_toolbar.tsx index 8b7310e43dee5..a1adf1eefb20e 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_toolbar.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_toolbar.tsx @@ -132,8 +132,8 @@ export const LogsToolbar = injectI18n(({ intl }) => { currentTime={visibleMidpointTime} isLiveStreaming={isAutoReloading} jumpToTime={jumpToTargetPositionTime} - startLiveStreaming={interval => { - startLiveStreaming(interval); + startLiveStreaming={() => { + startLiveStreaming(); setSurroundingLogsId(null); }} stopLiveStreaming={stopLiveStreaming} diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/actions.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/actions.ts index a5a165cc29275..ad83b6fda1b04 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/actions.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/log_position/actions.ts @@ -32,6 +32,8 @@ export const reportVisiblePositions = actionCreator('START_AUTO_RELOAD'); - +export const startAutoReload = actionCreator('START_AUTO_RELOAD'); export const stopAutoReload = actionCreator('STOP_AUTO_RELOAD'); + +export const lockAutoReloadScroll = actionCreator('LOCK_AUTO_RELOAD_SCROLL'); +export const unlockAutoReloadScroll = actionCreator('UNLOCK_AUTO_RELOAD_SCROLL'); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/epic.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/epic.ts index d8942e0c31ab1..de9e806469b06 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/epic.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/log_position/epic.ts @@ -14,14 +14,22 @@ import { jumpToTargetPositionTime, startAutoReload, stopAutoReload, - reportVisiblePositions, } from './actions'; -const createLiveStreamEpic = (): Epic => action$ => +const LIVE_STREAM_INTERVAL = 5000; + +const createLiveStreamEpic = (): Epic< + Action, + Action, + State, + { selectIsAutoReloadingScrollLocked: (state: State) => boolean } +> => (action$, state$, { selectIsAutoReloadingScrollLocked }) => action$.pipe( filter(startAutoReload.match), - exhaustMap(({ payload }) => - timer(0, payload).pipe( + exhaustMap(() => + timer(0, LIVE_STREAM_INTERVAL).pipe( + withLatestFrom(state$), + filter(([, state]) => selectIsAutoReloadingScrollLocked(state) === false), map(() => jumpToTargetPositionTime(Date.now(), true)), takeUntil(action$.pipe(filter(stopAutoReload.match))) ) @@ -35,11 +43,7 @@ const createLiveStreamScrollCancelEpic = (): Epic< { selectIsAutoReloadingLogEntries: (state: State) => boolean } > => (action$, state$, { selectIsAutoReloadingLogEntries }) => action$.pipe( - filter( - action => - (reportVisiblePositions.match(action) && action.payload.fromScroll) || - (jumpToTargetPosition.match(action) && !action.payload.fromAutoReload) - ), + filter(action => jumpToTargetPosition.match(action) && !action.payload.fromAutoReload), withLatestFrom(state$), filter(([, state]) => selectIsAutoReloadingLogEntries(state)), mapTo(stopAutoReload()) diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts index 552df52fcf0b8..3b99e2d4f4379 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts @@ -13,6 +13,8 @@ import { reportVisiblePositions, startAutoReload, stopAutoReload, + lockAutoReloadScroll, + unlockAutoReloadScroll, } from './actions'; import { loadEntriesActionCreators } from '../../remote/log_entries/operations/load'; @@ -23,7 +25,6 @@ interface ManualTargetPositionUpdatePolicy { interface IntervalTargetPositionUpdatePolicy { policy: 'interval'; - interval: number; } type TargetPositionUpdatePolicy = @@ -40,6 +41,7 @@ export interface LogPositionState { }; controlsShouldDisplayTargetPosition: boolean; autoReloadJustAborted: boolean; + autoReloadScrollLock: boolean; } export const initialLogPositionState: LogPositionState = { @@ -54,6 +56,7 @@ export const initialLogPositionState: LogPositionState = { }, controlsShouldDisplayTargetPosition: false, autoReloadJustAborted: false, + autoReloadScrollLock: false, }; const targetPositionReducer = reducerWithInitialState(initialLogPositionState.targetPosition).case( @@ -64,9 +67,8 @@ const targetPositionReducer = reducerWithInitialState(initialLogPositionState.ta const targetPositionUpdatePolicyReducer = reducerWithInitialState( initialLogPositionState.updatePolicy ) - .case(startAutoReload, (state, interval) => ({ + .case(startAutoReload, () => ({ policy: 'interval', - interval, })) .case(stopAutoReload, () => ({ policy: 'manual', @@ -107,10 +109,19 @@ const autoReloadJustAbortedReducer = reducerWithInitialState( .case(loadEntriesActionCreators.resolveFailed, () => false) .case(loadEntriesActionCreators.resolve, () => false); +const autoReloadScrollLockReducer = reducerWithInitialState( + initialLogPositionState.autoReloadScrollLock +) + .case(startAutoReload, () => false) + .case(stopAutoReload, () => false) + .case(lockAutoReloadScroll, () => true) + .case(unlockAutoReloadScroll, () => false); + export const logPositionReducer = combineReducers({ targetPosition: targetPositionReducer, updatePolicy: targetPositionUpdatePolicyReducer, visiblePositions: visiblePositionReducer, controlsShouldDisplayTargetPosition: controlsShouldDisplayTargetPositionReducer, autoReloadJustAborted: autoReloadJustAbortedReducer, + autoReloadScrollLock: autoReloadScrollLockReducer, }); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts index a48571103eed1..7a2fa86822c56 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts @@ -13,6 +13,8 @@ export const selectTargetPosition = (state: LogPositionState) => state.targetPos export const selectIsAutoReloading = (state: LogPositionState) => state.updatePolicy.policy === 'interval'; +export const selectAutoReloadScrollLock = (state: LogPositionState) => state.autoReloadScrollLock; + export const selectAutoReloadJustAborted = (state: LogPositionState) => state.autoReloadJustAborted; export const selectFirstVisiblePosition = (state: LogPositionState) => diff --git a/x-pack/legacy/plugins/infra/public/store/store.ts b/x-pack/legacy/plugins/infra/public/store/store.ts index bdddcf7a4cc25..d699db6af042e 100644 --- a/x-pack/legacy/plugins/infra/public/store/store.ts +++ b/x-pack/legacy/plugins/infra/public/store/store.ts @@ -44,6 +44,7 @@ export function createStore({ apolloClient, observableApi }: StoreDependencies) selectHasMoreLogEntriesAfterEnd: logEntriesSelectors.selectHasMoreAfterEnd, selectHasMoreLogEntriesBeforeStart: logEntriesSelectors.selectHasMoreBeforeStart, selectIsAutoReloadingLogEntries: logPositionSelectors.selectIsAutoReloading, + selectIsAutoReloadingScrollLocked: logPositionSelectors.selectAutoReloadScrollLock, selectLogFilterQueryAsJson: logFilterSelectors.selectLogFilterQueryAsJson, selectLogTargetPosition: logPositionSelectors.selectTargetPosition, selectVisibleLogMidpointOrTarget: logPositionSelectors.selectVisibleMidpointOrTarget, From 6dc24a1603ceb9636059197ae20496c6690ddf8d Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Mon, 9 Sep 2019 10:36:10 -0500 Subject: [PATCH 3/5] Fix type checking --- .../logging/log_text_stream/jump_to_tail.tsx | 2 +- .../scrollable_log_text_stream_view.tsx | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/jump_to_tail.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/jump_to_tail.tsx index 92bcdbdf83ca2..296ec0b28f7ca 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/jump_to_tail.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/jump_to_tail.tsx @@ -6,7 +6,7 @@ /* eslint-disable max-classes-per-file */ -import { EuiButtonEmpty, EuiIcon, EuiText } from '@elastic/eui'; +import { EuiButtonEmpty, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import * as React from 'react'; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx index 6dfeee7bcf0f0..a066048bdf3e9 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx @@ -105,11 +105,14 @@ class ScrollableLogTextStreamViewClass extends React.PureComponent< return null; } - public readonly state = { - target: null, - targetId: null, - items: [], - }; + private constructor(props: ScrollableLogTextStreamViewProps) { + super(props); + this.state = { + target: null, + targetId: null, + items: props.items, + }; + } public render() { const { From 9b7d8cdf3bee915b7f584c9f456748c6c9e20471 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Mon, 9 Sep 2019 11:32:30 -0500 Subject: [PATCH 4/5] Fix type check again --- .../logging/log_text_stream/scrollable_log_text_stream_view.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx index a066048bdf3e9..4261c9daaeef0 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx @@ -105,7 +105,7 @@ class ScrollableLogTextStreamViewClass extends React.PureComponent< return null; } - private constructor(props: ScrollableLogTextStreamViewProps) { + constructor(props: ScrollableLogTextStreamViewProps) { super(props); this.state = { target: null, From e41092e7c241a1ae6dbbeb230a2571b4e20f62b6 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Mon, 9 Sep 2019 14:02:45 -0500 Subject: [PATCH 5/5] Fix i18n --- .../public/components/logging/log_text_stream/jump_to_tail.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/jump_to_tail.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/jump_to_tail.tsx index 296ec0b28f7ca..05f85ceed49c0 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/jump_to_tail.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/jump_to_tail.tsx @@ -32,7 +32,7 @@ export class LogTextStreamJumpToTail extends React.PureComponent