mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 11:05:39 -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.name'?: string;
|
||||||
'orchestrator.cluster.id'?: string;
|
'orchestrator.cluster.id'?: string;
|
||||||
'orchestrator.resource.id'?: string;
|
'orchestrator.resource.id'?: string;
|
||||||
|
'orchestrator.namespace'?: string;
|
||||||
|
'container.name'?: string;
|
||||||
'cloud.provider'?: string;
|
'cloud.provider'?: string;
|
||||||
'cloud.region'?: string;
|
'cloud.region'?: string;
|
||||||
'cloud.availability_zone'?: 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 CLOUD_REGION = ['eu-central-1', 'us-east-1', 'area-51'];
|
||||||
|
|
||||||
const CLUSTER = [
|
const CLUSTER = [
|
||||||
{ clusterId: generateShortId(), clusterName: 'synth-cluster-1' },
|
{ clusterId: generateShortId(), clusterName: 'synth-cluster-1', namespace: 'default' },
|
||||||
{ clusterId: generateShortId(), clusterName: 'synth-cluster-2' },
|
{ clusterId: generateShortId(), clusterName: 'synth-cluster-2', namespace: 'production' },
|
||||||
{ clusterId: generateShortId(), clusterName: 'synth-cluster-3' },
|
{ clusterId: generateShortId(), clusterName: 'synth-cluster-3', namespace: 'kube' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const SERVICE_NAMES = Array(3)
|
const SERVICE_NAMES = Array(3)
|
||||||
|
@ -48,9 +48,11 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
|
||||||
.service(SERVICE_NAMES[index])
|
.service(SERVICE_NAMES[index])
|
||||||
.defaults({
|
.defaults({
|
||||||
'trace.id': generateShortId(),
|
'trace.id': generateShortId(),
|
||||||
'agent.name': 'synth-agent',
|
'agent.name': 'nodejs',
|
||||||
'orchestrator.cluster.name': CLUSTER[index].clusterName,
|
'orchestrator.cluster.name': CLUSTER[index].clusterName,
|
||||||
'orchestrator.cluster.id': CLUSTER[index].clusterId,
|
'orchestrator.cluster.id': CLUSTER[index].clusterId,
|
||||||
|
'orchestrator.namespace': CLUSTER[index].namespace,
|
||||||
|
'container.name': `${SERVICE_NAMES[index]}-${generateShortId()}`,
|
||||||
'orchestrator.resource.id': generateShortId(),
|
'orchestrator.resource.id': generateShortId(),
|
||||||
'cloud.provider': CLOUD_PROVIDERS[Math.floor(Math.random() * 3)],
|
'cloud.provider': CLOUD_PROVIDERS[Math.floor(Math.random() * 3)],
|
||||||
'cloud.region': CLOUD_REGION[index],
|
'cloud.region': CLOUD_REGION[index],
|
||||||
|
@ -77,10 +79,12 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
|
||||||
.defaults({
|
.defaults({
|
||||||
'trace.id': generateShortId(),
|
'trace.id': generateShortId(),
|
||||||
'error.message': MESSAGE_LOG_LEVELS[index].message,
|
'error.message': MESSAGE_LOG_LEVELS[index].message,
|
||||||
'agent.name': 'synth-agent',
|
'agent.name': 'nodejs',
|
||||||
'orchestrator.cluster.name': CLUSTER[index].clusterName,
|
'orchestrator.cluster.name': CLUSTER[index].clusterName,
|
||||||
'orchestrator.cluster.id': CLUSTER[index].clusterId,
|
'orchestrator.cluster.id': CLUSTER[index].clusterId,
|
||||||
'orchestrator.resource.id': generateShortId(),
|
'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.provider': CLOUD_PROVIDERS[Math.floor(Math.random() * 3)],
|
||||||
'cloud.region': CLOUD_REGION[index],
|
'cloud.region': CLOUD_REGION[index],
|
||||||
'cloud.availability_zone': `${CLOUD_REGION[index]}a`,
|
'cloud.availability_zone': `${CLOUD_REGION[index]}a`,
|
||||||
|
@ -107,10 +111,12 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
|
||||||
.defaults({
|
.defaults({
|
||||||
'trace.id': generateShortId(),
|
'trace.id': generateShortId(),
|
||||||
'error.message': MESSAGE_LOG_LEVELS[index].message,
|
'error.message': MESSAGE_LOG_LEVELS[index].message,
|
||||||
'agent.name': 'synth-agent',
|
'agent.name': 'nodejs',
|
||||||
'orchestrator.cluster.name': CLUSTER[index].clusterName,
|
'orchestrator.cluster.name': CLUSTER[index].clusterName,
|
||||||
'orchestrator.cluster.id': CLUSTER[index].clusterId,
|
'orchestrator.cluster.id': CLUSTER[index].clusterId,
|
||||||
'orchestrator.resource.id': generateShortId(),
|
'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.provider': CLOUD_PROVIDERS[Math.floor(Math.random() * 3)],
|
||||||
'cloud.region': CLOUD_REGION[index],
|
'cloud.region': CLOUD_REGION[index],
|
||||||
'cloud.availability_zone': `${CLOUD_REGION[index]}a`,
|
'cloud.availability_zone': `${CLOUD_REGION[index]}a`,
|
||||||
|
@ -137,10 +143,12 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
|
||||||
.defaults({
|
.defaults({
|
||||||
'trace.id': generateShortId(),
|
'trace.id': generateShortId(),
|
||||||
'event.original': MESSAGE_LOG_LEVELS[index].message,
|
'event.original': MESSAGE_LOG_LEVELS[index].message,
|
||||||
'agent.name': 'synth-agent',
|
'agent.name': 'nodejs',
|
||||||
'orchestrator.cluster.name': CLUSTER[index].clusterName,
|
'orchestrator.cluster.name': CLUSTER[index].clusterName,
|
||||||
'orchestrator.cluster.id': CLUSTER[index].clusterId,
|
'orchestrator.cluster.id': CLUSTER[index].clusterId,
|
||||||
'orchestrator.resource.id': generateShortId(),
|
'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.provider': CLOUD_PROVIDERS[Math.floor(Math.random() * 3)],
|
||||||
'cloud.region': CLOUD_REGION[index],
|
'cloud.region': CLOUD_REGION[index],
|
||||||
'cloud.availability_zone': `${CLOUD_REGION[index]}a`,
|
'cloud.availability_zone': `${CLOUD_REGION[index]}a`,
|
||||||
|
@ -166,10 +174,12 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
|
||||||
.service(SERVICE_NAMES[index])
|
.service(SERVICE_NAMES[index])
|
||||||
.defaults({
|
.defaults({
|
||||||
'trace.id': generateShortId(),
|
'trace.id': generateShortId(),
|
||||||
'agent.name': 'synth-agent',
|
'agent.name': 'nodejs',
|
||||||
'orchestrator.cluster.name': CLUSTER[index].clusterName,
|
'orchestrator.cluster.name': CLUSTER[index].clusterName,
|
||||||
'orchestrator.cluster.id': CLUSTER[index].clusterId,
|
'orchestrator.cluster.id': CLUSTER[index].clusterId,
|
||||||
'orchestrator.resource.id': generateShortId(),
|
'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.provider': CLOUD_PROVIDERS[Math.floor(Math.random() * 3)],
|
||||||
'cloud.region': CLOUD_REGION[index],
|
'cloud.region': CLOUD_REGION[index],
|
||||||
'cloud.availability_zone': `${CLOUD_REGION[index]}a`,
|
'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} />;
|
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
|
licensing: 29004
|
||||||
links: 44490
|
links: 44490
|
||||||
lists: 22900
|
lists: 22900
|
||||||
logExplorer: 44977
|
logExplorer: 50000
|
||||||
logsShared: 281060
|
logsShared: 281060
|
||||||
logstash: 53548
|
logstash: 53548
|
||||||
management: 46112
|
management: 46112
|
||||||
|
|
|
@ -15,7 +15,7 @@ const ColumnHeaderTruncateContainer = ({
|
||||||
headerRowHeight = 1,
|
headerRowHeight = 1,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
headerRowHeight: number;
|
headerRowHeight?: number;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -14,23 +14,29 @@ export const LOG_LEVEL_FIELD = 'log.level';
|
||||||
export const MESSAGE_FIELD = 'message';
|
export const MESSAGE_FIELD = 'message';
|
||||||
export const ERROR_MESSAGE_FIELD = 'error.message';
|
export const ERROR_MESSAGE_FIELD = 'error.message';
|
||||||
export const EVENT_ORIGINAL_FIELD = 'event.original';
|
export const EVENT_ORIGINAL_FIELD = 'event.original';
|
||||||
export const SERVICE_NAME_FIELD = 'service.name';
|
|
||||||
export const TRACE_ID_FIELD = 'trace.id';
|
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 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_PROVIDER_FIELD = 'cloud.provider';
|
||||||
export const CLOUD_REGION_FIELD = 'cloud.region';
|
export const CLOUD_REGION_FIELD = 'cloud.region';
|
||||||
export const CLOUD_AVAILABILITY_ZONE_FIELD = 'cloud.availability_zone';
|
export const CLOUD_AVAILABILITY_ZONE_FIELD = 'cloud.availability_zone';
|
||||||
export const CLOUD_PROJECT_ID_FIELD = 'cloud.project.id';
|
export const CLOUD_PROJECT_ID_FIELD = 'cloud.project.id';
|
||||||
export const CLOUD_INSTANCE_ID_FIELD = 'cloud.instance.id';
|
export const CLOUD_INSTANCE_ID_FIELD = 'cloud.instance.id';
|
||||||
export const LOG_FILE_PATH_FIELD = 'log.file.path';
|
export const SERVICE_NAME_FIELD = 'service.name';
|
||||||
export const DATASTREAM_NAMESPACE_FIELD = 'data_stream.namespace';
|
export const ORCHESTRATOR_CLUSTER_NAME_FIELD = 'orchestrator.cluster.name';
|
||||||
export const DATASTREAM_DATASET_FIELD = 'data_stream.dataset';
|
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
|
// Virtual column fields
|
||||||
export const CONTENT_FIELD = 'content';
|
export const CONTENT_FIELD = 'content';
|
||||||
|
export const RESOURCE_FIELD = 'resource';
|
||||||
|
|
||||||
// Sizing
|
// Sizing
|
||||||
export const DATA_GRID_COLUMN_WIDTH_SMALL = 240;
|
export const DATA_GRID_COLUMN_WIDTH_SMALL = 240;
|
||||||
|
@ -39,11 +45,7 @@ export const DATA_GRID_COLUMN_WIDTH_MEDIUM = 320;
|
||||||
// UI preferences
|
// UI preferences
|
||||||
export const DEFAULT_COLUMNS = [
|
export const DEFAULT_COLUMNS = [
|
||||||
{
|
{
|
||||||
field: SERVICE_NAME_FIELD,
|
field: RESOURCE_FIELD,
|
||||||
width: DATA_GRID_COLUMN_WIDTH_SMALL,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: HOST_NAME_FIELD,
|
|
||||||
width: DATA_GRID_COLUMN_WIDTH_MEDIUM,
|
width: DATA_GRID_COLUMN_WIDTH_MEDIUM,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,24 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { DataTableRecord } from '@kbn/discover-utils/types';
|
import type { DataTableRecord } from '@kbn/discover-utils/src/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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LogDocument extends DataTableRecord {
|
export interface LogDocument extends DataTableRecord {
|
||||||
flattened: {
|
flattened: {
|
||||||
|
@ -37,7 +20,11 @@ export interface LogDocument extends DataTableRecord {
|
||||||
'trace.id'?: string;
|
'trace.id'?: string;
|
||||||
'agent.name'?: string;
|
'agent.name'?: string;
|
||||||
'orchestrator.cluster.name'?: string;
|
'orchestrator.cluster.name'?: string;
|
||||||
|
'orchestrator.cluster.id'?: string;
|
||||||
'orchestrator.resource.id'?: string;
|
'orchestrator.resource.id'?: string;
|
||||||
|
'orchestrator.namespace'?: string;
|
||||||
|
'container.name'?: string;
|
||||||
|
'container.id'?: string;
|
||||||
'cloud.provider'?: string;
|
'cloud.provider'?: string;
|
||||||
'cloud.region'?: string;
|
'cloud.region'?: string;
|
||||||
'cloud.availability_zone'?: string;
|
'cloud.availability_zone'?: string;
|
||||||
|
@ -61,7 +48,10 @@ export interface FlyoutDoc {
|
||||||
'trace.id'?: string;
|
'trace.id'?: string;
|
||||||
'agent.name'?: string;
|
'agent.name'?: string;
|
||||||
'orchestrator.cluster.name'?: string;
|
'orchestrator.cluster.name'?: string;
|
||||||
|
'orchestrator.cluster.id'?: string;
|
||||||
'orchestrator.resource.id'?: string;
|
'orchestrator.resource.id'?: string;
|
||||||
|
'orchestrator.namespace'?: string;
|
||||||
|
'container.name'?: string;
|
||||||
'cloud.provider'?: string;
|
'cloud.provider'?: string;
|
||||||
'cloud.region'?: string;
|
'cloud.region'?: string;
|
||||||
'cloud.availability_zone'?: string;
|
'cloud.availability_zone'?: string;
|
||||||
|
@ -72,8 +62,15 @@ export interface FlyoutDoc {
|
||||||
'data_stream.dataset': string;
|
'data_stream.dataset': string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RenderContentCustomization<Props> = (
|
export interface ResourceFields {
|
||||||
renderPreviousContent: RenderPreviousContent<Props>
|
'host.name'?: string;
|
||||||
) => (props: Props) => React.ReactNode;
|
'service.name'?: string;
|
||||||
|
'agent.name'?: string;
|
||||||
export type RenderPreviousContent<Props> = (props: Props) => React.ReactNode;
|
'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.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { EuiPopover, EuiPopoverTitle } from '@elastic/eui';
|
import { EuiPopover, EuiPopoverTitle } from '@elastic/eui';
|
||||||
|
|
||||||
export const HoverPopover = ({
|
export const HoverPopover = ({
|
||||||
|
@ -20,10 +20,14 @@ export const HoverPopover = ({
|
||||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||||
const leaveTimer = useRef<NodeJS.Timeout | null>(null);
|
const leaveTimer = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
const onMouseEnter = () => {
|
const clearTimer = () => {
|
||||||
if (leaveTimer.current) {
|
if (leaveTimer.current) {
|
||||||
clearTimeout(leaveTimer.current);
|
clearTimeout(leaveTimer.current);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseEnter = () => {
|
||||||
|
clearTimer();
|
||||||
setIsPopoverOpen(true);
|
setIsPopoverOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -31,6 +35,12 @@ export const HoverPopover = ({
|
||||||
leaveTimer.current = setTimeout(() => setIsPopoverOpen(false), 100);
|
leaveTimer.current = setTimeout(() => setIsPopoverOpen(false), 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
clearTimer();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
<div onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||||
<EuiPopover
|
<EuiPopover
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useEuiTheme } from '@elastic/eui';
|
import { useEuiTheme } from '@elastic/eui';
|
||||||
import { FlyoutDoc } from '../flyout_detail/types';
|
import { FlyoutDoc } from '../../../common/document';
|
||||||
import { ChipWithPopover } from './popover_chip';
|
import { ChipWithPopover } from './popover_chip';
|
||||||
import * as constants from '../../../common/constants';
|
import * as constants from '../../../common/constants';
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ import {
|
||||||
type EuiBadgeProps,
|
type EuiBadgeProps,
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
EuiFlexItem,
|
EuiFlexItem,
|
||||||
EuiIcon,
|
|
||||||
EuiPopover,
|
EuiPopover,
|
||||||
useEuiFontSize,
|
useEuiFontSize,
|
||||||
EuiPopoverFooter,
|
EuiPopoverFooter,
|
||||||
|
@ -24,6 +23,7 @@ import { FilterInButton } from './filter_in_button';
|
||||||
import { FilterOutButton } from './filter_out_button';
|
import { FilterOutButton } from './filter_out_button';
|
||||||
import { CopyButton } from './copy_button';
|
import { CopyButton } from './copy_button';
|
||||||
import { dynamic } from '../../utils/dynamic';
|
import { dynamic } from '../../utils/dynamic';
|
||||||
|
|
||||||
const DataTablePopoverCellValue = dynamic(
|
const DataTablePopoverCellValue = dynamic(
|
||||||
() => import('@kbn/unified-data-table/src/components/data_table_cell_value')
|
() => import('@kbn/unified-data-table/src/components/data_table_cell_value')
|
||||||
);
|
);
|
||||||
|
@ -38,7 +38,7 @@ interface ChipWithPopoverProps {
|
||||||
*/
|
*/
|
||||||
text: string;
|
text: string;
|
||||||
dataTestSubj?: string;
|
dataTestSubj?: string;
|
||||||
leftSideIcon?: EuiBadgeProps['iconType'];
|
leftSideIcon?: React.ReactNode;
|
||||||
rightSideIcon?: EuiBadgeProps['iconType'];
|
rightSideIcon?: EuiBadgeProps['iconType'];
|
||||||
borderColor?: string | null;
|
borderColor?: string | null;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
|
@ -78,15 +78,12 @@ export function ChipWithPopover({
|
||||||
font-size: ${xsFontSize};
|
font-size: ${xsFontSize};
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
margin-top: -3px;
|
||||||
`}
|
`}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
<EuiFlexGroup gutterSize="xs">
|
<EuiFlexGroup gutterSize="xs">
|
||||||
{leftSideIcon && (
|
{leftSideIcon && <EuiFlexItem>{leftSideIcon}</EuiFlexItem>}
|
||||||
<EuiFlexItem>
|
|
||||||
<EuiIcon type={leftSideIcon} />
|
|
||||||
</EuiFlexItem>
|
|
||||||
)}
|
|
||||||
<EuiFlexItem>{text}</EuiFlexItem>
|
<EuiFlexItem>{text}</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</EuiBadge>
|
</EuiBadge>
|
||||||
|
|
|
@ -15,6 +15,10 @@ export const contentLabel = i18n.translate('xpack.logExplorer.dataTable.header.p
|
||||||
defaultMessage: 'Content',
|
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', {
|
export const flyoutServiceLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.service', {
|
||||||
defaultMessage: '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',
|
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 React from 'react';
|
||||||
import { LogExplorerFlyoutContentProps } from './types';
|
import { LogExplorerFlyoutContentProps } from '../../customizations/types';
|
||||||
import { useDocDetail } from '../../hooks/use_doc_detail';
|
import { useDocDetail } from '../../hooks/use_doc_detail';
|
||||||
import { FlyoutHeader } from './flyout_header';
|
import { FlyoutHeader } from './flyout_header';
|
||||||
import { FlyoutHighlights } from './flyout_highlights';
|
import { FlyoutHighlights } from './flyout_highlights';
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
useGeneratedHtmlId,
|
useGeneratedHtmlId,
|
||||||
EuiTitle,
|
EuiTitle,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import { FlyoutDoc } from './types';
|
import { FlyoutDoc } from '../../../common/document';
|
||||||
import { getMessageWithFallbacks } from '../../hooks/use_doc_detail';
|
import { getMessageWithFallbacks } from '../../hooks/use_doc_detail';
|
||||||
import { LogLevel } from '../common/log_level';
|
import { LogLevel } from '../common/log_level';
|
||||||
import { Timestamp } from './sub_components/timestamp';
|
import { Timestamp } from './sub_components/timestamp';
|
||||||
|
|
|
@ -8,7 +8,7 @@ import React from 'react';
|
||||||
import { CloudProvider, CloudProviderIcon } from '@kbn/custom-icons';
|
import { CloudProvider, CloudProviderIcon } from '@kbn/custom-icons';
|
||||||
import { useMeasure } from 'react-use/lib';
|
import { useMeasure } from 'react-use/lib';
|
||||||
import { first } from 'lodash';
|
import { first } from 'lodash';
|
||||||
import { FlyoutDoc, LogDocument } from './types';
|
import { FlyoutDoc, LogDocument } from '../../../common/document';
|
||||||
import * as constants from '../../../common/constants';
|
import * as constants from '../../../common/constants';
|
||||||
import { HighlightField } from './sub_components/highlight_field';
|
import { HighlightField } from './sub_components/highlight_field';
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -6,4 +6,3 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './flyout_detail';
|
export * from './flyout_detail';
|
||||||
export * from './types';
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { EuiBadge } from '@elastic/eui';
|
import { EuiBadge } from '@elastic/eui';
|
||||||
import { FlyoutDoc } from '../types';
|
import { FlyoutDoc } from '../../../../common/document';
|
||||||
|
|
||||||
interface TimestampProps {
|
interface TimestampProps {
|
||||||
timestamp: FlyoutDoc['@timestamp'];
|
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 React from 'react';
|
||||||
import { CustomGridColumnProps } from '@kbn/unified-data-table';
|
import { CustomGridColumnProps } from '@kbn/unified-data-table';
|
||||||
import { ContentColumnTooltip } from './column_tooltips/content_column_tooltip';
|
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 =
|
export const renderColumn =
|
||||||
(field: string) =>
|
(field: string) =>
|
||||||
|
@ -17,6 +18,11 @@ export const renderColumn =
|
||||||
case CONTENT_FIELD:
|
case CONTENT_FIELD:
|
||||||
column.display = <ContentColumnTooltip column={column} headerRowHeight={headerRowHeight} />;
|
column.display = <ContentColumnTooltip column={column} headerRowHeight={headerRowHeight} />;
|
||||||
break;
|
break;
|
||||||
|
case RESOURCE_FIELD:
|
||||||
|
column.display = (
|
||||||
|
<ResourceColumnTooltip column={column} headerRowHeight={headerRowHeight} />
|
||||||
|
);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiToken, useEuiTheme } from '@elastic/eui';
|
import { EuiText, useEuiTheme } from '@elastic/eui';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { CustomGridColumnProps } from '@kbn/unified-data-table';
|
import { CustomGridColumnProps } from '@kbn/unified-data-table';
|
||||||
import { css } from '@emotion/react';
|
import { css } from '@emotion/react';
|
||||||
|
@ -14,12 +14,10 @@ import {
|
||||||
contentHeaderTooltipParagraph2,
|
contentHeaderTooltipParagraph2,
|
||||||
contentLabel,
|
contentLabel,
|
||||||
} from '../../common/translations';
|
} from '../../common/translations';
|
||||||
import { dynamic } from '../../../utils/dynamic';
|
|
||||||
import { HoverPopover } from '../../common/hover_popover';
|
import { HoverPopover } from '../../common/hover_popover';
|
||||||
|
import { TooltipButtonComponent } from './tooltip_button';
|
||||||
const ColumnHeaderTruncateContainer = dynamic(
|
import { FieldWithToken } from './field_with_token';
|
||||||
() => import('@kbn/unified-data-table/src/components/column_header_truncate_container')
|
import * as constants from '../../../../common/constants';
|
||||||
);
|
|
||||||
|
|
||||||
export const ContentColumnTooltip = ({ column, headerRowHeight }: CustomGridColumnProps) => {
|
export const ContentColumnTooltip = ({ column, headerRowHeight }: CustomGridColumnProps) => {
|
||||||
const { euiTheme } = useEuiTheme();
|
const { euiTheme } = useEuiTheme();
|
||||||
|
@ -27,13 +25,16 @@ export const ContentColumnTooltip = ({ column, headerRowHeight }: CustomGridColu
|
||||||
margin-bottom: ${euiTheme.size.s};
|
margin-bottom: ${euiTheme.size.s};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const contentButtonComponent = (
|
|
||||||
<ColumnHeaderTruncateContainer headerRowHeight={headerRowHeight}>
|
|
||||||
{column.displayAsText} <EuiIcon type="questionInCircle" />
|
|
||||||
</ColumnHeaderTruncateContainer>
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<HoverPopover button={contentButtonComponent} title={contentLabel}>
|
<HoverPopover
|
||||||
|
button={
|
||||||
|
<TooltipButtonComponent
|
||||||
|
displayText={column.displayAsText}
|
||||||
|
headerRowHeight={headerRowHeight}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
title={contentLabel}
|
||||||
|
>
|
||||||
<div style={{ width: '230px' }}>
|
<div style={{ width: '230px' }}>
|
||||||
<EuiText size="s" css={spacingCSS}>
|
<EuiText size="s" css={spacingCSS}>
|
||||||
<p>{contentHeaderTooltipParagraph1}</p>
|
<p>{contentHeaderTooltipParagraph1}</p>
|
||||||
|
@ -41,36 +42,8 @@ export const ContentColumnTooltip = ({ column, headerRowHeight }: CustomGridColu
|
||||||
<EuiText size="s" css={spacingCSS}>
|
<EuiText size="s" css={spacingCSS}>
|
||||||
<p>{contentHeaderTooltipParagraph2}</p>
|
<p>{contentHeaderTooltipParagraph2}</p>
|
||||||
</EuiText>
|
</EuiText>
|
||||||
<EuiFlexGroup
|
<FieldWithToken field={constants.ERROR_MESSAGE_FIELD} />
|
||||||
responsive={false}
|
<FieldWithToken field={constants.EVENT_ORIGINAL_FIELD} iconType="tokenEvent" />
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
</HoverPopover>
|
</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 { i18n } from '@kbn/i18n';
|
||||||
import type { DataTableRecord } from '@kbn/discover-utils/src/types';
|
import type { DataTableRecord } from '@kbn/discover-utils/src/types';
|
||||||
import { useDocDetail, getMessageWithFallbacks } from '../../hooks/use_doc_detail';
|
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 { LogLevel } from '../common/log_level';
|
||||||
import * as constants from '../../../common/constants';
|
import * as constants from '../../../common/constants';
|
||||||
import { dynamic } from '../../utils/dynamic';
|
import { dynamic } from '../../utils/dynamic';
|
||||||
import { VirtualColumnServiceProvider } from '../../hooks/use_virtual_column_services';
|
|
||||||
import './virtual_column.scss';
|
import './virtual_column.scss';
|
||||||
|
|
||||||
const SourceDocument = dynamic(
|
const SourceDocument = dynamic(
|
||||||
|
@ -72,7 +71,7 @@ const SourcePopoverContent = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Content = ({
|
export const Content = ({
|
||||||
row,
|
row,
|
||||||
dataView,
|
dataView,
|
||||||
fieldFormats,
|
fieldFormats,
|
||||||
|
@ -116,18 +115,3 @@ const Content = ({
|
||||||
</span>
|
</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 { DatasetsService } from '../services/datasets';
|
||||||
import { createLogExplorerControllerStateMachine } from '../state_machines/log_explorer_controller';
|
import { createLogExplorerControllerStateMachine } from '../state_machines/log_explorer_controller';
|
||||||
import { LogExplorerStartDeps } from '../types';
|
import { LogExplorerStartDeps } from '../types';
|
||||||
import { LogExplorerCustomizations } from './controller_customizations';
|
import { LogExplorerCustomizations } from '../customizations/types';
|
||||||
import { createDataServiceProxy } from './custom_data_service';
|
import { createDataServiceProxy } from './custom_data_service';
|
||||||
import { createUiSettingsServiceProxy } from './custom_ui_settings_service';
|
import { createUiSettingsServiceProxy } from './custom_ui_settings_service';
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './controller_customizations';
|
export * from '../customizations/types';
|
||||||
export * from './create_controller';
|
export * from './create_controller';
|
||||||
export * from './provider';
|
export * from './provider';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|
|
@ -20,7 +20,7 @@ import {
|
||||||
LogExplorerControllerStateMachine,
|
LogExplorerControllerStateMachine,
|
||||||
LogExplorerControllerStateService,
|
LogExplorerControllerStateService,
|
||||||
} from '../state_machines/log_explorer_controller';
|
} from '../state_machines/log_explorer_controller';
|
||||||
import { LogExplorerCustomizations } from './controller_customizations';
|
import { LogExplorerCustomizations } from '../customizations/types';
|
||||||
|
|
||||||
export interface LogExplorerController {
|
export interface LogExplorerController {
|
||||||
actions: {};
|
actions: {};
|
||||||
|
|
|
@ -6,11 +6,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||||
import { CONTENT_FIELD } from '../../common/constants';
|
import { CONTENT_FIELD, RESOURCE_FIELD } from '../../common/constants';
|
||||||
import { renderContent } from '../components/virtual_columns/content';
|
import { renderCell } from '../components/virtual_columns/cell_renderer';
|
||||||
|
|
||||||
export const createCustomCellRenderer = ({ data }: { data: DataPublicPluginStart }) => {
|
export const createCustomCellRenderer = ({ data }: { data: DataPublicPluginStart }) => {
|
||||||
return {
|
return {
|
||||||
[CONTENT_FIELD]: renderContent({ data }),
|
[CONTENT_FIELD]: renderCell(CONTENT_FIELD, { data }),
|
||||||
|
[RESOURCE_FIELD]: renderCell(RESOURCE_FIELD, { data }),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,9 +5,10 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CONTENT_FIELD } from '../../common/constants';
|
import { CONTENT_FIELD, RESOURCE_FIELD } from '../../common/constants';
|
||||||
import { renderColumn } from '../components/virtual_columns/column';
|
import { renderColumn } from '../components/virtual_columns/column';
|
||||||
|
|
||||||
export const createCustomGridColumnsConfiguration = () => ({
|
export const createCustomGridColumnsConfiguration = () => ({
|
||||||
[CONTENT_FIELD]: renderColumn(CONTENT_FIELD),
|
[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 { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||||
import { DocViewRenderProps } from '@kbn/unified-doc-viewer/types';
|
import { DocViewRenderProps } from '@kbn/unified-doc-viewer/types';
|
||||||
import { FlyoutDetail } from '../components/flyout_detail/flyout_detail';
|
import { FlyoutDetail } from '../components/flyout_detail/flyout_detail';
|
||||||
import { LogExplorerFlyoutContentProps } from '../components/flyout_detail';
|
import { LogExplorerFlyoutContentProps } from './types';
|
||||||
import { LogDocument, useLogExplorerControllerContext } from '../controller';
|
import { useLogExplorerControllerContext } from '../controller';
|
||||||
|
import { LogDocument } from '../../common/document';
|
||||||
|
|
||||||
const CustomFlyoutContent = ({
|
const CustomFlyoutContent = ({
|
||||||
filter,
|
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.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
import createContainer from 'constate';
|
import createContainer from 'constate';
|
||||||
import type { LogExplorerFlyoutContentProps } from '../components/flyout_detail/types';
|
import type { LogExplorerFlyoutContentProps } from '../customizations/types';
|
||||||
|
|
||||||
interface UseFlyoutActionsDeps {
|
interface UseFlyoutActionsDeps {
|
||||||
value: LogExplorerFlyoutContentProps['actions'];
|
value: LogExplorerFlyoutContentProps['actions'];
|
||||||
|
|
|
@ -7,11 +7,8 @@
|
||||||
import { formatFieldValue } from '@kbn/discover-utils';
|
import { formatFieldValue } from '@kbn/discover-utils';
|
||||||
import * as constants from '../../common/constants';
|
import * as constants from '../../common/constants';
|
||||||
import { useKibanaContextForPlugin } from '../utils/use_kibana';
|
import { useKibanaContextForPlugin } from '../utils/use_kibana';
|
||||||
import {
|
import { LogExplorerFlyoutContentProps } from '../customizations/types';
|
||||||
FlyoutDoc,
|
import { FlyoutDoc, LogDocument } from '../../common/document';
|
||||||
LogExplorerFlyoutContentProps,
|
|
||||||
LogDocument,
|
|
||||||
} from '../components/flyout_detail/types';
|
|
||||||
|
|
||||||
export function useDocDetail(
|
export function useDocDetail(
|
||||||
doc: LogDocument,
|
doc: LogDocument,
|
||||||
|
|
|
@ -8,14 +8,17 @@
|
||||||
import type { PluginInitializerContext } from '@kbn/core/public';
|
import type { PluginInitializerContext } from '@kbn/core/public';
|
||||||
import type { LogExplorerConfig } from '../common/plugin_config';
|
import type { LogExplorerConfig } from '../common/plugin_config';
|
||||||
import { LogExplorerPlugin } from './plugin';
|
import { LogExplorerPlugin } from './plugin';
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
CreateLogExplorerController,
|
CreateLogExplorerController,
|
||||||
LogExplorerController,
|
LogExplorerController,
|
||||||
LogExplorerCustomizations,
|
|
||||||
LogExplorerFlyoutContentProps,
|
|
||||||
LogExplorerPublicState,
|
LogExplorerPublicState,
|
||||||
LogExplorerPublicStateUpdate,
|
LogExplorerPublicStateUpdate,
|
||||||
} from './controller';
|
} from './controller';
|
||||||
|
export type {
|
||||||
|
LogExplorerCustomizations,
|
||||||
|
LogExplorerFlyoutContentProps,
|
||||||
|
} from './customizations/types';
|
||||||
export type { LogExplorerControllerContext } from './state_machines/log_explorer_controller';
|
export type { LogExplorerControllerContext } from './state_machines/log_explorer_controller';
|
||||||
export type { LogExplorerPluginSetup, LogExplorerPluginStart } from './types';
|
export type { LogExplorerPluginSetup, LogExplorerPluginStart } from './types';
|
||||||
export {
|
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-field-list",
|
||||||
"@kbn/unified-search-plugin",
|
"@kbn/unified-search-plugin",
|
||||||
"@kbn/xstate-utils",
|
"@kbn/xstate-utils",
|
||||||
|
"@kbn/elastic-agent-utils",
|
||||||
|
"@kbn/ui-theme",
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"target/**/*"
|
"target/**/*"
|
||||||
|
|
|
@ -9,7 +9,7 @@ import moment from 'moment/moment';
|
||||||
import { log, timerange } from '@kbn/apm-synthtrace-client';
|
import { log, timerange } from '@kbn/apm-synthtrace-client';
|
||||||
import { FtrProviderContext } from './config';
|
import { FtrProviderContext } from './config';
|
||||||
|
|
||||||
const defaultLogColumns = ['@timestamp', 'service.name', 'host.name', 'content'];
|
const defaultLogColumns = ['@timestamp', 'resource', 'content'];
|
||||||
|
|
||||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
const retry = getService('retry');
|
const retry = getService('retry');
|
||||||
|
@ -58,8 +58,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
mode: 'absolute',
|
mode: 'absolute',
|
||||||
},
|
},
|
||||||
columns: [
|
columns: [
|
||||||
{ field: 'service.name' },
|
{ field: 'resource' },
|
||||||
{ field: 'host.name' },
|
|
||||||
{ field: 'content' },
|
{ field: 'content' },
|
||||||
{ field: 'data_stream.namespace' },
|
{ field: 'data_stream.namespace' },
|
||||||
],
|
],
|
||||||
|
@ -78,7 +77,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
describe('render content virtual column properly', async () => {
|
describe('render content virtual column properly', async () => {
|
||||||
it('should render log level and log message when present', async () => {
|
it('should render log level and log message when present', async () => {
|
||||||
await retry.tryForTime(TEST_TIMEOUT, 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();
|
const cellValue = await cellElement.getVisibleText();
|
||||||
expect(cellValue.includes('info')).to.be(true);
|
expect(cellValue.includes('info')).to.be(true);
|
||||||
expect(cellValue.includes('A sample log')).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 () => {
|
it('should render log message when present and skip log level when missing', async () => {
|
||||||
await retry.tryForTime(TEST_TIMEOUT, 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();
|
const cellValue = await cellElement.getVisibleText();
|
||||||
expect(cellValue.includes('info')).to.be(false);
|
expect(cellValue.includes('info')).to.be(false);
|
||||||
expect(cellValue.includes('A sample log')).to.be(true);
|
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 () => {
|
it('should render message from error object when top level message not present', async () => {
|
||||||
await retry.tryForTime(TEST_TIMEOUT, 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();
|
const cellValue = await cellElement.getVisibleText();
|
||||||
expect(cellValue.includes('info')).to.be(true);
|
expect(cellValue.includes('info')).to.be(true);
|
||||||
expect(cellValue.includes('error.message')).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 () => {
|
it('should render message from event.original when top level message and error.message not present', async () => {
|
||||||
await retry.tryForTime(TEST_TIMEOUT, 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();
|
const cellValue = await cellElement.getVisibleText();
|
||||||
expect(cellValue.includes('info')).to.be(true);
|
expect(cellValue.includes('info')).to.be(true);
|
||||||
expect(cellValue.includes('event.original')).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 () => {
|
it('should render the whole JSON when neither message, error.message and event.original are present', async () => {
|
||||||
await retry.tryForTime(TEST_TIMEOUT, 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();
|
const cellValue = await cellElement.getVisibleText();
|
||||||
expect(cellValue.includes('info')).to.be(true);
|
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 () => {
|
it('on cell expansion with no message field should open JSON Viewer', async () => {
|
||||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||||
await dataGrid.clickCellExpandButton(4, 5);
|
await dataGrid.clickCellExpandButton(4, 4);
|
||||||
await testSubjects.existOrFail('dataTableExpandCellActionJsonPopover');
|
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 () => {
|
it('on cell expansion with message field should open regular popover', async () => {
|
||||||
await navigateToLogExplorer();
|
await navigateToLogExplorer();
|
||||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||||
await dataGrid.clickCellExpandButton(3, 5);
|
await dataGrid.clickCellExpandButton(3, 4);
|
||||||
await testSubjects.existOrFail('euiDataGridExpansionPopover');
|
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 () => {
|
describe('virtual column cell actions', async () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await navigateToLogExplorer();
|
await navigateToLogExplorer();
|
||||||
});
|
});
|
||||||
it('should render a popover with cell actions when a chip on content column is clicked', async () => {
|
it('should render a popover with cell actions when a chip on content column is clicked', async () => {
|
||||||
await retry.tryForTime(TEST_TIMEOUT, 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(
|
const logLevelChip = await cellElement.findByTestSubject(
|
||||||
'dataTablePopoverChip_log.level'
|
'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 () => {
|
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 () => {
|
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(
|
const logLevelChip = await cellElement.findByTestSubject(
|
||||||
'dataTablePopoverChip_log.level'
|
'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 () => {
|
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 () => {
|
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(
|
const logLevelChip = await cellElement.findByTestSubject(
|
||||||
'dataTablePopoverChip_log.level'
|
'dataTablePopoverChip_log.level'
|
||||||
);
|
);
|
||||||
|
@ -203,6 +213,28 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
await testSubjects.missingOrFail('dataTablePopoverChip_log.level');
|
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)
|
Array(count)
|
||||||
.fill(0)
|
.fill(0)
|
||||||
.map(() => {
|
.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)
|
Array(count)
|
||||||
.fill(0)
|
.fill(0)
|
||||||
.map(() => {
|
.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)
|
Array(count)
|
||||||
.fill(0)
|
.fill(0)
|
||||||
.map(() => {
|
.map(() => {
|
||||||
return log
|
return log.create().logLevel('info').timestamp(timestamp).defaults({
|
||||||
.create()
|
'error.message': 'message in error object',
|
||||||
.logLevel('info')
|
'service.name': 'node-service',
|
||||||
.timestamp(timestamp)
|
});
|
||||||
.defaults({ 'error.message': 'message in error object' });
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -261,11 +301,10 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) {
|
||||||
Array(count)
|
Array(count)
|
||||||
.fill(0)
|
.fill(0)
|
||||||
.map(() => {
|
.map(() => {
|
||||||
return log
|
return log.create().logLevel('info').timestamp(timestamp).defaults({
|
||||||
.create()
|
'event.original': 'message in event original',
|
||||||
.logLevel('info')
|
'service.name': 'node-service',
|
||||||
.timestamp(timestamp)
|
});
|
||||||
.defaults({ 'event.original': 'message in event original' });
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -69,8 +69,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
await retry.try(async () => {
|
await retry.try(async () => {
|
||||||
expect(await PageObjects.discover.getColumnHeaders()).to.eql([
|
expect(await PageObjects.discover.getColumnHeaders()).to.eql([
|
||||||
'@timestamp',
|
'@timestamp',
|
||||||
'service.name',
|
'resource',
|
||||||
'host.name',
|
|
||||||
'content',
|
'content',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { log, timerange } from '@kbn/apm-synthtrace-client';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
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) {
|
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
const retry = getService('retry');
|
const retry = getService('retry');
|
||||||
|
@ -60,8 +60,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
mode: 'absolute',
|
mode: 'absolute',
|
||||||
},
|
},
|
||||||
columns: [
|
columns: [
|
||||||
{ field: 'service.name' },
|
{ field: 'resource' },
|
||||||
{ field: 'host.name' },
|
|
||||||
{ field: 'content' },
|
{ field: 'content' },
|
||||||
{ field: 'data_stream.namespace' },
|
{ field: 'data_stream.namespace' },
|
||||||
],
|
],
|
||||||
|
@ -80,7 +79,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
describe('render content virtual column properly', async () => {
|
describe('render content virtual column properly', async () => {
|
||||||
it('should render log level and log message when present', async () => {
|
it('should render log level and log message when present', async () => {
|
||||||
await retry.tryForTime(TEST_TIMEOUT, 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();
|
const cellValue = await cellElement.getVisibleText();
|
||||||
expect(cellValue.includes('info')).to.be(true);
|
expect(cellValue.includes('info')).to.be(true);
|
||||||
expect(cellValue.includes('A sample log')).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 () => {
|
it('should render log message when present and skip log level when missing', async () => {
|
||||||
await retry.tryForTime(TEST_TIMEOUT, 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();
|
const cellValue = await cellElement.getVisibleText();
|
||||||
expect(cellValue.includes('info')).to.be(false);
|
expect(cellValue.includes('info')).to.be(false);
|
||||||
expect(cellValue.includes('A sample log')).to.be(true);
|
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 () => {
|
it('should render message from error object when top level message not present', async () => {
|
||||||
await retry.tryForTime(TEST_TIMEOUT, 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();
|
const cellValue = await cellElement.getVisibleText();
|
||||||
expect(cellValue.includes('info')).to.be(true);
|
expect(cellValue.includes('info')).to.be(true);
|
||||||
expect(cellValue.includes('error.message')).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 () => {
|
it('should render message from event.original when top level message and error.message not present', async () => {
|
||||||
await retry.tryForTime(TEST_TIMEOUT, 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();
|
const cellValue = await cellElement.getVisibleText();
|
||||||
expect(cellValue.includes('info')).to.be(true);
|
expect(cellValue.includes('info')).to.be(true);
|
||||||
expect(cellValue.includes('event.original')).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 () => {
|
it('should render the whole JSON when neither message, error.message and event.original are present', async () => {
|
||||||
await retry.tryForTime(TEST_TIMEOUT, 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();
|
const cellValue = await cellElement.getVisibleText();
|
||||||
expect(cellValue.includes('info')).to.be(true);
|
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 () => {
|
it('on cell expansion with no message field should open JSON Viewer', async () => {
|
||||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||||
await dataGrid.clickCellExpandButton(4, 5);
|
await dataGrid.clickCellExpandButton(4, 4);
|
||||||
await testSubjects.existOrFail('dataTableExpandCellActionJsonPopover');
|
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 () => {
|
it('on cell expansion with message field should open regular popover', async () => {
|
||||||
await navigateToLogExplorer();
|
await navigateToLogExplorer();
|
||||||
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
await retry.tryForTime(TEST_TIMEOUT, async () => {
|
||||||
await dataGrid.clickCellExpandButton(3, 5);
|
await dataGrid.clickCellExpandButton(3, 4);
|
||||||
await testSubjects.existOrFail('euiDataGridExpansionPopover');
|
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 () => {
|
describe('virtual column cell actions', async () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await navigateToLogExplorer();
|
await navigateToLogExplorer();
|
||||||
});
|
});
|
||||||
it('should render a popover with cell actions when a chip on content column is clicked', async () => {
|
it('should render a popover with cell actions when a chip on content column is clicked', async () => {
|
||||||
await retry.tryForTime(TEST_TIMEOUT, 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(
|
const logLevelChip = await cellElement.findByTestSubject(
|
||||||
'dataTablePopoverChip_log.level'
|
'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 () => {
|
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 () => {
|
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(
|
const logLevelChip = await cellElement.findByTestSubject(
|
||||||
'dataTablePopoverChip_log.level'
|
'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 () => {
|
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 () => {
|
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(
|
const logLevelChip = await cellElement.findByTestSubject(
|
||||||
'dataTablePopoverChip_log.level'
|
'dataTablePopoverChip_log.level'
|
||||||
);
|
);
|
||||||
|
@ -205,6 +215,28 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
await testSubjects.missingOrFail('dataTablePopoverChip_log.level');
|
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)
|
Array(count)
|
||||||
.fill(0)
|
.fill(0)
|
||||||
.map(() => {
|
.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)
|
Array(count)
|
||||||
.fill(0)
|
.fill(0)
|
||||||
.map(() => {
|
.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)
|
Array(count)
|
||||||
.fill(0)
|
.fill(0)
|
||||||
.map(() => {
|
.map(() => {
|
||||||
return log
|
return log.create().logLevel('info').timestamp(timestamp).defaults({
|
||||||
.create()
|
'error.message': 'message in error object',
|
||||||
.logLevel('info')
|
'service.name': 'node-service',
|
||||||
.timestamp(timestamp)
|
});
|
||||||
.defaults({ 'error.message': 'message in error object' });
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -263,11 +303,10 @@ function generateLogsData({ to, count = 1 }: { to: string; count?: number }) {
|
||||||
Array(count)
|
Array(count)
|
||||||
.fill(0)
|
.fill(0)
|
||||||
.map(() => {
|
.map(() => {
|
||||||
return log
|
return log.create().logLevel('info').timestamp(timestamp).defaults({
|
||||||
.create()
|
'event.original': 'message in event original',
|
||||||
.logLevel('info')
|
'service.name': 'node-service',
|
||||||
.timestamp(timestamp)
|
});
|
||||||
.defaults({ 'event.original': 'message in event original' });
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -91,8 +91,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
await retry.try(async () => {
|
await retry.try(async () => {
|
||||||
expect(await PageObjects.discover.getColumnHeaders()).to.eql([
|
expect(await PageObjects.discover.getColumnHeaders()).to.eql([
|
||||||
'@timestamp',
|
'@timestamp',
|
||||||
'service.name',
|
'resource',
|
||||||
'host.name',
|
|
||||||
'content',
|
'content',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
@ -151,8 +150,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||||
expect(await PageObjects.discover.getColumnHeaders()).not.to.eql([
|
expect(await PageObjects.discover.getColumnHeaders()).not.to.eql([
|
||||||
'@timestamp',
|
'@timestamp',
|
||||||
'content',
|
'content',
|
||||||
'service.name',
|
'resource',
|
||||||
'host.name',
|
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue