[Logs UI] Refactor log position to hooks (#53540)

* Move URL state to hook

* Fix log filter URL state infinite loop

* Initial refactor of log position to hooks

* Simplify and reimplement controlsShouldDisplayTargetPosition

* Fix live streaming

* Flatten logposition destructuring

* Revert "Move URL state to hook"

This reverts commit 4e04aa061d.

# Conflicts:
#	x-pack/legacy/plugins/infra/public/containers/logs/log_filter/use_log_filter_url_state.tsx
#	x-pack/legacy/plugins/infra/public/pages/logs/stream/page_providers.tsx

* Fix unused imports

* Fix link-to test

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Zacqary Adam Xeper 2020-01-07 16:38:25 -06:00 committed by GitHub
parent b7a534b1b1
commit f5448bd9f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 391 additions and 781 deletions

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import React, { useContext } from 'react';
import { transparentize } from 'polished';
import euiStyled from '../../../../../../common/eui_styled_components';
@ -21,28 +21,26 @@ import {
LogEntryColumnWidths,
} from './log_entry_column';
import { ASSUMED_SCROLLBAR_WIDTH } from './vertical_scroll_panel';
import { WithLogPosition } from '../../../containers/logs/with_log_position';
import { LogPositionState } from '../../../containers/logs/log_position';
import { localizedDate } from '../../../utils/formatters/datetime';
export const LogColumnHeaders: React.FunctionComponent<{
columnConfigurations: LogColumnConfiguration[];
columnWidths: LogEntryColumnWidths;
}> = ({ columnConfigurations, columnWidths }) => {
const { firstVisiblePosition } = useContext(LogPositionState.Context);
return (
<LogColumnHeadersWrapper>
{columnConfigurations.map(columnConfiguration => {
if (isTimestampLogColumnConfiguration(columnConfiguration)) {
return (
<WithLogPosition key={columnConfiguration.timestampColumn.id}>
{({ firstVisiblePosition }) => (
<LogColumnHeader
columnWidth={columnWidths[columnConfiguration.timestampColumn.id]}
data-test-subj="logColumnHeader timestampLogColumnHeader"
>
{firstVisiblePosition ? localizedDate(firstVisiblePosition.time) : 'Timestamp'}
</LogColumnHeader>
)}
</WithLogPosition>
<LogColumnHeader
key={columnConfiguration.timestampColumn.id}
columnWidth={columnWidths[columnConfiguration.timestampColumn.id]}
data-test-subj="logColumnHeader timestampLogColumnHeader"
>
{firstVisiblePosition ? localizedDate(firstVisiblePosition.time) : 'Timestamp'}
</LogColumnHeader>
);
} else if (isMessageLogColumnConfiguration(columnConfiguration)) {
return (

View file

@ -55,17 +55,13 @@ interface ScrollableLogTextStreamViewProps {
setFlyoutVisibility: (visible: boolean) => void;
highlightedItem: string | null;
currentHighlightKey: UniqueTimeKey | null;
scrollLock: {
enable: () => void;
disable: () => void;
isEnabled: boolean;
};
}
interface ScrollableLogTextStreamViewState {
target: TimeKey | null;
targetId: string | null;
items: StreamItem[];
isScrollLocked: boolean;
}
export class ScrollableLogTextStreamView extends React.PureComponent<
@ -81,8 +77,7 @@ export class ScrollableLogTextStreamView extends React.PureComponent<
// 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;
const nextItems = hasItems && prevState.isScrollLocked ? prevState.items : nextProps.items;
if (nextProps.isStreaming && hasItems) {
return {
@ -121,6 +116,7 @@ export class ScrollableLogTextStreamView extends React.PureComponent<
target: null,
targetId: null,
items: props.items,
isScrollLocked: false,
};
}
@ -137,9 +133,8 @@ export class ScrollableLogTextStreamView extends React.PureComponent<
lastLoadedTime,
scale,
wrap,
scrollLock,
} = this.props;
const { targetId, items } = this.state;
const { targetId, items, isScrollLocked } = this.state;
const hasItems = items.length > 0;
return (
<ScrollableLogTextStreamViewWrapper>
@ -187,7 +182,7 @@ export class ScrollableLogTextStreamView extends React.PureComponent<
target={targetId}
hideScrollbar={true}
data-test-subj={'logStream'}
isLocked={scrollLock.isEnabled}
isLocked={isScrollLocked}
entriesCount={items.length}
>
{registerChild => (
@ -248,7 +243,7 @@ export class ScrollableLogTextStreamView extends React.PureComponent<
lastStreamingUpdate={isStreaming ? lastLoadedTime : null}
onLoadMore={this.handleLoadNewerItems}
/>
{scrollLock.isEnabled && (
{isScrollLocked && (
<LogTextStreamJumpToTail
width={width}
onClickJump={this.handleJumpToTail}
@ -308,7 +303,9 @@ export class ScrollableLogTextStreamView extends React.PureComponent<
fromScroll: boolean;
}) => {
if (fromScroll && this.props.isStreaming) {
this.props.scrollLock[pagesBelow === 0 ? 'disable' : 'enable']();
this.setState({
isScrollLocked: pagesBelow !== 0,
});
}
this.props.reportVisibleInterval({
endKey: parseStreamItemId(bottomChild),
@ -322,11 +319,11 @@ export class ScrollableLogTextStreamView extends React.PureComponent<
);
private handleJumpToTail = () => {
const { items, scrollLock } = this.props;
scrollLock.disable();
const { items } = this.props;
const lastItemTarget = getStreamItemId(items[items.length - 1]);
this.setState({
targetId: lastItemTarget,
isScrollLocked: false,
});
};
}

View file

@ -5,7 +5,7 @@
*/
import { useEffect, useState, useReducer, useCallback } from 'react';
import createContainer from 'constate';
import { pick, throttle } from 'lodash';
import { pick, throttle, omit } from 'lodash';
import { useGraphQLQueries } from './gql_queries';
import { TimeKey, timeKeyIsBetween } from '../../../../common/time';
import { InfraLogEntry } from './types';
@ -45,6 +45,7 @@ interface LogEntriesProps {
pagesAfterEnd: number | null;
sourceId: string;
isAutoReloading: boolean;
jumpToTargetPosition: (position: TimeKey) => void;
}
type FetchEntriesParams = Omit<LogEntriesProps, 'isAutoReloading'>;
@ -65,7 +66,7 @@ export type LogEntriesStateParams = {
} & LogEntriesResponse;
export interface LogEntriesCallbacks {
fetchNewerEntries: () => Promise<void>;
fetchNewerEntries: () => Promise<TimeKey | null | undefined>;
}
export const logEntriesInitialCallbacks = {
fetchNewerEntries: async () => {},
@ -127,10 +128,13 @@ const useFetchEntriesEffect = (
const [prevParams, cachePrevParams] = useState(props);
const [startedStreaming, setStartedStreaming] = useState(false);
const runFetchNewEntriesRequest = async () => {
const runFetchNewEntriesRequest = async (override = {}) => {
dispatch({ type: Action.FetchingNewEntries });
try {
const payload = await getLogEntriesAround(props);
const payload = await getLogEntriesAround({
...omit(props, 'jumpToTargetPosition'),
...override,
});
dispatch({ type: Action.ReceiveNewEntries, payload });
} catch (e) {
dispatch({ type: Action.ErrorOnNewEntries });
@ -150,6 +154,7 @@ const useFetchEntriesEffect = (
type: getEntriesBefore ? Action.ReceiveEntriesBefore : Action.ReceiveEntriesAfter,
payload,
});
return payload.entriesEnd;
} catch (e) {
dispatch({ type: Action.ErrorOnMoreEntries });
}
@ -185,19 +190,37 @@ const useFetchEntriesEffect = (
const fetchNewerEntries = useCallback(
throttle(() => runFetchMoreEntriesRequest(ShouldFetchMoreEntries.After), 500),
[props]
[props, state.entriesEnd]
);
const streamEntriesEffectDependencies = [props.isAutoReloading, state.isLoadingMore];
const streamEntriesEffectDependencies = [
props.isAutoReloading,
state.isLoadingMore,
state.isReloading,
];
const streamEntriesEffect = () => {
(async () => {
if (props.isAutoReloading && !state.isLoadingMore) {
if (props.isAutoReloading && !state.isLoadingMore && !state.isReloading) {
if (startedStreaming) {
await new Promise(res => setTimeout(res, 5000));
} else {
const nowKey = {
tiebreaker: 0,
time: Date.now(),
};
props.jumpToTargetPosition(nowKey);
setStartedStreaming(true);
if (state.hasMoreAfterEnd) {
runFetchNewEntriesRequest({
timeKey: nowKey,
});
return;
}
}
const newEntriesEnd = await runFetchMoreEntriesRequest(ShouldFetchMoreEntries.After);
if (newEntriesEnd) {
props.jumpToTargetPosition(newEntriesEnd);
}
fetchNewerEntries();
} else if (!props.isAutoReloading) {
setStartedStreaming(false);
}

View file

@ -5,4 +5,3 @@
*/
export * from './log_highlights';
export * from './redux_bridges';

View file

@ -9,9 +9,9 @@ import { useState, useContext } from 'react';
import { useLogEntryHighlights } from './log_entry_highlights';
import { useLogSummaryHighlights } from './log_summary_highlights';
import { useNextAndPrevious } from './next_and_previous';
import { useReduxBridgeSetters } from './redux_bridge_setters';
import { useLogSummaryBufferInterval } from '../log_summary';
import { LogViewConfiguration } from '../log_view_configuration';
import { LogPositionState } from '../log_position';
import { TimeKey } from '../../../../common/time';
export const useLogHighlightsState = ({
@ -28,14 +28,7 @@ export const useLogHighlightsState = ({
filterQuery: string | null;
}) => {
const [highlightTerms, setHighlightTerms] = useState<string[]>([]);
const {
visibleMidpoint,
setFilterQuery,
setVisibleMidpoint,
jumpToTarget,
setJumpToTarget,
} = useReduxBridgeSetters();
const { visibleMidpoint, jumpToTargetPosition } = useContext(LogPositionState.Context);
const { intervalSize: summaryIntervalSize } = useContext(LogViewConfiguration.Context);
const {
start: summaryStart,
@ -79,25 +72,22 @@ export const useLogHighlightsState = ({
visibleMidpoint,
logEntryHighlights,
highlightTerms,
jumpToTarget,
jumpToTargetPosition,
});
return {
highlightTerms,
setHighlightTerms,
setFilterQuery,
logEntryHighlights,
logEntryHighlightsById,
logSummaryHighlights,
loadLogEntryHighlightsRequest,
loadLogSummaryHighlightsRequest,
setVisibleMidpoint,
currentHighlightKey,
hasPreviousHighlight,
hasNextHighlight,
goToPreviousHighlight,
goToNextHighlight,
setJumpToTarget,
};
};

View file

@ -17,12 +17,12 @@ import { LogEntryHighlights } from './log_entry_highlights';
export const useNextAndPrevious = ({
highlightTerms,
jumpToTarget,
jumpToTargetPosition,
logEntryHighlights,
visibleMidpoint,
}: {
highlightTerms: string[];
jumpToTarget: (target: TimeKey) => void;
jumpToTargetPosition: (target: TimeKey) => void;
logEntryHighlights: LogEntryHighlights | undefined;
visibleMidpoint: TimeKey | null;
}) => {
@ -41,9 +41,9 @@ export const useNextAndPrevious = ({
useEffect(() => {
if (currentTimeKey) {
jumpToTarget(currentTimeKey);
jumpToTargetPosition(currentTimeKey);
}
}, [currentTimeKey, jumpToTarget]);
}, [currentTimeKey, jumpToTargetPosition]);
useEffect(() => {
if (currentTimeKey === null && entries.length > 0) {

View file

@ -1,23 +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 { useState } from 'react';
import { TimeKey } from '../../../../common/time';
export const useReduxBridgeSetters = () => {
const [filterQuery, setFilterQuery] = useState<string | null>(null);
const [visibleMidpoint, setVisibleMidpoint] = useState<TimeKey | null>(null);
const [jumpToTarget, setJumpToTarget] = useState<(target: TimeKey) => void>(() => undefined);
return {
filterQuery,
visibleMidpoint,
setFilterQuery,
setVisibleMidpoint,
jumpToTarget,
setJumpToTarget,
};
};

View file

@ -1,41 +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, { useEffect, useContext } from 'react';
import { TimeKey } from '../../../../common/time';
import { withLogPosition } from '../with_log_position';
import { LogHighlightsState } from './log_highlights';
// Bridges Redux container state with Hooks state. Once state is moved fully from
// Redux to Hooks this can be removed.
export const LogHighlightsPositionBridge = withLogPosition(
({
visibleMidpoint,
jumpToTargetPosition,
}: {
visibleMidpoint: TimeKey | null;
jumpToTargetPosition: (target: TimeKey) => void;
}) => {
const { setJumpToTarget, setVisibleMidpoint } = useContext(LogHighlightsState.Context);
useEffect(() => {
setVisibleMidpoint(visibleMidpoint);
}, [setVisibleMidpoint, visibleMidpoint]);
useEffect(() => {
setJumpToTarget(() => jumpToTargetPosition);
}, [jumpToTargetPosition, setJumpToTarget]);
return null;
}
);
export const LogHighlightsBridge = () => (
<>
<LogHighlightsPositionBridge />
</>
);

View file

@ -4,32 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { useContext } from 'react';
import createContainer from 'constate';
import { ReduxStateContext } from '../../../utils/redux_context';
import { logPositionSelectors as logPositionReduxSelectors } from '../../../store/local/selectors';
import { TimeKey } from '../../../../common/time';
export const useLogPositionState = () => {
const { local: state } = useContext(ReduxStateContext);
const timeKey = logPositionReduxSelectors.selectVisibleMidpointOrTarget(state);
const pages = logPositionReduxSelectors.selectPagesBeforeAndAfter(state);
const isAutoReloading = logPositionReduxSelectors.selectIsAutoReloading(state);
return { timeKey, isAutoReloading, ...pages };
};
export interface LogPositionStateParams {
timeKey: TimeKey | null;
pagesAfterEnd: number | null;
pagesBeforeStart: number | null;
isAutoReloading: boolean;
}
export const logPositionInitialState = {
timeKey: null,
pagesAfterEnd: null,
pagesBeforeStart: null,
isAutoReloading: false,
};
export const LogPositionState = createContainer(useLogPositionState);
export * from './log_position_state';
export * from './with_log_position_url_state';

View file

@ -0,0 +1,107 @@
/*
* 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, useMemo, useEffect, useCallback } from 'react';
import createContainer from 'constate';
import { TimeKey } from '../../../../common/time';
type TimeKeyOrNull = TimeKey | null;
interface VisiblePositions {
startKey: TimeKeyOrNull;
middleKey: TimeKeyOrNull;
endKey: TimeKeyOrNull;
pagesAfterEnd: number;
pagesBeforeStart: number;
}
export interface LogPositionStateParams {
targetPosition: TimeKeyOrNull;
isAutoReloading: boolean;
firstVisiblePosition: TimeKeyOrNull;
pagesBeforeStart: number;
pagesAfterEnd: number;
visibleMidpoint: TimeKeyOrNull;
visibleMidpointTime: number | null;
visibleTimeInterval: { start: number; end: number } | null;
}
export interface LogPositionCallbacks {
jumpToTargetPosition: (pos: TimeKeyOrNull) => void;
jumpToTargetPositionTime: (time: number) => void;
reportVisiblePositions: (visPos: VisiblePositions) => void;
startLiveStreaming: () => void;
stopLiveStreaming: () => void;
}
const useVisibleMidpoint = (middleKey: TimeKeyOrNull, targetPosition: TimeKeyOrNull) => {
// Of the two dependencies `middleKey` and `targetPosition`, return
// whichever one was the most recently updated. This allows the UI controls
// to display a newly-selected `targetPosition` before loading new data;
// otherwise the previous `middleKey` would linger in the UI for the entirety
// of the loading operation, which the user could perceive as unresponsiveness
const [store, update] = useState({
middleKey,
targetPosition,
currentValue: middleKey || targetPosition,
});
useEffect(() => {
if (middleKey !== store.middleKey) {
update({ targetPosition, middleKey, currentValue: middleKey });
} else if (targetPosition !== store.targetPosition) {
update({ targetPosition, middleKey, currentValue: targetPosition });
}
}, [middleKey, targetPosition]); // eslint-disable-line react-hooks/exhaustive-deps
return store.currentValue;
};
export const useLogPositionState: () => LogPositionStateParams & LogPositionCallbacks = () => {
const [targetPosition, jumpToTargetPosition] = useState<TimeKey | null>(null);
const [isAutoReloading, setIsAutoReloading] = useState(false);
const [visiblePositions, reportVisiblePositions] = useState<VisiblePositions>({
endKey: null,
middleKey: null,
startKey: null,
pagesBeforeStart: Infinity,
pagesAfterEnd: Infinity,
});
const { startKey, middleKey, endKey, pagesBeforeStart, pagesAfterEnd } = visiblePositions;
const visibleMidpoint = useVisibleMidpoint(middleKey, targetPosition);
const visibleTimeInterval = useMemo(
() => (startKey && endKey ? { start: startKey.time, end: endKey.time } : null),
[startKey, endKey]
);
const state = {
targetPosition,
isAutoReloading,
firstVisiblePosition: startKey,
pagesBeforeStart,
pagesAfterEnd,
visibleMidpoint,
visibleMidpointTime: visibleMidpoint ? visibleMidpoint.time : null,
visibleTimeInterval,
};
const callbacks = {
jumpToTargetPosition,
jumpToTargetPositionTime: useCallback(
(time: number) => jumpToTargetPosition({ tiebreaker: 0, time }),
[jumpToTargetPosition]
),
reportVisiblePositions,
startLiveStreaming: useCallback(() => setIsAutoReloading(true), [setIsAutoReloading]),
stopLiveStreaming: useCallback(() => setIsAutoReloading(false), [setIsAutoReloading]),
};
return { ...state, ...callbacks };
};
export const LogPositionState = createContainer(useLogPositionState);

View file

@ -0,0 +1,94 @@
/*
* 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, useMemo } from 'react';
import { pickTimeKey } from '../../../../common/time';
import { replaceStateKeyInQueryString, UrlStateContainer } from '../../../utils/url_state';
import { LogPositionState, LogPositionStateParams } from './log_position_state';
/**
* Url State
*/
interface LogPositionUrlState {
position: LogPositionStateParams['visibleMidpoint'] | undefined;
streamLive?: boolean | undefined;
}
export const WithLogPositionUrlState = () => {
const {
visibleMidpoint,
isAutoReloading,
jumpToTargetPosition,
jumpToTargetPositionTime,
startLiveStreaming,
stopLiveStreaming,
} = useContext(LogPositionState.Context);
const urlState = useMemo(
() => ({
position: visibleMidpoint ? pickTimeKey(visibleMidpoint) : null,
streamLive: isAutoReloading,
}),
[visibleMidpoint, isAutoReloading]
);
return (
<UrlStateContainer
urlState={urlState}
urlStateKey="logPosition"
mapToUrlState={mapToUrlState}
onChange={(newUrlState: LogPositionUrlState | undefined) => {
if (newUrlState && newUrlState.position) {
jumpToTargetPosition(newUrlState.position);
}
if (newUrlState && newUrlState.streamLive) {
startLiveStreaming();
} else if (
newUrlState &&
typeof newUrlState.streamLive !== 'undefined' &&
!newUrlState.streamLive
) {
stopLiveStreaming();
}
}}
onInitialize={(initialUrlState: LogPositionUrlState | undefined) => {
if (initialUrlState && initialUrlState.position) {
jumpToTargetPosition(initialUrlState.position);
} else {
jumpToTargetPositionTime(Date.now());
}
if (initialUrlState && initialUrlState.streamLive) {
startLiveStreaming();
}
}}
/>
);
};
const mapToUrlState = (value: any): LogPositionUrlState | undefined =>
value
? {
position: mapToPositionUrlState(value.position),
streamLive: mapToStreamLiveUrlState(value.streamLive),
}
: undefined;
const mapToPositionUrlState = (value: any) =>
value && typeof value.time === 'number' && typeof value.tiebreaker === 'number'
? pickTimeKey(value)
: undefined;
const mapToStreamLiveUrlState = (value: any) => (typeof value === 'boolean' ? value : undefined);
export const replaceLogPositionInQueryString = (time: number) =>
Number.isNaN(time)
? (value: string) => value
: replaceStateKeyInQueryString<LogPositionUrlState>('logPosition', {
position: {
time,
tiebreaker: 0,
},
});

View file

@ -5,40 +5,34 @@
*/
import { useContext } from 'react';
import { connect } from 'react-redux';
import { logPositionSelectors, State } from '../../../store';
import { RendererFunction } from '../../../utils/typed_react';
import { Source } from '../../source';
import { LogViewConfiguration } from '../log_view_configuration';
import { LogSummaryBuckets, useLogSummary } from './log_summary';
import { LogFilterState } from '../log_filter';
import { LogPositionState } from '../log_position';
export const WithSummary = connect((state: State) => ({
visibleMidpointTime: logPositionSelectors.selectVisibleMidpointOrTargetTime(state),
}))(
({
children,
export const WithSummary = ({
children,
}: {
children: RendererFunction<{
buckets: LogSummaryBuckets;
start: number | null;
end: number | null;
}>;
}) => {
const { intervalSize } = useContext(LogViewConfiguration.Context);
const { sourceId } = useContext(Source.Context);
const { filterQuery } = useContext(LogFilterState.Context);
const { visibleMidpointTime } = useContext(LogPositionState.Context);
const { buckets, start, end } = useLogSummary(
sourceId,
visibleMidpointTime,
}: {
children: RendererFunction<{
buckets: LogSummaryBuckets;
start: number | null;
end: number | null;
}>;
visibleMidpointTime: number | null;
}) => {
const { intervalSize } = useContext(LogViewConfiguration.Context);
const { sourceId } = useContext(Source.Context);
const { filterQuery } = useContext(LogFilterState.Context);
intervalSize,
filterQuery
);
const { buckets, start, end } = useLogSummary(
sourceId,
visibleMidpointTime,
intervalSize,
filterQuery
);
return children({ buckets, start, end });
}
);
return children({ buckets, start, end });
};

View file

@ -1,127 +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 from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { pickTimeKey } from '../../../common/time';
import { logPositionActions, logPositionSelectors, State } from '../../store';
import { asChildFunctionRenderer } from '../../utils/typed_react';
import { bindPlainActionCreators } from '../../utils/typed_redux';
import { replaceStateKeyInQueryString, UrlStateContainer } from '../../utils/url_state';
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),
visibleTimeInterval: logPositionSelectors.selectVisibleTimeInterval(state),
visibleMidpoint: logPositionSelectors.selectVisibleMidpointOrTarget(state),
visibleMidpointTime: logPositionSelectors.selectVisibleMidpointOrTargetTime(state),
}),
bindPlainActionCreators({
jumpToTargetPosition: logPositionActions.jumpToTargetPosition,
jumpToTargetPositionTime: logPositionActions.jumpToTargetPositionTime,
reportVisiblePositions: logPositionActions.reportVisiblePositions,
startLiveStreaming: logPositionActions.startAutoReload,
stopLiveStreaming: logPositionActions.stopAutoReload,
scrollLockLiveStreaming: logPositionActions.lockAutoReloadScroll,
scrollUnlockLiveStreaming: logPositionActions.unlockAutoReloadScroll,
})
);
export const WithLogPosition = asChildFunctionRenderer(withLogPosition, {
onCleanup: ({ stopLiveStreaming }) => stopLiveStreaming(),
});
/**
* Url State
*/
interface LogPositionUrlState {
position?: ReturnType<typeof logPositionSelectors.selectVisibleMidpointOrTarget>;
streamLive?: ReturnType<typeof logPositionSelectors.selectIsAutoReloading>;
}
export const WithLogPositionUrlState = () => (
<WithLogPosition>
{({
jumpToTargetPosition,
jumpToTargetPositionTime,
startLiveStreaming,
stopLiveStreaming,
urlState,
}) => (
<UrlStateContainer
urlState={urlState}
urlStateKey="logPosition"
mapToUrlState={mapToUrlState}
onChange={newUrlState => {
if (newUrlState && newUrlState.position) {
jumpToTargetPosition(newUrlState.position);
}
if (newUrlState && newUrlState.streamLive) {
startLiveStreaming();
} else if (
newUrlState &&
typeof newUrlState.streamLive !== 'undefined' &&
!newUrlState.streamLive
) {
stopLiveStreaming();
}
}}
onInitialize={initialUrlState => {
if (initialUrlState && initialUrlState.position) {
jumpToTargetPosition(initialUrlState.position);
} else {
jumpToTargetPositionTime(Date.now());
}
if (initialUrlState && initialUrlState.streamLive) {
startLiveStreaming();
}
}}
/>
)}
</WithLogPosition>
);
const selectPositionUrlState = createSelector(
logPositionSelectors.selectVisibleMidpointOrTarget,
logPositionSelectors.selectIsAutoReloading,
(position, streamLive) => ({
position: position ? pickTimeKey(position) : null,
streamLive,
})
);
const mapToUrlState = (value: any): LogPositionUrlState | undefined =>
value
? {
position: mapToPositionUrlState(value.position),
streamLive: mapToStreamLiveUrlState(value.streamLive),
}
: undefined;
const mapToPositionUrlState = (value: any) =>
value && typeof value.time === 'number' && typeof value.tiebreaker === 'number'
? pickTimeKey(value)
: undefined;
const mapToStreamLiveUrlState = (value: any) => (typeof value === 'boolean' ? value : undefined);
export const replaceLogPositionInQueryString = (time: number) =>
Number.isNaN(time)
? (value: string) => value
: replaceStateKeyInQueryString<LogPositionUrlState>('logPosition', {
position: {
time,
tiebreaker: 0,
},
});

View file

@ -1,35 +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.
*/
/**
* Temporary Workaround
* This is not a well-designed container. It only exists to enable quick
* migration of the redux-based logging ui into the infra-ui codebase. It will
* be removed during the refactoring to graphql/apollo.
*/
import { connect } from 'react-redux';
import { bindPlainActionCreators } from '../../utils/typed_redux';
import {
// searchActions,
// searchResultsSelectors,
// sharedSelectors,
logPositionActions,
State,
} from '../../store';
export const withLogSearchControlsProps = connect(
(state: State) => ({
// isLoadingSearchResults: searchResultsSelectors.selectIsLoadingSearchResults(state),
// nextSearchResult: sharedSelectors.selectNextSearchResultKey(state),
// previousSearchResult: sharedSelectors.selectPreviousSearchResultKey(state),
}),
bindPlainActionCreators({
// clearSearch: searchActions.clearSearch,
jumpToTarget: logPositionActions.jumpToTargetPosition,
// search: searchActions.search,
})
);

View file

@ -10,7 +10,6 @@ import { LogEntry, LogEntryHighlight } from '../../utils/log_entry';
import { RendererFunction } from '../../utils/typed_react';
// deep inporting to avoid a circular import problem
import { LogHighlightsState } from './log_highlights/log_highlights';
import { LogPositionState } from './log_position';
import { LogEntriesState, LogEntriesStateParams, LogEntriesCallbacks } from './log_entries';
import { UniqueTimeKey } from '../../../common/time';
@ -24,18 +23,17 @@ export const WithStreamItems: React.FunctionComponent<{
>;
}> = ({ children }) => {
const [logEntries, logEntriesCallbacks] = useContext(LogEntriesState.Context);
const { isAutoReloading } = useContext(LogPositionState.Context);
const { currentHighlightKey, logEntryHighlightsById } = useContext(LogHighlightsState.Context);
const items = useMemo(
() =>
logEntries.isReloading && !isAutoReloading
logEntries.isReloading
? []
: logEntries.entries.map(logEntry =>
createLogEntryStreamItem(logEntry, logEntryHighlightsById[logEntry.gid] || [])
),
[isAutoReloading, logEntries.entries, logEntries.isReloading, logEntryHighlightsById]
[logEntries.entries, logEntries.isReloading, logEntryHighlightsById]
);
return children({

View file

@ -9,7 +9,7 @@ import React from 'react';
import { match as RouteMatch, Redirect, RouteComponentProps } from 'react-router-dom';
import { replaceLogFilterInQueryString } from '../../containers/logs/log_filter';
import { replaceLogPositionInQueryString } from '../../containers/logs/with_log_position';
import { replaceLogPositionInQueryString } from '../../containers/logs/log_position';
import { replaceSourceIdInQueryString } from '../../containers/source_id';
import { getFilterFromLocation, getTimeFromLocation } from './query_params';

View file

@ -12,7 +12,7 @@ import { Redirect, RouteComponentProps } from 'react-router-dom';
import { LoadingPage } from '../../components/loading_page';
import { replaceLogFilterInQueryString } from '../../containers/logs/log_filter';
import { replaceLogPositionInQueryString } from '../../containers/logs/with_log_position';
import { replaceLogPositionInQueryString } from '../../containers/logs/log_position';
import { replaceSourceIdInQueryString } from '../../containers/source_id';
import { InfraNodeType, SourceConfigurationFields } from '../../graphql/types';
import { getFilterFromLocation, getTimeFromLocation } from './query_params';

View file

@ -21,14 +21,13 @@ import {
WithFlyoutOptionsUrlState,
} from '../../../containers/logs/log_flyout';
import { WithLogMinimapUrlState } from '../../../containers/logs/with_log_minimap';
import { WithLogPositionUrlState } from '../../../containers/logs/with_log_position';
import { WithLogPosition } from '../../../containers/logs/with_log_position';
import { LogPositionState } from '../../../containers/logs/log_position';
import { WithLogTextviewUrlState } from '../../../containers/logs/with_log_textview';
import { WithStreamItems } from '../../../containers/logs/with_stream_items';
import { Source } from '../../../containers/source';
import { LogsToolbar } from './page_toolbar';
import { LogHighlightsBridge, LogHighlightsState } from '../../../containers/logs/log_highlights';
import { LogHighlightsState } from '../../../containers/logs/log_highlights';
export const LogsPageLogsContent: React.FunctionComponent = () => {
const { source, sourceId, version } = useContext(Source.Context);
@ -44,110 +43,91 @@ export const LogsPageLogsContent: React.FunctionComponent = () => {
} = useContext(LogFlyoutState.Context);
const { logSummaryHighlights } = useContext(LogHighlightsState.Context);
const { applyLogFilterQuery } = useContext(LogFilterState.Context);
const {
isAutoReloading,
targetPosition,
visibleMidpointTime,
visibleTimeInterval,
reportVisiblePositions,
jumpToTargetPosition,
stopLiveStreaming,
} = useContext(LogPositionState.Context);
return (
<>
<LogHighlightsBridge />
<WithLogPositionUrlState />
<WithLogMinimapUrlState />
<WithLogTextviewUrlState />
<WithFlyoutOptionsUrlState />
<LogsToolbar />
<WithLogPosition>
{({ jumpToTargetPosition, stopLiveStreaming }) =>
flyoutVisible ? (
<LogEntryFlyout
setFilter={applyLogFilterQuery}
setTarget={(timeKey, flyoutItemId) => {
jumpToTargetPosition(timeKey);
setSurroundingLogsId(flyoutItemId);
stopLiveStreaming();
}}
setFlyoutVisibility={setFlyoutVisibility}
flyoutItem={flyoutItem}
loading={isLoading}
/>
) : null
}
</WithLogPosition>
{flyoutVisible ? (
<LogEntryFlyout
setFilter={applyLogFilterQuery}
setTarget={(timeKey, flyoutItemId) => {
jumpToTargetPosition(timeKey);
setSurroundingLogsId(flyoutItemId);
stopLiveStreaming();
}}
setFlyoutVisibility={setFlyoutVisibility}
flyoutItem={flyoutItem}
loading={isLoading}
/>
) : null}
<PageContent key={`${sourceId}-${version}`}>
<WithLogPosition>
<WithStreamItems>
{({
isAutoReloading,
jumpToTargetPosition,
reportVisiblePositions,
targetPosition,
scrollLockLiveStreaming,
scrollUnlockLiveStreaming,
isScrollLocked,
currentHighlightKey,
hasMoreAfterEnd,
hasMoreBeforeStart,
isLoadingMore,
isReloading,
items,
lastLoadedTime,
fetchNewerEntries,
}) => (
<WithStreamItems>
{({
currentHighlightKey,
hasMoreAfterEnd,
hasMoreBeforeStart,
isLoadingMore,
isReloading,
items,
lastLoadedTime,
fetchNewerEntries,
}) => (
<ScrollableLogTextStreamView
columnConfigurations={(source && source.configuration.logColumns) || []}
hasMoreAfterEnd={hasMoreAfterEnd}
hasMoreBeforeStart={hasMoreBeforeStart}
isLoadingMore={isLoadingMore}
isReloading={isReloading}
isStreaming={isAutoReloading}
items={items}
jumpToTarget={jumpToTargetPosition}
lastLoadedTime={lastLoadedTime}
loadNewerItems={fetchNewerEntries}
reportVisibleInterval={reportVisiblePositions}
scale={textScale}
target={targetPosition}
wrap={textWrap}
setFlyoutItem={setFlyoutId}
setFlyoutVisibility={setFlyoutVisibility}
highlightedItem={surroundingLogsId ? surroundingLogsId : null}
currentHighlightKey={currentHighlightKey}
scrollLock={{
enable: scrollLockLiveStreaming,
disable: scrollUnlockLiveStreaming,
isEnabled: isScrollLocked,
}}
/>
)}
</WithStreamItems>
<ScrollableLogTextStreamView
columnConfigurations={(source && source.configuration.logColumns) || []}
hasMoreAfterEnd={hasMoreAfterEnd}
hasMoreBeforeStart={hasMoreBeforeStart}
isLoadingMore={isLoadingMore}
isReloading={isReloading}
isStreaming={isAutoReloading}
items={items}
jumpToTarget={jumpToTargetPosition}
lastLoadedTime={lastLoadedTime}
loadNewerItems={fetchNewerEntries}
reportVisibleInterval={reportVisiblePositions}
scale={textScale}
target={targetPosition}
wrap={textWrap}
setFlyoutItem={setFlyoutId}
setFlyoutVisibility={setFlyoutVisibility}
highlightedItem={surroundingLogsId ? surroundingLogsId : null}
currentHighlightKey={currentHighlightKey}
/>
)}
</WithLogPosition>
</WithStreamItems>
<AutoSizer content bounds detectAnyWindowResize="height">
{({ measureRef, bounds: { height = 0 }, content: { width = 0 } }) => {
return (
<LogPageMinimapColumn ref={measureRef}>
<WithSummary>
{({ buckets }) => (
<WithLogPosition>
{({ jumpToTargetPosition, visibleMidpointTime, visibleTimeInterval }) => (
<WithStreamItems>
{({ isReloading }) => (
<LogMinimap
height={height}
width={width}
highlightedInterval={isReloading ? null : visibleTimeInterval}
intervalSize={intervalSize}
jumpToTarget={jumpToTargetPosition}
summaryBuckets={buckets}
summaryHighlightBuckets={
logSummaryHighlights.length > 0
? logSummaryHighlights[0].buckets
: []
}
target={visibleMidpointTime}
/>
)}
</WithStreamItems>
<WithStreamItems>
{({ isReloading }) => (
<LogMinimap
height={height}
width={width}
highlightedInterval={isReloading ? null : visibleTimeInterval}
intervalSize={intervalSize}
jumpToTarget={jumpToTargetPosition}
summaryBuckets={buckets}
summaryHighlightBuckets={
logSummaryHighlights.length > 0 ? logSummaryHighlights[0].buckets : []
}
target={visibleMidpointTime}
/>
)}
</WithLogPosition>
</WithStreamItems>
)}
</WithSummary>
</LogPageMinimapColumn>

View file

@ -9,7 +9,7 @@ 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 { LogPositionState } from '../../../containers/logs/log_position';
import { LogPositionState, WithLogPositionUrlState } from '../../../containers/logs/log_position';
import { LogFilterState, WithLogFilterUrlState } from '../../../containers/logs/log_filter';
import { LogEntriesState } from '../../../containers/logs/log_entries';
@ -28,17 +28,23 @@ const LogFilterStateProvider: React.FC = ({ children }) => {
const LogEntriesStateProvider: React.FC = ({ children }) => {
const { sourceId } = useContext(Source.Context);
const { timeKey, pagesBeforeStart, pagesAfterEnd, isAutoReloading } = useContext(
LogPositionState.Context
);
const {
targetPosition,
pagesBeforeStart,
pagesAfterEnd,
isAutoReloading,
jumpToTargetPosition,
} = useContext(LogPositionState.Context);
const { filterQuery } = useContext(LogFilterState.Context);
const entriesProps = {
timeKey,
timeKey: targetPosition,
pagesBeforeStart,
pagesAfterEnd,
filterQuery,
sourceId,
isAutoReloading,
jumpToTargetPosition,
};
return <LogEntriesState.Provider {...entriesProps}>{children}</LogEntriesState.Provider>;
};
@ -62,6 +68,7 @@ export const LogsPageProviders: React.FunctionComponent = ({ children }) => {
<LogViewConfiguration.Provider>
<LogFlyout.Provider>
<LogPositionState.Provider>
<WithLogPositionUrlState />
<LogFilterStateProvider>
<LogEntriesStateProvider>
<LogHighlightsStateProvider>{children}</LogHighlightsStateProvider>

View file

@ -20,7 +20,7 @@ import { LogTimeControls } from '../../../components/logging/log_time_controls';
import { LogFlyout } from '../../../containers/logs/log_flyout';
import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration';
import { LogFilterState } from '../../../containers/logs/log_filter';
import { WithLogPosition } from '../../../containers/logs/with_log_position';
import { LogPositionState } from '../../../containers/logs/log_position';
import { Source } from '../../../containers/source';
import { WithKueryAutocompletion } from '../../../containers/with_kuery_autocompletion';
@ -54,6 +54,13 @@ export const LogsToolbar = () => {
goToPreviousHighlight,
goToNextHighlight,
} = useContext(LogHighlightsState.Context);
const {
visibleMidpointTime,
isAutoReloading,
jumpToTargetPositionTime,
startLiveStreaming,
stopLiveStreaming,
} = useContext(LogPositionState.Context);
return (
<Toolbar>
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="s">
@ -114,26 +121,16 @@ export const LogsToolbar = () => {
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<WithLogPosition resetOnUnmount>
{({
visibleMidpointTime,
isAutoReloading,
jumpToTargetPositionTime,
startLiveStreaming,
stopLiveStreaming,
}) => (
<LogTimeControls
currentTime={visibleMidpointTime}
isLiveStreaming={isAutoReloading}
jumpToTime={jumpToTargetPositionTime}
startLiveStreaming={() => {
startLiveStreaming();
setSurroundingLogsId(null);
}}
stopLiveStreaming={stopLiveStreaming}
/>
)}
</WithLogPosition>
<LogTimeControls
currentTime={visibleMidpointTime}
isLiveStreaming={isAutoReloading}
jumpToTime={jumpToTargetPositionTime}
startLiveStreaming={() => {
startLiveStreaming();
setSurroundingLogsId(null);
}}
stopLiveStreaming={stopLiveStreaming}
/>
</EuiFlexItem>
</EuiFlexGroup>
</Toolbar>

View file

@ -4,9 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
export {
logPositionActions,
waffleFilterActions,
waffleTimeActions,
waffleOptionsActions,
} from './local';
export { waffleFilterActions, waffleTimeActions, waffleOptionsActions } from './local';

View file

@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { logPositionActions } from './log_position';
export { waffleFilterActions } from './waffle_filter';
export { waffleTimeActions } from './waffle_time';
export { waffleOptionsActions } from './waffle_options';

View file

@ -1,39 +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 actionCreatorFactory from 'typescript-fsa';
import { TimeKey } from '../../../../common/time';
const actionCreator = actionCreatorFactory('x-pack/infra/local/log_position');
export const jumpToTargetPosition = actionCreator<TimeKey>('JUMP_TO_TARGET_POSITION');
export const jumpToTargetPositionTime = (time: number, fromAutoReload: boolean = false) =>
jumpToTargetPosition({
tiebreaker: 0,
time,
fromAutoReload,
});
export interface ReportVisiblePositionsPayload {
pagesAfterEnd: number;
pagesBeforeStart: number;
endKey: TimeKey | null;
middleKey: TimeKey | null;
startKey: TimeKey | null;
fromScroll: boolean;
}
export const reportVisiblePositions = actionCreator<ReportVisiblePositionsPayload>(
'REPORT_VISIBLE_POSITIONS'
);
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');

View file

@ -1,53 +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 { Action } from 'redux';
import { Epic, combineEpics } from 'redux-observable';
import { timer } from 'rxjs';
import { exhaustMap, filter, map, takeUntil, mapTo, withLatestFrom } from 'rxjs/operators';
import {
jumpToTargetPosition,
jumpToTargetPositionTime,
startAutoReload,
stopAutoReload,
} from './actions';
const LIVE_STREAM_INTERVAL = 5000;
const createLiveStreamEpic = <State>(): Epic<
Action,
Action,
State,
{ selectIsAutoReloadingScrollLocked: (state: State) => boolean }
> => (action$, state$, { selectIsAutoReloadingScrollLocked }) =>
action$.pipe(
filter(startAutoReload.match),
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)))
)
)
);
const createLiveStreamScrollCancelEpic = <State>(): Epic<
Action,
Action,
State,
{ selectIsAutoReloadingLogEntries: (state: State) => boolean }
> => (action$, state$, { selectIsAutoReloadingLogEntries }) =>
action$.pipe(
filter(action => jumpToTargetPosition.match(action) && !action.payload.fromAutoReload),
withLatestFrom(state$),
filter(([, state]) => selectIsAutoReloadingLogEntries(state)),
mapTo(stopAutoReload())
);
export const createLogPositionEpic = <State>() =>
combineEpics(createLiveStreamEpic<State>(), createLiveStreamScrollCancelEpic<State>());

View file

@ -1,11 +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 * as logPositionActions from './actions';
import * as logPositionSelectors from './selectors';
export { logPositionActions, logPositionSelectors };
export * from './reducer';

View file

@ -1,120 +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 { combineReducers } from 'redux';
import { reducerWithInitialState } from 'typescript-fsa-reducers/dist';
import { TimeKey } from '../../../../common/time';
import {
jumpToTargetPosition,
reportVisiblePositions,
startAutoReload,
stopAutoReload,
lockAutoReloadScroll,
unlockAutoReloadScroll,
} from './actions';
interface ManualTargetPositionUpdatePolicy {
policy: 'manual';
}
interface IntervalTargetPositionUpdatePolicy {
policy: 'interval';
}
type TargetPositionUpdatePolicy =
| ManualTargetPositionUpdatePolicy
| IntervalTargetPositionUpdatePolicy;
export interface LogPositionState {
targetPosition: TimeKey | null;
updatePolicy: TargetPositionUpdatePolicy;
visiblePositions: {
startKey: TimeKey | null;
middleKey: TimeKey | null;
endKey: TimeKey | null;
pagesAfterEnd: number;
pagesBeforeStart: number;
};
controlsShouldDisplayTargetPosition: boolean;
autoReloadScrollLock: boolean;
}
export const initialLogPositionState: LogPositionState = {
targetPosition: null,
updatePolicy: {
policy: 'manual',
},
visiblePositions: {
endKey: null,
middleKey: null,
startKey: null,
pagesBeforeStart: Infinity,
pagesAfterEnd: Infinity,
},
controlsShouldDisplayTargetPosition: false,
autoReloadScrollLock: false,
};
const targetPositionReducer = reducerWithInitialState(initialLogPositionState.targetPosition).case(
jumpToTargetPosition,
(state, target) => target
);
const targetPositionUpdatePolicyReducer = reducerWithInitialState(
initialLogPositionState.updatePolicy
)
.case(startAutoReload, () => ({
policy: 'interval',
}))
.case(stopAutoReload, () => ({
policy: 'manual',
}));
const visiblePositionReducer = reducerWithInitialState(
initialLogPositionState.visiblePositions
).case(
reportVisiblePositions,
(state, { startKey, middleKey, endKey, pagesBeforeStart, pagesAfterEnd }) => ({
endKey,
middleKey,
startKey,
pagesBeforeStart,
pagesAfterEnd,
})
);
// Determines whether to use the target position or the visible midpoint when
// displaying a timestamp or time range in the toolbar and log minimap. When the
// user jumps to a new target, the final visible midpoint is indeterminate until
// all the new data has finished loading, so using this flag reduces the perception
// that the UI is jumping around inaccurately
const controlsShouldDisplayTargetPositionReducer = reducerWithInitialState(
initialLogPositionState.controlsShouldDisplayTargetPosition
)
.case(jumpToTargetPosition, () => true)
.case(stopAutoReload, () => false)
.case(startAutoReload, () => true)
.case(reportVisiblePositions, (state, { fromScroll }) => {
if (fromScroll) return false;
return state;
});
const autoReloadScrollLockReducer = reducerWithInitialState(
initialLogPositionState.autoReloadScrollLock
)
.case(startAutoReload, () => false)
.case(stopAutoReload, () => false)
.case(lockAutoReloadScroll, () => true)
.case(unlockAutoReloadScroll, () => false);
export const logPositionReducer = combineReducers<LogPositionState>({
targetPosition: targetPositionReducer,
updatePolicy: targetPositionUpdatePolicyReducer,
visiblePositions: visiblePositionReducer,
controlsShouldDisplayTargetPosition: controlsShouldDisplayTargetPositionReducer,
autoReloadScrollLock: autoReloadScrollLockReducer,
});

View file

@ -1,69 +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 { createSelector } from 'reselect';
import { LogPositionState } from './reducer';
export const selectTargetPosition = (state: LogPositionState) => state.targetPosition;
export const selectIsAutoReloading = (state: LogPositionState) =>
state.updatePolicy.policy === 'interval';
export const selectAutoReloadScrollLock = (state: LogPositionState) => state.autoReloadScrollLock;
export const selectFirstVisiblePosition = (state: LogPositionState) =>
state.visiblePositions.startKey ? state.visiblePositions.startKey : null;
export const selectMiddleVisiblePosition = (state: LogPositionState) =>
state.visiblePositions.middleKey ? state.visiblePositions.middleKey : null;
export const selectLastVisiblePosition = (state: LogPositionState) =>
state.visiblePositions.endKey ? state.visiblePositions.endKey : null;
export const selectPagesBeforeAndAfter = (state: LogPositionState) =>
state.visiblePositions
? {
pagesBeforeStart: state.visiblePositions.pagesBeforeStart,
pagesAfterEnd: state.visiblePositions.pagesAfterEnd,
}
: { pagesBeforeStart: null, pagesAfterEnd: null };
export const selectControlsShouldDisplayTargetPosition = (state: LogPositionState) =>
state.controlsShouldDisplayTargetPosition;
export const selectVisibleMidpointOrTarget = createSelector(
selectMiddleVisiblePosition,
selectTargetPosition,
selectControlsShouldDisplayTargetPosition,
(middleVisiblePosition, targetPosition, displayTargetPosition) => {
if (displayTargetPosition) {
return targetPosition;
} else if (middleVisiblePosition) {
return middleVisiblePosition;
} else if (targetPosition) {
return targetPosition;
} else {
return null;
}
}
);
export const selectVisibleMidpointOrTargetTime = createSelector(
selectVisibleMidpointOrTarget,
visibleMidpointOrTarget => (visibleMidpointOrTarget ? visibleMidpointOrTarget.time : null)
);
export const selectVisibleTimeInterval = createSelector(
selectFirstVisiblePosition,
selectLastVisiblePosition,
(firstVisiblePosition, lastVisiblePosition) =>
firstVisiblePosition && lastVisiblePosition
? {
start: firstVisiblePosition.time,
end: lastVisiblePosition.time,
}
: null
);

View file

@ -6,7 +6,6 @@
import { combineReducers } from 'redux';
import { initialLogPositionState, logPositionReducer, LogPositionState } from './log_position';
import { initialWaffleFilterState, waffleFilterReducer, WaffleFilterState } from './waffle_filter';
import {
initialWaffleOptionsState,
@ -16,21 +15,18 @@ import {
import { initialWaffleTimeState, waffleTimeReducer, WaffleTimeState } from './waffle_time';
export interface LocalState {
logPosition: LogPositionState;
waffleFilter: WaffleFilterState;
waffleTime: WaffleTimeState;
waffleMetrics: WaffleOptionsState;
}
export const initialLocalState: LocalState = {
logPosition: initialLogPositionState,
waffleFilter: initialWaffleFilterState,
waffleTime: initialWaffleTimeState,
waffleMetrics: initialWaffleOptionsState,
};
export const localReducer = combineReducers<LocalState>({
logPosition: logPositionReducer,
waffleFilter: waffleFilterReducer,
waffleTime: waffleTimeReducer,
waffleMetrics: waffleOptionsReducer,

View file

@ -5,17 +5,11 @@
*/
import { globalizeSelectors } from '../../utils/typed_redux';
import { logPositionSelectors as innerLogPositionSelectors } from './log_position';
import { LocalState } from './reducer';
import { waffleFilterSelectors as innerWaffleFilterSelectors } from './waffle_filter';
import { waffleOptionsSelectors as innerWaffleOptionsSelectors } from './waffle_options';
import { waffleTimeSelectors as innerWaffleTimeSelectors } from './waffle_time';
export const logPositionSelectors = globalizeSelectors(
(state: LocalState) => state.logPosition,
innerLogPositionSelectors
);
export const waffleFilterSelectors = globalizeSelectors(
(state: LocalState) => state.waffleFilter,
innerWaffleFilterSelectors

View file

@ -6,7 +6,6 @@
import { globalizeSelectors } from '../utils/typed_redux';
import {
logPositionSelectors as localLogPositionSelectors,
waffleFilterSelectors as localWaffleFilterSelectors,
waffleOptionsSelectors as localWaffleOptionsSelectors,
waffleTimeSelectors as localWaffleTimeSelectors,
@ -18,7 +17,6 @@ import { State } from './reducer';
const selectLocal = (state: State) => state.local;
export const logPositionSelectors = globalizeSelectors(selectLocal, localLogPositionSelectors);
export const waffleFilterSelectors = globalizeSelectors(selectLocal, localWaffleFilterSelectors);
export const waffleTimeSelectors = globalizeSelectors(selectLocal, localWaffleTimeSelectors);
export const waffleOptionsSelectors = globalizeSelectors(selectLocal, localWaffleOptionsSelectors);

View file

@ -9,14 +9,7 @@ import { createEpicMiddleware } from 'redux-observable';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import {
createRootEpic,
initialState,
logPositionSelectors,
reducer,
State,
waffleTimeSelectors,
} from '.';
import { createRootEpic, initialState, reducer, State, waffleTimeSelectors } from '.';
import { InfraApolloClient, InfraObservableApi } from '../lib/lib';
declare global {
@ -36,10 +29,6 @@ export function createStore({ apolloClient, observableApi }: StoreDependencies)
const middlewareDependencies = {
postToApi$: observableApi.pipe(map(({ post }) => post)),
apolloClient$: apolloClient,
selectIsAutoReloadingLogEntries: logPositionSelectors.selectIsAutoReloading,
selectIsAutoReloadingScrollLocked: logPositionSelectors.selectAutoReloadScrollLock,
selectLogTargetPosition: logPositionSelectors.selectTargetPosition,
selectVisibleLogMidpointOrTarget: logPositionSelectors.selectVisibleMidpointOrTarget,
selectWaffleTimeUpdatePolicyInterval: waffleTimeSelectors.selectTimeUpdatePolicyInterval,
};

View file

@ -22,7 +22,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
state: undefined,
};
const expectedSearchString =
"logFilter=(expression:'trace.id:433b4651687e18be2c6c8e3b11f53d09',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1565707203194))&sourceId=default";
"logFilter=(expression:'trace.id:433b4651687e18be2c6c8e3b11f53d09',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1565707203194),streamLive:!f)&sourceId=default";
const expectedRedirectPath = '/logs/stream?';
await pageObjects.common.navigateToActualUrl(