mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Log Explorer] Add logic to display highlights in Flyout (#170650)
## Summary
Closes https://github.com/elastic/kibana/issues/169504

## What's pending
- [ ] FTR Tests
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
02ccb7788a
commit
c62df52c04
20 changed files with 1457 additions and 55 deletions
|
@ -89,7 +89,7 @@ pageLoadAssetSize:
|
|||
licensing: 29004
|
||||
links: 44490
|
||||
lists: 22900
|
||||
logExplorer: 39045
|
||||
logExplorer: 54342
|
||||
logsShared: 281060
|
||||
logstash: 53548
|
||||
management: 46112
|
||||
|
|
|
@ -13,6 +13,19 @@ export const HOST_NAME_FIELD = 'host.name';
|
|||
export const LOG_LEVEL_FIELD = 'log.level';
|
||||
export const MESSAGE_FIELD = 'message';
|
||||
export const SERVICE_NAME_FIELD = 'service.name';
|
||||
export const TRACE_ID_FIELD = 'trace.id';
|
||||
|
||||
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';
|
||||
|
||||
// Sizing
|
||||
export const DATA_GRID_COLUMN_WIDTH_SMALL = 240;
|
||||
|
|
|
@ -6,42 +6,22 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { LogLevel } from './sub_components/log_level';
|
||||
import { Timestamp } from './sub_components/timestamp';
|
||||
import { FlyoutProps, LogDocument } from './types';
|
||||
import { getDocDetailRenderFlags, useDocDetail } from './use_doc_detail';
|
||||
import { Message } from './sub_components/message';
|
||||
import { useDocDetail } from './use_doc_detail';
|
||||
import { FlyoutHeader } from './flyout_header';
|
||||
import { FlyoutHighlights } from './flyout_highlights';
|
||||
|
||||
export function FlyoutDetail({ dataView, doc }: Pick<FlyoutProps, 'dataView' | 'doc' | 'actions'>) {
|
||||
export function FlyoutDetail({
|
||||
dataView,
|
||||
doc,
|
||||
actions,
|
||||
}: Pick<FlyoutProps, 'dataView' | 'doc' | 'actions'>) {
|
||||
const parsedDoc = useDocDetail(doc as LogDocument, { dataView });
|
||||
|
||||
const { hasTimestamp, hasLogLevel, hasMessage, hasBadges, hasFlyoutHeader } =
|
||||
getDocDetailRenderFlags(parsedDoc);
|
||||
|
||||
return hasFlyoutHeader ? (
|
||||
<EuiFlexGroup direction="column" gutterSize="m" data-test-subj="logExplorerFlyoutDetail">
|
||||
<EuiFlexItem grow={false}>
|
||||
{hasBadges && (
|
||||
<EuiFlexGroup responsive={false} gutterSize="m">
|
||||
{hasLogLevel && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<LogLevel level={parsedDoc['log.level']} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{hasTimestamp && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<Timestamp timestamp={parsedDoc['@timestamp']} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
{hasMessage && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<Message message={parsedDoc.message} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
) : null;
|
||||
return (
|
||||
<>
|
||||
<FlyoutHeader doc={parsedDoc} />
|
||||
<FlyoutHighlights formattedDoc={parsedDoc} flattenedDoc={doc.flattened} actions={actions} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { FlyoutDoc } from './types';
|
||||
import { getDocDetailHeaderRenderFlags } from './use_doc_detail';
|
||||
import { LogLevel } from './sub_components/log_level';
|
||||
import { Timestamp } from './sub_components/timestamp';
|
||||
import { Message } from './sub_components/message';
|
||||
import * as constants from '../../../common/constants';
|
||||
|
||||
export function FlyoutHeader({ doc }: { doc: FlyoutDoc }) {
|
||||
const { hasTimestamp, hasLogLevel, hasMessage, hasBadges, hasFlyoutHeader } =
|
||||
getDocDetailHeaderRenderFlags(doc);
|
||||
|
||||
return hasFlyoutHeader ? (
|
||||
<EuiFlexGroup direction="column" gutterSize="m" data-test-subj="logExplorerFlyoutDetail">
|
||||
<EuiFlexItem grow={false}>
|
||||
{hasBadges && (
|
||||
<EuiFlexGroup responsive={false} gutterSize="m">
|
||||
{hasLogLevel && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<LogLevel level={doc[constants.LOG_LEVEL_FIELD]} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{hasTimestamp && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<Timestamp timestamp={doc[constants.TIMESTAMP_FIELD]} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
{hasMessage && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<Message message={doc[constants.MESSAGE_FIELD]} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
) : null;
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* 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 { FlyoutContentActions } from '@kbn/discover-plugin/public';
|
||||
import { DataTableRecord } from '@kbn/discover-utils/src/types';
|
||||
import { useMeasure } from 'react-use/lib';
|
||||
import { FlyoutDoc } from './types';
|
||||
import * as constants from '../../../common/constants';
|
||||
import { HighlightField } from './sub_components/highlight_field';
|
||||
import {
|
||||
cloudAccordionTitle,
|
||||
flyoutCloudAvailabilityZoneLabel,
|
||||
flyoutCloudInstanceIdLabel,
|
||||
flyoutCloudProjectIdLabel,
|
||||
flyoutCloudProviderLabel,
|
||||
flyoutCloudRegionLabel,
|
||||
flyoutDatasetLabel,
|
||||
flyoutHostNameLabel,
|
||||
flyoutLogPathFileLabel,
|
||||
flyoutNamespaceLabel,
|
||||
flyoutOrchestratorClusterNameLabel,
|
||||
flyoutOrchestratorResourceIdLabel,
|
||||
flyoutServiceLabel,
|
||||
flyoutShipperLabel,
|
||||
flyoutTraceLabel,
|
||||
infraAccordionTitle,
|
||||
otherAccordionTitle,
|
||||
serviceAccordionTitle,
|
||||
} from './translations';
|
||||
import { HighlightSection } from './sub_components/highlight_section';
|
||||
import { DiscoverActionsProvider } from '../../hooks/use_discover_action';
|
||||
import { HighlightContainer } from './sub_components/highlight_container';
|
||||
import { useFlyoutColumnWidth } from '../../hooks/use_flyouot_column_width';
|
||||
|
||||
export function FlyoutHighlights({
|
||||
formattedDoc,
|
||||
flattenedDoc,
|
||||
actions,
|
||||
}: {
|
||||
formattedDoc: FlyoutDoc;
|
||||
flattenedDoc: DataTableRecord['flattened'];
|
||||
actions: FlyoutContentActions;
|
||||
}) {
|
||||
const [ref, dimensions] = useMeasure<HTMLDivElement>();
|
||||
const { columns, fieldWidth } = useFlyoutColumnWidth(dimensions.width);
|
||||
return (
|
||||
<DiscoverActionsProvider value={actions}>
|
||||
<HighlightContainer ref={ref}>
|
||||
<HighlightSection title={serviceAccordionTitle} columns={columns}>
|
||||
{formattedDoc[constants.SERVICE_NAME_FIELD] && (
|
||||
<HighlightField
|
||||
label={flyoutServiceLabel}
|
||||
field={constants.SERVICE_NAME_FIELD}
|
||||
value={flattenedDoc[constants.SERVICE_NAME_FIELD]}
|
||||
formattedValue={formattedDoc[constants.SERVICE_NAME_FIELD]}
|
||||
dataTestSubj="logExplorerFlyoutService"
|
||||
width={fieldWidth}
|
||||
/>
|
||||
)}
|
||||
{formattedDoc[constants.TRACE_ID_FIELD] && (
|
||||
<HighlightField
|
||||
label={flyoutTraceLabel}
|
||||
field={constants.TRACE_ID_FIELD}
|
||||
value={flattenedDoc[constants.TRACE_ID_FIELD]}
|
||||
formattedValue={formattedDoc[constants.TRACE_ID_FIELD]}
|
||||
dataTestSubj="logExplorerFlyoutTrace"
|
||||
width={fieldWidth}
|
||||
/>
|
||||
)}
|
||||
</HighlightSection>
|
||||
|
||||
<HighlightSection title={infraAccordionTitle} columns={columns}>
|
||||
{formattedDoc[constants.HOST_NAME_FIELD] && (
|
||||
<HighlightField
|
||||
label={flyoutHostNameLabel}
|
||||
field={constants.HOST_NAME_FIELD}
|
||||
value={flattenedDoc[constants.HOST_NAME_FIELD]}
|
||||
formattedValue={formattedDoc[constants.HOST_NAME_FIELD]}
|
||||
dataTestSubj="logExplorerFlyoutHostName"
|
||||
width={fieldWidth}
|
||||
/>
|
||||
)}
|
||||
{formattedDoc[constants.ORCHESTRATOR_CLUSTER_NAME_FIELD] && (
|
||||
<HighlightField
|
||||
label={flyoutOrchestratorClusterNameLabel}
|
||||
field={constants.ORCHESTRATOR_CLUSTER_NAME_FIELD}
|
||||
value={flattenedDoc[constants.ORCHESTRATOR_CLUSTER_NAME_FIELD]}
|
||||
formattedValue={formattedDoc[constants.ORCHESTRATOR_CLUSTER_NAME_FIELD]}
|
||||
dataTestSubj="logExplorerFlyoutClusterName"
|
||||
width={fieldWidth}
|
||||
/>
|
||||
)}
|
||||
{formattedDoc[constants.ORCHESTRATOR_RESOURCE_ID_FIELD] && (
|
||||
<HighlightField
|
||||
label={flyoutOrchestratorResourceIdLabel}
|
||||
field={constants.ORCHESTRATOR_RESOURCE_ID_FIELD}
|
||||
value={flattenedDoc[constants.ORCHESTRATOR_RESOURCE_ID_FIELD]}
|
||||
formattedValue={formattedDoc[constants.ORCHESTRATOR_RESOURCE_ID_FIELD]}
|
||||
dataTestSubj="logExplorerFlyoutResourceId"
|
||||
width={fieldWidth}
|
||||
/>
|
||||
)}
|
||||
</HighlightSection>
|
||||
|
||||
<HighlightSection title={cloudAccordionTitle} columns={columns}>
|
||||
{formattedDoc[constants.CLOUD_PROVIDER_FIELD] && (
|
||||
<HighlightField
|
||||
label={flyoutCloudProviderLabel}
|
||||
field={constants.CLOUD_PROVIDER_FIELD}
|
||||
value={flattenedDoc[constants.CLOUD_PROVIDER_FIELD]}
|
||||
formattedValue={formattedDoc[constants.CLOUD_PROVIDER_FIELD]}
|
||||
dataTestSubj="logExplorerFlyoutCloudProvider"
|
||||
width={fieldWidth}
|
||||
/>
|
||||
)}
|
||||
{formattedDoc[constants.CLOUD_REGION_FIELD] && (
|
||||
<HighlightField
|
||||
label={flyoutCloudRegionLabel}
|
||||
field={constants.CLOUD_REGION_FIELD}
|
||||
value={flattenedDoc[constants.CLOUD_REGION_FIELD]}
|
||||
formattedValue={formattedDoc[constants.CLOUD_REGION_FIELD]}
|
||||
dataTestSubj="logExplorerFlyoutCloudRegion"
|
||||
width={fieldWidth}
|
||||
/>
|
||||
)}
|
||||
{formattedDoc[constants.CLOUD_AVAILABILITY_ZONE_FIELD] && (
|
||||
<HighlightField
|
||||
label={flyoutCloudAvailabilityZoneLabel}
|
||||
field={constants.CLOUD_AVAILABILITY_ZONE_FIELD}
|
||||
value={flattenedDoc[constants.CLOUD_AVAILABILITY_ZONE_FIELD]}
|
||||
formattedValue={formattedDoc[constants.CLOUD_AVAILABILITY_ZONE_FIELD]}
|
||||
dataTestSubj="logExplorerFlyoutCloudAz"
|
||||
width={fieldWidth}
|
||||
/>
|
||||
)}
|
||||
{formattedDoc[constants.CLOUD_PROJECT_ID_FIELD] && (
|
||||
<HighlightField
|
||||
label={flyoutCloudProjectIdLabel}
|
||||
field={constants.CLOUD_PROJECT_ID_FIELD}
|
||||
value={flattenedDoc[constants.CLOUD_PROJECT_ID_FIELD]}
|
||||
formattedValue={formattedDoc[constants.CLOUD_PROJECT_ID_FIELD]}
|
||||
dataTestSubj="logExplorerFlyoutCloudProjectId"
|
||||
width={fieldWidth}
|
||||
/>
|
||||
)}
|
||||
{formattedDoc[constants.CLOUD_INSTANCE_ID_FIELD] && (
|
||||
<HighlightField
|
||||
label={flyoutCloudInstanceIdLabel}
|
||||
field={constants.CLOUD_INSTANCE_ID_FIELD}
|
||||
value={flattenedDoc[constants.CLOUD_INSTANCE_ID_FIELD]}
|
||||
formattedValue={formattedDoc[constants.CLOUD_INSTANCE_ID_FIELD]}
|
||||
dataTestSubj="logExplorerFlyoutCloudInstanceId"
|
||||
width={fieldWidth}
|
||||
/>
|
||||
)}
|
||||
</HighlightSection>
|
||||
|
||||
<HighlightSection title={otherAccordionTitle} showBottomRule={false} columns={columns}>
|
||||
{formattedDoc[constants.LOG_FILE_PATH_FIELD] && (
|
||||
<HighlightField
|
||||
label={flyoutLogPathFileLabel}
|
||||
field={constants.LOG_FILE_PATH_FIELD}
|
||||
value={flattenedDoc[constants.LOG_FILE_PATH_FIELD]}
|
||||
formattedValue={formattedDoc[constants.LOG_FILE_PATH_FIELD]}
|
||||
dataTestSubj="logExplorerFlyoutLogPathFile"
|
||||
width={fieldWidth}
|
||||
/>
|
||||
)}
|
||||
{formattedDoc[constants.DATASTREAM_NAMESPACE_FIELD] && (
|
||||
<HighlightField
|
||||
label={flyoutNamespaceLabel}
|
||||
field={constants.DATASTREAM_NAMESPACE_FIELD}
|
||||
value={flattenedDoc[constants.DATASTREAM_NAMESPACE_FIELD]}
|
||||
formattedValue={formattedDoc[constants.DATASTREAM_NAMESPACE_FIELD]}
|
||||
dataTestSubj="logExplorerFlyoutNamespace"
|
||||
width={fieldWidth}
|
||||
/>
|
||||
)}
|
||||
{formattedDoc[constants.DATASTREAM_DATASET_FIELD] && (
|
||||
<HighlightField
|
||||
label={flyoutDatasetLabel}
|
||||
field={constants.DATASTREAM_DATASET_FIELD}
|
||||
value={flattenedDoc[constants.DATASTREAM_DATASET_FIELD]}
|
||||
formattedValue={formattedDoc[constants.DATASTREAM_DATASET_FIELD]}
|
||||
dataTestSubj="logExplorerFlyoutDataset"
|
||||
width={fieldWidth}
|
||||
/>
|
||||
)}
|
||||
{formattedDoc[constants.AGENT_NAME_FIELD] && (
|
||||
<HighlightField
|
||||
label={flyoutShipperLabel}
|
||||
field={constants.AGENT_NAME_FIELD}
|
||||
value={flattenedDoc[constants.AGENT_NAME_FIELD]}
|
||||
formattedValue={formattedDoc[constants.AGENT_NAME_FIELD]}
|
||||
dataTestSubj="logExplorerFlyoutLogShipper"
|
||||
width={fieldWidth}
|
||||
/>
|
||||
)}
|
||||
</HighlightSection>
|
||||
</HighlightContainer>
|
||||
</DiscoverActionsProvider>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { EuiHorizontalRule, EuiPanel } from '@elastic/eui';
|
||||
|
||||
interface HighlightContainerProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const hasNonUndefinedSubChild = (children: React.ReactNode[]): boolean => {
|
||||
return children.some((child) => {
|
||||
if (React.isValidElement(child)) {
|
||||
const subChildren = React.Children.toArray(child.props.children);
|
||||
return subChildren.some((subChild) => subChild !== undefined && subChild !== null);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
export const HighlightContainer = React.forwardRef<HTMLDivElement, HighlightContainerProps>(
|
||||
({ children }, ref) => {
|
||||
const validChildren = React.Children.toArray(children).filter(Boolean);
|
||||
const hasChildren = validChildren.length > 0;
|
||||
const shouldRender = hasChildren && hasNonUndefinedSubChild(validChildren);
|
||||
|
||||
const flexChildren = validChildren.map((child, idx) => <div key={idx}>{child}</div>);
|
||||
|
||||
return shouldRender ? (
|
||||
<div ref={ref}>
|
||||
<EuiHorizontalRule margin="xs" />
|
||||
<EuiPanel paddingSize="m" hasShadow={false} hasBorder={true}>
|
||||
{flexChildren}
|
||||
</EuiPanel>
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
);
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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, copyToClipboard } from '@elastic/eui';
|
||||
import React, { ReactNode, useMemo, useState } from 'react';
|
||||
import { HoverAction, HoverActionType } from './hover_action';
|
||||
import {
|
||||
flyoutHoverActionFilterForText,
|
||||
flyoutHoverActionFilterOutText,
|
||||
flyoutHoverActionFilterForFieldPresentText,
|
||||
flyoutHoverActionToggleColumnText,
|
||||
flyoutHoverActionCopyToClipboardText,
|
||||
} from '../translations';
|
||||
import { useDiscoverActionsContext } from '../../../hooks/use_discover_action';
|
||||
|
||||
interface HighlightFieldProps {
|
||||
label: string | ReactNode;
|
||||
field: string;
|
||||
value: unknown;
|
||||
formattedValue: string;
|
||||
dataTestSubj: string;
|
||||
width: number;
|
||||
}
|
||||
|
||||
export function HighlightField({
|
||||
label,
|
||||
field,
|
||||
value,
|
||||
formattedValue,
|
||||
dataTestSubj,
|
||||
width,
|
||||
}: HighlightFieldProps) {
|
||||
const filterForText = flyoutHoverActionFilterForText(value);
|
||||
const filterOutText = flyoutHoverActionFilterOutText(value);
|
||||
const actions = useDiscoverActionsContext();
|
||||
const [columnAdded, setColumnAdded] = useState(false);
|
||||
|
||||
const hoverActions: HoverActionType[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'addToFilterAction',
|
||||
tooltipContent: filterForText,
|
||||
iconType: 'plusInCircle',
|
||||
onClick: () => actions?.addFilter && actions.addFilter(field, value, '+'),
|
||||
display: true,
|
||||
},
|
||||
{
|
||||
id: 'removeFromFilterAction',
|
||||
tooltipContent: filterOutText,
|
||||
iconType: 'minusInCircle',
|
||||
onClick: () => actions?.addFilter && actions.addFilter(field, value, '-'),
|
||||
display: true,
|
||||
},
|
||||
{
|
||||
id: 'filterForFieldPresentAction',
|
||||
tooltipContent: flyoutHoverActionFilterForFieldPresentText,
|
||||
iconType: 'filter',
|
||||
onClick: () => actions?.addFilter && actions.addFilter('_exists_', field, '+'),
|
||||
display: true,
|
||||
},
|
||||
{
|
||||
id: 'toggleColumnAction',
|
||||
tooltipContent: flyoutHoverActionToggleColumnText,
|
||||
iconType: 'listAdd',
|
||||
onClick: () => {
|
||||
if (actions) {
|
||||
if (columnAdded) {
|
||||
actions?.removeColumn?.(field);
|
||||
} else {
|
||||
actions?.addColumn?.(field);
|
||||
}
|
||||
setColumnAdded(!columnAdded);
|
||||
}
|
||||
},
|
||||
display: true,
|
||||
},
|
||||
{
|
||||
id: 'copyToClipboardAction',
|
||||
tooltipContent: flyoutHoverActionCopyToClipboardText,
|
||||
iconType: 'copyClipboard',
|
||||
onClick: () => copyToClipboard(value as string),
|
||||
display: true,
|
||||
},
|
||||
],
|
||||
[filterForText, filterOutText, actions, field, value, columnAdded]
|
||||
);
|
||||
return formattedValue ? (
|
||||
<EuiFlexGroup direction="column" gutterSize="none" data-test-subj={dataTestSubj}>
|
||||
<EuiFlexItem>
|
||||
<EuiText color="subdued" size="xs">
|
||||
{label}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<HoverAction displayText={formattedValue} actions={hoverActions} width={width} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : null;
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiAccordion,
|
||||
EuiFlexGrid,
|
||||
EuiHorizontalRule,
|
||||
EuiTitle,
|
||||
EuiFlexItem,
|
||||
useGeneratedHtmlId,
|
||||
} from '@elastic/eui';
|
||||
|
||||
interface HighlightSectionProps {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
showBottomRule?: boolean;
|
||||
columns: 1 | 2 | 3;
|
||||
}
|
||||
|
||||
export function HighlightSection({
|
||||
title,
|
||||
children,
|
||||
showBottomRule = true,
|
||||
columns,
|
||||
}: HighlightSectionProps) {
|
||||
const validChildren = React.Children.toArray(children).filter(Boolean);
|
||||
const shouldRenderSection = validChildren.length > 0;
|
||||
const accordionTitle = (
|
||||
<EuiTitle size="xs">
|
||||
<p>{title}</p>
|
||||
</EuiTitle>
|
||||
);
|
||||
|
||||
const flexChildren = validChildren.map((child, idx) => (
|
||||
<EuiFlexItem key={idx}>{child}</EuiFlexItem>
|
||||
));
|
||||
|
||||
const accordionId = useGeneratedHtmlId({
|
||||
prefix: title,
|
||||
});
|
||||
|
||||
return shouldRenderSection ? (
|
||||
<>
|
||||
<EuiAccordion
|
||||
id={accordionId}
|
||||
buttonContent={accordionTitle}
|
||||
paddingSize="s"
|
||||
initialIsOpen={true}
|
||||
data-test-subj={`logExplorerFlyoutHighlightSection${title}`}
|
||||
>
|
||||
<EuiFlexGrid columns={columns}>{flexChildren}</EuiFlexGrid>
|
||||
</EuiAccordion>
|
||||
{showBottomRule && <EuiHorizontalRule margin="xs" />}
|
||||
</>
|
||||
) : null;
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiFlexGroup,
|
||||
EuiToolTip,
|
||||
EuiButtonIcon,
|
||||
useEuiTheme,
|
||||
EuiTextTruncate,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import type { IconType } from '@elastic/eui';
|
||||
|
||||
export interface HoverActionType {
|
||||
id: string;
|
||||
tooltipContent: string;
|
||||
iconType: IconType;
|
||||
onClick: () => void;
|
||||
display: boolean;
|
||||
}
|
||||
|
||||
interface HoverActionProps {
|
||||
displayText: string;
|
||||
actions: HoverActionType[];
|
||||
width: number;
|
||||
}
|
||||
|
||||
export const HoverAction = ({ displayText, actions, width }: HoverActionProps) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
responsive={false}
|
||||
alignItems="center"
|
||||
justifyContent="flexStart"
|
||||
gutterSize="s"
|
||||
css={{
|
||||
':hover, :focus-within': {
|
||||
'.visibleOnHoverFocus': {
|
||||
opacity: 1,
|
||||
visibility: 'visible',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<EuiTextTruncate text={displayText} truncation="end" width={width}>
|
||||
{(truncatedText: string) => (
|
||||
<EuiText
|
||||
// Value returned from formatFieldValue is always sanitized
|
||||
dangerouslySetInnerHTML={{ __html: truncatedText }}
|
||||
/>
|
||||
)}
|
||||
</EuiTextTruncate>
|
||||
<EuiFlexGroup
|
||||
className="visibleOnHoverFocus"
|
||||
css={{
|
||||
flexGrow: 0,
|
||||
flexShrink: 0,
|
||||
opacity: 0,
|
||||
visibility: 'hidden',
|
||||
transition: `opacity ${euiTheme.animation.normal} ${euiTheme.animation.resistance}, visibility ${euiTheme.animation.normal} ${euiTheme.animation.resistance}`,
|
||||
}}
|
||||
responsive={false}
|
||||
alignItems="center"
|
||||
gutterSize="none"
|
||||
>
|
||||
{actions.map((action) => (
|
||||
<EuiToolTip content={action.tooltipContent} key={action.id}>
|
||||
<EuiButtonIcon
|
||||
size="xs"
|
||||
iconType={action.iconType}
|
||||
aria-label={action.tooltipContent as string}
|
||||
onClick={() => action.onClick()}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -10,3 +10,151 @@ import { i18n } from '@kbn/i18n';
|
|||
export const flyoutMessageLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.message', {
|
||||
defaultMessage: 'Message',
|
||||
});
|
||||
|
||||
export const flyoutServiceLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.service', {
|
||||
defaultMessage: 'Service',
|
||||
});
|
||||
|
||||
export const flyoutTraceLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.trace', {
|
||||
defaultMessage: 'Trace',
|
||||
});
|
||||
|
||||
export const flyoutHostNameLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.hostName', {
|
||||
defaultMessage: 'Host name',
|
||||
});
|
||||
|
||||
export const serviceAccordionTitle = i18n.translate(
|
||||
'xpack.logExplorer.flyoutDetail.accordion.title.service',
|
||||
{
|
||||
defaultMessage: 'Service',
|
||||
}
|
||||
);
|
||||
|
||||
export const infraAccordionTitle = i18n.translate(
|
||||
'xpack.logExplorer.flyoutDetail.accordion.title.infrastructure',
|
||||
{
|
||||
defaultMessage: 'Infrastructure',
|
||||
}
|
||||
);
|
||||
|
||||
export const cloudAccordionTitle = i18n.translate(
|
||||
'xpack.logExplorer.flyoutDetail.accordion.title.cloud',
|
||||
{
|
||||
defaultMessage: 'Cloud',
|
||||
}
|
||||
);
|
||||
|
||||
export const otherAccordionTitle = i18n.translate(
|
||||
'xpack.logExplorer.flyoutDetail.accordion.title.other',
|
||||
{
|
||||
defaultMessage: 'Other',
|
||||
}
|
||||
);
|
||||
|
||||
export const flyoutOrchestratorClusterNameLabel = i18n.translate(
|
||||
'xpack.logExplorer.flyoutDetail.label.orchestratorClusterName',
|
||||
{
|
||||
defaultMessage: 'Orchestrator cluster Name',
|
||||
}
|
||||
);
|
||||
|
||||
export const flyoutOrchestratorResourceIdLabel = i18n.translate(
|
||||
'xpack.logExplorer.flyoutDetail.label.orchestratorResourceId',
|
||||
{
|
||||
defaultMessage: 'Orchestrator resource ID',
|
||||
}
|
||||
);
|
||||
|
||||
export const flyoutCloudProviderLabel = i18n.translate(
|
||||
'xpack.logExplorer.flyoutDetail.label.cloudProvider',
|
||||
{
|
||||
defaultMessage: 'Cloud provider',
|
||||
}
|
||||
);
|
||||
|
||||
export const flyoutCloudRegionLabel = i18n.translate(
|
||||
'xpack.logExplorer.flyoutDetail.label.cloudRegion',
|
||||
{
|
||||
defaultMessage: 'Cloud region',
|
||||
}
|
||||
);
|
||||
|
||||
export const flyoutCloudAvailabilityZoneLabel = i18n.translate(
|
||||
'xpack.logExplorer.flyoutDetail.label.cloudAvailabilityZone',
|
||||
{
|
||||
defaultMessage: 'Cloud availability zone',
|
||||
}
|
||||
);
|
||||
|
||||
export const flyoutCloudProjectIdLabel = i18n.translate(
|
||||
'xpack.logExplorer.flyoutDetail.label.cloudProjectId',
|
||||
{
|
||||
defaultMessage: 'Cloud project ID',
|
||||
}
|
||||
);
|
||||
|
||||
export const flyoutCloudInstanceIdLabel = i18n.translate(
|
||||
'xpack.logExplorer.flyoutDetail.label.cloudInstanceId',
|
||||
{
|
||||
defaultMessage: 'Cloud instance ID',
|
||||
}
|
||||
);
|
||||
|
||||
export const flyoutLogPathFileLabel = i18n.translate(
|
||||
'xpack.logExplorer.flyoutDetail.label.logPathFile',
|
||||
{
|
||||
defaultMessage: 'Log path file',
|
||||
}
|
||||
);
|
||||
|
||||
export const flyoutNamespaceLabel = i18n.translate(
|
||||
'xpack.logExplorer.flyoutDetail.label.namespace',
|
||||
{
|
||||
defaultMessage: 'Namespace',
|
||||
}
|
||||
);
|
||||
|
||||
export const flyoutDatasetLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.dataset', {
|
||||
defaultMessage: 'Dataset',
|
||||
});
|
||||
|
||||
export const flyoutShipperLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.shipper', {
|
||||
defaultMessage: 'Shipper',
|
||||
});
|
||||
|
||||
export const flyoutHoverActionFilterForText = (text: unknown) =>
|
||||
i18n.translate('xpack.logExplorer.flyoutDetail.value.hover.filterFor', {
|
||||
defaultMessage: 'Filter for this {value}',
|
||||
values: {
|
||||
value: text as string,
|
||||
},
|
||||
});
|
||||
|
||||
export const flyoutHoverActionFilterOutText = (text: unknown) =>
|
||||
i18n.translate('xpack.logExplorer.flyoutDetail.value.hover.filterOut', {
|
||||
defaultMessage: 'Filter out this {value}',
|
||||
values: {
|
||||
value: text as string,
|
||||
},
|
||||
});
|
||||
|
||||
export const flyoutHoverActionFilterForFieldPresentText = i18n.translate(
|
||||
'xpack.logExplorer.flyoutDetail.value.hover.filterForFieldPresent',
|
||||
{
|
||||
defaultMessage: 'Filter for field present',
|
||||
}
|
||||
);
|
||||
|
||||
export const flyoutHoverActionToggleColumnText = i18n.translate(
|
||||
'xpack.logExplorer.flyoutDetail.value.hover.toggleColumn',
|
||||
{
|
||||
defaultMessage: 'Toggle column in table',
|
||||
}
|
||||
);
|
||||
|
||||
export const flyoutHoverActionCopyToClipboardText = i18n.translate(
|
||||
'xpack.logExplorer.flyoutDetail.value.hover.copyToClipboard',
|
||||
{
|
||||
defaultMessage: 'Copy to clipboard',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { EuiIconType } from '@elastic/eui/src/components/icon/icon';
|
||||
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||
import type { FlyoutContentProps } from '@kbn/discover-plugin/public';
|
||||
import type { DataTableRecord } from '@kbn/discover-utils/types';
|
||||
|
@ -19,6 +18,21 @@ export interface LogDocument extends DataTableRecord {
|
|||
'@timestamp': string;
|
||||
'log.level'?: string;
|
||||
message?: string;
|
||||
|
||||
'host.name'?: string;
|
||||
'service.name'?: string;
|
||||
'trace.id'?: string;
|
||||
'agent.name'?: string;
|
||||
'orchestrator.cluster.name'?: string;
|
||||
'orchestrator.resource.id'?: string;
|
||||
'cloud.provider'?: string;
|
||||
'cloud.region'?: string;
|
||||
'cloud.availability_zone'?: string;
|
||||
'cloud.project.id'?: string;
|
||||
'cloud.instance.id'?: string;
|
||||
'log.file.path'?: string;
|
||||
'data_stream.namespace': string;
|
||||
'data_stream.dataset': string;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -26,10 +40,19 @@ export interface FlyoutDoc {
|
|||
'@timestamp': string;
|
||||
'log.level'?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface FlyoutHighlightField {
|
||||
label: string;
|
||||
value: string;
|
||||
iconType?: EuiIconType;
|
||||
'host.name'?: string;
|
||||
'service.name'?: string;
|
||||
'trace.id'?: string;
|
||||
'agent.name'?: string;
|
||||
'orchestrator.cluster.name'?: string;
|
||||
'orchestrator.resource.id'?: string;
|
||||
'cloud.provider'?: string;
|
||||
'cloud.region'?: string;
|
||||
'cloud.availability_zone'?: string;
|
||||
'cloud.project.id'?: string;
|
||||
'cloud.instance.id'?: string;
|
||||
'log.file.path'?: string;
|
||||
'data_stream.namespace': string;
|
||||
'data_stream.dataset': string;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { formatFieldValue } from '@kbn/discover-utils';
|
||||
import { LOG_LEVEL_FIELD, MESSAGE_FIELD, TIMESTAMP_FIELD } from '../../../common/constants';
|
||||
import he from 'he';
|
||||
import * as constants from '../../../common/constants';
|
||||
import { useKibanaContextForPlugin } from '../../utils/use_kibana';
|
||||
import { FlyoutDoc, FlyoutProps, LogDocument } from './types';
|
||||
|
||||
|
@ -30,21 +31,59 @@ export function useDocDetail(
|
|||
);
|
||||
};
|
||||
|
||||
const level = formatField(LOG_LEVEL_FIELD)?.toLowerCase();
|
||||
const timestamp = formatField(TIMESTAMP_FIELD);
|
||||
const message = formatField(MESSAGE_FIELD);
|
||||
// Flyout Headers
|
||||
const level = formatField(constants.LOG_LEVEL_FIELD)?.toLowerCase();
|
||||
const timestamp = formatField(constants.TIMESTAMP_FIELD);
|
||||
const formattedMessage = formatField(constants.MESSAGE_FIELD);
|
||||
const message = formattedMessage ? he.decode(formattedMessage) : undefined;
|
||||
|
||||
// Service Highlights
|
||||
const serviceName = formatField(constants.SERVICE_NAME_FIELD);
|
||||
const traceId = formatField(constants.TRACE_ID_FIELD);
|
||||
|
||||
// Infrastructure Highlights
|
||||
const hostname = formatField(constants.HOST_NAME_FIELD);
|
||||
const orchestratorClusterName = formatField(constants.ORCHESTRATOR_CLUSTER_NAME_FIELD);
|
||||
const orchestratorResourceId = formatField(constants.ORCHESTRATOR_RESOURCE_ID_FIELD);
|
||||
|
||||
// Cloud Highlights
|
||||
const cloudProvider = formatField(constants.CLOUD_PROVIDER_FIELD);
|
||||
const cloudRegion = formatField(constants.CLOUD_REGION_FIELD);
|
||||
const cloudAz = formatField(constants.CLOUD_AVAILABILITY_ZONE_FIELD);
|
||||
const cloudProjectId = formatField(constants.CLOUD_PROJECT_ID_FIELD);
|
||||
const cloudInstanceId = formatField(constants.CLOUD_INSTANCE_ID_FIELD);
|
||||
|
||||
// Other Highlights
|
||||
const logFilePath = formatField(constants.LOG_FILE_PATH_FIELD);
|
||||
const namespace = formatField(constants.DATASTREAM_NAMESPACE_FIELD);
|
||||
const dataset = formatField(constants.DATASTREAM_DATASET_FIELD);
|
||||
const agentName = formatField(constants.AGENT_NAME_FIELD);
|
||||
|
||||
return {
|
||||
[LOG_LEVEL_FIELD]: level,
|
||||
[TIMESTAMP_FIELD]: timestamp,
|
||||
[MESSAGE_FIELD]: message,
|
||||
[constants.LOG_LEVEL_FIELD]: level,
|
||||
[constants.TIMESTAMP_FIELD]: timestamp,
|
||||
[constants.MESSAGE_FIELD]: message,
|
||||
[constants.SERVICE_NAME_FIELD]: serviceName,
|
||||
[constants.TRACE_ID_FIELD]: traceId,
|
||||
[constants.HOST_NAME_FIELD]: hostname,
|
||||
[constants.ORCHESTRATOR_CLUSTER_NAME_FIELD]: orchestratorClusterName,
|
||||
[constants.ORCHESTRATOR_RESOURCE_ID_FIELD]: orchestratorResourceId,
|
||||
[constants.CLOUD_PROVIDER_FIELD]: cloudProvider,
|
||||
[constants.CLOUD_REGION_FIELD]: cloudRegion,
|
||||
[constants.CLOUD_AVAILABILITY_ZONE_FIELD]: cloudAz,
|
||||
[constants.CLOUD_PROJECT_ID_FIELD]: cloudProjectId,
|
||||
[constants.CLOUD_INSTANCE_ID_FIELD]: cloudInstanceId,
|
||||
[constants.LOG_FILE_PATH_FIELD]: logFilePath,
|
||||
[constants.DATASTREAM_NAMESPACE_FIELD]: namespace,
|
||||
[constants.DATASTREAM_DATASET_FIELD]: dataset,
|
||||
[constants.AGENT_NAME_FIELD]: agentName,
|
||||
};
|
||||
}
|
||||
|
||||
export const getDocDetailRenderFlags = (doc: FlyoutDoc) => {
|
||||
const hasTimestamp = Boolean(doc['@timestamp']);
|
||||
const hasLogLevel = Boolean(doc['log.level']);
|
||||
const hasMessage = Boolean(doc.message);
|
||||
export const getDocDetailHeaderRenderFlags = (doc: FlyoutDoc) => {
|
||||
const hasTimestamp = Boolean(doc[constants.TIMESTAMP_FIELD]);
|
||||
const hasLogLevel = Boolean(doc[constants.LOG_LEVEL_FIELD]);
|
||||
const hasMessage = Boolean(doc[constants.MESSAGE_FIELD]);
|
||||
|
||||
const hasBadges = hasTimestamp || hasLogLevel;
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 createContainer from 'constate';
|
||||
import { FlyoutContentActions } from '@kbn/discover-plugin/public';
|
||||
|
||||
interface UseFlyoutActionsDeps {
|
||||
value: FlyoutContentActions;
|
||||
}
|
||||
|
||||
const useDiscoverActions = ({ value }: UseFlyoutActionsDeps) => value;
|
||||
|
||||
export const [DiscoverActionsProvider, useDiscoverActionsContext] =
|
||||
createContainer(useDiscoverActions);
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 { useEuiTheme } from '@elastic/eui';
|
||||
|
||||
interface FlyoutColumnWidth {
|
||||
columns: 1 | 2 | 3;
|
||||
fieldWidth: number;
|
||||
}
|
||||
|
||||
export const useFlyoutColumnWidth = (width: number): FlyoutColumnWidth => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const numberOfColumns = width > euiTheme.breakpoint.m ? 3 : width > euiTheme.breakpoint.s ? 2 : 1;
|
||||
const widthFactor = numberOfColumns === 3 ? 2.5 : 2.2;
|
||||
const fieldWidth = width / (numberOfColumns * widthFactor);
|
||||
|
||||
return {
|
||||
columns: numberOfColumns,
|
||||
fieldWidth,
|
||||
};
|
||||
};
|
|
@ -54,9 +54,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
after('clean up archives', async () => {
|
||||
after('clean up DataStream', async () => {
|
||||
if (cleanupDataStreamSetup) {
|
||||
cleanupDataStreamSetup();
|
||||
await cleanupDataStreamSetup();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
const DATASET_NAME = 'flyout';
|
||||
const NAMESPACE = 'default';
|
||||
const DATA_STREAM_NAME = `logs-${DATASET_NAME}-${NAMESPACE}`;
|
||||
const NOW = Date.now();
|
||||
|
||||
const sharedDoc = {
|
||||
time: NOW + 1000,
|
||||
logFilepath: '/flyout.log',
|
||||
serviceName: 'frontend-node',
|
||||
datasetName: DATASET_NAME,
|
||||
namespace: NAMESPACE,
|
||||
message: 'full document',
|
||||
logLevel: 'info',
|
||||
traceId: 'abcdef',
|
||||
hostName: 'gke-edge-oblt-pool',
|
||||
orchestratorClusterId: 'my-cluster-id',
|
||||
orchestratorClusterName: 'my-cluster-id',
|
||||
orchestratorResourceId: 'orchestratorResourceId',
|
||||
cloudProvider: 'gcp',
|
||||
cloudRegion: 'us-central-1',
|
||||
cloudAz: 'us-central-1a',
|
||||
cloudProjectId: 'elastic-project',
|
||||
cloudInstanceId: 'BgfderflkjTheUiGuy',
|
||||
agentName: 'node',
|
||||
};
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const dataGrid = getService('dataGrid');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const PageObjects = getPageObjects(['observabilityLogExplorer']);
|
||||
|
||||
describe('Flyout highlight customization', () => {
|
||||
let cleanupDataStreamSetup: () => Promise<void>;
|
||||
|
||||
describe('Service container', () => {
|
||||
const { serviceName, traceId, ...rest } = sharedDoc;
|
||||
const docWithoutServiceName = { ...rest, traceId, time: NOW - 1000 };
|
||||
const docWithoutTraceId = { ...rest, serviceName, time: NOW - 2000 };
|
||||
const docWithoutServiceContainer = { ...rest, time: NOW - 4000 };
|
||||
|
||||
const docs = [
|
||||
sharedDoc,
|
||||
docWithoutServiceName,
|
||||
docWithoutTraceId,
|
||||
docWithoutServiceContainer,
|
||||
];
|
||||
before('setup DataStream', async () => {
|
||||
cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream(
|
||||
DATASET_NAME,
|
||||
NAMESPACE
|
||||
);
|
||||
await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs);
|
||||
});
|
||||
|
||||
after('clean up DataStream', async () => {
|
||||
if (cleanupDataStreamSetup) {
|
||||
await cleanupDataStreamSetup();
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await PageObjects.observabilityLogExplorer.navigateTo({
|
||||
from: new Date(NOW - 60_000).toISOString(),
|
||||
to: new Date(NOW + 60_000).toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
it('should load the service container with all fields', async () => {
|
||||
await dataGrid.clickRowToggle();
|
||||
await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionService');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutService');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutTrace');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
|
||||
it('should load the service container even when 1 field is missing', async () => {
|
||||
await dataGrid.clickRowToggle({ rowIndex: 1 });
|
||||
await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionService');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutService');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutTrace');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
|
||||
it('should not load the service container if all fields are missing', async () => {
|
||||
await dataGrid.clickRowToggle({ rowIndex: 3 });
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutHighlightSectionService');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutService');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutTrace');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Infrastructure container', () => {
|
||||
const { hostName, orchestratorClusterName, orchestratorResourceId, ...rest } = sharedDoc;
|
||||
const docWithoutHostName = {
|
||||
...rest,
|
||||
orchestratorClusterName,
|
||||
orchestratorResourceId,
|
||||
time: NOW - 1000,
|
||||
};
|
||||
const docWithoutInfrastructureContainer = { ...rest, time: NOW - 2000 };
|
||||
|
||||
const docs = [sharedDoc, docWithoutHostName, docWithoutInfrastructureContainer];
|
||||
before('setup DataStream', async () => {
|
||||
cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream(
|
||||
DATASET_NAME,
|
||||
NAMESPACE
|
||||
);
|
||||
await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs);
|
||||
});
|
||||
|
||||
after('clean up DataStream', async () => {
|
||||
if (cleanupDataStreamSetup) {
|
||||
await cleanupDataStreamSetup();
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await PageObjects.observabilityLogExplorer.navigateTo({
|
||||
from: new Date(NOW - 60_000).toISOString(),
|
||||
to: new Date(NOW + 60_000).toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
it('should load the infrastructure container with all fields', async () => {
|
||||
await dataGrid.clickRowToggle();
|
||||
await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionInfrastructure');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutHostName');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutClusterName');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutResourceId');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
|
||||
it('should load the infrastructure container even when 1 field is missing', async () => {
|
||||
await dataGrid.clickRowToggle({ rowIndex: 1 });
|
||||
await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionInfrastructure');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutHostName');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutClusterName');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutResourceId');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
|
||||
it('should not load the infrastructure container if all fields are missing', async () => {
|
||||
await dataGrid.clickRowToggle({ rowIndex: 2 });
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutHighlightSectionInfrastructure');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutHostName');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutClusterName');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutResourceId');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Cloud container', () => {
|
||||
const { cloudProvider, cloudInstanceId, cloudProjectId, cloudRegion, cloudAz, ...rest } =
|
||||
sharedDoc;
|
||||
const docWithoutCloudProviderAndInstanceId = {
|
||||
...rest,
|
||||
cloudProjectId,
|
||||
cloudRegion,
|
||||
cloudAz,
|
||||
time: NOW - 1000,
|
||||
};
|
||||
const docWithoutCloudContainer = { ...rest, time: NOW - 2000 };
|
||||
|
||||
const docs = [sharedDoc, docWithoutCloudProviderAndInstanceId, docWithoutCloudContainer];
|
||||
before('setup DataStream', async () => {
|
||||
cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream(
|
||||
DATASET_NAME,
|
||||
NAMESPACE
|
||||
);
|
||||
await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs);
|
||||
});
|
||||
|
||||
after('clean up DataStream', async () => {
|
||||
if (cleanupDataStreamSetup) {
|
||||
await cleanupDataStreamSetup();
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await PageObjects.observabilityLogExplorer.navigateTo({
|
||||
from: new Date(NOW - 60_000).toISOString(),
|
||||
to: new Date(NOW + 60_000).toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
it('should load the cloud container with all fields', async () => {
|
||||
await dataGrid.clickRowToggle();
|
||||
await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionCloud');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutCloudProvider');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutCloudRegion');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutCloudAz');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutCloudProjectId');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutCloudInstanceId');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
|
||||
it('should load the cloud container even when some fields are missing', async () => {
|
||||
await dataGrid.clickRowToggle({ rowIndex: 1 });
|
||||
await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionCloud');
|
||||
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutCloudProvider');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutCloudInstanceId');
|
||||
|
||||
await testSubjects.existOrFail('logExplorerFlyoutCloudRegion');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutCloudAz');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutCloudProjectId');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
|
||||
it('should not load the cloud container if all fields are missing', async () => {
|
||||
await dataGrid.clickRowToggle({ rowIndex: 2 });
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutHighlightSectionCloud');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutCloudProvider');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutCloudRegion');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutCloudAz');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutCloudProjectId');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutCloudInstanceId');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Other container', () => {
|
||||
const { logFilepath, agentName, ...rest } = sharedDoc;
|
||||
const docWithoutLogPathAndAgentName = {
|
||||
...rest,
|
||||
time: NOW - 1000,
|
||||
};
|
||||
|
||||
const docs = [sharedDoc, docWithoutLogPathAndAgentName];
|
||||
before('setup DataStream', async () => {
|
||||
cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream(
|
||||
DATASET_NAME,
|
||||
NAMESPACE
|
||||
);
|
||||
await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs);
|
||||
});
|
||||
|
||||
after('clean up DataStream', async () => {
|
||||
if (cleanupDataStreamSetup) {
|
||||
await cleanupDataStreamSetup();
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await PageObjects.observabilityLogExplorer.navigateTo({
|
||||
from: new Date(NOW - 60_000).toISOString(),
|
||||
to: new Date(NOW + 60_000).toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
it('should load the other container with all fields', async () => {
|
||||
await dataGrid.clickRowToggle();
|
||||
await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionOther');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutLogPathFile');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutNamespace');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutDataset');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutLogShipper');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
|
||||
it('should load the other container even when some fields are missing', async () => {
|
||||
await dataGrid.clickRowToggle({ rowIndex: 1 });
|
||||
await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionOther');
|
||||
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutLogPathFile');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutLogShipper');
|
||||
|
||||
await testSubjects.existOrFail('logExplorerFlyoutNamespace');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutDataset');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -16,5 +16,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
|||
loadTestFile(require.resolve('./filter_controls'));
|
||||
loadTestFile(require.resolve('./flyout'));
|
||||
loadTestFile(require.resolve('./header_menu'));
|
||||
loadTestFile(require.resolve('./flyout_highlights.ts'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -406,12 +406,24 @@ export function ObservabilityLogExplorerPageObject({
|
|||
|
||||
interface MockLogDoc {
|
||||
time: number;
|
||||
logFilepath: string;
|
||||
logFilepath?: string;
|
||||
serviceName?: string;
|
||||
namespace: string;
|
||||
datasetName: string;
|
||||
message?: string;
|
||||
logLevel?: string;
|
||||
traceId?: string;
|
||||
hostName?: string;
|
||||
orchestratorClusterId?: string;
|
||||
orchestratorClusterName?: string;
|
||||
orchestratorResourceId?: string;
|
||||
cloudProvider?: string;
|
||||
cloudRegion?: string;
|
||||
cloudAz?: string;
|
||||
cloudProjectId?: string;
|
||||
cloudInstanceId?: string;
|
||||
agentName?: string;
|
||||
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
|
@ -423,6 +435,17 @@ export function createLogDoc({
|
|||
datasetName,
|
||||
message,
|
||||
logLevel,
|
||||
traceId,
|
||||
hostName,
|
||||
orchestratorClusterId,
|
||||
orchestratorClusterName,
|
||||
orchestratorResourceId,
|
||||
cloudProvider,
|
||||
cloudRegion,
|
||||
cloudAz,
|
||||
cloudProjectId,
|
||||
cloudInstanceId,
|
||||
agentName,
|
||||
...extraFields
|
||||
}: MockLogDoc) {
|
||||
return {
|
||||
|
@ -452,6 +475,17 @@ export function createLogDoc({
|
|||
dataset: datasetName,
|
||||
},
|
||||
...(logLevel && { 'log.level': logLevel }),
|
||||
...(traceId && { 'trace.id': traceId }),
|
||||
...(hostName && { 'host.name': hostName }),
|
||||
...(orchestratorClusterId && { 'orchestrator.cluster.id': orchestratorClusterId }),
|
||||
...(orchestratorClusterName && { 'orchestrator.cluster.name': orchestratorClusterName }),
|
||||
...(orchestratorResourceId && { 'orchestrator.resource.id': orchestratorResourceId }),
|
||||
...(cloudProvider && { 'cloud.provider': cloudProvider }),
|
||||
...(cloudRegion && { 'cloud.region': cloudRegion }),
|
||||
...(cloudAz && { 'cloud.availability_zone': cloudAz }),
|
||||
...(cloudProjectId && { 'cloud.project.id': cloudProjectId }),
|
||||
...(cloudInstanceId && { 'cloud.instance.id': cloudInstanceId }),
|
||||
...(agentName && { 'agent.name': agentName }),
|
||||
...extraFields,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,291 @@
|
|||
/*
|
||||
* 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 { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
const DATASET_NAME = 'flyout';
|
||||
const NAMESPACE = 'default';
|
||||
const DATA_STREAM_NAME = `logs-${DATASET_NAME}-${NAMESPACE}`;
|
||||
const NOW = Date.now();
|
||||
|
||||
const sharedDoc = {
|
||||
time: NOW + 1000,
|
||||
logFilepath: '/flyout.log',
|
||||
serviceName: 'frontend-node',
|
||||
datasetName: DATASET_NAME,
|
||||
namespace: NAMESPACE,
|
||||
message: 'full document',
|
||||
logLevel: 'info',
|
||||
traceId: 'abcdef',
|
||||
hostName: 'gke-edge-oblt-pool',
|
||||
orchestratorClusterId: 'my-cluster-id',
|
||||
orchestratorClusterName: 'my-cluster-id',
|
||||
orchestratorResourceId: 'orchestratorResourceId',
|
||||
cloudProvider: 'gcp',
|
||||
cloudRegion: 'us-central-1',
|
||||
cloudAz: 'us-central-1a',
|
||||
cloudProjectId: 'elastic-project',
|
||||
cloudInstanceId: 'BgfderflkjTheUiGuy',
|
||||
agentName: 'node',
|
||||
};
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const dataGrid = getService('dataGrid');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const PageObjects = getPageObjects(['observabilityLogExplorer', 'svlCommonPage']);
|
||||
|
||||
describe('Flyout highlight customization', () => {
|
||||
let cleanupDataStreamSetup: () => Promise<void>;
|
||||
|
||||
describe('Service container', () => {
|
||||
const { serviceName, traceId, ...rest } = sharedDoc;
|
||||
const docWithoutServiceName = { ...rest, traceId, time: NOW - 1000 };
|
||||
const docWithoutTraceId = { ...rest, serviceName, time: NOW - 2000 };
|
||||
const docWithoutServiceContainer = { ...rest, time: NOW - 4000 };
|
||||
|
||||
const docs = [
|
||||
sharedDoc,
|
||||
docWithoutServiceName,
|
||||
docWithoutTraceId,
|
||||
docWithoutServiceContainer,
|
||||
];
|
||||
before('setup DataStream', async () => {
|
||||
cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream(
|
||||
DATASET_NAME,
|
||||
NAMESPACE
|
||||
);
|
||||
await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs);
|
||||
await PageObjects.svlCommonPage.login();
|
||||
});
|
||||
|
||||
after('clean up DataStream', async () => {
|
||||
await PageObjects.svlCommonPage.forceLogout();
|
||||
if (cleanupDataStreamSetup) {
|
||||
await cleanupDataStreamSetup();
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await PageObjects.observabilityLogExplorer.navigateTo({
|
||||
from: new Date(NOW - 60_000).toISOString(),
|
||||
to: new Date(NOW + 60_000).toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
it('should load the service container with all fields', async () => {
|
||||
await dataGrid.clickRowToggle();
|
||||
await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionService');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutService');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutTrace');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
|
||||
it('should load the service container even when 1 field is missing', async () => {
|
||||
await dataGrid.clickRowToggle({ rowIndex: 1 });
|
||||
await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionService');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutService');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutTrace');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
|
||||
it('should not load the service container if all fields are missing', async () => {
|
||||
await dataGrid.clickRowToggle({ rowIndex: 3 });
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutHighlightSectionService');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutService');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutTrace');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Infrastructure container', () => {
|
||||
const { hostName, orchestratorClusterName, orchestratorResourceId, ...rest } = sharedDoc;
|
||||
const docWithoutHostName = {
|
||||
...rest,
|
||||
orchestratorClusterName,
|
||||
orchestratorResourceId,
|
||||
time: NOW - 1000,
|
||||
};
|
||||
const docWithoutInfrastructureContainer = { ...rest, time: NOW - 2000 };
|
||||
|
||||
const docs = [sharedDoc, docWithoutHostName, docWithoutInfrastructureContainer];
|
||||
before('setup DataStream', async () => {
|
||||
cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream(
|
||||
DATASET_NAME,
|
||||
NAMESPACE
|
||||
);
|
||||
await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs);
|
||||
await PageObjects.svlCommonPage.login();
|
||||
});
|
||||
|
||||
after('clean up DataStream', async () => {
|
||||
await PageObjects.svlCommonPage.forceLogout();
|
||||
if (cleanupDataStreamSetup) {
|
||||
await cleanupDataStreamSetup();
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await PageObjects.observabilityLogExplorer.navigateTo({
|
||||
from: new Date(NOW - 60_000).toISOString(),
|
||||
to: new Date(NOW + 60_000).toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
it('should load the infrastructure container with all fields', async () => {
|
||||
await dataGrid.clickRowToggle();
|
||||
await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionInfrastructure');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutHostName');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutClusterName');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutResourceId');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
|
||||
it('should load the infrastructure container even when 1 field is missing', async () => {
|
||||
await dataGrid.clickRowToggle({ rowIndex: 1 });
|
||||
await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionInfrastructure');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutHostName');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutClusterName');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutResourceId');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
|
||||
it('should not load the infrastructure container if all fields are missing', async () => {
|
||||
await dataGrid.clickRowToggle({ rowIndex: 2 });
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutHighlightSectionInfrastructure');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutHostName');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutClusterName');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutResourceId');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Cloud container', () => {
|
||||
const { cloudProvider, cloudInstanceId, cloudProjectId, cloudRegion, cloudAz, ...rest } =
|
||||
sharedDoc;
|
||||
const docWithoutCloudProviderAndInstanceId = {
|
||||
...rest,
|
||||
cloudProjectId,
|
||||
cloudRegion,
|
||||
cloudAz,
|
||||
time: NOW - 1000,
|
||||
};
|
||||
const docWithoutCloudContainer = { ...rest, time: NOW - 2000 };
|
||||
|
||||
const docs = [sharedDoc, docWithoutCloudProviderAndInstanceId, docWithoutCloudContainer];
|
||||
before('setup DataStream', async () => {
|
||||
cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream(
|
||||
DATASET_NAME,
|
||||
NAMESPACE
|
||||
);
|
||||
await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs);
|
||||
await PageObjects.svlCommonPage.login();
|
||||
});
|
||||
|
||||
after('clean up DataStream', async () => {
|
||||
await PageObjects.svlCommonPage.forceLogout();
|
||||
if (cleanupDataStreamSetup) {
|
||||
await cleanupDataStreamSetup();
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await PageObjects.observabilityLogExplorer.navigateTo({
|
||||
from: new Date(NOW - 60_000).toISOString(),
|
||||
to: new Date(NOW + 60_000).toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
it('should load the cloud container with all fields', async () => {
|
||||
await dataGrid.clickRowToggle();
|
||||
await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionCloud');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutCloudProvider');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutCloudRegion');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutCloudAz');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutCloudProjectId');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutCloudInstanceId');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
|
||||
it('should load the cloud container even when some fields are missing', async () => {
|
||||
await dataGrid.clickRowToggle({ rowIndex: 1 });
|
||||
await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionCloud');
|
||||
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutCloudProvider');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutCloudInstanceId');
|
||||
|
||||
await testSubjects.existOrFail('logExplorerFlyoutCloudRegion');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutCloudAz');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutCloudProjectId');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
|
||||
it('should not load the cloud container if all fields are missing', async () => {
|
||||
await dataGrid.clickRowToggle({ rowIndex: 2 });
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutHighlightSectionCloud');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutCloudProvider');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutCloudRegion');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutCloudAz');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutCloudProjectId');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutCloudInstanceId');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Other container', () => {
|
||||
const { logFilepath, agentName, ...rest } = sharedDoc;
|
||||
const docWithoutLogPathAndAgentName = {
|
||||
...rest,
|
||||
time: NOW - 1000,
|
||||
};
|
||||
|
||||
const docs = [sharedDoc, docWithoutLogPathAndAgentName];
|
||||
before('setup DataStream', async () => {
|
||||
cleanupDataStreamSetup = await PageObjects.observabilityLogExplorer.setupDataStream(
|
||||
DATASET_NAME,
|
||||
NAMESPACE
|
||||
);
|
||||
await PageObjects.observabilityLogExplorer.ingestLogEntries(DATA_STREAM_NAME, docs);
|
||||
await PageObjects.svlCommonPage.login();
|
||||
});
|
||||
|
||||
after('clean up DataStream', async () => {
|
||||
await PageObjects.svlCommonPage.forceLogout();
|
||||
if (cleanupDataStreamSetup) {
|
||||
await cleanupDataStreamSetup();
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await PageObjects.observabilityLogExplorer.navigateTo({
|
||||
from: new Date(NOW - 60_000).toISOString(),
|
||||
to: new Date(NOW + 60_000).toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
it('should load the other container with all fields', async () => {
|
||||
await dataGrid.clickRowToggle();
|
||||
await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionOther');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutLogPathFile');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutNamespace');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutDataset');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutLogShipper');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
|
||||
it('should load the other container even when some fields are missing', async () => {
|
||||
await dataGrid.clickRowToggle({ rowIndex: 1 });
|
||||
await testSubjects.existOrFail('logExplorerFlyoutHighlightSectionOther');
|
||||
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutLogPathFile');
|
||||
await testSubjects.missingOrFail('logExplorerFlyoutLogShipper');
|
||||
|
||||
await testSubjects.existOrFail('logExplorerFlyoutNamespace');
|
||||
await testSubjects.existOrFail('logExplorerFlyoutDataset');
|
||||
await dataGrid.closeFlyout();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -16,5 +16,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
|||
loadTestFile(require.resolve('./filter_controls'));
|
||||
loadTestFile(require.resolve('./flyout'));
|
||||
loadTestFile(require.resolve('./header_menu'));
|
||||
loadTestFile(require.resolve('./header_menu'));
|
||||
loadTestFile(require.resolve('./flyout_highlights.ts'));
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue