Skip to content

Commit

Permalink
[Log UI] Flyout for Log Events (elastic#28885) (elastic#29729)
Browse files Browse the repository at this point in the history
* Adding flyout to log viewer

* Adding filtering

* Fixing typescript errors

* Adding a test for graphql; fixing test data for 7.0.0

* Adding terminate_after:1 to logItem request

* fixing test data

* Switching back to old data

* Fixing data for tests

* Adding i18n translations

* changing label from add to set

* Make flyout call more robust; fixing typings

* Adding loading screen to flyout

* Fixing linting errors

* Update x-pack/plugins/infra/public/components/logging/log_flyout.tsx

Co-Authored-By: simianhacker <[email protected]>

* Fixing visible mis-spelling

* Fixing types

* Change withLogFlyout to be conditional; Add icon instead of onClick for flyout

* Adding dark mode support

* Adding user-select:none to icon div

* Removing remnants of a failed experiment

* Adding aria-label to view details button

* Fixing padding on date element

* Removing unused variable that somehow got past the linters

* Fixing empty_kibana

* Fixing data for infra

* Fixing merge weirdness
  • Loading branch information
simianhacker authored Jan 31, 2019
1 parent 5c19a8f commit bb7a67a
Show file tree
Hide file tree
Showing 35 changed files with 1,170 additions and 56 deletions.
114 changes: 114 additions & 0 deletions x-pack/plugins/infra/public/components/logging/log_flyout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* 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 {
EuiBasicTable,
EuiButtonIcon,
EuiFlyout,
EuiFlyoutBody,
EuiFlyoutHeader,
EuiTitle,
EuiToolTip,
} from '@elastic/eui';
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
import React from 'react';
import styled from 'styled-components';
import { InfraLogItem, InfraLogItemField } from '../../graphql/types';
import { InfraLoadingPanel } from '../loading';
interface Props {
flyoutItem: InfraLogItem | null;
hideFlyout: () => void;
setFilter: (filter: string) => void;
intl: InjectedIntl;
loading: boolean;
}

export const LogFlyout = injectI18n(
({ flyoutItem, loading, hideFlyout, setFilter, intl }: Props) => {
const handleFilter = (field: InfraLogItemField) => () => {
const filter = `${field.field}:"${field.value}"`;
setFilter(filter);
};

const columns = [
{
field: 'field',
name: intl.formatMessage({
defaultMessage: 'Field',
id: 'xpack.infra.logFlyout.fieldColumnLabel',
}),
sortable: true,
},
{
field: 'value',
name: intl.formatMessage({
defaultMessage: 'Value',
id: 'xpack.infra.logFlyout.valueColumnLabel',
}),
sortable: true,
render: (name: string, item: InfraLogItemField) => (
<span>
<EuiToolTip
content={intl.formatMessage({
id: 'xpack.infra.logFlyout.setFilterTooltip',
defaultMessage: 'Set Filter',
})}
>
<EuiButtonIcon
color="text"
iconType="filter"
aria-label={intl.formatMessage({
id: 'xpack.infra.logFlyout.filterAriaLabel',
defaultMessage: 'Filter',
})}
onClick={handleFilter(item)}
/>
</EuiToolTip>
{item.value}
</span>
),
},
];
return (
<EuiFlyout onClose={() => hideFlyout()} size="m">
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s">
<h3 id="flyoutTitle">
<FormattedMessage
defaultMessage="Log event document details"
id="xpack.infra.logFlyout.flyoutTitle"
/>
</h3>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
{loading || flyoutItem === null ? (
<InfraFlyoutLoadingPanel>
<InfraLoadingPanel
height="100%"
width="100%"
text={intl.formatMessage({
id: 'xpack.infra.logFlyout.loadingMessage',
defaultMessage: 'Loading Event',
})}
/>
</InfraFlyoutLoadingPanel>
) : (
<EuiBasicTable columns={columns} items={flyoutItem.fields} />
)}
</EuiFlyoutBody>
</EuiFlyout>
);
}
);

export const InfraFlyoutLoadingPanel = styled.div`
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const LogTextStreamItemDateFieldWrapper = LogTextStreamItemField.extend.attrs<{
border-right: solid 2px ${props => props.theme.eui.euiColorLightShade};
color: ${props => props.theme.eui.euiColorDarkShade};
white-space: pre;
padding: 0 ${props => props.theme.eui.paddingSizes.l};
${props => (props.hasHighlights ? highlightedFieldStyle : '')};
${props => (props.isHovered ? hoveredFieldStyle : '')};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ export const LogTextStreamItemField = styled.div.attrs<{
[switchProp.default]: props.theme.eui.euiFontSize,
})};
line-height: ${props => props.theme.eui.euiLineHeight};
padding: 2px ${props => props.theme.eui.euiSize};
padding: 2px ${props => props.theme.eui.euiSize} 2px 0;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ const LogTextStreamItemMessageFieldWrapper = LogTextStreamItemField.extend.attrs

const HighlightSpan = styled.span`
display: inline-block;
padding: 0 ${props => props.theme.eui.euiSizeXs};
background-color: ${props => props.theme.eui.euiColorSecondary};
color: ${props => props.theme.eui.euiColorGhost};
font-weight: ${props => props.theme.eui.euiFontWeightMedium};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ interface StreamItemProps {
item: StreamItem;
scale: TextScale;
wrap: boolean;
openFlyoutWithItem: (id: string) => void;
}

export const LogTextStreamItemView = React.forwardRef<Element, StreamItemProps>(
({ item, scale, wrap }, ref) => {
({ item, scale, wrap, openFlyoutWithItem }, ref) => {
switch (item.kind) {
case 'logEntry':
return (
Expand All @@ -27,6 +28,7 @@ export const LogTextStreamItemView = React.forwardRef<Element, StreamItemProps>(
searchResult={item.searchResult}
scale={scale}
wrap={wrap}
openFlyoutWithItem={openFlyoutWithItem}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { darken, transparentize } from 'polished';
import * as React from 'react';
import styled from 'styled-components';

import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
import { LogEntry } from '../../../../common/log_entry';
import { SearchResult } from '../../../../common/log_search_result';
import { TextScale } from '../../../../common/log_text_scale';
Expand All @@ -20,65 +23,110 @@ interface LogTextStreamLogEntryItemViewProps {
searchResult?: SearchResult;
scale: TextScale;
wrap: boolean;
openFlyoutWithItem: (id: string) => void;
intl: InjectedIntl;
}

interface LogTextStreamLogEntryItemViewState {
isHovered: boolean;
}

export class LogTextStreamLogEntryItemView extends React.PureComponent<
LogTextStreamLogEntryItemViewProps,
LogTextStreamLogEntryItemViewState
> {
public readonly state = {
isHovered: false,
};
export const LogTextStreamLogEntryItemView = injectI18n(
class extends React.PureComponent<
LogTextStreamLogEntryItemViewProps,
LogTextStreamLogEntryItemViewState
> {
public readonly state = {
isHovered: false,
};

public handleMouseEnter: React.MouseEventHandler<HTMLDivElement> = () => {
this.setState({
isHovered: true,
});
};
public handleMouseEnter: React.MouseEventHandler<HTMLDivElement> = () => {
this.setState({
isHovered: true,
});
};

public handleMouseLeave: React.MouseEventHandler<HTMLDivElement> = () => {
this.setState({
isHovered: false,
});
};
public handleMouseLeave: React.MouseEventHandler<HTMLDivElement> = () => {
this.setState({
isHovered: false,
});
};

public render() {
const { boundingBoxRef, logEntry, scale, searchResult, wrap } = this.props;
const { isHovered } = this.state;
public handleClick: React.MouseEventHandler<HTMLButtonElement> = () => {
this.props.openFlyoutWithItem(this.props.logEntry.gid);
};

return (
<LogTextStreamLogEntryItemDiv
innerRef={
/* Workaround for missing RefObject support in styled-components */
boundingBoxRef as any
}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
>
<LogTextStreamItemDateField
hasHighlights={!!searchResult}
isHovered={isHovered}
scale={scale}
>
{formatTime(logEntry.fields.time)}
</LogTextStreamItemDateField>
<LogTextStreamItemMessageField
highlights={searchResult ? searchResult.matches.message || [] : []}
isHovered={isHovered}
isWrapped={wrap}
scale={scale}
public render() {
const { intl, boundingBoxRef, logEntry, scale, searchResult, wrap } = this.props;
const { isHovered } = this.state;
const viewDetailsLabel = intl.formatMessage({
id: 'xpack.infra.logEntryItemView.viewDetailsToolTip',
defaultMessage: 'View Details',
});

return (
<LogTextStreamLogEntryItemDiv
innerRef={
/* Workaround for missing RefObject support in styled-components */
boundingBoxRef as any
}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
>
{logEntry.fields.message}
</LogTextStreamItemMessageField>
</LogTextStreamLogEntryItemDiv>
);
<LogTextStreamItemDateField
hasHighlights={!!searchResult}
isHovered={isHovered}
scale={scale}
>
{formatTime(logEntry.fields.time)}
</LogTextStreamItemDateField>
<LogTextStreamIconDiv isHovered={isHovered}>
{isHovered ? (
<EuiToolTip content={viewDetailsLabel}>
<EuiButtonIcon
onClick={this.handleClick}
iconType="expand"
aria-label={viewDetailsLabel}
/>
</EuiToolTip>
) : (
<EmptyIcon />
)}
</LogTextStreamIconDiv>
<LogTextStreamItemMessageField
highlights={searchResult ? searchResult.matches.message || [] : []}
isHovered={isHovered}
isWrapped={wrap}
scale={scale}
>
{logEntry.fields.message}
</LogTextStreamItemMessageField>
</LogTextStreamLogEntryItemDiv>
);
}
}
);

interface IconProps {
isHovered: boolean;
}

const EmptyIcon = styled.div`
width: 24px;
`;

const LogTextStreamIconDiv = styled<IconProps, 'div'>('div')`
flex-grow: 0;
background-color: ${props =>
props.isHovered
? props.theme.darkMode
? transparentize(0.9, darken(0.05, props.theme.eui.euiColorHighlight))
: darken(0.05, props.theme.eui.euiColorHighlight)
: 'transparent'};
text-align: center;
user-select: none;
`;

const LogTextStreamLogEntryItemDiv = styled.div`
font-family: ${props => props.theme.eui.euiCodeFontFamily};
font-size: ${props => props.theme.eui.euiFontSize};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ interface ScrollableLogTextStreamViewProps {
}
) => any;
loadNewerItems: () => void;
setFlyoutItem: (id: string) => void;
showFlyout: () => void;
}

interface ScrollableLogTextStreamViewState {
Expand Down Expand Up @@ -141,7 +143,13 @@ export class ScrollableLogTextStreamView extends React.PureComponent<
key={getStreamItemId(item)}
>
{measureRef => (
<LogTextStreamItemView ref={measureRef} item={item} scale={scale} wrap={wrap} />
<LogTextStreamItemView
openFlyoutWithItem={this.handleOpenFlyout}
ref={measureRef}
item={item}
scale={scale}
wrap={wrap}
/>
)}
</MeasurableItemView>
))}
Expand All @@ -160,6 +168,11 @@ export class ScrollableLogTextStreamView extends React.PureComponent<
}
}

private handleOpenFlyout = (id: string) => {
this.props.setFlyoutItem(id);
this.props.showFlyout();
};

private handleReload = () => {
const { jumpToTarget, target } = this.props;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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 gql from 'graphql-tag';

export const flyoutItemQuery = gql`
query FlyoutItemQuery($sourceId: ID!, $itemId: ID!) {
source(id: $sourceId) {
id
logItem(id: $itemId) {
id
index
fields {
field
value
}
}
}
}
`;
Loading

0 comments on commit bb7a67a

Please sign in to comment.