[Logs UI] Logs ui context menu (#69915)

This commit is contained in:
Alejandro Fernández 2020-06-25 16:27:17 +02:00 committed by GitHub
parent b02e2d9de4
commit 14ac056be9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 143 additions and 131 deletions

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 React, { useCallback } from 'react';
import { EuiButtonIcon, EuiPopover, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { LogEntryColumnContent } from './log_entry_column';
import { euiStyled } from '../../../../../observability/public';
interface LogEntryActionsColumnProps {
isHovered: boolean;
isMenuOpen: boolean;
onOpenMenu: () => void;
onCloseMenu: () => void;
onViewDetails?: () => void;
onViewLogInContext?: () => void;
}
const MENU_LABEL = i18n.translate('xpack.infra.logEntryItemView.logEntryActionsMenuToolTip', {
defaultMessage: 'View actions for line',
});
const LOG_DETAILS_LABEL = i18n.translate('xpack.infra.logs.logEntryActionsDetailsButton', {
defaultMessage: 'View details',
});
const LOG_VIEW_IN_CONTEXT_LABEL = i18n.translate(
'xpack.infra.lobs.logEntryActionsViewInContextButton',
{
defaultMessage: 'View in context',
}
);
export const LogEntryActionsColumn: React.FC<LogEntryActionsColumnProps> = ({
isHovered,
isMenuOpen,
onOpenMenu,
onCloseMenu,
onViewDetails,
onViewLogInContext,
}) => {
const handleClickViewDetails = useCallback(() => {
onCloseMenu();
// Function might be `undefined` and the linter doesn't like that.
// eslint-disable-next-line no-unused-expressions
onViewDetails?.();
}, [onCloseMenu, onViewDetails]);
const handleClickViewInContext = useCallback(() => {
onCloseMenu();
// Function might be `undefined` and the linter doesn't like that.
// eslint-disable-next-line no-unused-expressions
onViewLogInContext?.();
}, [onCloseMenu, onViewLogInContext]);
const button = (
<ButtonWrapper>
<EuiButtonIcon
aria-label={MENU_LABEL}
color="ghost"
iconType="boxesHorizontal"
onClick={onOpenMenu}
/>
</ButtonWrapper>
);
const items = [
<EuiContextMenuItem key="log_details" onClick={handleClickViewDetails}>
{LOG_DETAILS_LABEL}
</EuiContextMenuItem>,
];
if (onViewLogInContext !== undefined) {
items.push(
<EuiContextMenuItem key="view_in_context" onClick={handleClickViewInContext}>
{LOG_VIEW_IN_CONTEXT_LABEL}
</EuiContextMenuItem>
);
}
return (
<ActionsColumnContent>
{isHovered || isMenuOpen ? (
<AbsoluteWrapper>
<EuiPopover
closePopover={onCloseMenu}
isOpen={isMenuOpen}
button={button}
ownFocus={true}
>
<EuiContextMenuPanel items={items} />
</EuiPopover>
</AbsoluteWrapper>
) : null}
</ActionsColumnContent>
);
};
const ActionsColumnContent = euiStyled(LogEntryColumnContent)`
overflow: hidden;
user-select: none;
`;
const ButtonWrapper = euiStyled.div`
background: ${(props) => props.theme.eui.euiColorPrimary};
border-radius: 50%;
padding: 4px;
transform: translateY(-6px);
`;
// this prevents the button from influencing the line height
const AbsoluteWrapper = euiStyled.div`
position: absolute;
`;

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, { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButtonIcon, EuiPopover, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui';
import { euiStyled } from '../../../../../observability/public';
import { LogEntryColumnContent } from './log_entry_column';
interface LogEntryContextMenuItem {
label: string;
onClick: () => void;
}
interface LogEntryContextMenuProps {
'aria-label'?: string;
isOpen: boolean;
onOpen: () => void;
onClose: () => void;
items: LogEntryContextMenuItem[];
}
const DEFAULT_MENU_LABEL = i18n.translate(
'xpack.infra.logEntryItemView.logEntryActionsMenuToolTip',
{
defaultMessage: 'View actions for line',
}
);
export const LogEntryContextMenu: React.FC<LogEntryContextMenuProps> = ({
'aria-label': ariaLabel,
isOpen,
onOpen,
onClose,
items,
}) => {
const closeMenuAndCall = useMemo(() => {
return (callback: LogEntryContextMenuItem['onClick']) => {
return () => {
onClose();
callback();
};
};
}, [onClose]);
const button = (
<ButtonWrapper>
<EuiButtonIcon
aria-label={ariaLabel || DEFAULT_MENU_LABEL}
color="ghost"
iconType="boxesHorizontal"
onClick={onOpen}
/>
</ButtonWrapper>
);
const wrappedItems = useMemo(() => {
return items.map((item, i) => (
<EuiContextMenuItem key={i} onClick={closeMenuAndCall(item.onClick)}>
{item.label}
</EuiContextMenuItem>
));
}, [items, closeMenuAndCall]);
return (
<LogEntryContextMenuContent>
<AbsoluteWrapper>
<EuiPopover closePopover={onClose} isOpen={isOpen} button={button} ownFocus={true}>
<EuiContextMenuPanel items={wrappedItems} />
</EuiPopover>
</AbsoluteWrapper>
</LogEntryContextMenuContent>
);
};
const LogEntryContextMenuContent = euiStyled(LogEntryColumnContent)`
overflow: hidden;
user-select: none;
`;
const AbsoluteWrapper = euiStyled.div`
position: absolute;
`;
const ButtonWrapper = euiStyled.div`
background: ${(props) => props.theme.eui.euiColorPrimary};
border-radius: 50%;
padding: 4px;
transform: translateY(-6px);
`;

View file

@ -5,6 +5,7 @@
*/
import React, { memo, useState, useCallback, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { isEmpty } from 'lodash';
import { euiStyled } from '../../../../../observability/public';
@ -18,11 +19,26 @@ import {
import { TextScale } from '../../../../common/log_text_scale';
import { LogEntryColumn, LogEntryColumnWidths, iconColumnId } from './log_entry_column';
import { LogEntryFieldColumn } from './log_entry_field_column';
import { LogEntryActionsColumn } from './log_entry_actions_column';
import { LogEntryMessageColumn } from './log_entry_message_column';
import { LogEntryTimestampColumn } from './log_entry_timestamp_column';
import { monospaceTextStyle, hoveredContentStyle, highlightedContentStyle } from './text_styles';
import { LogEntry, LogColumn } from '../../../../common/http_api';
import { LogEntryContextMenu } from './log_entry_context_menu';
const MENU_LABEL = i18n.translate('xpack.infra.logEntryItemView.logEntryActionsMenuToolTip', {
defaultMessage: 'View actions for line',
});
const LOG_DETAILS_LABEL = i18n.translate('xpack.infra.logs.logEntryActionsDetailsButton', {
defaultMessage: 'View details',
});
const LOG_VIEW_IN_CONTEXT_LABEL = i18n.translate(
'xpack.infra.lobs.logEntryActionsViewInContextButton',
{
defaultMessage: 'View in context',
}
);
interface LogEntryRowProps {
boundingBoxRef?: React.Ref<Element>;
@ -76,6 +92,29 @@ export const LogEntryRow = memo(
const hasActionViewLogInContext = hasContext && openViewLogInContext !== undefined;
const hasActionsMenu = hasActionFlyoutWithItem || hasActionViewLogInContext;
const menuItems = useMemo(() => {
const items = [];
if (hasActionFlyoutWithItem) {
items.push({
label: LOG_DETAILS_LABEL,
onClick: openFlyout,
});
}
if (hasActionViewLogInContext) {
items.push({
label: LOG_VIEW_IN_CONTEXT_LABEL,
onClick: handleOpenViewLogInContext,
});
}
return items;
}, [
hasActionFlyoutWithItem,
hasActionViewLogInContext,
openFlyout,
handleOpenViewLogInContext,
]);
const logEntryColumnsById = useMemo(
() =>
logEntry.columns.reduce<{
@ -183,16 +222,15 @@ export const LogEntryRow = memo(
key="logColumn iconLogColumn iconLogColumn:details"
{...columnWidths[iconColumnId]}
>
<LogEntryActionsColumn
isHovered={isHovered}
isMenuOpen={isMenuOpen}
onOpenMenu={openMenu}
onCloseMenu={closeMenu}
onViewDetails={hasActionFlyoutWithItem ? openFlyout : undefined}
onViewLogInContext={
hasActionViewLogInContext ? handleOpenViewLogInContext : undefined
}
/>
{isHovered || isMenuOpen ? (
<LogEntryContextMenu
aria-label={MENU_LABEL}
isOpen={isMenuOpen}
onOpen={openMenu}
onClose={closeMenu}
items={menuItems}
/>
) : null}
</LogEntryColumn>
) : null}
</LogEntryRowWrapper>