[Discover] SCSS to Emotions migration part 4/4 (#224424)

## Summary

Parent issue: [[DataDiscovery] Replace SCSS with
CSS-in-JS](https://github.com/elastic/kibana/issues/209807#top)

Followed [Emotion standards
guide](https://docs.google.com/document/d/1CPflY8yCc3lZDg2BQkaMTgIkZlqiAEyMcAAvZbsjcTc/edit?pli=1&tab=t.0#heading=h.4zj1jq66y5an)

Part 4 of SCSS -> Emotion migration. Files included:
-
`src/platform/plugins/shared/data_view_editor/public/components/_templates.scss`
-
`src/platform/plugins/shared/data_view_editor/public/components/_variables.scss`
-
`src/platform/plugins/shared/data_view_editor/public/components/data_view_editor.scss`
-
`src/platform/plugins/shared/data_view_editor/public/components/flyout_panels/flyout_panels.scss`
-
`src/platform/plugins/shared/data_view_field_editor/public/components/field_format_editor/samples/samples.scss`
-
`src/platform/plugins/shared/data_view_field_editor/public/components/flyout_panels/flyout_panels.scss`
-
`src/platform/plugins/shared/data_view_field_editor/public/components/preview/field_list/field_list.scss`
-
`src/platform/plugins/shared/data_view_field_editor/public/components/preview/field_preview.scss`
-
`src/platform/plugins/shared/data_view_management/public/_templates.scss`
-
`src/platform/plugins/shared/data_view_management/public/_variables.scss`
-
`src/platform/plugins/shared/data_view_management/public/components/empty_index_list_prompt/empty_index_list_prompt.scss`

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Ania Kowalska 2025-06-23 13:41:46 +02:00 committed by GitHub
parent f149325412
commit 9e4798139f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 468 additions and 667 deletions

View file

@ -1,11 +0,0 @@
%inp-empty-state-footer {
background: $euiColorLightestShade;
margin: 0 (-$euiSizeL) (-$euiSizeL);
padding: $euiSizeL;
border-radius: 0 0 $euiBorderRadius $euiBorderRadius;
// sass-lint:disable-block mixins-before-declarations
@include euiBreakpoint('xs', 's') {
text-align: center;
}
}

View file

@ -1 +0,0 @@
$inpEmptyStateMaxWidth: $euiSizeXXL * 19;

View file

@ -1,13 +0,0 @@
.indexPatternEditor__form {
flex-grow: 1;
}
.fieldEditor__mainFlyoutPanel {
display: flex;
flex-direction: column;
}
.indexPatternEditor__footer {
margin-left: -$euiSizeL;
margin-right: -$euiSizeL;
}

View file

@ -12,7 +12,6 @@ import { EuiFlyout } from '@elastic/eui';
import { DataViewEditorLazy } from './data_view_editor_lazy';
import { DataViewEditorContext, DataViewEditorProps } from '../types';
import { createKibanaReactContext } from '../shared_imports';
import './data_view_editor.scss';
export interface DataViewEditorPropsWithServices extends DataViewEditorProps {
services: DataViewEditorContext;

View file

@ -8,6 +8,7 @@
*/
import React, { useEffect, useCallback } from 'react';
import { css } from '@emotion/react';
import {
EuiTitle,
EuiFlexGroup,
@ -16,12 +17,13 @@ import {
EuiLink,
EuiSkeletonRectangle,
EuiSkeletonTitle,
useEuiTheme,
type UseEuiTheme,
} from '@elastic/eui';
import useDebounce from 'react-use/lib/useDebounce';
import { i18n } from '@kbn/i18n';
import useObservable from 'react-use/lib/useObservable';
import { INDEX_PATTERN_TYPE } from '@kbn/data-views-plugin/public';
import { useMemoCss } from '@kbn/css-utils/public/use_memo_css';
import {
DataView,
@ -94,11 +96,12 @@ const IndexPatternEditorFlyoutContentComponent = ({
showManagementLink,
dataViewEditorService,
}: Props) => {
const styles = useMemoCss(componentStyles);
const {
services: { application, dataViews, uiSettings, overlays, docLinks },
} = useKibana<DataViewEditorContext>();
const { euiTheme } = useEuiTheme();
const canSave = dataViews.getCanSaveSync();
const { form } = useForm<IndexPatternConfig, FormInternal>({
@ -214,9 +217,9 @@ const IndexPatternEditorFlyoutContentComponent = ({
if (isLoadingSources || !existingDataViewNames) {
return (
<EuiFlexGroup css={{ margin: euiTheme.size.l }}>
<EuiFlexGroup css={styles.loadingWrapper}>
<EuiFlexItem>
<EuiSkeletonTitle size="l" css={{ width: '25vw' }} />
<EuiSkeletonTitle size="l" css={styles.skeletonTitle} />
{Array.from({ length: 3 }).map((_, index) => (
<React.Fragment key={index}>
<EuiSpacer size="xl" />
@ -261,7 +264,7 @@ const IndexPatternEditorFlyoutContentComponent = ({
return (
<FlyoutPanels.Group flyoutClassName={'indexPatternEditorFlyout'} maxWidth={1180}>
<FlyoutPanels.Item
className="fieldEditor__mainFlyoutPanel"
css={styles.flyoutPanel}
data-test-subj="indexPatternEditorFlyout"
border="right"
>
@ -281,7 +284,7 @@ const IndexPatternEditorFlyoutContentComponent = ({
)}
<Form
form={form}
className="indexPatternEditor__form"
css={styles.patternEditorForm}
error={form.getErrors()}
isInvalid={form.isSubmitted && !form.isValid && form.getErrors().length}
data-validation-error={form.getErrors().length ? '1' : '0'}
@ -370,4 +373,21 @@ const IndexPatternEditorFlyoutContentComponent = ({
);
};
const componentStyles = {
patternEditorForm: css({
flexGrow: 1,
}),
flyoutPanel: css({
display: 'flex',
flexDirection: 'column',
}),
loadingWrapper: ({ euiTheme }: UseEuiTheme) =>
css({
margin: euiTheme.size.l,
}),
skeletonTitle: css({
width: '25vw',
}),
};
export const IndexPatternEditorFlyoutContent = React.memo(IndexPatternEditorFlyoutContentComponent);

View file

@ -15,8 +15,9 @@ import React, {
createContext,
useContext,
} from 'react';
import classnames from 'classnames';
import { EuiFlexItem } from '@elastic/eui';
import { css } from '@emotion/react';
import { EuiFlexItem, type UseEuiTheme } from '@elastic/eui';
import { useMemoCss } from '@kbn/css-utils/public/use_memo_css';
import { useFlyoutPanelsContext } from './flyout_panels';
@ -47,19 +48,12 @@ export const Panel: React.FC<Props & React.HTMLProps<HTMLDivElement>> = ({
border,
...rest
}) => {
const styles = useMemoCss(componentStyles);
const [config, setConfig] = useState<{ hasFooter: boolean; hasContent: boolean }>({
hasContent: false,
hasFooter: false,
});
const classes = classnames('fieldEditor__flyoutPanel', 'eui-scrollBar', className, {
'fieldEditor__flyoutPanel--pageBackground': backgroundColor === 'euiPageBackground',
'fieldEditor__flyoutPanel--emptyShade': backgroundColor === 'euiEmptyShade',
'fieldEditor__flyoutPanel--leftBorder': border === 'left',
'fieldEditor__flyoutPanel--rightBorder': border === 'right',
'fieldEditor__flyoutPanel--withContent': config.hasContent,
});
const { addPanel } = useFlyoutPanelsContext();
const registerContent = useCallback(() => {
@ -91,16 +85,26 @@ export const Panel: React.FC<Props & React.HTMLProps<HTMLDivElement>> = ({
return removePanel;
}, [width, addPanel]);
const styles: CSSProperties = {};
const dynamicStyles: CSSProperties = {};
if (width) {
styles.flexBasis = `${width}%`;
dynamicStyles.flexBasis = `${width}%`;
}
return (
<EuiFlexItem style={styles}>
<EuiFlexItem css={styles.flyoutColumn} style={dynamicStyles}>
<flyoutPanelContext.Provider value={{ registerContent, registerFooter }}>
<div className={classes} {...rest}>
<div
css={[
styles.flyoutPanel,
backgroundColor === 'euiPageBackground' && styles.pageBackground,
backgroundColor === 'euiEmptyShade' && styles.emptyShade,
border === 'left' && styles.leftBorder,
border === 'right' && styles.rightBorder,
config.hasContent && styles.withContent,
]}
{...rest}
>
{children}
</div>
</flyoutPanelContext.Provider>
@ -117,3 +121,38 @@ export const useFlyoutPanelContext = (): Context => {
return ctx;
};
const componentStyles = {
flyoutColumn: css({
height: '100%',
overflow: 'hidden',
}),
flyoutPanel: ({ euiTheme }: UseEuiTheme) =>
css({
height: '100%',
overflowY: 'auto',
padding: euiTheme.size.l,
}),
pageBackground: ({ euiTheme }: UseEuiTheme) =>
css({
backgroundColor: euiTheme.colors.body,
}),
emptyShade: ({ euiTheme }: UseEuiTheme) =>
css({
backgroundColor: euiTheme.colors.emptyShade,
}),
leftBorder: ({ euiTheme }: UseEuiTheme) =>
css({
borderLeft: euiTheme.border.thin,
}),
rightBorder: ({ euiTheme }: UseEuiTheme) =>
css({
borderRight: euiTheme.border.thin,
}),
withContent: css({
padding: 0,
overflowY: 'hidden',
display: 'flex',
flexDirection: 'column',
}),
};

View file

@ -1,42 +0,0 @@
.fieldEditor__flyoutPanels {
height: 100%;
}
.fieldEditor__flyoutPanel {
height: 100%;
overflow-y: auto;
padding: $euiSizeL $euiSizeL 0 $euiSizeL;
&--pageBackground {
background-color: $euiPageBackgroundColor;
}
&--emptyShade {
background-color: $euiColorEmptyShade;
}
&--leftBorder {
border-left: $euiBorderThin;
}
&--rightBorder {
border-right: $euiBorderThin;
}
&--withContent {
padding: 0;
overflow-y: hidden;
display: flex;
flex-direction: column;
}
&__header {
padding: 0 !important;
}
&__content {
flex: 1;
overflow-y: auto;
padding: $euiSizeL;
}
&__footer {
flex: 0;
}
}

View file

@ -17,10 +17,9 @@ import React, {
FC,
PropsWithChildren,
} from 'react';
import { css } from '@emotion/react';
import { EuiFlexGroup, EuiFlexGroupProps } from '@elastic/eui';
import './flyout_panels.scss';
interface Panel {
width?: number;
}
@ -106,7 +105,7 @@ export const Panels: FC<PropsWithChildren<Props>> = ({ maxWidth, flyoutClassName
return (
<flyoutPanelsContext.Provider value={ctx}>
<EuiFlexGroup className="fieldEditor__flyoutPanels" gutterSize="none" {...props} />
<EuiFlexGroup css={styles.flyoutPanels} gutterSize="none" {...props} />
</flyoutPanelsContext.Provider>
);
};
@ -120,3 +119,9 @@ export const useFlyoutPanelsContext = (): Context => {
return ctx;
};
const styles = {
flyoutPanels: css({
height: '100%',
}),
};

View file

@ -8,15 +8,28 @@
*/
import React, { useEffect, FC, PropsWithChildren } from 'react';
import { css } from '@emotion/react';
import type { UseEuiTheme } from '@elastic/eui';
import { useMemoCss } from '@kbn/css-utils/public/use_memo_css';
import { useFlyoutPanelContext } from './flyout_panel';
export const PanelContent: FC<PropsWithChildren<unknown>> = (props) => {
const styles = useMemoCss(componentStyles);
const { registerContent } = useFlyoutPanelContext();
useEffect(() => {
registerContent();
}, [registerContent]);
return <div className="fieldEditor__flyoutPanel__content" {...props} />;
return <div css={styles.content} {...props} />;
};
const componentStyles = {
content: ({ euiTheme }: UseEuiTheme) =>
css({
flex: 1,
overflowY: 'auto',
padding: euiTheme.size.l,
}),
};

View file

@ -8,6 +8,7 @@
*/
import React, { useEffect } from 'react';
import { css } from '@emotion/react';
import { EuiFlyoutFooter, EuiFlyoutFooterProps } from '@elastic/eui';
import { useFlyoutPanelContext } from './flyout_panel';
@ -21,5 +22,11 @@ export const PanelFooter: React.FC<
registerFooter();
}, [registerFooter]);
return <EuiFlyoutFooter className="fieldEditor__flyoutPanel__footer" {...props} />;
return <EuiFlyoutFooter css={styles.footer} {...props} />;
};
const styles = {
footer: css({
flex: 0,
}),
};

View file

@ -8,13 +8,19 @@
*/
import React from 'react';
import { css } from '@emotion/react';
import { EuiSpacer, EuiFlyoutHeader, EuiFlyoutHeaderProps } from '@elastic/eui';
export const PanelHeader: React.FunctionComponent<
{ children: React.ReactNode } & Omit<EuiFlyoutHeaderProps, 'children'>
> = (props) => (
<>
<EuiFlyoutHeader className="fieldEditor__flyoutPanel__header" {...props} />
<EuiFlyoutHeader
css={css`
padding: 0 !important;
`}
{...props}
/>
<EuiSpacer />
</>
);

View file

@ -9,6 +9,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { css } from '@emotion/react';
import {
EuiFlyoutFooter,
@ -16,7 +17,9 @@ import {
EuiFlexItem,
EuiButtonEmpty,
EuiButton,
type UseEuiTheme,
} from '@elastic/eui';
import { useMemoCss } from '@kbn/css-utils/public/use_memo_css';
export enum SubmittingType {
savingAsAdHoc = 'savingAsAdHoc',
@ -67,6 +70,7 @@ export const Footer = ({
isPersisted,
canSave,
}: FooterProps) => {
const styles = useMemoCss(componentStyles);
const isEditingAdHoc = isEdit && !isPersisted;
const submitPersisted = () => {
onSubmit(false);
@ -76,7 +80,7 @@ export const Footer = ({
};
return (
<EuiFlyoutFooter className="indexPatternEditor__footer">
<EuiFlyoutFooter css={styles.footer}>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem grow={false}>
<EuiButtonEmpty
@ -135,3 +139,11 @@ export const Footer = ({
</EuiFlyoutFooter>
);
};
const componentStyles = {
footer: ({ euiTheme }: UseEuiTheme) =>
css({
marginLeft: -euiTheme.size.l,
marginRight: -euiTheme.size.l,
}),
};

View file

@ -21,6 +21,7 @@
"@kbn/react-kibana-mount",
"@kbn/code-editor",
"@kbn/rollup",
"@kbn/css-utils",
],
"exclude": [
"target/**/*",

View file

@ -260,7 +260,7 @@ exports[`UrlFormatEditor should render normally 1`] = `
class="euiFormRow__fieldWrapper"
>
<div
class="euiBasicTable kbnFieldFormatEditor__samples"
class="euiBasicTable kbnFieldFormatEditor__samples css-p1tgq2-samples"
id="generated-id"
>
<table

View file

@ -1,59 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`FormatEditorSamples should render normally 1`] = `
<EuiFormRow
label={
<Memo(MemoizedFormattedMessage)
defaultMessage="Samples"
id="indexPatternFieldEditor.samplesHeader"
/>
}
>
<EuiBasicTable
className="kbnFieldFormatEditor__samples"
columns={
Array [
Object {
"field": "input",
"name": "Input",
"render": [Function],
},
Object {
"field": "output",
"name": "Output",
"render": [Function],
},
]
}
compressed={true}
items={
Array [
Object {
"input": "test",
"output": "TEST",
},
Object {
"input": 123,
"output": "456",
},
Object {
"input": Array [
"foo",
"bar",
],
"output": "<span>foo</span>, <span>bar</span>",
},
]
}
noItemsMessage={
<EuiI18n
default="No items found"
token="euiBasicTable.noItemsMessage"
/>
}
tableLayout="fixed"
/>
</EuiFormRow>
`;
exports[`FormatEditorSamples should render nothing if there are no samples 1`] = `""`;

View file

@ -1,5 +0,0 @@
.kbnFieldFormatEditor__samples {
audio {
max-width: 100%;
}
}

View file

@ -8,28 +8,43 @@
*/
import React from 'react';
import { shallowWithI18nProvider } from '@kbn/test-jest-helpers';
import { render, screen } from '@testing-library/react';
import { I18nProvider } from '@kbn/i18n-react';
import { FormatEditorSamples } from './samples';
describe('FormatEditorSamples', () => {
it('should render normally', async () => {
const component = shallowWithI18nProvider(
<FormatEditorSamples
samples={[
{ input: 'test', output: 'TEST' },
{ input: 123, output: '456' },
{ input: ['foo', 'bar'], output: '<span>foo</span>, <span>bar</span>' },
]}
/>
it('should render normally', () => {
render(
<I18nProvider>
<FormatEditorSamples
samples={[
{ input: 'test', output: 'TEST' },
{ input: 123, output: '456' },
{ input: ['foo', 'bar'], output: '<span>foo</span>, <span>bar</span>' },
]}
/>
</I18nProvider>
);
expect(component).toMatchSnapshot();
expect(screen.getByRole('table')).toBeInTheDocument();
expect(screen.getByText('Input')).toBeInTheDocument();
expect(screen.getByText('Output')).toBeInTheDocument();
expect(screen.getByText('test')).toBeInTheDocument();
expect(screen.getByText('TEST')).toBeInTheDocument();
expect(screen.getByText('123')).toBeInTheDocument();
expect(screen.getByText('456')).toBeInTheDocument();
expect(screen.getByText('["foo","bar"]')).toBeInTheDocument();
});
it('should render nothing if there are no samples', async () => {
const component = shallowWithI18nProvider(<FormatEditorSamples samples={[]} />);
it('should render nothing if there are no samples', () => {
const { container } = render(
<I18nProvider>
<FormatEditorSamples samples={[]} />
</I18nProvider>
);
expect(component).toMatchSnapshot();
expect(container).toBeEmptyDOMElement();
});
});

View file

@ -7,9 +7,8 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import './samples.scss';
import React, { PureComponent } from 'react';
import { css } from '@emotion/react';
import { EuiBasicTable, EuiFormRow } from '@elastic/eui';
@ -69,6 +68,7 @@ export class FormatEditorSamples extends PureComponent<FormatEditorSamplesProps>
>
<EuiBasicTable<Sample>
className="kbnFieldFormatEditor__samples"
css={styles.samples}
compressed={true}
items={samples}
columns={columns}
@ -77,3 +77,11 @@ export class FormatEditorSamples extends PureComponent<FormatEditorSamplesProps>
) : null;
}
}
const styles = {
samples: css`
audio {
max-width: 100%;
}
`,
};

View file

@ -16,8 +16,9 @@ import React, {
useContext,
useMemo,
} from 'react';
import classnames from 'classnames';
import { EuiFlexItem } from '@elastic/eui';
import { css } from '@emotion/react';
import { EuiFlexItem, type UseEuiTheme } from '@elastic/eui';
import { useMemoCss } from '@kbn/css-utils/public/use_memo_css';
import { useFlyoutPanelsContext } from './flyout_panels';
@ -50,21 +51,14 @@ export const Panel: React.FC<Props & React.HTMLProps<HTMLDivElement>> = ({
'data-test-subj': dataTestSubj,
...rest
}) => {
const styles = useMemoCss(componentStyles);
const [dynamicStyles, setDynamicStyles] = useState<CSSProperties>({});
const [config, setConfig] = useState<{ hasFooter: boolean; hasContent: boolean }>({
hasContent: false,
hasFooter: false,
});
const [styles, setStyles] = useState<CSSProperties>({});
const classes = classnames('fieldEditor__flyoutPanel', className, {
'fieldEditor__flyoutPanel--pageBackground': backgroundColor === 'euiPageBackground',
'fieldEditor__flyoutPanel--emptyShade': backgroundColor === 'euiEmptyShade',
'fieldEditor__flyoutPanel--leftBorder': border === 'left',
'fieldEditor__flyoutPanel--rightBorder': border === 'right',
'fieldEditor__flyoutPanel--withContent': config.hasContent,
});
const { addPanel } = useFlyoutPanelsContext();
const registerContent = useCallback(() => {
@ -99,7 +93,7 @@ export const Panel: React.FC<Props & React.HTMLProps<HTMLDivElement>> = ({
const { removePanel, isFixedWidth } = addPanel({ width });
if (width) {
setStyles((prev) => {
setDynamicStyles((prev) => {
if (isFixedWidth) {
return {
...prev,
@ -118,13 +112,23 @@ export const Panel: React.FC<Props & React.HTMLProps<HTMLDivElement>> = ({
return (
<EuiFlexItem
className="fieldEditor__flyoutPanels__column"
style={styles}
css={styles.flyoutColumn}
style={dynamicStyles}
grow={false}
data-test-subj={dataTestSubj}
>
<flyoutPanelContext.Provider value={ctx}>
<div className={classes} {...rest}>
<div
css={[
styles.flyoutPanel,
backgroundColor === 'euiPageBackground' && styles.pageBackground,
backgroundColor === 'euiEmptyShade' && styles.emptyShade,
border === 'left' && styles.leftBorder,
border === 'right' && styles.rightBorder,
config.hasContent && styles.withContent,
]}
{...rest}
>
{children}
</div>
</flyoutPanelContext.Provider>
@ -141,3 +145,38 @@ export const useFlyoutPanelContext = (): Context => {
return ctx;
};
const componentStyles = {
flyoutColumn: css({
height: '100%',
overflow: 'hidden',
}),
flyoutPanel: ({ euiTheme }: UseEuiTheme) =>
css({
height: '100%',
overflowY: 'auto',
padding: euiTheme.size.l,
}),
pageBackground: ({ euiTheme }: UseEuiTheme) =>
css({
backgroundColor: euiTheme.colors.body,
}),
emptyShade: ({ euiTheme }: UseEuiTheme) =>
css({
backgroundColor: euiTheme.colors.emptyShade,
}),
leftBorder: ({ euiTheme }: UseEuiTheme) =>
css({
borderLeft: euiTheme.border.thin,
}),
rightBorder: ({ euiTheme }: UseEuiTheme) =>
css({
borderRight: euiTheme.border.thin,
}),
withContent: css({
padding: 0,
overflowY: 'hidden',
display: 'flex',
flexDirection: 'column',
}),
};

View file

@ -1,49 +0,0 @@
.fieldEditor__flyoutPanels {
height: 100%;
&__column {
height: 100%;
overflow: hidden;
}
}
.fieldEditor__flyoutPanel {
height: 100%;
overflow-y: auto;
padding: $euiSizeL;
&--pageBackground {
background-color: $euiPageBackgroundColor;
}
&--emptyShade {
background-color: $euiColorEmptyShade;
}
&--leftBorder {
border-left: $euiBorderThin;
}
&--rightBorder {
border-right: $euiBorderThin;
}
&--withContent {
padding: 0;
overflow-y: hidden;
display: flex;
flex-direction: column;
}
&__header {
padding: 0 !important;
@include euiTextBreakWord;
}
&__content {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: $euiSizeL;
}
&__footer {
flex: 0;
}
}

View file

@ -15,10 +15,9 @@ import React, {
useMemo,
useLayoutEffect,
} from 'react';
import { css } from '@emotion/react';
import { EuiFlexGroup, EuiFlexGroupProps, EuiFlyoutProps } from '@elastic/eui';
import './flyout_panels.scss';
interface Panel {
width?: number;
}
@ -138,7 +137,7 @@ export const Panels: React.FC<Props> = ({
return (
<flyoutPanelsContext.Provider value={ctx}>
<EuiFlexGroup className="fieldEditor__flyoutPanels" gutterSize="none" {...props} />
<EuiFlexGroup css={styles.flyoutPanels} gutterSize="none" {...props} />
</flyoutPanelsContext.Provider>
);
};
@ -152,3 +151,9 @@ export const useFlyoutPanelsContext = (): Context => {
return ctx;
};
const styles = {
flyoutPanels: css({
height: '100%',
}),
};

View file

@ -8,10 +8,14 @@
*/
import React, { useEffect } from 'react';
import { css } from '@emotion/react';
import type { UseEuiTheme } from '@elastic/eui';
import { useMemoCss } from '@kbn/css-utils/public/use_memo_css';
import { useFlyoutPanelContext } from './flyout_panel';
export const PanelContent: React.FC<{ children: React.ReactNode }> = (props) => {
const styles = useMemoCss(componentStyles);
const { registerContent } = useFlyoutPanelContext();
useEffect(() => {
@ -19,7 +23,15 @@ export const PanelContent: React.FC<{ children: React.ReactNode }> = (props) =>
}, [registerContent]);
// Adding a tabIndex prop to the div as it is the body of the flyout which is scrollable.
return (
<div className="fieldEditor__flyoutPanel__content eui-scrollBar" tabIndex={0} {...props} />
);
return <div css={styles.content} className="eui-scrollBar" tabIndex={0} {...props} />;
};
const componentStyles = {
content: ({ euiTheme }: UseEuiTheme) =>
css({
flex: 1,
overflowY: 'auto',
overflowX: 'hidden',
padding: euiTheme.size.l,
}),
};

View file

@ -8,6 +8,7 @@
*/
import React, { useEffect } from 'react';
import { css } from '@emotion/react';
import { EuiFlyoutFooter, EuiFlyoutFooterProps } from '@elastic/eui';
import { useFlyoutPanelContext } from './flyout_panel';
@ -21,5 +22,11 @@ export const PanelFooter: React.FC<
registerFooter();
}, [registerFooter]);
return <EuiFlyoutFooter className="fieldEditor__flyoutPanel__footer" {...props} />;
return <EuiFlyoutFooter css={styles.footer} {...props} />;
};
const styles = {
footer: css({
flex: 0,
}),
};

View file

@ -8,13 +8,20 @@
*/
import React from 'react';
import { EuiSpacer, EuiFlyoutHeader, EuiFlyoutHeaderProps } from '@elastic/eui';
import { css } from '@emotion/react';
import { EuiSpacer, EuiFlyoutHeader, EuiFlyoutHeaderProps, euiTextBreakWord } from '@elastic/eui';
export const PanelHeader: React.FunctionComponent<
{ children: React.ReactNode } & Omit<EuiFlyoutHeaderProps, 'children'>
> = (props) => (
<>
<EuiFlyoutHeader className="fieldEditor__flyoutPanel__header" {...props} />
<EuiFlyoutHeader
css={css`
padding: 0 !important;
${euiTextBreakWord()}
`}
{...props}
/>
<EuiSpacer />
</>
);

View file

@ -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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
export const ITEM_HEIGHT = 40;
export const SHOW_MORE_HEIGHT = 40;
export const INITIAL_MAX_NUMBER_OF_FIELDS = 7;

View file

@ -1,52 +0,0 @@
/**
[1] This corresponds to the ITEM_HEIGHT declared in "field_list.tsx"
[2] This corresponds to the SHOW_MORE_HEIGHT declared in "field_list.tsx"
[3] We need the tooltip <span /> to be 100% to display the text ellipsis of the field value
*/
$previewFieldItemHeight: 40px; /* [1] */
$previewShowMoreHeight: 40px; /* [2] */
.indexPatternFieldEditor__previewFieldList {
position: relative;
&--ligthWeight {
font-weight: 400;
}
&__item {
border-bottom: $euiBorderThin;
height: $previewFieldItemHeight;
align-items: center;
overflow: hidden;
&__key, &__value {
overflow: hidden;
}
&__actions {
flex-basis: 24px !important;
}
&__value .euiToolTipAnchor {
width: 100%; /* [3] */
}
&__key__wrapper, &__value__wrapper {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
width: 100%;
}
}
&__showMore {
position: absolute;
width: 100%;
height: $previewShowMoreHeight;
bottom: $previewShowMoreHeight * -1;
display: flex;
align-items: flex-end;
}
}

View file

@ -10,8 +10,17 @@
import React, { useState, useMemo, useCallback, useRef } from 'react';
import { FixedSizeList as VirtualList, areEqual } from 'react-window';
import { i18n } from '@kbn/i18n';
import { css } from '@emotion/react';
import { get, isEqual } from 'lodash';
import { EuiButtonEmpty, EuiButton, EuiSpacer, EuiEmptyPrompt, EuiTextColor } from '@elastic/eui';
import {
EuiButtonEmpty,
EuiButton,
EuiSpacer,
EuiEmptyPrompt,
EuiTextColor,
type UseEuiTheme,
} from '@elastic/eui';
import { useMemoCss } from '@kbn/css-utils/public/use_memo_css';
import { useFieldEditorContext } from '../../field_editor_context';
import { useFieldPreviewContext } from '../field_preview_context';
@ -19,12 +28,8 @@ import type { FieldPreview, PreviewState } from '../types';
import { PreviewListItem } from './field_list_item';
import type { PreviewListItemProps } from './field_list_item';
import { useStateSelector } from '../../../state_utils';
import './field_list.scss';
import { getPositionAfterToggling } from './get_item_position';
const ITEM_HEIGHT = 40;
const SHOW_MORE_HEIGHT = 40;
const INITIAL_MAX_NUMBER_OF_FIELDS = 7;
import { ITEM_HEIGHT, SHOW_MORE_HEIGHT, INITIAL_MAX_NUMBER_OF_FIELDS } from './constants';
export type DocumentField = FieldPreview & {
isPinned?: boolean;
@ -73,6 +78,7 @@ const Row = React.memo<RowProps>(({ data, index, style }) => {
}, areEqual);
export const PreviewFieldList: React.FC<Props> = ({ height, clearSearch, searchValue = '' }) => {
const styles = useMemoCss(componentStyles);
const { dataView } = useFieldEditorContext();
const { controller } = useFieldPreviewContext();
const virtualListRef = useRef<VirtualList>(null);
@ -160,7 +166,7 @@ export const PreviewFieldList: React.FC<Props> = ({ height, clearSearch, searchV
iconType="search"
title={
<EuiTextColor color="subdued">
<h3 className="indexPatternFieldEditor__previewEmptySearchResult__title">
<h3 css={styles.emptySearchResult}>
{i18n.translate(
'indexPatternFieldEditor.fieldPreview.searchResult.emptyPromptTitle',
{
@ -189,7 +195,7 @@ export const PreviewFieldList: React.FC<Props> = ({ height, clearSearch, searchV
const renderToggleFieldsButton = () =>
totalFields <= INITIAL_MAX_NUMBER_OF_FIELDS ? null : (
<div className="indexPatternFieldEditor__previewFieldList__showMore">
<div css={styles.showMore}>
<EuiButtonEmpty onClick={toggleShowAllFields} flush="left">
{showAllFields
? i18n.translate('indexPatternFieldEditor.fieldPreview.showLessFieldsButtonLabel', {
@ -234,7 +240,7 @@ export const PreviewFieldList: React.FC<Props> = ({ height, clearSearch, searchV
}
return (
<div className="indexPatternFieldEditor__previewFieldList">
<div className="indexPatternFieldEditor__previewFieldList" css={styles.previewFieldList}>
{isEmptySearchResultVisible ? (
renderEmptyResult()
) : (
@ -256,3 +262,21 @@ export const PreviewFieldList: React.FC<Props> = ({ height, clearSearch, searchV
</div>
);
};
const componentStyles = {
emptySearchResult: ({ euiTheme }: UseEuiTheme) =>
css({
fontWeight: euiTheme.font.weight.medium,
}),
previewFieldList: css({
position: 'relative',
}),
showMore: css({
position: 'absolute',
width: '100%',
height: SHOW_MORE_HEIGHT,
bottom: -SHOW_MORE_HEIGHT,
display: 'flex',
alignItems: 'flex-end',
}),
};

View file

@ -8,7 +8,6 @@
*/
import React, { useState } from 'react';
import classnames from 'classnames';
import { i18n } from '@kbn/i18n';
import {
EuiFlexGroup,
@ -18,9 +17,10 @@ import {
EuiButtonEmpty,
EuiBadge,
EuiTextColor,
useEuiTheme,
type UseEuiTheme,
} from '@elastic/eui';
import { css } from '@emotion/react';
import { useMemoCss } from '@kbn/css-utils/public/use_memo_css';
import { useFieldPreviewContext } from '../field_preview_context';
import { IsUpdatingIndicator } from '../is_updating_indicator';
@ -28,6 +28,7 @@ import { ImagePreviewModal } from '../image_preview_modal';
import type { DocumentField } from './field_list';
import { PreviewState } from '../types';
import { useStateSelector } from '../../../state_utils';
import { ITEM_HEIGHT } from './constants';
export interface PreviewListItemProps {
field: DocumentField;
@ -48,9 +49,10 @@ export const PreviewListItem: React.FC<PreviewListItemProps> = ({
hasScriptError,
isFromScript = false,
}) => {
const styles = useMemoCss(componentStyles);
const pinButtonId = `fieldPreview.pinFieldButtonLabel.${key}`;
const { euiTheme } = useEuiTheme();
const { controller } = useFieldPreviewContext();
const isLoadingPreview = useStateSelector(controller.state$, isLoadingPreviewSelector);
@ -61,17 +63,12 @@ export const PreviewListItem: React.FC<PreviewListItemProps> = ({
const showPinIcon = isPinHovered || isPinFocused || isPinned;
const classes = classnames('indexPatternFieldEditor__previewFieldList__item', {
'indexPatternFieldEditor__previewFieldList__item--highlighted': isFromScript,
'indexPatternFieldEditor__previewFieldList__item--pinned': isPinned,
});
const doesContainImage = formattedValue?.includes('<img');
const renderName = () => {
if (isFromScript && !Boolean(key)) {
return (
<span className="indexPatternFieldEditor__previewFieldList--ligthWeight">
<span css={styles.lightWeight}>
<EuiTextColor color="subdued">
{i18n.translate('indexPatternFieldEditor.fieldPreview.fieldNameNotSetLabel', {
defaultMessage: 'Field name not set',
@ -93,7 +90,7 @@ export const PreviewListItem: React.FC<PreviewListItemProps> = ({
const renderValue = () => {
if (isFromScript && isLoadingPreview) {
return (
<span className="indexPatternFieldEditor__previewFieldList--ligthWeight">
<span css={styles.lightWeight}>
<IsUpdatingIndicator />
</span>
);
@ -113,7 +110,7 @@ export const PreviewListItem: React.FC<PreviewListItemProps> = ({
if (isFromScript && value === undefined) {
return (
<span className="indexPatternFieldEditor__previewFieldList--ligthWeight">
<span css={styles.lightWeight}>
<EuiTextColor color="subdued">
{i18n.translate('indexPatternFieldEditor.fieldPreview.valueNotSetLabel', {
defaultMessage: 'Value not set',
@ -140,57 +137,35 @@ export const PreviewListItem: React.FC<PreviewListItemProps> = ({
if (formattedValue !== undefined) {
return withTooltip(
<span
className="indexPatternFieldEditor__previewFieldList__item__value__wrapper"
css={styles.keyAndValueWrapper}
// We can dangerously set HTML here because this content is guaranteed to have been run through a valid field formatter first.
dangerouslySetInnerHTML={{ __html: formattedValue! }} // eslint-disable-line react/no-danger
/>
);
}
return withTooltip(
<span className="indexPatternFieldEditor__previewFieldList__item__value__wrapper">
{JSON.stringify(value)}
</span>
);
return withTooltip(<span css={styles.keyAndValueWrapper}>{JSON.stringify(value)}</span>);
};
return (
<>
<EuiFlexGroup
className={classes}
// highlights the field using token, TODO: migrate whole SCSS file to emotions
css={
isFromScript
? css`
background-color: ${euiTheme.colors.backgroundBasePrimary};
font-weight: ${euiTheme.font.weight.bold};
`
: undefined
}
css={[styles.listItem, isFromScript ? styles.highlightedRow : undefined]}
gutterSize="none"
data-test-subj="listItem"
onMouseEnter={() => setIsPinHovered(true)}
onMouseLeave={() => setIsPinHovered(false)}
>
<EuiFlexItem className="indexPatternFieldEditor__previewFieldList__item__key">
<div
className="indexPatternFieldEditor__previewFieldList__item__key__wrapper"
data-test-subj="key"
>
<EuiFlexItem css={styles.keyAndValue}>
<div css={styles.keyAndValueWrapper} data-test-subj="key">
{renderName()}
</div>
</EuiFlexItem>
<EuiFlexItem
className="indexPatternFieldEditor__previewFieldList__item__value"
data-test-subj="value"
>
<EuiFlexItem css={styles.keyAndValue} data-test-subj="value">
{renderValue()}
</EuiFlexItem>
<EuiFlexItem
className="indexPatternFieldEditor__previewFieldList__item__actions"
grow={false}
>
<EuiFlexItem css={styles.actions} grow={false}>
{toggleIsPinned && (
<EuiButtonIcon
onClick={(e: { detail: number }) => {
@ -222,3 +197,40 @@ export const PreviewListItem: React.FC<PreviewListItemProps> = ({
</>
);
};
const componentStyles = {
listItem: ({ euiTheme }: UseEuiTheme) =>
css({
borderBottom: euiTheme.border.thin,
height: ITEM_HEIGHT,
alignItems: 'center',
overflow: 'hidden',
}),
highlightedRow: ({ euiTheme }: UseEuiTheme) =>
css({
backgroundColor: euiTheme.colors.backgroundBasePrimary,
fontWeight: euiTheme.font.weight.bold,
}),
lightWeight: ({ euiTheme }: UseEuiTheme) =>
css({
fontWeight: euiTheme.font.weight.regular,
}),
keyAndValueWrapper: css({
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
display: 'block',
width: '100%',
}),
keyAndValue: css({
overflow: 'hidden',
'& .euiToolTipAnchor': {
width: '100%', // We need the tooltip <span /> to be 100% to display the text ellipsis of the field value
},
}),
actions: ({ euiTheme }: UseEuiTheme) =>
css({
flexBasis: `${euiTheme.size.l} !important`,
}),
};

View file

@ -1,19 +0,0 @@
.indexPatternFieldEditor {
&__previewPannel {
display: flex;
flex-direction: column;
height: 100%;
}
&__previewImageModal__wrapper {
padding: $euiSize;
img {
max-width: 100%;
}
}
&__previewEmptySearchResult__title {
font-weight: 400;
}
}

View file

@ -8,6 +8,7 @@
*/
import React, { useState, useCallback } from 'react';
import { css } from '@emotion/react';
import { i18n } from '@kbn/i18n';
import { EuiSpacer, EuiResizeObserver, EuiFieldSearch, EuiCallOut } from '@elastic/eui';
@ -21,8 +22,6 @@ import { PreviewFieldList } from './field_list/field_list';
import { useStateSelector } from '../../state_utils';
import { PreviewState } from './types';
import './field_preview.scss';
const previewResponseSelector = (state: PreviewState) => state.previewResponse;
const fetchDocErrorSelector = (state: PreviewState) => state.fetchDocError;
const isLoadingPreviewSelector = (state: PreviewState) => state.isLoadingPreview;
@ -80,7 +79,7 @@ export const FieldPreview = () => {
return (
<div
className="indexPatternFieldEditor__previewPannel"
css={styles.previewPanel}
// This tabIndex is for the scrollable area of the flyout panel.
tabIndex={0}
>
@ -168,3 +167,11 @@ export const FieldPreview = () => {
</div>
);
};
const styles = {
previewPanel: css({
display: 'flex',
flexDirection: 'column',
height: '100%',
}),
};

View file

@ -8,7 +8,9 @@
*/
import React from 'react';
import { EuiModal, EuiModalBody } from '@elastic/eui';
import { css } from '@emotion/react';
import { EuiModal, EuiModalBody, type UseEuiTheme } from '@elastic/eui';
import { useMemoCss } from '@kbn/css-utils/public/use_memo_css';
/**
* By default the image formatter sets the max-width to "none" on the <img /> tag
@ -30,11 +32,13 @@ interface Props {
}
export const ImagePreviewModal = ({ imgHTML, closeModal }: Props) => {
const styles = useMemoCss(componentStyles);
return (
<EuiModal onClose={closeModal}>
<EuiModalBody>
<div
className="indexPatternFieldEditor__previewImageModal__wrapper"
css={styles.previewImageModal}
// We can dangerously set HTML here because this content is guaranteed to have been run through a valid field formatter first.
dangerouslySetInnerHTML={{ __html: setMaxWidthImage(imgHTML) }} // eslint-disable-line react/no-danger
/>
@ -42,3 +46,14 @@ export const ImagePreviewModal = ({ imgHTML, closeModal }: Props) => {
</EuiModal>
);
};
const componentStyles = {
previewImageModal: ({ euiTheme }: UseEuiTheme) =>
css({
padding: euiTheme.size.base,
'& img': {
maxWidth: '100%',
},
}),
};

View file

@ -1,14 +1,14 @@
{
"extends": "../../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"outDir": "target/types"
},
"include": [
"../../../../../typings/**/*",
"__jest__/**/*",
"common/**/*",
"public/**/*",
"server/**/*",
"server/**/*"
],
"kbn_references": [
"@kbn/core",
@ -28,8 +28,7 @@
"@kbn/config-schema",
"@kbn/react-kibana-mount",
"@kbn/code-editor",
"@kbn/css-utils"
],
"exclude": [
"target/**/*",
]
"exclude": ["target/**/*"]
}

View file

@ -1,11 +0,0 @@
%inp-empty-state-footer {
background: $euiColorLightestShade;
margin: 0 (-$euiSizeL) (-$euiSizeL);
padding: $euiSizeL;
border-radius: 0 0 $euiBorderRadius $euiBorderRadius;
// sass-lint:disable-block mixins-before-declarations
@include euiBreakpoint('xs', 's') {
text-align: center;
}
}

View file

@ -1 +0,0 @@
$inpEmptyStateMaxWidth: $euiSizeXXL * 19;

View file

@ -1,201 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EmptyIndexListPrompt should render normally 1`] = `
<EuiPanel
color="subdued"
css="unknown styles"
data-test-subj="indexPatternEmptyState"
hasShadow={false}
paddingSize="xl"
>
<EuiPageHeader>
<EuiTitle>
<h2>
<MemoizedFormattedMessage
defaultMessage="Ready to try Kibana? First, you need data."
id="indexPatternManagement.createDataView.emptyState.noDataTitle"
/>
</h2>
</EuiTitle>
</EuiPageHeader>
<EuiSpacer
size="xl"
/>
<div>
<EuiFlexGrid
className="inpEmptyState__cardGrid"
columns={3}
responsive={true}
>
<EuiFlexItem>
<EuiCard
className="inpEmptyState__card"
description={
<Memo(MemoizedFormattedMessage)
defaultMessage="Add data from a variety of sources."
id="indexPatternManagement.createDataView.emptyState.integrationCardDescription"
/>
}
icon={
<EuiIcon
color="subdued"
size="xl"
type="database"
/>
}
onClick={[Function]}
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Add integration"
id="indexPatternManagement.createDataView.emptyState.integrationCardTitle"
/>
}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiCard
className="inpEmptyState__card"
description={
<Memo(MemoizedFormattedMessage)
defaultMessage="Import a CSV, NDJSON, or log file."
id="indexPatternManagement.createDataView.emptyState.uploadCardDescription"
/>
}
icon={
<EuiIcon
color="subdued"
size="xl"
type="document"
/>
}
onClick={[Function]}
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Upload a file"
id="indexPatternManagement.createDataView.emptyState.uploadCardTitle"
/>
}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiCard
className="inpEmptyState__card"
description={
<Memo(MemoizedFormattedMessage)
defaultMessage="Load a data set and a Kibana dashboard."
id="indexPatternManagement.createDataView.emptyState.sampleDataCardDescription"
/>
}
icon={
<EuiIcon
color="subdued"
size="xl"
type="heatmap"
/>
}
onClick={[Function]}
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Add sample data"
id="indexPatternManagement.createDataView.emptyState.sampleDataCardTitle"
/>
}
/>
</EuiFlexItem>
</EuiFlexGrid>
<EuiSpacer
size="xxl"
/>
<div
className="inpEmptyState__footer"
>
<EuiFlexGroup
justifyContent="center"
>
<EuiFlexItem
className="inpEmptyState__footerFlexItem"
grow={false}
>
<EuiDescriptionList
listItems={
Array [
Object {
"description": <EuiLink
external={true}
href="http://elastic.co"
target="_blank"
>
<Memo(MemoizedFormattedMessage)
defaultMessage="Read documentation"
id="indexPatternManagement.createDataView.emptyState.readDocs"
/>
</EuiLink>,
"title": <Memo(MemoizedFormattedMessage)
defaultMessage="Want to learn more?"
id="indexPatternManagement.createDataView.emptyState.learnMore"
/>,
},
]
}
/>
</EuiFlexItem>
<EuiFlexItem
className="inpEmptyState__footerFlexItem"
grow={false}
>
<EuiDescriptionList
listItems={
Array [
Object {
"description": <EuiLink
data-test-subj="refreshIndicesButton"
onClick={[Function]}
>
<Memo(MemoizedFormattedMessage)
defaultMessage="Check for new data"
id="indexPatternManagement.createDataView.emptyState.checkDataButton"
/>
<EuiIcon
size="s"
type="refresh"
/>
</EuiLink>,
"title": <Memo(MemoizedFormattedMessage)
defaultMessage="Think you already have data?"
id="indexPatternManagement.createDataView.emptyState.haveData"
/>,
},
]
}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer />
<EuiText
color="subdued"
size="xs"
textAlign="center"
>
<MemoizedFormattedMessage
defaultMessage="You can also {link}"
id="indexPatternManagement.createDataView.emptyState.createAnywayTxt"
values={
Object {
"link": <EuiLink
data-test-subj="createAnyway"
onClick={[Function]}
>
<Memo(MemoizedFormattedMessage)
defaultMessage="create a data view against hidden, system or default indices."
id="indexPatternManagement.createDataView.emptyState.createAnywayLink"
/>
</EuiLink>,
}
}
/>
</EuiText>
</div>
</div>
</EuiPanel>
`;

View file

@ -1,23 +0,0 @@
@import '../../variables';
@import '../../templates';
.inpEmptyState {
// override EUI specificity
max-width: $inpEmptyStateMaxWidth !important; // sass-lint:disable-line no-important
}
.inpEmptyState__cardGrid {
justify-content: center;
}
.inpEmptyState__card {
min-width: $euiSizeXL * 6;
}
.inpEmptyState__footer {
@extend %inp-empty-state-footer;
}
.inpEmptyState__footerFlexItem {
min-width: $euiSizeXL * 7;
}

View file

@ -9,51 +9,48 @@
import React from 'react';
import { EmptyIndexListPrompt } from './empty_index_list_prompt';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import { findTestSubject } from '@elastic/eui/lib/test';
import { mountWithIntl } from '@kbn/test-jest-helpers';
jest.mock('react-router-dom', () => ({
useHistory: () => ({
createHref: jest.fn(),
}),
}));
import { render, screen } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import { I18nProvider } from '@kbn/i18n-react';
describe('EmptyIndexListPrompt', () => {
it('should render normally', () => {
const component = shallow(
<EmptyIndexListPrompt
onRefresh={() => {}}
createAnyway={() => {}}
addDataUrl={'http://elastic.co'}
navigateToApp={async (appId) => {}}
canSaveIndexPattern={true}
/>
it('should render normally', async () => {
render(
<I18nProvider>
<EmptyIndexListPrompt
onRefresh={jest.fn()}
createAnyway={jest.fn()}
addDataUrl={'http://elastic.co'}
navigateToApp={jest.fn()}
canSaveIndexPattern
/>
</I18nProvider>
);
expect(component).toMatchSnapshot();
const emptyStatePanel = screen.getByTestId('indexPatternEmptyState');
expect(emptyStatePanel).toBeInTheDocument();
const refreshButton = screen.getByTestId('refreshIndicesButton');
expect(refreshButton).toBeInTheDocument();
});
describe('props', () => {
describe('onRefresh', () => {
it('is called when refresh button is clicked', () => {
const onRefreshHandler = sinon.stub();
it('calls onRefresh when refresh button is clicked', async () => {
const onRefresh = jest.fn();
render(
<I18nProvider>
<EmptyIndexListPrompt
onRefresh={onRefresh}
createAnyway={jest.fn()}
addDataUrl={'http://elastic.co'}
navigateToApp={jest.fn()}
canSaveIndexPattern
/>
</I18nProvider>
);
const component = mountWithIntl(
<EmptyIndexListPrompt
onRefresh={onRefreshHandler}
createAnyway={() => {}}
addDataUrl={'http://elastic.co'}
navigateToApp={async (appId) => {}}
canSaveIndexPattern={true}
/>
);
const refreshButton = screen.getByTestId('refreshIndicesButton');
await userEvent.click(refreshButton);
findTestSubject(component, 'refreshIndicesButton').simulate('click');
sinon.assert.calledOnce(onRefreshHandler);
});
});
expect(onRefresh).toHaveBeenCalledTimes(1);
});
});

View file

@ -7,7 +7,6 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import './empty_index_list_prompt.scss';
import React from 'react';
import { css } from '@emotion/react';
import { FormattedMessage } from '@kbn/i18n-react';
@ -24,8 +23,10 @@ import {
EuiLink,
EuiText,
EuiFlexGroup,
type UseEuiTheme,
} from '@elastic/eui';
import { ApplicationStart } from '@kbn/core/public';
import { useMemoCss } from '@kbn/css-utils/public/use_memo_css';
export const EmptyIndexListPrompt = ({
onRefresh,
@ -40,6 +41,8 @@ export const EmptyIndexListPrompt = ({
addDataUrl: string;
navigateToApp: ApplicationStart['navigateToApp'];
}) => {
const styles = useMemoCss(componentStyles);
const createAnywayLink = (
<EuiText color="subdued" textAlign="center" size="xs">
<FormattedMessage
@ -61,14 +64,11 @@ export const EmptyIndexListPrompt = ({
return (
<EuiPanel
className="inpEmptyState"
data-test-subj="indexPatternEmptyState"
color="subdued"
hasShadow={false}
paddingSize="xl"
css={css`
margin: auto;
`}
css={styles.wrapper}
>
<EuiPageHeader>
<EuiTitle>
@ -82,10 +82,10 @@ export const EmptyIndexListPrompt = ({
</EuiPageHeader>
<EuiSpacer size="xl" />
<div>
<EuiFlexGrid className="inpEmptyState__cardGrid" columns={3} responsive={true}>
<EuiFlexGrid css={styles.cardGrid} columns={3} responsive={true}>
<EuiFlexItem>
<EuiCard
className="inpEmptyState__card"
css={styles.card}
onClick={() => {
navigateToApp('integrations', { path: '/browse' });
}}
@ -107,7 +107,7 @@ export const EmptyIndexListPrompt = ({
<EuiFlexItem>
<EuiCard
onClick={() => navigateToApp('home', { path: '#/tutorial_directory/fileDataViz' })}
className="inpEmptyState__card"
css={styles.card}
icon={<EuiIcon size="xl" type="document" color="subdued" />}
title={
<FormattedMessage
@ -125,7 +125,7 @@ export const EmptyIndexListPrompt = ({
</EuiFlexItem>
<EuiFlexItem>
<EuiCard
className="inpEmptyState__card"
css={styles.card}
onClick={() => {
navigateToApp('home', { path: '#/tutorial_directory/sampleData' });
}}
@ -146,9 +146,9 @@ export const EmptyIndexListPrompt = ({
</EuiFlexItem>
</EuiFlexGrid>
<EuiSpacer size="xxl" />
<div className="inpEmptyState__footer">
<div css={styles.footer}>
<EuiFlexGroup justifyContent="center">
<EuiFlexItem grow={false} className="inpEmptyState__footerFlexItem">
<EuiFlexItem grow={false} css={styles.footerItem}>
<EuiDescriptionList
listItems={[
{
@ -170,7 +170,7 @@ export const EmptyIndexListPrompt = ({
]}
/>
</EuiFlexItem>
<EuiFlexItem grow={false} className="inpEmptyState__footerFlexItem">
<EuiFlexItem grow={false} css={styles.footerItem}>
<EuiDescriptionList
listItems={[
{
@ -201,3 +201,29 @@ export const EmptyIndexListPrompt = ({
</EuiPanel>
);
};
const componentStyles = {
wrapper: ({ euiTheme }: UseEuiTheme) =>
css({
maxWidth: `calc(${euiTheme.size.xxl} * 19)`,
margin: 'auto',
}),
cardGrid: css({
justifyContent: 'center',
}),
card: ({ euiTheme }: UseEuiTheme) =>
css({
minWidth: `calc(${euiTheme.size.xl} * 6)`,
}),
footer: ({ euiTheme }: UseEuiTheme) =>
css({
backgroundColor: euiTheme.colors.lightestShade,
margin: `0 -${euiTheme.size.l} -${euiTheme.size.l}`,
padding: euiTheme.size.l,
borderRadius: `0 0 ${euiTheme.border.radius.small} ${euiTheme.border.radius.small}`,
}),
footerItem: ({ euiTheme }: UseEuiTheme) =>
css({
minWidth: `calc(${euiTheme.size.xl} * 7)`,
}),
};

View file

@ -47,6 +47,7 @@
"@kbn/rollup",
"@kbn/share-plugin",
"@kbn/shared-ux-table-persist",
"@kbn/css-utils",
],
"exclude": [
"target/**/*",