Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Logs Explorer] Implement Flyout content header #169832

Merged
merged 34 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9b8104d
feat(discover): enhance flyout customization to update content
Oct 24, 2023
7977fa0
Merge branch 'main' into 169394-enhance-flyout-extension-point
tonyghiani Oct 24, 2023
0b8514a
Merge branch 'main' into 169394-enhance-flyout-extension-point
tonyghiani Oct 24, 2023
e48d26a
refactor(discover): add request changes
Oct 25, 2023
faa0c3e
refactor(discover): update test
Oct 25, 2023
ca1accf
feat(discover): begin flyout header implementation
Oct 25, 2023
1e0f740
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Oct 25, 2023
fdd9edf
feat(discover): remove trailing comma
Oct 25, 2023
98b7b8b
feat(discover): fix conditional rendering
Oct 25, 2023
4c481f8
Merge branch 'main' into 169501-log-flyout-header
achyutjhunjhunwala Oct 26, 2023
2c9a014
feat(log-explorer): minor changes
Oct 26, 2023
3b8b680
Merge branch '169501-log-flyout-header' of github.com:tonyghiani/kiba…
Oct 26, 2023
5f8e53a
Merge branch 'main' into 169501-log-flyout-header
tonyghiani Oct 26, 2023
e72c32c
test(log-explorer): wip test flyout
Oct 26, 2023
8919efb
Merge branch '169501-log-flyout-header' of github.com:tonyghiani/kiba…
Oct 27, 2023
dca7add
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Oct 27, 2023
4a888e6
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Oct 27, 2023
e84a21c
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Oct 27, 2023
f180439
test(log-explorer): finish stateful tests
Oct 27, 2023
cdfc9b9
Merge branch '169501-log-flyout-header' of github.com:tonyghiani/kiba…
Oct 27, 2023
fc25832
Merge branch 'main' into 169501-log-flyout-header
tonyghiani Oct 27, 2023
9e31b51
test(log-explorer): copy serverless test suite
Oct 27, 2023
9761534
test(log-explorer): small page object refactor
Oct 27, 2023
c0cf9dc
refactor(log-explorer): request changes
Oct 30, 2023
31b9f81
Merge branch 'main' into 169501-log-flyout-header
tonyghiani Oct 30, 2023
c1f97d6
Merge branch 'main' into 169501-log-flyout-header
tonyghiani Oct 31, 2023
cabb230
Merge branch 'main' into 169501-log-flyout-header
tonyghiani Oct 31, 2023
09e3cf6
Merge branch 'main' into 169501-log-flyout-header
tonyghiani Oct 31, 2023
bb11af7
Merge branch 'main' into 169501-log-flyout-header
tonyghiani Oct 31, 2023
223a490
feat(log-explorer): comment content customization application
Nov 2, 2023
9b980d8
tests(log-explorer): skip tests
Nov 2, 2023
aa8bf51
Merge branch 'main' into 169501-log-flyout-header
tonyghiani Nov 2, 2023
c0be1bb
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Nov 2, 2023
46507aa
feat(log-explorer): re-enable flyout customization
Nov 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export function DiscoverGridFlyout({
const bodyContent = flyoutCustomization?.Content ? (
<flyoutCustomization.Content
actions={contentActions}
dataView={dataView}
doc={actualHit}
renderDefaultContent={renderDefaultContent}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import type { DataView } from '@kbn/data-views-plugin/common';
import type { DataTableRecord } from '@kbn/discover-utils/types';
import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
import React, { type ComponentType } from 'react';
Expand Down Expand Up @@ -33,6 +33,7 @@ export interface FlyoutContentActions {

export interface FlyoutContentProps {
actions: FlyoutContentActions;
dataView: DataView;
doc: DataTableRecord;
renderDefaultContent: () => React.ReactNode;
}
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/discover/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export type {
DiscoverCustomization,
DiscoverCustomizationService,
FlyoutCustomization,
FlyoutContentActions,
FlyoutContentProps,
SearchBarCustomization,
UnifiedHistogramCustomization,
TopNavCustomization,
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/log_explorer/kibana.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"data",
"dataViews",
"discover",
"fieldFormats",
"fleet",
"kibanaReact",
"kibanaUtils",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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 { FlyoutContentProps } from '@kbn/discover-plugin/public';
import { LogLevel } from './sub_components/log_level';
import { Timestamp } from './sub_components/timestamp';
import { LogDocument } from './types';
import { getDocDetailRenderFlags, useDocDetail } from './use_doc_detail';
import { Message } from './sub_components/message';

export function FlyoutDetail({
dataView,
doc,
}: Pick<FlyoutContentProps, '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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* 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 * from './flyout_detail';
export * from './types';
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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 { EuiBadge, type EuiBadgeProps } from '@elastic/eui';
import { FlyoutDoc } from '../types';

const LEVEL_DICT: Record<string, EuiBadgeProps['color']> = {
error: 'danger',
warn: 'warning',
info: 'primary',
default: 'default',
};

interface LogLevelProps {
level: FlyoutDoc['log.level'];
}

export function LogLevel({ level }: LogLevelProps) {
if (!level) return null;

const levelColor = LEVEL_DICT[level] ?? LEVEL_DICT.default;

return (
<EuiBadge color={levelColor} data-test-subj="logExplorerFlyoutLogLevel">
{level}
</EuiBadge>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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 { EuiCodeBlock, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import { FlyoutDoc } from '../types';
import { flyoutMessageLabel } from '../translations';

interface MessageProps {
message: FlyoutDoc['message'];
}

export function Message({ message }: MessageProps) {
if (!message) return null;

return (
<EuiFlexGroup direction="column" gutterSize="xs" data-test-subj="logExplorerFlyoutLogMessage">
<EuiFlexItem>
<EuiText color="subdued" size="xs">
{flyoutMessageLabel}
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiCodeBlock overflowHeight={100} paddingSize="m" isCopyable language="txt" fontSize="m">
{message}
</EuiCodeBlock>
</EuiFlexItem>
</EuiFlexGroup>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 { EuiBadge } from '@elastic/eui';
import { FlyoutDoc } from '../types';

interface TimestampProps {
timestamp: FlyoutDoc['@timestamp'];
}

export function Timestamp({ timestamp }: TimestampProps) {
if (!timestamp) return null;

return (
<EuiBadge color="hollow" data-test-subj="logExplorerFlyoutLogTimestamp">
{timestamp}
</EuiBadge>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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 { i18n } from '@kbn/i18n';

export const flyoutMessageLabel = i18n.translate('xpack.logExplorer.flyoutDetail.label.message', {
defaultMessage: 'Message',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 type { EuiIconType } from '@elastic/eui/src/components/icon/icon';
import { DataTableRecord } from '@kbn/discover-utils/types';

export interface LogDocument extends DataTableRecord {
flattened: {
'@timestamp': string;
'log.level'?: string;
message?: string;
};
}

export interface FlyoutDoc {
'@timestamp': string;
'log.level'?: string;
message?: string;
}

export interface FlyoutHighlightField {
label: string;
value: string;
iconType?: EuiIconType;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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 { FlyoutContentProps } from '@kbn/discover-plugin/public';
import { formatFieldValue } from '@kbn/discover-utils';
import { LOG_LEVEL_FIELD, MESSAGE_FIELD, TIMESTAMP_FIELD } from '../../../common/constants';
import { useKibanaContextForPlugin } from '../../utils/use_kibana';
import { FlyoutDoc, LogDocument } from './types';

export function useDocDetail(
doc: LogDocument,
{ dataView }: Pick<FlyoutContentProps, 'dataView'>
): FlyoutDoc {
const { services } = useKibanaContextForPlugin();

const formatField = <F extends keyof LogDocument['flattened']>(
field: F
): LogDocument['flattened'][F] => {
return (
doc.flattened[field] &&
formatFieldValue(
doc.flattened[field],
doc.raw,
services.fieldFormats,
dataView,
dataView.fields.getByName(field)
)
);
};

const level = formatField(LOG_LEVEL_FIELD)?.toLowerCase();
const timestamp = formatField(TIMESTAMP_FIELD);
const message = formatField(MESSAGE_FIELD);

return {
[LOG_LEVEL_FIELD]: level,
[TIMESTAMP_FIELD]: timestamp,
[MESSAGE_FIELD]: message,
};
}

export const getDocDetailRenderFlags = (doc: FlyoutDoc) => {
const hasTimestamp = Boolean(doc['@timestamp']);
const hasLogLevel = Boolean(doc['log.level']);
const hasMessage = Boolean(doc.message);

const hasBadges = hasTimestamp || hasLogLevel;

const hasFlyoutHeader = hasBadges || hasMessage;

return {
hasTimestamp,
hasLogLevel,
hasMessage,
hasBadges,
hasFlyoutHeader,
};
};
Original file line number Diff line number Diff line change
@@ -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 { FlyoutContentProps } from '@kbn/discover-plugin/public';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FlyoutDetail } from '../components/flyout_detail/flyout_detail';

export const CustomFlyoutContent = ({
actions,
dataView,
doc,
renderDefaultContent,
}: FlyoutContentProps) => {
return (
<EuiFlexGroup direction="column">
{/* Apply custom Log Explorer detail */}
<EuiFlexItem>
<FlyoutDetail actions={actions} dataView={dataView} doc={doc} />
</EuiFlexItem>
{/* Restore default content */}
<EuiFlexItem>{renderDefaultContent()}</EuiFlexItem>
</EuiFlexGroup>
);
};

// eslint-disable-next-line import/no-default-export
export default CustomFlyoutContent;
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import { LogExplorerStateContainer } from '../components/log_explorer';
import { LogExplorerStartDeps } from '../types';
import { useKibanaContextForPluginProvider } from '../utils/use_kibana';

const LazyCustomDatasetSelector = dynamic(() => import('./custom_dataset_selector'));
const LazyCustomDatasetFilters = dynamic(() => import('./custom_dataset_filters'));
const LazyCustomDatasetSelector = dynamic(() => import('./custom_dataset_selector'));
const LazyCustomFlyoutContent = dynamic(() => import('./custom_flyout_content'));

export interface CreateLogExplorerProfileCustomizationsDeps {
core: CoreStart;
Expand Down Expand Up @@ -115,6 +116,15 @@ export const createLogExplorerProfileCustomizations =
viewSurroundingDocument: { disabled: true },
},
},
Content: (props) => {
const KibanaContextProviderForPlugin = useKibanaContextForPluginProvider(core, plugins);

return (
<KibanaContextProviderForPlugin>
<LazyCustomFlyoutContent {...props} />
</KibanaContextProviderForPlugin>
);
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Content: (props) => {
const KibanaContextProviderForPlugin = useKibanaContextForPluginProvider(core, plugins);
return (
<KibanaContextProviderForPlugin>
<LazyCustomFlyoutContent {...props} />
</KibanaContextProviderForPlugin>
);
},
Content: (props) => {
const KibanaContextProviderForPlugin = useKibanaContextForPluginProvider(core, plugins);
const internalState = useObservable(
stateContainer.internalState.state$,
stateContainer.internalState.get()
);
return (
<KibanaContextProviderForPlugin>
<LazyCustomFlyoutContent {...props} dataView={internalState.dataView} />
</KibanaContextProviderForPlugin>
);
},

Instead of adding dataView to FlyoutContentProps, could we instead retrieve it from the state container directly and pass it to your custom component? I'd prefer to keep a single source of truth for customizations to access the shared state if possible.

On a side note, requiring useObservable to access our state in customization components feels a bit inconvenient IMO. Internally we use state selector functions instead, and we could likely give customizations access to the same selectors by passing them to the CustomizationCallback. Just curious if it would be useful for your team to have access to these selectors too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Davis, our team is working on extracting the persistent system on top of the state container, so that each consumer of the log explorer won't depend on the URL sync. I think we'll consume the internal state reacting to the observable changes anyway, I can't say if we'll need those selectors.

Regarding your suggestion I see your point, I'll apply your suggestion and revert the changes on the customization contract.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes applied in c0cf9dc

});

return () => {
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/log_explorer/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import type { DiscoverSetup, DiscoverStart } from '@kbn/discover-plugin/public';
import { SharePluginSetup } from '@kbn/share-plugin/public';
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { LogExplorerLocators } from '../common/locators';
import type { LogExplorerProps } from './components/log_explorer';

Expand All @@ -28,4 +29,5 @@ export interface LogExplorerStartDeps {
data: DataPublicPluginStart;
dataViews: DataViewsPublicPluginStart;
discover: DiscoverStart;
fieldFormats: FieldFormatsStart;
}
3 changes: 2 additions & 1 deletion x-pack/plugins/log_explorer/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"@kbn/unified-data-table",
"@kbn/core-ui-settings-browser",
"@kbn/discover-utils",
"@kbn/deeplinks-observability"
"@kbn/deeplinks-observability",
"@kbn/field-formats-plugin"
],
"exclude": ["target/**/*"]
}
Loading