Skip to content

Commit

Permalink
[Log Explorer] Add logic to display highlights in Flyout (#170650)
Browse files Browse the repository at this point in the history
## Summary

Closes #169504



![Highlights](https://github.com/elastic/kibana/assets/7416358/06f21ac5-38e1-4521-843f-064c16ddd034)

## What's pending

- [ ] FTR Tests

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
achyutjhunjhunwala and kibanamachine authored Nov 10, 2023
1 parent 02ccb77 commit c62df52
Show file tree
Hide file tree
Showing 20 changed files with 1,457 additions and 55 deletions.
2 changes: 1 addition & 1 deletion packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ pageLoadAssetSize:
licensing: 29004
links: 44490
lists: 22900
logExplorer: 39045
logExplorer: 54342
logsShared: 281060
logstash: 53548
management: 46112
Expand Down
13 changes: 13 additions & 0 deletions x-pack/plugins/log_explorer/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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} />
</>
);
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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>
);
}
Original file line number Diff line number Diff line change
@@ -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;
}
);
Loading

0 comments on commit c62df52

Please sign in to comment.