[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 React, { memo, useState, useCallback, useMemo } from 'react';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
|
|
||||||
import { euiStyled } from '../../../../../observability/public';
|
import { euiStyled } from '../../../../../observability/public';
|
||||||
|
@ -18,11 +19,26 @@ import {
|
||||||
import { TextScale } from '../../../../common/log_text_scale';
|
import { TextScale } from '../../../../common/log_text_scale';
|
||||||
import { LogEntryColumn, LogEntryColumnWidths, iconColumnId } from './log_entry_column';
|
import { LogEntryColumn, LogEntryColumnWidths, iconColumnId } from './log_entry_column';
|
||||||
import { LogEntryFieldColumn } from './log_entry_field_column';
|
import { LogEntryFieldColumn } from './log_entry_field_column';
|
||||||
import { LogEntryActionsColumn } from './log_entry_actions_column';
|
|
||||||
import { LogEntryMessageColumn } from './log_entry_message_column';
|
import { LogEntryMessageColumn } from './log_entry_message_column';
|
||||||
import { LogEntryTimestampColumn } from './log_entry_timestamp_column';
|
import { LogEntryTimestampColumn } from './log_entry_timestamp_column';
|
||||||
import { monospaceTextStyle, hoveredContentStyle, highlightedContentStyle } from './text_styles';
|
import { monospaceTextStyle, hoveredContentStyle, highlightedContentStyle } from './text_styles';
|
||||||
import { LogEntry, LogColumn } from '../../../../common/http_api';
|
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 {
|
interface LogEntryRowProps {
|
||||||
boundingBoxRef?: React.Ref<Element>;
|
boundingBoxRef?: React.Ref<Element>;
|
||||||
|
@ -76,6 +92,29 @@ export const LogEntryRow = memo(
|
||||||
const hasActionViewLogInContext = hasContext && openViewLogInContext !== undefined;
|
const hasActionViewLogInContext = hasContext && openViewLogInContext !== undefined;
|
||||||
const hasActionsMenu = hasActionFlyoutWithItem || hasActionViewLogInContext;
|
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(
|
const logEntryColumnsById = useMemo(
|
||||||
() =>
|
() =>
|
||||||
logEntry.columns.reduce<{
|
logEntry.columns.reduce<{
|
||||||
|
@ -183,16 +222,15 @@ export const LogEntryRow = memo(
|
||||||
key="logColumn iconLogColumn iconLogColumn:details"
|
key="logColumn iconLogColumn iconLogColumn:details"
|
||||||
{...columnWidths[iconColumnId]}
|
{...columnWidths[iconColumnId]}
|
||||||
>
|
>
|
||||||
<LogEntryActionsColumn
|
{isHovered || isMenuOpen ? (
|
||||||
isHovered={isHovered}
|
<LogEntryContextMenu
|
||||||
isMenuOpen={isMenuOpen}
|
aria-label={MENU_LABEL}
|
||||||
onOpenMenu={openMenu}
|
isOpen={isMenuOpen}
|
||||||
onCloseMenu={closeMenu}
|
onOpen={openMenu}
|
||||||
onViewDetails={hasActionFlyoutWithItem ? openFlyout : undefined}
|
onClose={closeMenu}
|
||||||
onViewLogInContext={
|
items={menuItems}
|
||||||
hasActionViewLogInContext ? handleOpenViewLogInContext : undefined
|
/>
|
||||||
}
|
) : null}
|
||||||
/>
|
|
||||||
</LogEntryColumn>
|
</LogEntryColumn>
|
||||||
) : null}
|
) : null}
|
||||||
</LogEntryRowWrapper>
|
</LogEntryRowWrapper>
|
||||||
|
|
Loading…
Reference in a new issue