[Logs UI] Slim down local view configuration state (#31723)

This commit is contained in:
Felix Stürmer 2019-03-05 12:32:21 +01:00 committed by GitHub
parent 748c18b8f4
commit a9d75988bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 584 additions and 600 deletions

View file

@ -172,6 +172,7 @@
"classnames": "2.2.5",
"concat-stream": "1.5.1",
"constate": "^0.9.0",
"constate-latest": "npm:constate@^1.0.0",
"content-disposition": "0.5.3",
"copy-to-clipboard": "^3.0.8",
"core-js": "2.5.3",

View file

@ -0,0 +1,79 @@
/*
* 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 { mountHook } from 'test_utils/enzyme_helpers';
import { useLogViewConfiguration } from './log_view_configuration';
describe('useLogViewConfiguration hook', () => {
describe('textScale state', () => {
it('has a default value', () => {
const { getLastHookValue } = mountHook(() => useLogViewConfiguration().textScale);
expect(getLastHookValue()).toEqual('medium');
});
it('can be updated', () => {
const { act, getLastHookValue } = mountHook(() => useLogViewConfiguration());
act(({ setTextScale }) => {
setTextScale('small');
});
expect(getLastHookValue().textScale).toEqual('small');
});
});
describe('textWrap state', () => {
it('has a default value', () => {
const { getLastHookValue } = mountHook(() => useLogViewConfiguration().textWrap);
expect(getLastHookValue()).toEqual(true);
});
it('can be updated', () => {
const { act, getLastHookValue } = mountHook(() => useLogViewConfiguration());
act(({ setTextWrap }) => {
setTextWrap(false);
});
expect(getLastHookValue().textWrap).toEqual(false);
});
});
describe('intervalSize state', () => {
it('has a default value', () => {
const { getLastHookValue } = mountHook(() => useLogViewConfiguration().intervalSize);
expect(getLastHookValue()).toEqual(86400000);
});
it('can be updated', () => {
const { act, getLastHookValue } = mountHook(() => useLogViewConfiguration());
act(({ setIntervalSize }) => {
setIntervalSize(90000000);
});
expect(getLastHookValue().intervalSize).toEqual(90000000);
});
});
it('provides the available text scales', () => {
const { getLastHookValue } = mountHook(() => useLogViewConfiguration().availableTextScales);
expect(getLastHookValue()).toEqual(expect.any(Array));
expect(getLastHookValue().length).toBeGreaterThan(0);
});
it('provides the available interval sizes', () => {
const { getLastHookValue } = mountHook(() => useLogViewConfiguration().availableIntervalSizes);
expect(getLastHookValue()).toEqual(expect.any(Array));
expect(getLastHookValue().length).toBeGreaterThan(0);
});
});

View file

@ -0,0 +1,80 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import createContainer from 'constate-latest';
import { useState } from 'react';
export type TextScale = 'small' | 'medium' | 'large';
export const useLogViewConfiguration = () => {
// text scale
const [textScale, setTextScale] = useState<TextScale>('medium');
// text wrap
const [textWrap, setTextWrap] = useState<boolean>(true);
// minimap interval
const [intervalSize, setIntervalSize] = useState<number>(1000 * 60 * 60 * 24);
return {
availableIntervalSizes,
availableTextScales,
setTextScale,
setTextWrap,
textScale,
textWrap,
intervalSize,
setIntervalSize,
};
};
export const LogViewConfiguration = createContainer(useLogViewConfiguration);
/**
* constants
*/
export const availableTextScales: TextScale[] = ['large', 'medium', 'small'];
export const availableIntervalSizes = [
{
label: i18n.translate('xpack.infra.mapLogs.oneYearLabel', {
defaultMessage: '1 Year',
}),
intervalSize: 1000 * 60 * 60 * 24 * 365,
},
{
label: i18n.translate('xpack.infra.mapLogs.oneMonthLabel', {
defaultMessage: '1 Month',
}),
intervalSize: 1000 * 60 * 60 * 24 * 30,
},
{
label: i18n.translate('xpack.infra.mapLogs.oneWeekLabel', {
defaultMessage: '1 Week',
}),
intervalSize: 1000 * 60 * 60 * 24 * 7,
},
{
label: i18n.translate('xpack.infra.mapLogs.oneDayLabel', {
defaultMessage: '1 Day',
}),
intervalSize: 1000 * 60 * 60 * 24,
},
{
label: i18n.translate('xpack.infra.mapLogs.oneHourLabel', {
defaultMessage: '1 Hour',
}),
intervalSize: 1000 * 60 * 60,
},
{
label: i18n.translate('xpack.infra.mapLogs.oneMinuteLabel', {
defaultMessage: '1 Minute',
}),
intervalSize: 1000 * 60,
},
];

View file

@ -4,97 +4,42 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import React from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import React, { useContext, useMemo } from 'react';
import { logMinimapActions, logMinimapSelectors, State } from '../../store';
import { asChildFunctionRenderer } from '../../utils/typed_react';
import { bindPlainActionCreators } from '../../utils/typed_redux';
import { UrlStateContainer } from '../../utils/url_state';
export const withLogMinimap = connect(
(state: State) => ({
availableIntervalSizes,
intervalSize: logMinimapSelectors.selectMinimapIntervalSize(state),
urlState: selectMinimapUrlState(state),
}),
bindPlainActionCreators({
setIntervalSize: logMinimapActions.setMinimapIntervalSize,
})
);
export const WithLogMinimap = asChildFunctionRenderer(withLogMinimap);
export const availableIntervalSizes = [
{
label: i18n.translate('xpack.infra.mapLogs.oneYearLabel', {
defaultMessage: '1 Year',
}),
intervalSize: 1000 * 60 * 60 * 24 * 365,
},
{
label: i18n.translate('xpack.infra.mapLogs.oneMonthLabel', {
defaultMessage: '1 Month',
}),
intervalSize: 1000 * 60 * 60 * 24 * 30,
},
{
label: i18n.translate('xpack.infra.mapLogs.oneWeekLabel', {
defaultMessage: '1 Week',
}),
intervalSize: 1000 * 60 * 60 * 24 * 7,
},
{
label: i18n.translate('xpack.infra.mapLogs.oneDayLabel', {
defaultMessage: '1 Day',
}),
intervalSize: 1000 * 60 * 60 * 24,
},
{
label: i18n.translate('xpack.infra.mapLogs.oneHourLabel', {
defaultMessage: '1 Hour',
}),
intervalSize: 1000 * 60 * 60,
},
{
label: i18n.translate('xpack.infra.mapLogs.oneMinuteLabel', {
defaultMessage: '1 Minute',
}),
intervalSize: 1000 * 60,
},
];
import { LogViewConfiguration } from './log_view_configuration';
/**
* Url State
*/
interface LogMinimapUrlState {
intervalSize?: ReturnType<typeof logMinimapSelectors.selectMinimapIntervalSize>;
intervalSize?: number;
}
export const WithLogMinimapUrlState = () => (
<WithLogMinimap>
{({ urlState, setIntervalSize }) => (
<UrlStateContainer
urlState={urlState}
urlStateKey="logMinimap"
mapToUrlState={mapToUrlState}
onChange={newUrlState => {
if (newUrlState && newUrlState.intervalSize) {
setIntervalSize(newUrlState.intervalSize);
}
}}
onInitialize={newUrlState => {
if (newUrlState && newUrlState.intervalSize) {
setIntervalSize(newUrlState.intervalSize);
}
}}
/>
)}
</WithLogMinimap>
);
export const WithLogMinimapUrlState = () => {
const { intervalSize, setIntervalSize } = useContext(LogViewConfiguration.Context);
const urlState = useMemo(() => ({ intervalSize }), [intervalSize]);
return (
<UrlStateContainer
urlState={urlState}
urlStateKey="logMinimap"
mapToUrlState={mapToUrlState}
onChange={newUrlState => {
if (newUrlState && newUrlState.intervalSize) {
setIntervalSize(newUrlState.intervalSize);
}
}}
onInitialize={newUrlState => {
if (newUrlState && newUrlState.intervalSize) {
setIntervalSize(newUrlState.intervalSize);
}
}}
/>
);
};
const mapToUrlState = (value: any): LogMinimapUrlState | undefined =>
value
@ -105,10 +50,3 @@ const mapToUrlState = (value: any): LogMinimapUrlState | undefined =>
const mapToIntervalSizeUrlState = (value: any) =>
value && typeof value === 'number' ? value : undefined;
const selectMinimapUrlState = createSelector(
logMinimapSelectors.selectMinimapIntervalSize,
intervalSize => ({
intervalSize,
})
);

View file

@ -4,69 +4,47 @@
* 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 React, { useContext, useMemo } from 'react';
import { TextScale } from '../../../common/log_text_scale';
import { logTextviewActions, logTextviewSelectors, State } from '../../store';
import { asChildFunctionRenderer } from '../../utils/typed_react';
import { bindPlainActionCreators } from '../../utils/typed_redux';
import { UrlStateContainer } from '../../utils/url_state';
const availableTextScales = ['large', 'medium', 'small'] as TextScale[];
export const withLogTextview = connect(
(state: State) => ({
availableTextScales,
textScale: logTextviewSelectors.selectTextviewScale(state),
urlState: selectTextviewUrlState(state),
wrap: logTextviewSelectors.selectTextviewWrap(state),
}),
bindPlainActionCreators({
setTextScale: logTextviewActions.setTextviewScale,
setTextWrap: logTextviewActions.setTextviewWrap,
})
);
export const WithLogTextview = asChildFunctionRenderer(withLogTextview);
/**
* Url State
*/
import { availableTextScales, LogViewConfiguration, TextScale } from './log_view_configuration';
interface LogTextviewUrlState {
textScale?: ReturnType<typeof logTextviewSelectors.selectTextviewScale>;
wrap?: ReturnType<typeof logTextviewSelectors.selectTextviewWrap>;
textScale?: TextScale;
wrap?: boolean;
}
export const WithLogTextviewUrlState = () => (
<WithLogTextview>
{({ urlState, setTextScale, setTextWrap }) => (
<UrlStateContainer
urlState={urlState}
urlStateKey="logTextview"
mapToUrlState={mapToUrlState}
onChange={newUrlState => {
if (newUrlState && newUrlState.textScale) {
setTextScale(newUrlState.textScale);
}
if (newUrlState && typeof newUrlState.wrap !== 'undefined') {
setTextWrap(newUrlState.wrap);
}
}}
onInitialize={newUrlState => {
if (newUrlState && newUrlState.textScale) {
setTextScale(newUrlState.textScale);
}
if (newUrlState && typeof newUrlState.wrap !== 'undefined') {
setTextWrap(newUrlState.wrap);
}
}}
/>
)}
</WithLogTextview>
);
export const WithLogTextviewUrlState = () => {
const { textScale, textWrap, setTextScale, setTextWrap } = useContext(
LogViewConfiguration.Context
);
const urlState = useMemo(() => ({ textScale, wrap: textWrap }), [textScale, textWrap]);
return (
<UrlStateContainer
urlState={urlState}
urlStateKey="logTextview"
mapToUrlState={mapToUrlState}
onChange={newUrlState => {
if (newUrlState && newUrlState.textScale) {
setTextScale(newUrlState.textScale);
}
if (newUrlState && typeof newUrlState.wrap !== 'undefined') {
setTextWrap(newUrlState.wrap);
}
}}
onInitialize={newUrlState => {
if (newUrlState && newUrlState.textScale) {
setTextScale(newUrlState.textScale);
}
if (newUrlState && typeof newUrlState.wrap !== 'undefined') {
setTextWrap(newUrlState.wrap);
}
}}
/>
);
};
const mapToUrlState = (value: any): LogTextviewUrlState | undefined =>
value
@ -80,12 +58,3 @@ const mapToTextScaleUrlState = (value: any) =>
availableTextScales.includes(value) ? (value as TextScale) : undefined;
const mapToWrapUrlState = (value: any) => (typeof value === 'boolean' ? value : undefined);
const selectTextviewUrlState = createSelector(
logTextviewSelectors.selectTextviewScale,
logTextviewSelectors.selectTextviewWrap,
(textScale, wrap) => ({
textScale,
wrap,
})
);

View file

@ -21,6 +21,7 @@ import { ColumnarPage } from '../../components/page';
import { SourceConfigurationFlyout } from '../../components/source_configuration';
import { WithSourceConfigurationFlyoutState } from '../../components/source_configuration/source_configuration_flyout_state';
import { LogViewConfiguration } from '../../containers/logs/log_view_configuration';
import { WithLogFilter, WithLogFilterUrlState } from '../../containers/logs/with_log_filter';
import { WithLogFlyout } from '../../containers/logs/with_log_flyout';
import { WithFlyoutOptions } from '../../containers/logs/with_log_flyout_options';
@ -43,128 +44,130 @@ export const LogsPage = injectI18n(
const { intl } = this.props;
return (
<ColumnarPage>
<Header
breadcrumbs={[
{
text: intl.formatMessage({
id: 'xpack.infra.logsPage.logsBreadcrumbsText',
defaultMessage: 'Logs',
}),
},
]}
/>
<WithSource>
{({
derivedIndexPattern,
hasFailed,
isLoading,
lastFailureMessage,
load,
logIndicesExist,
sourceId,
}) => (
<>
<DocumentTitle
title={intl.formatMessage({
id: 'xpack.infra.logsPage.documentTitle',
<LogViewConfiguration.Provider>
<ColumnarPage>
<Header
breadcrumbs={[
{
text: intl.formatMessage({
id: 'xpack.infra.logsPage.logsBreadcrumbsText',
defaultMessage: 'Logs',
})}
/>
<HelpCenterContent
feedbackLink="https://discuss.elastic.co/c/logs"
feedbackLinkText={intl.formatMessage({
id: 'xpack.infra.logsPage.logsHelpContent.feedbackLinkText',
defaultMessage: 'Provide feedback for Logs',
})}
/>
<SourceConfigurationFlyout />
{isLoading ? (
<SourceLoadingPage />
) : logIndicesExist ? (
<>
<WithLogFilterUrlState indexPattern={derivedIndexPattern} />
<WithLogPositionUrlState />
<WithLogMinimapUrlState />
<WithLogTextviewUrlState />
<WithFlyoutOptionsUrlState />
<LogsToolbar />
<WithLogFilter indexPattern={derivedIndexPattern}>
{({ applyFilterQueryFromKueryExpression }) => (
<React.Fragment>
<WithFlyoutOptions>
{({ showFlyout, setFlyoutItem }) => (
<LogsPageContent
showFlyout={showFlyout}
setFlyoutItem={setFlyoutItem}
/>
)}
</WithFlyoutOptions>
<WithLogFlyout sourceId={sourceId}>
{({ flyoutItem, hideFlyout, loading }) => (
<LogFlyout
setFilter={applyFilterQueryFromKueryExpression}
flyoutItem={flyoutItem}
hideFlyout={hideFlyout}
loading={loading}
/>
)}
</WithLogFlyout>
</React.Fragment>
}),
},
]}
/>
<WithSource>
{({
derivedIndexPattern,
hasFailed,
isLoading,
lastFailureMessage,
load,
logIndicesExist,
sourceId,
}) => (
<>
<DocumentTitle
title={intl.formatMessage({
id: 'xpack.infra.logsPage.documentTitle',
defaultMessage: 'Logs',
})}
/>
<HelpCenterContent
feedbackLink="https://discuss.elastic.co/c/logs"
feedbackLinkText={intl.formatMessage({
id: 'xpack.infra.logsPage.logsHelpContent.feedbackLinkText',
defaultMessage: 'Provide feedback for Logs',
})}
/>
<SourceConfigurationFlyout />
{isLoading ? (
<SourceLoadingPage />
) : logIndicesExist ? (
<>
<WithLogFilterUrlState indexPattern={derivedIndexPattern} />
<WithLogPositionUrlState />
<WithLogMinimapUrlState />
<WithLogTextviewUrlState />
<WithFlyoutOptionsUrlState />
<LogsToolbar />
<WithLogFilter indexPattern={derivedIndexPattern}>
{({ applyFilterQueryFromKueryExpression }) => (
<React.Fragment>
<WithFlyoutOptions>
{({ showFlyout, setFlyoutItem }) => (
<LogsPageContent
showFlyout={showFlyout}
setFlyoutItem={setFlyoutItem}
/>
)}
</WithFlyoutOptions>
<WithLogFlyout sourceId={sourceId}>
{({ flyoutItem, hideFlyout, loading }) => (
<LogFlyout
setFilter={applyFilterQueryFromKueryExpression}
flyoutItem={flyoutItem}
hideFlyout={hideFlyout}
loading={loading}
/>
)}
</WithLogFlyout>
</React.Fragment>
)}
</WithLogFilter>
</>
) : hasFailed ? (
<SourceErrorPage errorMessage={lastFailureMessage || ''} retry={load} />
) : (
<WithKibanaChrome>
{({ basePath }) => (
<NoIndices
title={intl.formatMessage({
id: 'xpack.infra.logsPage.noLoggingIndicesTitle',
defaultMessage: "Looks like you don't have any logging indices.",
})}
message={intl.formatMessage({
id: 'xpack.infra.logsPage.noLoggingIndicesDescription',
defaultMessage: "Let's add some!",
})}
actions={
<EuiFlexGroup>
<EuiFlexItem>
<EuiButton
href={`${basePath}/app/kibana#/home/tutorial_directory/logging`}
color="primary"
fill
>
{intl.formatMessage({
id:
'xpack.infra.logsPage.noLoggingIndicesInstructionsActionLabel',
defaultMessage: 'View setup instructions',
})}
</EuiButton>
</EuiFlexItem>
<EuiFlexItem>
<WithSourceConfigurationFlyoutState>
{({ enable }) => (
<EuiButton color="primary" onClick={enable}>
{intl.formatMessage({
id: 'xpack.infra.configureSourceActionLabel',
defaultMessage: 'Change source configuration',
})}
</EuiButton>
)}
</WithSourceConfigurationFlyoutState>
</EuiFlexItem>
</EuiFlexGroup>
}
/>
)}
</WithLogFilter>
</>
) : hasFailed ? (
<SourceErrorPage errorMessage={lastFailureMessage || ''} retry={load} />
) : (
<WithKibanaChrome>
{({ basePath }) => (
<NoIndices
title={intl.formatMessage({
id: 'xpack.infra.logsPage.noLoggingIndicesTitle',
defaultMessage: "Looks like you don't have any logging indices.",
})}
message={intl.formatMessage({
id: 'xpack.infra.logsPage.noLoggingIndicesDescription',
defaultMessage: "Let's add some!",
})}
actions={
<EuiFlexGroup>
<EuiFlexItem>
<EuiButton
href={`${basePath}/app/kibana#/home/tutorial_directory/logging`}
color="primary"
fill
>
{intl.formatMessage({
id:
'xpack.infra.logsPage.noLoggingIndicesInstructionsActionLabel',
defaultMessage: 'View setup instructions',
})}
</EuiButton>
</EuiFlexItem>
<EuiFlexItem>
<WithSourceConfigurationFlyoutState>
{({ enable }) => (
<EuiButton color="primary" onClick={enable}>
{intl.formatMessage({
id: 'xpack.infra.configureSourceActionLabel',
defaultMessage: 'Change source configuration',
})}
</EuiButton>
)}
</WithSourceConfigurationFlyoutState>
</EuiFlexItem>
</EuiFlexGroup>
}
/>
)}
</WithKibanaChrome>
)}
</>
)}
</WithSource>
</ColumnarPage>
</WithKibanaChrome>
)}
</>
)}
</WithSource>
</ColumnarPage>
</LogViewConfiguration.Provider>
);
}
}

View file

@ -4,16 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import React, { useContext } from 'react';
import styled from 'styled-components';
import { AutoSizer } from '../../components/auto_sizer';
import { LogMinimap } from '../../components/logging/log_minimap';
import { ScrollableLogTextStreamView } from '../../components/logging/log_text_stream';
import { PageContent } from '../../components/page';
import { WithLogMinimap } from '../../containers/logs/with_log_minimap';
import { LogViewConfiguration } from '../../containers/logs/log_view_configuration';
import { WithLogPosition } from '../../containers/logs/with_log_position';
import { WithLogTextview } from '../../containers/logs/with_log_textview';
import { WithStreamItems } from '../../containers/logs/with_stream_items';
import { WithSummary } from '../../containers/logs/with_summary';
@ -22,95 +21,91 @@ interface Props {
showFlyout: () => void;
}
export const LogsPageContent: React.SFC<Props> = ({ showFlyout, setFlyoutItem }) => (
<PageContent>
<AutoSizer content>
{({ measureRef, content: { width = 0, height = 0 } }) => (
<LogPageEventStreamColumn innerRef={measureRef}>
<WithLogTextview>
{({ textScale, wrap }) => (
<WithLogPosition>
{({
isAutoReloading,
jumpToTargetPosition,
reportVisiblePositions,
targetPosition,
}) => (
<WithStreamItems>
export const LogsPageContent: React.FunctionComponent<Props> = ({ showFlyout, setFlyoutItem }) => {
const { intervalSize, textScale, textWrap } = useContext(LogViewConfiguration.Context);
return (
<PageContent>
<AutoSizer content>
{({ measureRef, content: { width = 0, height = 0 } }) => (
<LogPageEventStreamColumn innerRef={measureRef}>
<WithLogPosition>
{({
isAutoReloading,
jumpToTargetPosition,
reportVisiblePositions,
targetPosition,
}) => (
<WithStreamItems>
{({
hasMoreAfterEnd,
hasMoreBeforeStart,
isLoadingMore,
isReloading,
items,
lastLoadedTime,
loadNewerEntries,
}) => (
<ScrollableLogTextStreamView
hasMoreAfterEnd={hasMoreAfterEnd}
hasMoreBeforeStart={hasMoreBeforeStart}
height={height}
isLoadingMore={isLoadingMore}
isReloading={isReloading}
isStreaming={isAutoReloading}
items={items}
jumpToTarget={jumpToTargetPosition}
lastLoadedTime={lastLoadedTime}
loadNewerItems={loadNewerEntries}
reportVisibleInterval={reportVisiblePositions}
scale={textScale}
target={targetPosition}
width={width}
wrap={textWrap}
setFlyoutItem={setFlyoutItem}
showFlyout={showFlyout}
/>
)}
</WithStreamItems>
)}
</WithLogPosition>
</LogPageEventStreamColumn>
)}
</AutoSizer>
<AutoSizer content>
{({ measureRef, content: { width = 0, height = 0 } }) => {
return (
<LogPageMinimapColumn innerRef={measureRef}>
<WithSummary>
{({ buckets }) => (
<WithLogPosition>
{({
hasMoreAfterEnd,
hasMoreBeforeStart,
isLoadingMore,
isReloading,
items,
lastLoadedTime,
loadNewerEntries,
jumpToTargetPosition,
reportVisibleSummary,
visibleMidpointTime,
visibleTimeInterval,
}) => (
<ScrollableLogTextStreamView
hasMoreAfterEnd={hasMoreAfterEnd}
hasMoreBeforeStart={hasMoreBeforeStart}
<LogMinimap
height={height}
isLoadingMore={isLoadingMore}
isReloading={isReloading}
isStreaming={isAutoReloading}
items={items}
jumpToTarget={jumpToTargetPosition}
lastLoadedTime={lastLoadedTime}
loadNewerItems={loadNewerEntries}
reportVisibleInterval={reportVisiblePositions}
scale={textScale}
target={targetPosition}
width={width}
wrap={wrap}
setFlyoutItem={setFlyoutItem}
showFlyout={showFlyout}
highlightedInterval={visibleTimeInterval}
intervalSize={intervalSize}
jumpToTarget={jumpToTargetPosition}
reportVisibleInterval={reportVisibleSummary}
summaryBuckets={buckets}
target={visibleMidpointTime}
/>
)}
</WithStreamItems>
</WithLogPosition>
)}
</WithLogPosition>
)}
</WithLogTextview>
</LogPageEventStreamColumn>
)}
</AutoSizer>
<AutoSizer content>
{({ measureRef, content: { width = 0, height = 0 } }) => {
return (
<LogPageMinimapColumn innerRef={measureRef}>
<WithLogMinimap>
{({ intervalSize }) => (
<WithSummary>
{({ buckets }) => (
<WithLogPosition>
{({
jumpToTargetPosition,
reportVisibleSummary,
visibleMidpointTime,
visibleTimeInterval,
}) => (
<LogMinimap
height={height}
width={width}
highlightedInterval={visibleTimeInterval}
intervalSize={intervalSize}
jumpToTarget={jumpToTargetPosition}
reportVisibleInterval={reportVisibleSummary}
summaryBuckets={buckets}
target={visibleMidpointTime}
/>
)}
</WithLogPosition>
)}
</WithSummary>
)}
</WithLogMinimap>
</LogPageMinimapColumn>
);
}}
</AutoSizer>
</PageContent>
);
</WithSummary>
</LogPageMinimapColumn>
);
}}
</AutoSizer>
</PageContent>
);
};
const LogPageEventStreamColumn = styled.div`
flex: 1 0 0%;

View file

@ -6,7 +6,7 @@
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { injectI18n } from '@kbn/i18n/react';
import React from 'react';
import React, { useContext } from 'react';
import { AutocompleteField } from '../../components/autocomplete_field';
import { Toolbar } from '../../components/eui';
@ -16,95 +16,97 @@ import { LogTextScaleControls } from '../../components/logging/log_text_scale_co
import { LogTextWrapControls } from '../../components/logging/log_text_wrap_controls';
import { LogTimeControls } from '../../components/logging/log_time_controls';
import { SourceConfigurationButton } from '../../components/source_configuration';
import { LogViewConfiguration } from '../../containers/logs/log_view_configuration';
import { WithLogFilter } from '../../containers/logs/with_log_filter';
import { WithLogMinimap } from '../../containers/logs/with_log_minimap';
import { WithLogPosition } from '../../containers/logs/with_log_position';
import { WithLogTextview } from '../../containers/logs/with_log_textview';
import { WithKueryAutocompletion } from '../../containers/with_kuery_autocompletion';
import { WithSource } from '../../containers/with_source';
export const LogsToolbar = injectI18n(({ intl }) => (
<Toolbar>
<WithSource>
{({ configuration, derivedIndexPattern }) => (
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="s">
<EuiFlexItem>
<WithKueryAutocompletion indexPattern={derivedIndexPattern}>
{({ isLoadingSuggestions, loadSuggestions, suggestions }) => (
<WithLogFilter indexPattern={derivedIndexPattern}>
{({
applyFilterQueryFromKueryExpression,
filterQueryDraft,
isFilterQueryDraftValid,
setFilterQueryDraftFromKueryExpression,
}) => (
<AutocompleteField
isLoadingSuggestions={isLoadingSuggestions}
isValid={isFilterQueryDraftValid}
loadSuggestions={loadSuggestions}
onChange={setFilterQueryDraftFromKueryExpression}
onSubmit={applyFilterQueryFromKueryExpression}
placeholder={intl.formatMessage({
id: 'xpack.infra.logsPage.toolbar.kqlSearchFieldPlaceholder',
defaultMessage: 'Search for log entries… (e.g. host.name:host-1)',
})}
suggestions={suggestions}
value={filterQueryDraft ? filterQueryDraft.expression : ''}
/>
)}
</WithLogFilter>
)}
</WithKueryAutocompletion>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<SourceConfigurationButton />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<LogCustomizationMenu>
<WithLogMinimap>
{({ availableIntervalSizes, intervalSize, setIntervalSize }) => (
<LogMinimapScaleControls
availableIntervalSizes={availableIntervalSizes}
setIntervalSize={setIntervalSize}
intervalSize={intervalSize}
export const LogsToolbar = injectI18n(({ intl }) => {
const {
availableIntervalSizes,
availableTextScales,
intervalSize,
setIntervalSize,
setTextScale,
setTextWrap,
textScale,
textWrap,
} = useContext(LogViewConfiguration.Context);
return (
<Toolbar>
<WithSource>
{({ derivedIndexPattern }) => (
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="s">
<EuiFlexItem>
<WithKueryAutocompletion indexPattern={derivedIndexPattern}>
{({ isLoadingSuggestions, loadSuggestions, suggestions }) => (
<WithLogFilter indexPattern={derivedIndexPattern}>
{({
applyFilterQueryFromKueryExpression,
filterQueryDraft,
isFilterQueryDraftValid,
setFilterQueryDraftFromKueryExpression,
}) => (
<AutocompleteField
isLoadingSuggestions={isLoadingSuggestions}
isValid={isFilterQueryDraftValid}
loadSuggestions={loadSuggestions}
onChange={setFilterQueryDraftFromKueryExpression}
onSubmit={applyFilterQueryFromKueryExpression}
placeholder={intl.formatMessage({
id: 'xpack.infra.logsPage.toolbar.kqlSearchFieldPlaceholder',
defaultMessage: 'Search for log entries… (e.g. host.name:host-1)',
})}
suggestions={suggestions}
value={filterQueryDraft ? filterQueryDraft.expression : ''}
/>
)}
</WithLogFilter>
)}
</WithKueryAutocompletion>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<SourceConfigurationButton />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<LogCustomizationMenu>
<LogMinimapScaleControls
availableIntervalSizes={availableIntervalSizes}
setIntervalSize={setIntervalSize}
intervalSize={intervalSize}
/>
<LogTextWrapControls wrap={textWrap} setTextWrap={setTextWrap} />
<LogTextScaleControls
availableTextScales={availableTextScales}
textScale={textScale}
setTextScale={setTextScale}
/>
</LogCustomizationMenu>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<WithLogPosition resetOnUnmount>
{({
visibleMidpointTime,
isAutoReloading,
jumpToTargetPositionTime,
startLiveStreaming,
stopLiveStreaming,
}) => (
<LogTimeControls
currentTime={visibleMidpointTime}
isLiveStreaming={isAutoReloading}
jumpToTime={jumpToTargetPositionTime}
startLiveStreaming={startLiveStreaming}
stopLiveStreaming={stopLiveStreaming}
/>
)}
</WithLogMinimap>
<WithLogTextview>
{({ availableTextScales, textScale, setTextScale, setTextWrap, wrap }) => (
<>
<LogTextWrapControls wrap={wrap} setTextWrap={setTextWrap} />
<LogTextScaleControls
availableTextScales={availableTextScales}
textScale={textScale}
setTextScale={setTextScale}
/>
</>
)}
</WithLogTextview>
</LogCustomizationMenu>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<WithLogPosition resetOnUnmount>
{({
visibleMidpointTime,
isAutoReloading,
jumpToTargetPositionTime,
startLiveStreaming,
stopLiveStreaming,
}) => (
<LogTimeControls
currentTime={visibleMidpointTime}
isLiveStreaming={isAutoReloading}
jumpToTime={jumpToTargetPositionTime}
startLiveStreaming={startLiveStreaming}
stopLiveStreaming={stopLiveStreaming}
/>
)}
</WithLogPosition>
</EuiFlexItem>
</EuiFlexGroup>
)}
</WithSource>
</Toolbar>
));
</WithLogPosition>
</EuiFlexItem>
</EuiFlexGroup>
)}
</WithSource>
</Toolbar>
);
});

View file

@ -6,9 +6,7 @@
export {
logFilterActions,
logMinimapActions,
logPositionActions,
logTextviewActions,
metricTimeActions,
waffleFilterActions,
waffleTimeActions,

View file

@ -5,9 +5,7 @@
*/
export { logFilterActions } from './log_filter';
export { logMinimapActions } from './log_minimap';
export { logPositionActions } from './log_position';
export { logTextviewActions } from './log_textview';
export { metricTimeActions } from './metric_time';
export { waffleFilterActions } from './waffle_filter';
export { waffleTimeActions } from './waffle_time';

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 actionCreatorFactory from 'typescript-fsa';
const actionCreator = actionCreatorFactory('x-pack/infra/local/log_minimap');
export const setMinimapIntervalSize = actionCreator<number>('SET_MINIMAP_INTERVAL_SIZE');

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 logMinimapActions from './actions';
import * as logMinimapSelectors from './selectors';
export { logMinimapActions, logMinimapSelectors };
export * from './reducer';

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 { reducerWithInitialState } from 'typescript-fsa-reducers/dist';
import { setMinimapIntervalSize } from './actions';
export interface LogMinimapState {
intervalSize: number;
}
export const initialLogMinimapState: LogMinimapState = {
intervalSize: 1000 * 60 * 60 * 24,
};
export const logMinimapReducer = reducerWithInitialState(initialLogMinimapState)
.case(setMinimapIntervalSize, (state, intervalSize) => ({
intervalSize,
}))
.build();

View file

@ -1,9 +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 { LogMinimapState } from './reducer';
export const selectMinimapIntervalSize = (state: LogMinimapState) => state.intervalSize;

View file

@ -1,15 +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 { TextScale } from '../../../../common/log_text_scale';
const actionCreator = actionCreatorFactory('x-pack/infra/local/log_textview');
export const setTextviewScale = actionCreator<TextScale>('SET_TEXTVIEW_SCALE');
export const setTextviewWrap = actionCreator<boolean>('SET_TEXTVIEW_WRAP');

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 logTextviewActions from './actions';
import * as logTextviewSelectors from './selectors';
export { logTextviewActions, logTextviewSelectors };
export * from './reducer';

View file

@ -1,36 +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 { TextScale } from '../../../../common/log_text_scale';
import { setTextviewScale, setTextviewWrap } from './actions';
export interface LogTextviewState {
scale: TextScale;
wrap: boolean;
}
export const initialLogTextviewState: LogTextviewState = {
scale: 'medium',
wrap: true,
};
const textviewScaleReducer = reducerWithInitialState(initialLogTextviewState.scale).case(
setTextviewScale,
(state, scale) => scale
);
const textviewWrapReducer = reducerWithInitialState(initialLogTextviewState.wrap).case(
setTextviewWrap,
(state, wrap) => wrap
);
export const logTextviewReducer = combineReducers<LogTextviewState>({
scale: textviewScaleReducer,
wrap: textviewWrapReducer,
});

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 { LogTextviewState } from './reducer';
export const selectTextviewScale = (state: LogTextviewState) => state.scale;
export const selectTextviewWrap = (state: LogTextviewState) => state.wrap;

View file

@ -8,9 +8,7 @@ import { combineReducers } from 'redux';
import { initialLogFilterState, logFilterReducer, LogFilterState } from './log_filter';
import { flyoutOptionsReducer, FlyoutOptionsState, initialFlyoutOptionsState } from './log_flyout';
import { initialLogMinimapState, logMinimapReducer, LogMinimapState } from './log_minimap';
import { initialLogPositionState, logPositionReducer, LogPositionState } from './log_position';
import { initialLogTextviewState, logTextviewReducer, LogTextviewState } from './log_textview';
import { initialMetricTimeState, metricTimeReducer, MetricTimeState } from './metric_time';
import { initialWaffleFilterState, waffleFilterReducer, WaffleFilterState } from './waffle_filter';
import {
@ -22,9 +20,7 @@ import { initialWaffleTimeState, waffleTimeReducer, WaffleTimeState } from './wa
export interface LocalState {
logFilter: LogFilterState;
logMinimap: LogMinimapState;
logPosition: LogPositionState;
logTextview: LogTextviewState;
metricTime: MetricTimeState;
waffleFilter: WaffleFilterState;
waffleTime: WaffleTimeState;
@ -34,9 +30,7 @@ export interface LocalState {
export const initialLocalState: LocalState = {
logFilter: initialLogFilterState,
logMinimap: initialLogMinimapState,
logPosition: initialLogPositionState,
logTextview: initialLogTextviewState,
metricTime: initialMetricTimeState,
waffleFilter: initialWaffleFilterState,
waffleTime: initialWaffleTimeState,
@ -46,9 +40,7 @@ export const initialLocalState: LocalState = {
export const localReducer = combineReducers<LocalState>({
logFilter: logFilterReducer,
logMinimap: logMinimapReducer,
logPosition: logPositionReducer,
logTextview: logTextviewReducer,
metricTime: metricTimeReducer,
waffleFilter: waffleFilterReducer,
waffleTime: waffleTimeReducer,

View file

@ -7,9 +7,7 @@
import { globalizeSelectors } from '../../utils/typed_redux';
import { logFilterSelectors as innerLogFilterSelectors } from './log_filter';
import { flyoutOptionsSelectors as innerFlyoutOptionsSelectors } from './log_flyout';
import { logMinimapSelectors as innerLogMinimapSelectors } from './log_minimap';
import { logPositionSelectors as innerLogPositionSelectors } from './log_position';
import { logTextviewSelectors as innerLogTextviewSelectors } from './log_textview';
import { metricTimeSelectors as innerMetricTimeSelectors } from './metric_time';
import { LocalState } from './reducer';
import { waffleFilterSelectors as innerWaffleFilterSelectors } from './waffle_filter';
@ -21,21 +19,11 @@ export const logFilterSelectors = globalizeSelectors(
innerLogFilterSelectors
);
export const logMinimapSelectors = globalizeSelectors(
(state: LocalState) => state.logMinimap,
innerLogMinimapSelectors
);
export const logPositionSelectors = globalizeSelectors(
(state: LocalState) => state.logPosition,
innerLogPositionSelectors
);
export const logTextviewSelectors = globalizeSelectors(
(state: LocalState) => state.logTextview,
innerLogTextviewSelectors
);
export const metricTimeSelectors = globalizeSelectors(
(state: LocalState) => state.metricTime,
innerMetricTimeSelectors

View file

@ -11,9 +11,7 @@ import { globalizeSelectors } from '../utils/typed_redux';
import {
flyoutOptionsSelectors as localFlyoutOptionsSelectors,
logFilterSelectors as localLogFilterSelectors,
logMinimapSelectors as localLogMinimapSelectors,
logPositionSelectors as localLogPositionSelectors,
logTextviewSelectors as localLogTextviewSelectors,
metricTimeSelectors as localMetricTimeSelectors,
waffleFilterSelectors as localWaffleFilterSelectors,
waffleOptionsSelectors as localWaffleOptionsSelectors,
@ -32,9 +30,7 @@ import {
const selectLocal = (state: State) => state.local;
export const logFilterSelectors = globalizeSelectors(selectLocal, localLogFilterSelectors);
export const logMinimapSelectors = globalizeSelectors(selectLocal, localLogMinimapSelectors);
export const logPositionSelectors = globalizeSelectors(selectLocal, localLogPositionSelectors);
export const logTextviewSelectors = globalizeSelectors(selectLocal, localLogTextviewSelectors);
export const metricTimeSelectors = globalizeSelectors(selectLocal, localMetricTimeSelectors);
export const waffleFilterSelectors = globalizeSelectors(selectLocal, localWaffleFilterSelectors);
export const waffleTimeSelectors = globalizeSelectors(selectLocal, localWaffleTimeSelectors);

View file

@ -14,6 +14,7 @@
import { I18nProvider, InjectedIntl, intlShape } from '@kbn/i18n/react';
import { mount, ReactWrapper, render, shallow } from 'enzyme';
import React, { ReactElement, ValidationMap } from 'react';
import { act as reactAct } from 'react-dom/test-utils';
// Use fake component to extract `intl` property to use in tests.
const { intl } = (mount(
@ -113,3 +114,69 @@ export function renderWithIntl<T>(
return render(nodeWithIntlProp(node), options);
}
/**
* A wrapper object to provide access to the state of a hook under test and to
* enable interaction with that hook.
*/
interface ReactHookWrapper<HookValue> {
/* Ensures that async React operations have settled before and after the
* given actor callback is called. */
act: (actor: (lastHookValue: HookValue) => void) => void;
/* The enzyme wrapper around the test component. */
component: ReactWrapper;
/* The most recent value return the by test harness of the hook. */
getLastHookValue: () => HookValue;
/* The jest Mock function that receives the hook values for introspection. */
hookValueCallback: jest.Mock;
}
/**
* Allows for execution of hooks inside of a test component which records the
* returned values.
*
* @param body A function that calls the hook and returns data derived from it
* @param WrapperComponent A component that, if provided, will be wrapped
* around the test component. This can be useful to provide context values.
* @return {ReactHookWrapper} An object providing access to the hook state and
* functions to interact with it.
*/
export const mountHook = <HookValue extends any>(
body: () => HookValue,
WrapperComponent?: React.ComponentType
): ReactHookWrapper<HookValue> => {
const hookValueCallback = jest.fn();
const act = (actor: (lastHookValue: HookValue) => void) => {
reactAct(() => actor(getLastHookValue()));
component.update();
};
const getLastHookValue = () => {
const calls = hookValueCallback.mock.calls;
if (calls.length <= 0) {
throw Error('No recent hook value present.');
}
return calls[calls.length - 1][0];
};
const TestComponent = () => {
hookValueCallback(body());
return null;
};
const component = WrapperComponent
? mount(
<WrapperComponent>
<TestComponent />
</WrapperComponent>
)
: mount(<TestComponent />);
return {
act,
component,
getLastHookValue,
hookValueCallback,
};
};

View file

@ -6947,6 +6947,11 @@ constants-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=
"constate-latest@npm:constate@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/constate/-/constate-1.0.0.tgz#93fa87108e364a05e93b3597e22d53adaf86776d"
integrity sha512-b1Pip712fAQ1el4pndiYeVMXAzlbrD8+Z8ik79TOQc3ZJNjxKGawT0gCPpANsNbT4eHszDZ/8472hvL1rowUhQ==
constate@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/constate/-/constate-0.9.0.tgz#877197ef8fbcacee95672a7e98f7b21dec818891"