mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Logs Explorer] Add resource column with tooltip (#175287)
## Summary
Closes https://github.com/elastic/kibana/issues/171731
This PR does the following things -
- Add a new virtual column `Resource`
- Add custom tooltip for he resource column
- Refactor TS types for the Log Document and place them in proper files
and folders
### Demo

### Why is this PR still a WIP
- [x] Fix tests
- [x] Add more tests
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
beff74e19e
commit
2c65c43ace
41 changed files with 564 additions and 201 deletions
|
@ -29,6 +29,8 @@ export type LogDocument = Fields &
|
|||
'orchestrator.cluster.name'?: string;
|
||||
'orchestrator.cluster.id'?: string;
|
||||
'orchestrator.resource.id'?: string;
|
||||
'orchestrator.namespace'?: string;
|
||||
'container.name'?: string;
|
||||
'cloud.provider'?: string;
|
||||
'cloud.region'?: string;
|
||||
'cloud.availability_zone'?: string;
|
||||
|
|
|
@ -24,9 +24,9 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
|
|||
const CLOUD_REGION = ['eu-central-1', 'us-east-1', 'area-51'];
|
||||
|
||||
const CLUSTER = [
|
||||
{ clusterId: generateShortId(), clusterName: 'synth-cluster-1' },
|
||||
{ clusterId: generateShortId(), clusterName: 'synth-cluster-2' },
|
||||
{ clusterId: generateShortId(), clusterName: 'synth-cluster-3' },
|
||||
{ clusterId: generateShortId(), clusterName: 'synth-cluster-1', namespace: 'default' },
|
||||
{ clusterId: generateShortId(), clusterName: 'synth-cluster-2', namespace: 'production' },
|
||||
{ clusterId: generateShortId(), clusterName: 'synth-cluster-3', namespace: 'kube' },
|
||||
];
|
||||
|
||||
const SERVICE_NAMES = Array(3)
|
||||
|
@ -48,9 +48,11 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
|
|||
.service(SERVICE_NAMES[index])
|
||||
.defaults({
|
||||
'trace.id': generateShortId(),
|
||||
'agent.name': 'synth-agent',
|
||||
'agent.name': 'nodejs',
|
||||
'orchestrator.cluster.name': CLUSTER[index].clusterName,
|
||||
'orchestrator.cluster.id': CLUSTER[index].clusterId,
|
||||
'orchestrator.namespace': CLUSTER[index].namespace,
|
||||
'container.name': `${SERVICE_NAMES[index]}-${generateShortId()}`,
|
||||
'orchestrator.resource.id': generateShortId(),
|
||||
'cloud.provider': CLOUD_PROVIDERS[Math.floor(Math.random() * 3)],
|
||||
'cloud.region': CLOUD_REGION[index],
|
||||
|
@ -77,10 +79,12 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
|
|||
.defaults({
|
||||
'trace.id': generateShortId(),
|
||||
'error.message': MESSAGE_LOG_LEVELS[index].message,
|
||||
'agent.name': 'synth-agent',
|
||||
'agent.name': 'nodejs',
|
||||
'orchestrator.cluster.name': CLUSTER[index].clusterName,
|
||||
'orchestrator.cluster.id': CLUSTER[index].clusterId,
|
||||
'orchestrator.resource.id': generateShortId(),
|
||||
'orchestrator.namespace': CLUSTER[index].namespace,
|
||||
'container.name': `${SERVICE_NAMES[index]}-${generateShortId()}`,
|
||||
'cloud.provider': CLOUD_PROVIDERS[Math.floor(Math.random() * 3)],
|
||||
'cloud.region': CLOUD_REGION[index],
|
||||
'cloud.availability_zone': `${CLOUD_REGION[index]}a`,
|
||||
|
@ -107,10 +111,12 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
|
|||
.defaults({
|
||||
'trace.id': generateShortId(),
|
||||
'error.message': MESSAGE_LOG_LEVELS[index].message,
|
||||
'agent.name': 'synth-agent',
|
||||
'agent.name': 'nodejs',
|
||||
'orchestrator.cluster.name': CLUSTER[index].clusterName,
|
||||
'orchestrator.cluster.id': CLUSTER[index].clusterId,
|
||||
'orchestrator.resource.id': generateShortId(),
|
||||
'orchestrator.namespace': CLUSTER[index].namespace,
|
||||
'container.name': `${SERVICE_NAMES[index]}-${generateShortId()}`,
|
||||
'cloud.provider': CLOUD_PROVIDERS[Math.floor(Math.random() * 3)],
|
||||
'cloud.region': CLOUD_REGION[index],
|
||||
'cloud.availability_zone': `${CLOUD_REGION[index]}a`,
|
||||
|
@ -137,10 +143,12 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
|
|||
.defaults({
|
||||
'trace.id': generateShortId(),
|
||||
'event.original': MESSAGE_LOG_LEVELS[index].message,
|
||||
'agent.name': 'synth-agent',
|
||||
'agent.name': 'nodejs',
|
||||
'orchestrator.cluster.name': CLUSTER[index].clusterName,
|
||||
'orchestrator.cluster.id': CLUSTER[index].clusterId,
|
||||
'orchestrator.resource.id': generateShortId(),
|
||||
'orchestrator.namespace': CLUSTER[index].namespace,
|
||||
'container.name': `${SERVICE_NAMES[index]}-${generateShortId()}`,
|
||||
'cloud.provider': CLOUD_PROVIDERS[Math.floor(Math.random() * 3)],
|
||||
'cloud.region': CLOUD_REGION[index],
|
||||
'cloud.availability_zone': `${CLOUD_REGION[index]}a`,
|
||||
|
@ -166,10 +174,12 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
|
|||
.service(SERVICE_NAMES[index])
|
||||
.defaults({
|
||||
'trace.id': generateShortId(),
|
||||
'agent.name': 'synth-agent',
|
||||
'agent.name': 'nodejs',
|
||||
'orchestrator.cluster.name': CLUSTER[index].clusterName,
|
||||
'orchestrator.cluster.id': CLUSTER[index].clusterId,
|
||||
'orchestrator.resource.id': generateShortId(),
|
||||
'orchestrator.namespace': CLUSTER[index].namespace,
|
||||
'container.name': `${SERVICE_NAMES[index]}-${generateShortId()}`,
|
||||
'cloud.provider': CLOUD_PROVIDERS[Math.floor(Math.random() * 3)],
|
||||
'cloud.region': CLOUD_REGION[index],
|
||||
'cloud.availability_zone': `${CLOUD_REGION[index]}a`,
|
||||
|
|
|
@ -21,3 +21,6 @@ export function AgentIcon({ agentName, size = 'l', ...props }: AgentIconProps) {
|
|||
|
||||
return <EuiIcon type={icon} size={size} title={agentName} {...props} />;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default AgentIcon;
|
||||
|
|
|
@ -90,7 +90,7 @@ pageLoadAssetSize:
|
|||
licensing: 29004
|
||||
links: 44490
|
||||
lists: 22900
|
||||
logExplorer: 44977
|
||||
logExplorer: 50000
|
||||
logsShared: 281060
|
||||
logstash: 53548
|
||||
management: 46112
|
||||
|
|
|
@ -15,7 +15,7 @@ const ColumnHeaderTruncateContainer = ({
|
|||
headerRowHeight = 1,
|
||||
children,
|
||||
}: {
|
||||
headerRowHeight: number;
|
||||
headerRowHeight?: number;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
|
|
|
@ -14,23 +14,29 @@ export const LOG_LEVEL_FIELD = 'log.level';
|
|||
export const MESSAGE_FIELD = 'message';
|
||||
export const ERROR_MESSAGE_FIELD = 'error.message';
|
||||
export const EVENT_ORIGINAL_FIELD = 'event.original';
|
||||
export const SERVICE_NAME_FIELD = 'service.name';
|
||||
export const TRACE_ID_FIELD = 'trace.id';
|
||||
|
||||
export const LOG_FILE_PATH_FIELD = 'log.file.path';
|
||||
export const DATASTREAM_NAMESPACE_FIELD = 'data_stream.namespace';
|
||||
export const DATASTREAM_DATASET_FIELD = 'data_stream.dataset';
|
||||
|
||||
// Resource Fields
|
||||
export const AGENT_NAME_FIELD = 'agent.name';
|
||||
export const ORCHESTRATOR_CLUSTER_NAME_FIELD = 'orchestrator.cluster.name';
|
||||
export const ORCHESTRATOR_RESOURCE_ID_FIELD = 'orchestrator.resource.id';
|
||||
export const CLOUD_PROVIDER_FIELD = 'cloud.provider';
|
||||
export const CLOUD_REGION_FIELD = 'cloud.region';
|
||||
export const CLOUD_AVAILABILITY_ZONE_FIELD = 'cloud.availability_zone';
|
||||
export const CLOUD_PROJECT_ID_FIELD = 'cloud.project.id';
|
||||
export const CLOUD_INSTANCE_ID_FIELD = 'cloud.instance.id';
|
||||
export const LOG_FILE_PATH_FIELD = 'log.file.path';
|
||||
export const DATASTREAM_NAMESPACE_FIELD = 'data_stream.namespace';
|
||||
export const DATASTREAM_DATASET_FIELD = 'data_stream.dataset';
|
||||
export const SERVICE_NAME_FIELD = 'service.name';
|
||||
export const ORCHESTRATOR_CLUSTER_NAME_FIELD = 'orchestrator.cluster.name';
|
||||
export const ORCHESTRATOR_RESOURCE_ID_FIELD = 'orchestrator.resource.id';
|
||||
export const ORCHESTRATOR_NAMESPACE_FIELD = 'orchestrator.namespace';
|
||||
export const CONTAINER_NAME_FIELD = 'container.name';
|
||||
export const CONTAINER_ID_FIELD = 'container.id';
|
||||
|
||||
// Virtual column fields
|
||||
export const CONTENT_FIELD = 'content';
|
||||
export const RESOURCE_FIELD = 'resource';
|
||||
|
||||
// Sizing
|
||||
export const DATA_GRID_COLUMN_WIDTH_SMALL = 240;
|
||||
|
@ -39,11 +45,7 @@ export const DATA_GRID_COLUMN_WIDTH_MEDIUM = 320;
|
|||
// UI preferences
|
||||
export const DEFAULT_COLUMNS = [
|
||||
{
|
||||
field: SERVICE_NAME_FIELD,
|
||||
width: DATA_GRID_COLUMN_WIDTH_SMALL,
|
||||
},
|
||||
{
|
||||
field: HOST_NAME_FIELD,
|
||||
field: RESOURCE_FIELD,
|
||||
width: DATA_GRID_COLUMN_WIDTH_MEDIUM,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -5,24 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { DataTableRecord } from '@kbn/discover-utils/types';
|
||||
import { DocViewRenderProps } from '@kbn/unified-doc-viewer/types';
|
||||
|
||||
export interface LogExplorerCustomizations {
|
||||
flyout?: {
|
||||
renderContent?: RenderContentCustomization<LogExplorerFlyoutContentProps>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LogExplorerFlyoutContentProps {
|
||||
actions: {
|
||||
addFilter: DocViewRenderProps['filter'];
|
||||
addColumn: DocViewRenderProps['onAddColumn'];
|
||||
removeColumn: DocViewRenderProps['onRemoveColumn'];
|
||||
};
|
||||
dataView: DocViewRenderProps['dataView'];
|
||||
doc: LogDocument;
|
||||
}
|
||||
import type { DataTableRecord } from '@kbn/discover-utils/src/types';
|
||||
|
||||
export interface LogDocument extends DataTableRecord {
|
||||
flattened: {
|
||||
|
@ -37,7 +20,11 @@ export interface LogDocument extends DataTableRecord {
|
|||
'trace.id'?: string;
|
||||
'agent.name'?: string;
|
||||
'orchestrator.cluster.name'?: string;
|
||||
'orchestrator.cluster.id'?: string;
|
||||
'orchestrator.resource.id'?: string;
|
||||
'orchestrator.namespace'?: string;
|
||||
'container.name'?: string;
|
||||
'container.id'?: string;
|
||||
'cloud.provider'?: string;
|
||||
'cloud.region'?: string;
|
||||
'cloud.availability_zone'?: string;
|
||||
|
@ -61,7 +48,10 @@ export interface FlyoutDoc {
|
|||
'trace.id'?: string;
|
||||
'agent.name'?: string;
|
||||
'orchestrator.cluster.name'?: string;
|
||||
'orchestrator.cluster.id'?: string;
|
||||
'orchestrator.resource.id'?: string;
|
||||
'orchestrator.namespace'?: string;
|
||||
'container.name'?: string;
|
||||
'cloud.provider'?: string;
|
||||
'cloud.region'?: string;
|
||||
'cloud.availability_zone'?: string;
|
||||
|
@ -72,8 +62,15 @@ export interface FlyoutDoc {
|
|||
'data_stream.dataset': string;
|
||||
}
|
||||
|
||||
export type RenderContentCustomization<Props> = (
|
||||
renderPreviousContent: RenderPreviousContent<Props>
|
||||
) => (props: Props) => React.ReactNode;
|
||||
|
||||
export type RenderPreviousContent<Props> = (props: Props) => React.ReactNode;
|
||||
export interface ResourceFields {
|
||||
'host.name'?: string;
|
||||
'service.name'?: string;
|
||||
'agent.name'?: string;
|
||||
'orchestrator.cluster.name'?: string;
|
||||
'orchestrator.cluster.id'?: string;
|
||||
'orchestrator.resource.id'?: string;
|
||||
'orchestrator.namespace'?: string;
|
||||
'container.name'?: string;
|
||||
'container.id'?: string;
|
||||
'cloud.instance.id'?: string;
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useRef, useState } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { EuiPopover, EuiPopoverTitle } from '@elastic/eui';
|
||||
|
||||
export const HoverPopover = ({
|
||||
|
@ -20,10 +20,14 @@ export const HoverPopover = ({
|
|||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const leaveTimer = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const onMouseEnter = () => {
|
||||
const clearTimer = () => {
|
||||
if (leaveTimer.current) {
|
||||
clearTimeout(leaveTimer.current);
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseEnter = () => {
|
||||
clearTimer();
|
||||
setIsPopoverOpen(true);
|
||||
};
|
||||
|
||||
|
@ -31,6 +35,12 @@ export const HoverPopover = ({
|
|||
leaveTimer.current = setTimeout(() => setIsPopoverOpen(false), 100);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTimer();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||
<EuiPopover
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { useEuiTheme } from '@elastic/eui';
|
||||
import { FlyoutDoc } from '../flyout_detail/types';
|
||||
import { FlyoutDoc } from '../../../common/document';
|
||||
import { ChipWithPopover } from './popover_chip';
|
||||
import * as constants from '../../../common/constants';
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
type EuiBadgeProps,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiPopover,
|
||||
useEuiFontSize,
|
||||
EuiPopoverFooter,
|
||||
|
@ -24,6 +23,7 @@ import { FilterInButton } from './filter_in_button';
|
|||
import { FilterOutButton } from './filter_out_button';
|
||||
import { CopyButton } from './copy_button';
|
||||
import { dynamic } from '../../utils/dynamic';
|
||||
|
||||
const DataTablePopoverCellValue = dynamic(
|
||||
() => import('@kbn/unified-data-table/src/components/data_table_cell_value')
|
||||
);
|
||||
|
@ -38,7 +38,7 @@ interface ChipWithPopoverProps {
|
|||
*/
|
||||
text: string;
|
||||
dataTestSubj?: string;
|
||||
leftSideIcon?: EuiBadgeProps['iconType'];
|
||||
leftSideIcon?: React.ReactNode;
|
||||
rightSideIcon?: EuiBadgeProps['iconType'];
|
||||
borderColor?: string | null;
|
||||
style?: React.CSSProperties;
|
||||
|
@ -78,15 +78,12 @@ export function ChipWithPopover({
|
|||
font-size: ${xsFontSize};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: -3px;
|
||||
`}
|
||||
style={style}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
{leftSideIcon && (
|
||||
<EuiFlexItem>
|
||||
<EuiIcon type={leftSideIcon} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{leftSideIcon && <EuiFlexItem>{leftSideIcon}</EuiFlexItem>}
|
||||
<EuiFlexItem>{text}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiBadge>
|
||||
|
|
|
@ -15,6 +15,10 @@ export const contentLabel = i18n.translate('xpack.logExplorer.dataTable.header.p
|
|||
defaultMessage: 'Content',
|
||||
});
|
||||
|
||||
export const resourceLabel = i18n.translate('xpack.logExplorer.dataTable.header.popover.resource', {
|
||||
defaultMessage: 'Resource',
|
||||
});
|
||||
|
||||
export const flyoutServiceLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.service', {
|
||||
defaultMessage: 'Service',
|
||||
});
|
||||
|
@ -211,3 +215,10 @@ export const contentHeaderTooltipParagraph2 = i18n.translate(
|
|||
defaultMessage: 'When the message field is empty, one of the following is displayed',
|
||||
}
|
||||
);
|
||||
|
||||
export const resourceHeaderTooltipParagraph = i18n.translate(
|
||||
'xpack.logExplorer.dataTable.header.resource.tooltip.paragraph',
|
||||
{
|
||||
defaultMessage: "Fields that provide information on the document's source, such as:",
|
||||
}
|
||||
);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { LogExplorerFlyoutContentProps } from './types';
|
||||
import { LogExplorerFlyoutContentProps } from '../../customizations/types';
|
||||
import { useDocDetail } from '../../hooks/use_doc_detail';
|
||||
import { FlyoutHeader } from './flyout_header';
|
||||
import { FlyoutHighlights } from './flyout_highlights';
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
useGeneratedHtmlId,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { FlyoutDoc } from './types';
|
||||
import { FlyoutDoc } from '../../../common/document';
|
||||
import { getMessageWithFallbacks } from '../../hooks/use_doc_detail';
|
||||
import { LogLevel } from '../common/log_level';
|
||||
import { Timestamp } from './sub_components/timestamp';
|
||||
|
|
|
@ -8,7 +8,7 @@ import React from 'react';
|
|||
import { CloudProvider, CloudProviderIcon } from '@kbn/custom-icons';
|
||||
import { useMeasure } from 'react-use/lib';
|
||||
import { first } from 'lodash';
|
||||
import { FlyoutDoc, LogDocument } from './types';
|
||||
import { FlyoutDoc, LogDocument } from '../../../common/document';
|
||||
import * as constants from '../../../common/constants';
|
||||
import { HighlightField } from './sub_components/highlight_field';
|
||||
import {
|
||||
|
|
|
@ -6,4 +6,3 @@
|
|||
*/
|
||||
|
||||
export * from './flyout_detail';
|
||||
export * from './types';
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { EuiBadge } from '@elastic/eui';
|
||||
import { FlyoutDoc } from '../types';
|
||||
import { FlyoutDoc } from '../../../../common/document';
|
||||
|
||||
interface TimestampProps {
|
||||
timestamp: FlyoutDoc['@timestamp'];
|
||||
|
|
|
@ -1,8 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export type { FlyoutDoc, LogDocument, LogExplorerFlyoutContentProps } from '../../controller';
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { DataGridCellValueElementProps } from '@kbn/unified-data-table';
|
||||
import { LogExplorerDiscoverServices } from '../../controller';
|
||||
import { VirtualColumnServiceProvider } from '../../hooks/use_virtual_column_services';
|
||||
import { CONTENT_FIELD, RESOURCE_FIELD } from '../../../common/constants';
|
||||
import { Content } from './content';
|
||||
import { Resource } from './resource';
|
||||
|
||||
export const renderCell =
|
||||
(type: string, { data }: { data: LogExplorerDiscoverServices['data'] }) =>
|
||||
(props: DataGridCellValueElementProps) => {
|
||||
const { dataView } = props;
|
||||
const virtualColumnServices = {
|
||||
data,
|
||||
dataView,
|
||||
};
|
||||
|
||||
let renderedCell = null;
|
||||
|
||||
switch (type) {
|
||||
case CONTENT_FIELD:
|
||||
renderedCell = <Content {...props} />;
|
||||
break;
|
||||
case RESOURCE_FIELD:
|
||||
renderedCell = <Resource {...props} />;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<VirtualColumnServiceProvider services={virtualColumnServices}>
|
||||
{renderedCell}
|
||||
</VirtualColumnServiceProvider>
|
||||
);
|
||||
};
|
|
@ -8,7 +8,8 @@
|
|||
import React from 'react';
|
||||
import { CustomGridColumnProps } from '@kbn/unified-data-table';
|
||||
import { ContentColumnTooltip } from './column_tooltips/content_column_tooltip';
|
||||
import { CONTENT_FIELD } from '../../../common/constants';
|
||||
import { CONTENT_FIELD, RESOURCE_FIELD } from '../../../common/constants';
|
||||
import { ResourceColumnTooltip } from './column_tooltips/resource_column_tooltip';
|
||||
|
||||
export const renderColumn =
|
||||
(field: string) =>
|
||||
|
@ -17,6 +18,11 @@ export const renderColumn =
|
|||
case CONTENT_FIELD:
|
||||
column.display = <ContentColumnTooltip column={column} headerRowHeight={headerRowHeight} />;
|
||||
break;
|
||||
case RESOURCE_FIELD:
|
||||
column.display = (
|
||||
<ResourceColumnTooltip column={column} headerRowHeight={headerRowHeight} />
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiToken, useEuiTheme } from '@elastic/eui';
|
||||
import { EuiText, useEuiTheme } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { CustomGridColumnProps } from '@kbn/unified-data-table';
|
||||
import { css } from '@emotion/react';
|
||||
|
@ -14,12 +14,10 @@ import {
|
|||
contentHeaderTooltipParagraph2,
|
||||
contentLabel,
|
||||
} from '../../common/translations';
|
||||
import { dynamic } from '../../../utils/dynamic';
|
||||
import { HoverPopover } from '../../common/hover_popover';
|
||||
|
||||
const ColumnHeaderTruncateContainer = dynamic(
|
||||
() => import('@kbn/unified-data-table/src/components/column_header_truncate_container')
|
||||
);
|
||||
import { TooltipButtonComponent } from './tooltip_button';
|
||||
import { FieldWithToken } from './field_with_token';
|
||||
import * as constants from '../../../../common/constants';
|
||||
|
||||
export const ContentColumnTooltip = ({ column, headerRowHeight }: CustomGridColumnProps) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
@ -27,13 +25,16 @@ export const ContentColumnTooltip = ({ column, headerRowHeight }: CustomGridColu
|
|||
margin-bottom: ${euiTheme.size.s};
|
||||
`;
|
||||
|
||||
const contentButtonComponent = (
|
||||
<ColumnHeaderTruncateContainer headerRowHeight={headerRowHeight}>
|
||||
{column.displayAsText} <EuiIcon type="questionInCircle" />
|
||||
</ColumnHeaderTruncateContainer>
|
||||
);
|
||||
return (
|
||||
<HoverPopover button={contentButtonComponent} title={contentLabel}>
|
||||
<HoverPopover
|
||||
button={
|
||||
<TooltipButtonComponent
|
||||
displayText={column.displayAsText}
|
||||
headerRowHeight={headerRowHeight}
|
||||
/>
|
||||
}
|
||||
title={contentLabel}
|
||||
>
|
||||
<div style={{ width: '230px' }}>
|
||||
<EuiText size="s" css={spacingCSS}>
|
||||
<p>{contentHeaderTooltipParagraph1}</p>
|
||||
|
@ -41,36 +42,8 @@ export const ContentColumnTooltip = ({ column, headerRowHeight }: CustomGridColu
|
|||
<EuiText size="s" css={spacingCSS}>
|
||||
<p>{contentHeaderTooltipParagraph2}</p>
|
||||
</EuiText>
|
||||
<EuiFlexGroup
|
||||
responsive={false}
|
||||
alignItems="center"
|
||||
justifyContent="flexStart"
|
||||
gutterSize="xs"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToken iconType="tokenKeyword" size="s" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">
|
||||
<strong>error.message</strong>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup
|
||||
responsive={false}
|
||||
alignItems="center"
|
||||
justifyContent="flexStart"
|
||||
gutterSize="xs"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToken iconType="tokenEvent" size="s" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">
|
||||
<strong>event.original</strong>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<FieldWithToken field={constants.ERROR_MESSAGE_FIELD} />
|
||||
<FieldWithToken field={constants.EVENT_ORIGINAL_FIELD} iconType="tokenEvent" />
|
||||
</div>
|
||||
</HoverPopover>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiText, EuiToken } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
|
||||
const spacingXsCss = css`
|
||||
margin-bottom: ${euiThemeVars.euiSizeXS};
|
||||
`;
|
||||
|
||||
export const FieldWithToken = ({
|
||||
field,
|
||||
iconType = 'tokenKeyword',
|
||||
}: {
|
||||
field: string;
|
||||
iconType?: string;
|
||||
}) => {
|
||||
return (
|
||||
<div css={spacingXsCss}>
|
||||
<EuiFlexGroup
|
||||
responsive={false}
|
||||
alignItems="center"
|
||||
justifyContent="flexStart"
|
||||
gutterSize="xs"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToken iconType={iconType} size="s" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">
|
||||
<strong>{field}</strong>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import type { CustomGridColumnProps } from '@kbn/unified-data-table';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { resourceHeaderTooltipParagraph, resourceLabel } from '../../common/translations';
|
||||
import { HoverPopover } from '../../common/hover_popover';
|
||||
import { TooltipButtonComponent } from './tooltip_button';
|
||||
import * as constants from '../../../../common/constants';
|
||||
import { FieldWithToken } from './field_with_token';
|
||||
|
||||
const spacingCSS = css`
|
||||
margin-bottom: ${euiThemeVars.euiSizeS};
|
||||
`;
|
||||
|
||||
export const ResourceColumnTooltip = ({ column, headerRowHeight }: CustomGridColumnProps) => {
|
||||
return (
|
||||
<HoverPopover
|
||||
button={
|
||||
<TooltipButtonComponent
|
||||
displayText={column.displayAsText}
|
||||
headerRowHeight={headerRowHeight}
|
||||
/>
|
||||
}
|
||||
title={resourceLabel}
|
||||
>
|
||||
<div style={{ width: '230px' }}>
|
||||
<EuiText size="s" css={spacingCSS}>
|
||||
<p>{resourceHeaderTooltipParagraph}</p>
|
||||
</EuiText>
|
||||
{[
|
||||
constants.SERVICE_NAME_FIELD,
|
||||
constants.CONTAINER_NAME_FIELD,
|
||||
constants.ORCHESTRATOR_NAMESPACE_FIELD,
|
||||
constants.HOST_NAME_FIELD,
|
||||
constants.CLOUD_INSTANCE_ID_FIELD,
|
||||
].map((field) => (
|
||||
<FieldWithToken field={field} />
|
||||
))}
|
||||
</div>
|
||||
</HoverPopover>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiIcon } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import ColumnHeaderTruncateContainer from '@kbn/unified-data-table/src/components/column_header_truncate_container';
|
||||
|
||||
export const TooltipButtonComponent = ({
|
||||
displayText,
|
||||
headerRowHeight,
|
||||
}: {
|
||||
displayText?: string;
|
||||
headerRowHeight?: number;
|
||||
}) => (
|
||||
<ColumnHeaderTruncateContainer headerRowHeight={headerRowHeight}>
|
||||
{displayText} <EuiIcon type="questionInCircle" />
|
||||
</ColumnHeaderTruncateContainer>
|
||||
);
|
|
@ -12,11 +12,10 @@ import { getShouldShowFieldHandler } from '@kbn/discover-utils';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import type { DataTableRecord } from '@kbn/discover-utils/src/types';
|
||||
import { useDocDetail, getMessageWithFallbacks } from '../../hooks/use_doc_detail';
|
||||
import { LogDocument, LogExplorerDiscoverServices } from '../../controller';
|
||||
import { LogDocument } from '../../../common/document';
|
||||
import { LogLevel } from '../common/log_level';
|
||||
import * as constants from '../../../common/constants';
|
||||
import { dynamic } from '../../utils/dynamic';
|
||||
import { VirtualColumnServiceProvider } from '../../hooks/use_virtual_column_services';
|
||||
import './virtual_column.scss';
|
||||
|
||||
const SourceDocument = dynamic(
|
||||
|
@ -72,7 +71,7 @@ const SourcePopoverContent = ({
|
|||
);
|
||||
};
|
||||
|
||||
const Content = ({
|
||||
export const Content = ({
|
||||
row,
|
||||
dataView,
|
||||
fieldFormats,
|
||||
|
@ -116,18 +115,3 @@ const Content = ({
|
|||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export const renderContent =
|
||||
({ data }: { data: LogExplorerDiscoverServices['data'] }) =>
|
||||
(props: DataGridCellValueElementProps) => {
|
||||
const { dataView } = props;
|
||||
const virtualColumnServices = {
|
||||
data,
|
||||
dataView,
|
||||
};
|
||||
return (
|
||||
<VirtualColumnServiceProvider services={virtualColumnServices}>
|
||||
<Content {...props} />
|
||||
</VirtualColumnServiceProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { DataGridCellValueElementProps } from '@kbn/unified-data-table';
|
||||
import { first } from 'lodash';
|
||||
import { AgentName } from '@kbn/elastic-agent-utils';
|
||||
import { ChipWithPopover } from '../common/popover_chip';
|
||||
import * as constants from '../../../common/constants';
|
||||
import { getUnformattedResourceFields } from '../../utils/resource';
|
||||
import { LogDocument } from '../../../common/document';
|
||||
import { dynamic } from '../../utils/dynamic';
|
||||
|
||||
const AgentIcon = dynamic(() => import('@kbn/custom-icons/src/components/agent_icon'));
|
||||
|
||||
export const Resource = ({ row }: DataGridCellValueElementProps) => {
|
||||
const resourceDoc = getUnformattedResourceFields(row as LogDocument);
|
||||
return (
|
||||
<div>
|
||||
{(resourceDoc[constants.SERVICE_NAME_FIELD] as string) && (
|
||||
<ChipWithPopover
|
||||
property={constants.SERVICE_NAME_FIELD}
|
||||
text={resourceDoc[constants.SERVICE_NAME_FIELD] as string}
|
||||
rightSideIcon="arrowDown"
|
||||
leftSideIcon={
|
||||
<AgentIcon
|
||||
agentName={first((resourceDoc[constants.AGENT_NAME_FIELD] ?? []) as AgentName[])}
|
||||
size="m"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{resourceDoc[constants.CONTAINER_NAME_FIELD] && (
|
||||
<ChipWithPopover
|
||||
property={constants.CONTAINER_NAME_FIELD}
|
||||
text={resourceDoc[constants.CONTAINER_NAME_FIELD] as string}
|
||||
rightSideIcon="arrowDown"
|
||||
/>
|
||||
)}
|
||||
{resourceDoc[constants.HOST_NAME_FIELD] && (
|
||||
<ChipWithPopover
|
||||
property={constants.HOST_NAME_FIELD}
|
||||
text={resourceDoc[constants.HOST_NAME_FIELD]}
|
||||
rightSideIcon="arrowDown"
|
||||
/>
|
||||
)}
|
||||
{resourceDoc[constants.ORCHESTRATOR_NAMESPACE_FIELD] && (
|
||||
<ChipWithPopover
|
||||
property={constants.ORCHESTRATOR_NAMESPACE_FIELD}
|
||||
text={resourceDoc[constants.ORCHESTRATOR_NAMESPACE_FIELD] as string}
|
||||
rightSideIcon="arrowDown"
|
||||
/>
|
||||
)}
|
||||
{resourceDoc[constants.CLOUD_INSTANCE_ID_FIELD] && (
|
||||
<ChipWithPopover
|
||||
property={constants.CLOUD_INSTANCE_ID_FIELD}
|
||||
text={resourceDoc[constants.CLOUD_INSTANCE_ID_FIELD] as string}
|
||||
rightSideIcon="arrowDown"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -13,7 +13,7 @@ import { interpret } from 'xstate';
|
|||
import { DatasetsService } from '../services/datasets';
|
||||
import { createLogExplorerControllerStateMachine } from '../state_machines/log_explorer_controller';
|
||||
import { LogExplorerStartDeps } from '../types';
|
||||
import { LogExplorerCustomizations } from './controller_customizations';
|
||||
import { LogExplorerCustomizations } from '../customizations/types';
|
||||
import { createDataServiceProxy } from './custom_data_service';
|
||||
import { createUiSettingsServiceProxy } from './custom_ui_settings_service';
|
||||
import {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './controller_customizations';
|
||||
export * from '../customizations/types';
|
||||
export * from './create_controller';
|
||||
export * from './provider';
|
||||
export * from './types';
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
LogExplorerControllerStateMachine,
|
||||
LogExplorerControllerStateService,
|
||||
} from '../state_machines/log_explorer_controller';
|
||||
import { LogExplorerCustomizations } from './controller_customizations';
|
||||
import { LogExplorerCustomizations } from '../customizations/types';
|
||||
|
||||
export interface LogExplorerController {
|
||||
actions: {};
|
||||
|
|
|
@ -6,11 +6,12 @@
|
|||
*/
|
||||
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import { CONTENT_FIELD } from '../../common/constants';
|
||||
import { renderContent } from '../components/virtual_columns/content';
|
||||
import { CONTENT_FIELD, RESOURCE_FIELD } from '../../common/constants';
|
||||
import { renderCell } from '../components/virtual_columns/cell_renderer';
|
||||
|
||||
export const createCustomCellRenderer = ({ data }: { data: DataPublicPluginStart }) => {
|
||||
return {
|
||||
[CONTENT_FIELD]: renderContent({ data }),
|
||||
[CONTENT_FIELD]: renderCell(CONTENT_FIELD, { data }),
|
||||
[RESOURCE_FIELD]: renderCell(RESOURCE_FIELD, { data }),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { CONTENT_FIELD } from '../../common/constants';
|
||||
import { CONTENT_FIELD, RESOURCE_FIELD } from '../../common/constants';
|
||||
import { renderColumn } from '../components/virtual_columns/column';
|
||||
|
||||
export const createCustomGridColumnsConfiguration = () => ({
|
||||
[CONTENT_FIELD]: renderColumn(CONTENT_FIELD),
|
||||
[RESOURCE_FIELD]: renderColumn(RESOURCE_FIELD),
|
||||
});
|
||||
|
|
|
@ -9,8 +9,9 @@ import React, { useMemo } from 'react';
|
|||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import { DocViewRenderProps } from '@kbn/unified-doc-viewer/types';
|
||||
import { FlyoutDetail } from '../components/flyout_detail/flyout_detail';
|
||||
import { LogExplorerFlyoutContentProps } from '../components/flyout_detail';
|
||||
import { LogDocument, useLogExplorerControllerContext } from '../controller';
|
||||
import { LogExplorerFlyoutContentProps } from './types';
|
||||
import { useLogExplorerControllerContext } from '../controller';
|
||||
import { LogDocument } from '../../common/document';
|
||||
|
||||
const CustomFlyoutContent = ({
|
||||
filter,
|
||||
|
|
32
x-pack/plugins/log_explorer/public/customizations/types.ts
Normal file
32
x-pack/plugins/log_explorer/public/customizations/types.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { DocViewRenderProps } from '@kbn/unified-doc-viewer/src/services/types';
|
||||
import { LogDocument } from '../../common/document';
|
||||
|
||||
export type RenderPreviousContent<Props> = (props: Props) => React.ReactNode;
|
||||
|
||||
export type RenderContentCustomization<Props> = (
|
||||
renderPreviousContent: RenderPreviousContent<Props>
|
||||
) => (props: Props) => React.ReactNode;
|
||||
|
||||
export interface LogExplorerFlyoutContentProps {
|
||||
actions: {
|
||||
addFilter: DocViewRenderProps['filter'];
|
||||
addColumn: DocViewRenderProps['onAddColumn'];
|
||||
removeColumn: DocViewRenderProps['onRemoveColumn'];
|
||||
};
|
||||
dataView: DocViewRenderProps['dataView'];
|
||||
doc: LogDocument;
|
||||
}
|
||||
|
||||
export interface LogExplorerCustomizations {
|
||||
flyout?: {
|
||||
renderContent?: RenderContentCustomization<LogExplorerFlyoutContentProps>;
|
||||
};
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import createContainer from 'constate';
|
||||
import type { LogExplorerFlyoutContentProps } from '../components/flyout_detail/types';
|
||||
import type { LogExplorerFlyoutContentProps } from '../customizations/types';
|
||||
|
||||
interface UseFlyoutActionsDeps {
|
||||
value: LogExplorerFlyoutContentProps['actions'];
|
||||
|
|
|
@ -7,11 +7,8 @@
|
|||
import { formatFieldValue } from '@kbn/discover-utils';
|
||||
import * as constants from '../../common/constants';
|
||||
import { useKibanaContextForPlugin } from '../utils/use_kibana';
|
||||
import {
|
||||
FlyoutDoc,
|
||||
LogExplorerFlyoutContentProps,
|
||||
LogDocument,
|
||||
} from '../components/flyout_detail/types';
|
||||
import { LogExplorerFlyoutContentProps } from '../customizations/types';
|
||||
import { FlyoutDoc, LogDocument } from '../../common/document';
|
||||
|
||||
export function useDocDetail(
|
||||
doc: LogDocument,
|
||||
|
|
|
@ -8,14 +8,17 @@
|
|||
import type { PluginInitializerContext } from '@kbn/core/public';
|
||||
import type { LogExplorerConfig } from '../common/plugin_config';
|
||||
import { LogExplorerPlugin } from './plugin';
|
||||
|
||||
export type {
|
||||
CreateLogExplorerController,
|
||||
LogExplorerController,
|
||||
LogExplorerCustomizations,
|
||||
LogExplorerFlyoutContentProps,
|
||||
LogExplorerPublicState,
|
||||
LogExplorerPublicStateUpdate,
|
||||
} from './controller';
|
||||
export type {
|
||||
LogExplorerCustomizations,
|
||||
LogExplorerFlyoutContentProps,
|
||||
} from './customizations/types';
|
||||
export type { LogExplorerControllerContext } from './state_machines/log_explorer_controller';
|
||||
export type { LogExplorerPluginSetup, LogExplorerPluginStart } from './types';
|
||||
export {
|
||||
|
|
40
x-pack/plugins/log_explorer/public/utils/resource.ts
Normal file
40
x-pack/plugins/log_explorer/public/utils/resource.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { LogDocument, ResourceFields } from '../../common/document';
|
||||
import * as constants from '../../common/constants';
|
||||
|
||||
type Field = keyof LogDocument['flattened'];
|
||||
|
||||
const getFieldFromDoc = <T extends Field>(doc: LogDocument, field: T) => {
|
||||
const fieldValueArray = doc.flattened[field];
|
||||
return fieldValueArray && fieldValueArray.length ? fieldValueArray[0] : undefined;
|
||||
};
|
||||
|
||||
export const getUnformattedResourceFields = (doc: LogDocument): ResourceFields => {
|
||||
const serviceName = getFieldFromDoc(doc, constants.SERVICE_NAME_FIELD);
|
||||
const hostName = getFieldFromDoc(doc, constants.HOST_NAME_FIELD);
|
||||
const agentName = getFieldFromDoc(doc, constants.AGENT_NAME_FIELD);
|
||||
const orchestratorClusterName = getFieldFromDoc(doc, constants.ORCHESTRATOR_CLUSTER_NAME_FIELD);
|
||||
const orchestratorResourceId = getFieldFromDoc(doc, constants.ORCHESTRATOR_RESOURCE_ID_FIELD);
|
||||
const orchestratorNamespace = getFieldFromDoc(doc, constants.ORCHESTRATOR_NAMESPACE_FIELD);
|
||||
const containerName = getFieldFromDoc(doc, constants.CONTAINER_NAME_FIELD);
|
||||
const containerId = getFieldFromDoc(doc, constants.CONTAINER_ID_FIELD);
|
||||
const cloudInstanceId = getFieldFromDoc(doc, constants.CLOUD_INSTANCE_ID_FIELD);
|
||||
|
||||
return {
|
||||
[constants.SERVICE_NAME_FIELD]: serviceName,
|
||||
[constants.HOST_NAME_FIELD]: hostName,
|
||||
[constants.AGENT_NAME_FIELD]: agentName,
|
||||
[constants.ORCHESTRATOR_CLUSTER_NAME_FIELD]: orchestratorClusterName,
|
||||
[constants.ORCHESTRATOR_RESOURCE_ID_FIELD]: orchestratorResourceId,
|
||||
[constants.ORCHESTRATOR_NAMESPACE_FIELD]: orchestratorNamespace,
|
||||
[constants.CONTAINER_NAME_FIELD]: containerName,
|
||||
[constants.CONTAINER_ID_FIELD]: containerId,
|
||||
[constants.CLOUD_INSTANCE_ID_FIELD]: cloudInstanceId,
|
||||
};
|
||||
};
|
|
@ -41,6 +41,8 @@
|
|||
"@kbn/unified-field-list",
|
||||
"@kbn/unified-search-plugin",
|
||||
"@kbn/xstate-utils",
|
||||
"@kbn/elastic-agent-utils",
|
||||
"@kbn/ui-theme",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
|
|
|
@ -9,7 +9,7 @@ import moment from 'moment/moment';
|
|||
import { log, timerange } from '@kbn/apm-synthtrace-client';
|
||||
import { FtrProviderContext } from './config';
|
||||
|
||||
const defaultLogColumns = ['@timestamp', 'service.name', 'host.name', 'content'];
|
||||
const defaultLogColumns = ['@timestamp', 'resource', 'content'];
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const retry = getService('retry');
|
||||
|
@ -58,8 +58,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
mode: 'absolute',
|
||||
},
|
||||
columns: [
|
||||
{ field: 'service.name' },
|
||||
{ field: 'host.name' },
|
||||
{ field: 'resource' },
|
||||
{ field: 'content' },
|
||||
{ field: 'data_stream.namespace' },
|
||||
],
|
||||
|
@ -78,7 +77,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
describe('render content virtual column properly', async () => {
|
||||
it('should render log level and log message when present', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(0, 5);
|
||||
const cellElement = await dataGrid.getCellElement(0, 4);
|
||||
const cellValue = await cellElement.getVisibleText();
|
||||
expect(cellValue.includes('info')).to.be(true);
|
||||
expect(cellValue.includes('A sample log')).to.be(true);
|
||||
|
@ -87,7 +86,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('should render log message when present and skip log level when missing', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(1, 5);
|
||||
const cellElement = await dataGrid.getCellElement(1, 4);
|
||||
const cellValue = await cellElement.getVisibleText();
|
||||
expect(cellValue.includes('info')).to.be(false);
|
||||
expect(cellValue.includes('A sample log')).to.be(true);
|
||||
|
@ -96,7 +95,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('should render message from error object when top level message not present', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(2, 5);
|
||||
const cellElement = await dataGrid.getCellElement(2, 4);
|
||||
const cellValue = await cellElement.getVisibleText();
|
||||
expect(cellValue.includes('info')).to.be(true);
|
||||
expect(cellValue.includes('error.message')).to.be(true);
|
||||
|
@ -106,7 +105,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('should render message from event.original when top level message and error.message not present', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(3, 5);
|
||||
const cellElement = await dataGrid.getCellElement(3, 4);
|
||||
const cellValue = await cellElement.getVisibleText();
|
||||
expect(cellValue.includes('info')).to.be(true);
|
||||
expect(cellValue.includes('event.original')).to.be(true);
|
||||
|
@ -116,7 +115,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('should render the whole JSON when neither message, error.message and event.original are present', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(4, 5);
|
||||
const cellElement = await dataGrid.getCellElement(4, 4);
|
||||
const cellValue = await cellElement.getVisibleText();
|
||||
expect(cellValue.includes('info')).to.be(true);
|
||||
|
||||
|
@ -132,7 +131,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('on cell expansion with no message field should open JSON Viewer', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
await dataGrid.clickCellExpandButton(4, 5);
|
||||
await dataGrid.clickCellExpandButton(4, 4);
|
||||
await testSubjects.existOrFail('dataTableExpandCellActionJsonPopover');
|
||||
});
|
||||
});
|
||||
|
@ -140,19 +139,30 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
it('on cell expansion with message field should open regular popover', async () => {
|
||||
await navigateToLogExplorer();
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
await dataGrid.clickCellExpandButton(3, 5);
|
||||
await dataGrid.clickCellExpandButton(3, 4);
|
||||
await testSubjects.existOrFail('euiDataGridExpansionPopover');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('render resource virtual column properly', async () => {
|
||||
it('should render service name and host name when present', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(0, 3);
|
||||
const cellValue = await cellElement.getVisibleText();
|
||||
expect(cellValue.includes('synth-service')).to.be(true);
|
||||
expect(cellValue.includes('synth-host')).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('virtual column cell actions', async () => {
|
||||
beforeEach(async () => {
|
||||
await navigateToLogExplorer();
|
||||
});
|
||||
it('should render a popover with cell actions when a chip on content column is clicked', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(0, 5);
|
||||
const cellElement = await dataGrid.getCellElement(0, 4);
|
||||
const logLevelChip = await cellElement.findByTestSubject(
|
||||
'dataTablePopoverChip_log.level'
|
||||
);
|
||||
|
@ -168,7 +178,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('should render the table filtered where log.level value is info when filter in action is clicked', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(0, 5);
|
||||
const cellElement = await dataGrid.getCellElement(0, 4);
|
||||
const logLevelChip = await cellElement.findByTestSubject(
|
||||
'dataTablePopoverChip_log.level'
|
||||
);
|
||||
|
@ -188,7 +198,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('should render the table filtered where log.level value is not info when filter out action is clicked', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(0, 5);
|
||||
const cellElement = await dataGrid.getCellElement(0, 4);
|
||||
const logLevelChip = await cellElement.findByTestSubject(
|
||||
'dataTablePopoverChip_log.level'
|
||||
);
|
||||
|
@ -203,6 +213,28 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await testSubjects.missingOrFail('dataTablePopoverChip_log.level');
|
||||
});
|
||||
});
|
||||
|
||||
it('should render the table filtered where service.name value is selected', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(0, 3);
|
||||
const serviceNameChip = await cellElement.findByTestSubject(
|
||||
'dataTablePopoverChip_service.name'
|
||||
);
|
||||
await serviceNameChip.click();
|
||||
|
||||
// Find Filter In button
|
||||
const filterInButton = await testSubjects.find(
|
||||
'dataTableCellAction_addToFilterAction_service.name'
|
||||
);
|
||||
|
||||
await filterInButton.click();
|
||||
const rowWithLogLevelInfo = await testSubjects.findAll(
|
||||
'dataTablePopoverChip_service.name'
|
||||
);
|
||||
|
||||
expect(rowWithLogLevelInfo.length).to.be(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -215,7 +247,12 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) {
|
|||
Array(count)
|
||||
.fill(0)
|
||||
.map(() => {
|
||||
return log.create().message('A sample log').logLevel('info').timestamp(timestamp);
|
||||
return log
|
||||
.create()
|
||||
.message('A sample log')
|
||||
.logLevel('info')
|
||||
.timestamp(timestamp)
|
||||
.defaults({ 'service.name': 'synth-service' });
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -229,7 +266,11 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) {
|
|||
Array(count)
|
||||
.fill(0)
|
||||
.map(() => {
|
||||
return log.create().message('A sample log').timestamp(timestamp);
|
||||
return log
|
||||
.create()
|
||||
.message('A sample log')
|
||||
.timestamp(timestamp)
|
||||
.defaults({ 'service.name': 'synth-service' });
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -243,11 +284,10 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) {
|
|||
Array(count)
|
||||
.fill(0)
|
||||
.map(() => {
|
||||
return log
|
||||
.create()
|
||||
.logLevel('info')
|
||||
.timestamp(timestamp)
|
||||
.defaults({ 'error.message': 'message in error object' });
|
||||
return log.create().logLevel('info').timestamp(timestamp).defaults({
|
||||
'error.message': 'message in error object',
|
||||
'service.name': 'node-service',
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -261,11 +301,10 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) {
|
|||
Array(count)
|
||||
.fill(0)
|
||||
.map(() => {
|
||||
return log
|
||||
.create()
|
||||
.logLevel('info')
|
||||
.timestamp(timestamp)
|
||||
.defaults({ 'event.original': 'message in event original' });
|
||||
return log.create().logLevel('info').timestamp(timestamp).defaults({
|
||||
'event.original': 'message in event original',
|
||||
'service.name': 'node-service',
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -69,8 +69,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await retry.try(async () => {
|
||||
expect(await PageObjects.discover.getColumnHeaders()).to.eql([
|
||||
'@timestamp',
|
||||
'service.name',
|
||||
'host.name',
|
||||
'resource',
|
||||
'content',
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ import { log, timerange } from '@kbn/apm-synthtrace-client';
|
|||
import moment from 'moment';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
const defaultLogColumns = ['@timestamp', 'service.name', 'host.name', 'content'];
|
||||
const defaultLogColumns = ['@timestamp', 'resource', 'content'];
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const retry = getService('retry');
|
||||
|
@ -60,8 +60,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
mode: 'absolute',
|
||||
},
|
||||
columns: [
|
||||
{ field: 'service.name' },
|
||||
{ field: 'host.name' },
|
||||
{ field: 'resource' },
|
||||
{ field: 'content' },
|
||||
{ field: 'data_stream.namespace' },
|
||||
],
|
||||
|
@ -80,7 +79,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
describe('render content virtual column properly', async () => {
|
||||
it('should render log level and log message when present', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(0, 5);
|
||||
const cellElement = await dataGrid.getCellElement(0, 4);
|
||||
const cellValue = await cellElement.getVisibleText();
|
||||
expect(cellValue.includes('info')).to.be(true);
|
||||
expect(cellValue.includes('A sample log')).to.be(true);
|
||||
|
@ -89,7 +88,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('should render log message when present and skip log level when missing', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(1, 5);
|
||||
const cellElement = await dataGrid.getCellElement(1, 4);
|
||||
const cellValue = await cellElement.getVisibleText();
|
||||
expect(cellValue.includes('info')).to.be(false);
|
||||
expect(cellValue.includes('A sample log')).to.be(true);
|
||||
|
@ -98,7 +97,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('should render message from error object when top level message not present', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(2, 5);
|
||||
const cellElement = await dataGrid.getCellElement(2, 4);
|
||||
const cellValue = await cellElement.getVisibleText();
|
||||
expect(cellValue.includes('info')).to.be(true);
|
||||
expect(cellValue.includes('error.message')).to.be(true);
|
||||
|
@ -108,7 +107,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('should render message from event.original when top level message and error.message not present', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(3, 5);
|
||||
const cellElement = await dataGrid.getCellElement(3, 4);
|
||||
const cellValue = await cellElement.getVisibleText();
|
||||
expect(cellValue.includes('info')).to.be(true);
|
||||
expect(cellValue.includes('event.original')).to.be(true);
|
||||
|
@ -118,7 +117,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('should render the whole JSON when neither message, error.message and event.original are present', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(4, 5);
|
||||
const cellElement = await dataGrid.getCellElement(4, 4);
|
||||
const cellValue = await cellElement.getVisibleText();
|
||||
expect(cellValue.includes('info')).to.be(true);
|
||||
|
||||
|
@ -134,7 +133,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('on cell expansion with no message field should open JSON Viewer', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
await dataGrid.clickCellExpandButton(4, 5);
|
||||
await dataGrid.clickCellExpandButton(4, 4);
|
||||
await testSubjects.existOrFail('dataTableExpandCellActionJsonPopover');
|
||||
});
|
||||
});
|
||||
|
@ -142,19 +141,30 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
it('on cell expansion with message field should open regular popover', async () => {
|
||||
await navigateToLogExplorer();
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
await dataGrid.clickCellExpandButton(3, 5);
|
||||
await dataGrid.clickCellExpandButton(3, 4);
|
||||
await testSubjects.existOrFail('euiDataGridExpansionPopover');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('render resource virtual column properly', async () => {
|
||||
it('should render service name and host name when present', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(0, 3);
|
||||
const cellValue = await cellElement.getVisibleText();
|
||||
expect(cellValue.includes('synth-service')).to.be(true);
|
||||
expect(cellValue.includes('synth-host')).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('virtual column cell actions', async () => {
|
||||
beforeEach(async () => {
|
||||
await navigateToLogExplorer();
|
||||
});
|
||||
it('should render a popover with cell actions when a chip on content column is clicked', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(0, 5);
|
||||
const cellElement = await dataGrid.getCellElement(0, 4);
|
||||
const logLevelChip = await cellElement.findByTestSubject(
|
||||
'dataTablePopoverChip_log.level'
|
||||
);
|
||||
|
@ -170,7 +180,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('should render the table filtered where log.level value is info when filter in action is clicked', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(0, 5);
|
||||
const cellElement = await dataGrid.getCellElement(0, 4);
|
||||
const logLevelChip = await cellElement.findByTestSubject(
|
||||
'dataTablePopoverChip_log.level'
|
||||
);
|
||||
|
@ -190,7 +200,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
|
||||
it('should render the table filtered where log.level value is not info when filter out action is clicked', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(0, 5);
|
||||
const cellElement = await dataGrid.getCellElement(0, 4);
|
||||
const logLevelChip = await cellElement.findByTestSubject(
|
||||
'dataTablePopoverChip_log.level'
|
||||
);
|
||||
|
@ -205,6 +215,28 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await testSubjects.missingOrFail('dataTablePopoverChip_log.level');
|
||||
});
|
||||
});
|
||||
|
||||
it('should render the table filtered where service.name value is selected', async () => {
|
||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||
const cellElement = await dataGrid.getCellElement(0, 3);
|
||||
const serviceNameChip = await cellElement.findByTestSubject(
|
||||
'dataTablePopoverChip_service.name'
|
||||
);
|
||||
await serviceNameChip.click();
|
||||
|
||||
// Find Filter In button
|
||||
const filterInButton = await testSubjects.find(
|
||||
'dataTableCellAction_addToFilterAction_service.name'
|
||||
);
|
||||
|
||||
await filterInButton.click();
|
||||
const rowWithLogLevelInfo = await testSubjects.findAll(
|
||||
'dataTablePopoverChip_service.name'
|
||||
);
|
||||
|
||||
expect(rowWithLogLevelInfo.length).to.be(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -217,7 +249,12 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) {
|
|||
Array(count)
|
||||
.fill(0)
|
||||
.map(() => {
|
||||
return log.create().message('A sample log').logLevel('info').timestamp(timestamp);
|
||||
return log
|
||||
.create()
|
||||
.message('A sample log')
|
||||
.logLevel('info')
|
||||
.timestamp(timestamp)
|
||||
.defaults({ 'service.name': 'synth-service' });
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -231,7 +268,11 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) {
|
|||
Array(count)
|
||||
.fill(0)
|
||||
.map(() => {
|
||||
return log.create().message('A sample log').timestamp(timestamp);
|
||||
return log
|
||||
.create()
|
||||
.message('A sample log')
|
||||
.timestamp(timestamp)
|
||||
.defaults({ 'service.name': 'synth-service' });
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -245,11 +286,10 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) {
|
|||
Array(count)
|
||||
.fill(0)
|
||||
.map(() => {
|
||||
return log
|
||||
.create()
|
||||
.logLevel('info')
|
||||
.timestamp(timestamp)
|
||||
.defaults({ 'error.message': 'message in error object' });
|
||||
return log.create().logLevel('info').timestamp(timestamp).defaults({
|
||||
'error.message': 'message in error object',
|
||||
'service.name': 'node-service',
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -263,11 +303,10 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) {
|
|||
Array(count)
|
||||
.fill(0)
|
||||
.map(() => {
|
||||
return log
|
||||
.create()
|
||||
.logLevel('info')
|
||||
.timestamp(timestamp)
|
||||
.defaults({ 'event.original': 'message in event original' });
|
||||
return log.create().logLevel('info').timestamp(timestamp).defaults({
|
||||
'event.original': 'message in event original',
|
||||
'service.name': 'node-service',
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -91,8 +91,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await retry.try(async () => {
|
||||
expect(await PageObjects.discover.getColumnHeaders()).to.eql([
|
||||
'@timestamp',
|
||||
'service.name',
|
||||
'host.name',
|
||||
'resource',
|
||||
'content',
|
||||
]);
|
||||
});
|
||||
|
@ -151,8 +150,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
expect(await PageObjects.discover.getColumnHeaders()).not.to.eql([
|
||||
'@timestamp',
|
||||
'content',
|
||||
'service.name',
|
||||
'host.name',
|
||||
'resource',
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue