[Logs UI] Logs ui context menu (#69915)
This commit is contained in:
parent
b02e2d9de4
commit
14ac056be9
|
@ -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;
|
||||
`;
|
|
@ -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);
|
||||
`;
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue