Skip to content

Commit 7d58f94

Browse files
iblancofkibanamachinecauemarcondes
authored
[Discover] Adapt logs flyout to use the new content framework UI (elastic#234650)
## Summary Closes elastic#233743 This PR is a continuation of [another one](elastic#233661), where the UI for the trace docs overview is updated, aiming to achieve visual consistency across Discover flyouts. In the logs case, the basic information sections have been grouped at the top, and the other sections have been adapted to align with the Content Framework as much as possible, without making major changes. **Flyout** |Before|After| |-|-| |<img width="325" height="716" alt="Screenshot 2025-09-10 at 16 58 17" src="https://github.com/user-attachments/assets/b9bd6ae0-3164-45b1-bba6-33ebf2ada596" />|<img width="482" height="839" alt="Screenshot 2025-09-10 at 17 08 22" src="https://github.com/user-attachments/assets/2cdb76e8-6c17-498f-845d-e1c3380213a4" />| **Interactions** |Before|After| |-|-| |![Screen Recording 2025-09-10 at 14 41 10](https://github.com/user-attachments/assets/caa2c7ea-52e0-4416-bfb9-41f41ba2f7fe)|![Screen Recording 2025-09-10 at 17 09 23](https://github.com/user-attachments/assets/638778f4-f418-4d66-97ae-a26fb4d4b5e7)| ### How to test - Make sure you're in an Observability space - Go to discover and check for logs - Open doc flyout To get logs with degraded fields filter by `_ignored:*`. --------- Co-authored-by: kibanamachine <[email protected]> Co-authored-by: Cauê Marcondes <[email protected]>
1 parent ca18439 commit 7d58f94

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+833
-983
lines changed

src/platform/plugins/shared/unified_doc_viewer/public/components/content_framework/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@
88
*/
99

1010
export * from './table';
11+
export * from './section';
12+
export * from './chart';
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import React from 'react';
11+
import { withSuspense } from '@kbn/shared-ux-utility';
12+
import type { ContentFrameworkSectionProps } from './section/section';
13+
14+
const LazyContentFrameworkSection = React.lazy(() => import('./section'));
15+
export const ContentFrameworkSection = withSuspense<ContentFrameworkSectionProps>(
16+
LazyContentFrameworkSection,
17+
<></>
18+
);

src/platform/plugins/shared/unified_doc_viewer/public/components/content_framework/section/index.tsx

Lines changed: 4 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -7,127 +7,8 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10-
import React, { useState } from 'react';
11-
import type { IconType } from '@elastic/eui';
12-
import {
13-
EuiAccordion,
14-
EuiButtonEmpty,
15-
EuiButtonIcon,
16-
EuiFlexGroup,
17-
EuiFlexItem,
18-
EuiHorizontalRule,
19-
EuiIconTip,
20-
EuiPanel,
21-
EuiSpacer,
22-
EuiTitle,
23-
} from '@elastic/eui';
10+
import { ContentFrameworkSection } from './section';
2411

25-
interface BaseAction {
26-
icon: IconType;
27-
ariaLabel: string;
28-
dataTestSubj?: string;
29-
label?: string;
30-
}
31-
32-
type Action =
33-
| (BaseAction & { onClick: () => void; href?: never })
34-
| (BaseAction & { href: string; onClick?: never });
35-
36-
export interface ContentFrameworkSectionProps {
37-
id: string;
38-
title: string;
39-
description?: string;
40-
actions?: Action[];
41-
children: React.ReactNode;
42-
'data-test-subj'?: string;
43-
initialIsOpen?: boolean;
44-
}
45-
46-
export function ContentFrameworkSection({
47-
id,
48-
title,
49-
description,
50-
actions,
51-
children,
52-
'data-test-subj': accordionDataTestSubj,
53-
initialIsOpen = true,
54-
}: ContentFrameworkSectionProps) {
55-
const [isAccordionExpanded, setIsAccordionExpanded] = useState(initialIsOpen);
56-
const renderActions = () => (
57-
<EuiFlexGroup gutterSize="s" justifyContent="flexEnd" alignItems="center">
58-
{actions?.map((action, idx) => {
59-
const { icon, onClick, ariaLabel, label, dataTestSubj, href } = action;
60-
const size = 'xs';
61-
const buttonProps = onClick ? { onClick } : { href };
62-
return (
63-
<EuiFlexItem grow={false} key={idx}>
64-
{label ? (
65-
<EuiButtonEmpty
66-
size={size}
67-
iconType={icon}
68-
aria-label={ariaLabel}
69-
data-test-subj={dataTestSubj}
70-
{...buttonProps}
71-
>
72-
{label}
73-
</EuiButtonEmpty>
74-
) : (
75-
<EuiButtonIcon
76-
size={size}
77-
iconType={icon}
78-
aria-label={ariaLabel}
79-
data-test-subj={dataTestSubj}
80-
{...buttonProps}
81-
/>
82-
)}
83-
</EuiFlexItem>
84-
);
85-
})}
86-
</EuiFlexGroup>
87-
);
88-
89-
return (
90-
<>
91-
<EuiAccordion
92-
data-test-subj={accordionDataTestSubj}
93-
id={`sectionAccordion-${id}`}
94-
initialIsOpen={isAccordionExpanded}
95-
onToggle={setIsAccordionExpanded}
96-
buttonContent={
97-
<EuiFlexGroup alignItems="center" gutterSize="s">
98-
<EuiFlexItem grow={false}>
99-
<EuiTitle size="xs">
100-
<h3>{title}</h3>
101-
</EuiTitle>
102-
</EuiFlexItem>
103-
{description && (
104-
<EuiFlexItem grow={false}>
105-
<EuiIconTip
106-
content={description}
107-
size="s"
108-
color="subdued"
109-
aria-label={description}
110-
/>
111-
</EuiFlexItem>
112-
)}
113-
</EuiFlexGroup>
114-
}
115-
extraAction={
116-
actions?.length ? (
117-
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
118-
<EuiFlexItem grow={false}>{renderActions()}</EuiFlexItem>
119-
</EuiFlexGroup>
120-
) : null
121-
}
122-
>
123-
<>
124-
<EuiSpacer size="s" />
125-
<EuiPanel hasBorder={true} hasShadow={false} paddingSize="s">
126-
{children}
127-
</EuiPanel>
128-
</>
129-
</EuiAccordion>
130-
{!isAccordionExpanded ? <EuiHorizontalRule margin="xs" /> : <EuiSpacer size="m" />}
131-
</>
132-
);
133-
}
12+
// Required for usage in React.lazy
13+
// eslint-disable-next-line import/no-default-export
14+
export default ContentFrameworkSection;

src/platform/plugins/shared/unified_doc_viewer/public/components/content_framework/section/section.stories.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
import type { Meta, StoryObj } from '@storybook/react';
1111
import React from 'react';
1212
import type { UnifiedDocViewerStorybookArgs } from '../../../../.storybook/preview';
13-
import { ContentFrameworkSection, type ContentFrameworkSectionProps } from '.';
1413
import APMSpanFixture from '../../../__fixtures__/span_apm_minimal.json';
14+
import ContentFrameworkSection from '.';
15+
import type { ContentFrameworkSectionProps } from './section';
1516

1617
type Args = UnifiedDocViewerStorybookArgs<ContentFrameworkSectionProps>;
1718
const meta = {

src/platform/plugins/shared/unified_doc_viewer/public/components/content_framework/section/section.test.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99

1010
import React from 'react';
1111
import { render, screen, fireEvent } from '@testing-library/react';
12-
import type { ContentFrameworkSectionProps } from '.';
13-
import { ContentFrameworkSection } from '.';
12+
import userEvent from '@testing-library/user-event';
13+
import { ContentFrameworkSection, type ContentFrameworkSectionProps } from './section';
1414

1515
const defaultProps: ContentFrameworkSectionProps = {
1616
title: 'Test Section',
@@ -32,6 +32,7 @@ const defaultProps: ContentFrameworkSectionProps = {
3232
],
3333
children: <div>Section children</div>,
3434
id: 'testSection',
35+
'data-test-subj': 'testSection',
3536
};
3637

3738
describe('ContentFrameworkSection', () => {
@@ -40,13 +41,20 @@ describe('ContentFrameworkSection', () => {
4041
expect(screen.getByText('Test Section')).toBeInTheDocument();
4142
});
4243

44+
it('renders EuiBetaBadge when isTechPreview is true', () => {
45+
const props = { ...defaultProps, isTechPreview: true };
46+
render(<ContentFrameworkSection {...props} />);
47+
expect(screen.getByTestId('ContentFrameworkSectionEuiBetaBadge')).toBeInTheDocument();
48+
});
49+
4350
it('renders the description as EuiIconTip', () => {
4451
render(<ContentFrameworkSection {...defaultProps} />);
4552
expect(screen.getByText('Section description')).toBeInTheDocument();
4653
});
4754

4855
it('renders actions as buttons', () => {
4956
render(<ContentFrameworkSection {...defaultProps} />);
57+
5058
expect(screen.getByTestId('unifiedDocViewerSectionActionButton-expand')).toBeInTheDocument();
5159
expect(
5260
screen.getByTestId('unifiedDocViewerSectionActionButton-fullScreen')
@@ -84,4 +92,12 @@ describe('ContentFrameworkSection', () => {
8492
screen.queryByTestId('unifiedDocViewerSectionActionButton-fullScreen')
8593
).not.toBeInTheDocument();
8694
});
95+
96+
it('calls onToggle when the accordion is toggled', async () => {
97+
const onToggle = jest.fn();
98+
render(<ContentFrameworkSection {...defaultProps} onToggle={onToggle} forceState="open" />);
99+
const toggleBtn = screen.getByText('Test Section');
100+
await userEvent.click(toggleBtn);
101+
expect(onToggle).toHaveBeenCalled();
102+
});
87103
});
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import React, { useEffect, useState } from 'react';
11+
import type { EuiAccordionProps, IconType } from '@elastic/eui';
12+
import {
13+
EuiAccordion,
14+
EuiBetaBadge,
15+
EuiButtonEmpty,
16+
EuiButtonIcon,
17+
EuiFlexGroup,
18+
EuiFlexItem,
19+
EuiHorizontalRule,
20+
EuiIconTip,
21+
EuiPanel,
22+
EuiSpacer,
23+
EuiTitle,
24+
} from '@elastic/eui';
25+
26+
interface BaseAction {
27+
icon: IconType;
28+
ariaLabel: string;
29+
dataTestSubj?: string;
30+
label?: string;
31+
}
32+
33+
type Action =
34+
| (BaseAction & { onClick: () => void; href?: never })
35+
| (BaseAction & { href: string; onClick?: never });
36+
37+
export interface ContentFrameworkSectionProps {
38+
id: string;
39+
title: string;
40+
description?: string;
41+
actions?: Action[];
42+
children: React.ReactNode;
43+
'data-test-subj'?: string;
44+
onToggle?: (isOpen: boolean) => void;
45+
forceState?: EuiAccordionProps['forceState'];
46+
isTechPreview?: boolean;
47+
hasBorder?: boolean;
48+
hasPadding?: boolean;
49+
}
50+
51+
export function ContentFrameworkSection({
52+
id,
53+
title,
54+
description,
55+
actions,
56+
children,
57+
onToggle,
58+
forceState = 'open',
59+
'data-test-subj': accordionDataTestSubj,
60+
isTechPreview = false,
61+
hasBorder = true,
62+
hasPadding = true,
63+
}: ContentFrameworkSectionProps) {
64+
const [accordionState, setAccordionState] = useState<EuiAccordionProps['forceState']>(forceState);
65+
66+
const renderActions = () => (
67+
<EuiFlexGroup gutterSize="s" justifyContent="flexEnd" alignItems="center">
68+
{actions?.map((action, idx) => {
69+
const { icon, onClick, ariaLabel, label, dataTestSubj, href } = action;
70+
const size = 'xs';
71+
const buttonProps = onClick ? { onClick } : { href };
72+
return (
73+
<EuiFlexItem grow={false} key={idx}>
74+
{label ? (
75+
<EuiButtonEmpty
76+
size={size}
77+
iconType={icon}
78+
aria-label={ariaLabel}
79+
data-test-subj={dataTestSubj}
80+
{...buttonProps}
81+
>
82+
{label}
83+
</EuiButtonEmpty>
84+
) : (
85+
<EuiButtonIcon
86+
size={size}
87+
iconType={icon}
88+
aria-label={ariaLabel}
89+
data-test-subj={dataTestSubj}
90+
{...buttonProps}
91+
/>
92+
)}
93+
</EuiFlexItem>
94+
);
95+
})}
96+
</EuiFlexGroup>
97+
);
98+
99+
useEffect(() => {
100+
setAccordionState(forceState);
101+
}, [forceState]);
102+
103+
const handleToggle = (isOpen: boolean) => {
104+
setAccordionState(isOpen ? 'open' : 'closed');
105+
onToggle?.(isOpen);
106+
};
107+
108+
return (
109+
<>
110+
<EuiAccordion
111+
data-test-subj={accordionDataTestSubj}
112+
id={`sectionAccordion-${id}`}
113+
initialIsOpen={forceState === 'open'}
114+
onToggle={handleToggle}
115+
forceState={accordionState}
116+
buttonContent={
117+
<EuiFlexGroup alignItems="center" gutterSize="s">
118+
<EuiFlexItem grow={false}>
119+
<EuiTitle size="xs">
120+
<h3>{title}</h3>
121+
</EuiTitle>
122+
</EuiFlexItem>
123+
{description ? (
124+
<EuiFlexItem grow={false}>
125+
{isTechPreview ? (
126+
<EuiBetaBadge
127+
size="s"
128+
label={description}
129+
alignment="middle"
130+
color="hollow"
131+
iconType="beaker"
132+
title={title}
133+
data-test-subj="ContentFrameworkSectionEuiBetaBadge"
134+
/>
135+
) : (
136+
<EuiIconTip
137+
content={description}
138+
size="s"
139+
color="subdued"
140+
aria-label={description}
141+
/>
142+
)}
143+
</EuiFlexItem>
144+
) : null}
145+
</EuiFlexGroup>
146+
}
147+
extraAction={
148+
actions?.length && (
149+
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
150+
<EuiFlexItem grow={false}>{renderActions()}</EuiFlexItem>
151+
</EuiFlexGroup>
152+
)
153+
}
154+
>
155+
<>
156+
<EuiSpacer size="s" />
157+
<EuiPanel hasBorder={hasBorder} hasShadow={false} paddingSize={hasPadding ? 's' : 'none'}>
158+
{children}
159+
</EuiPanel>
160+
</>
161+
</EuiAccordion>
162+
{accordionState === 'closed' ? <EuiHorizontalRule margin="xs" /> : <EuiSpacer size="m" />}
163+
</>
164+
);
165+
}

0 commit comments

Comments
 (0)