mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Cases] add tooltip component to kbn-cases-components package (#148561)
## Summary This PR adds a tooltip component (High OrderComponent) to @kbn/cases-components package. #146864 **Details of tooltip**  **status: Open, tooltip position : Top**  **status: In-progress, tooltip position: bottom, long title and description**  ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### How to Test: - run `yarn storybook cases` and test on http://localhost:9001/ Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
066ee1c9e8
commit
ed32d89848
17 changed files with 618 additions and 0 deletions
|
@ -14,6 +14,7 @@ import path from 'path';
|
|||
const STORYBOOKS = [
|
||||
'apm',
|
||||
'canvas',
|
||||
'cases',
|
||||
'ci_composite',
|
||||
'cloud_chat',
|
||||
'coloring',
|
||||
|
|
9
packages/kbn-cases-components/.storybook/main.js
Normal file
9
packages/kbn-cases-components/.storybook/main.js
Normal file
|
@ -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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = require('@kbn/storybook').defaultConfig;
|
|
@ -13,3 +13,34 @@ import { Status, CaseStatuses } from '@kbn/cases-components';
|
|||
|
||||
<Status status={CaseStatuses.open} />
|
||||
```
|
||||
|
||||
### Tooltip
|
||||
|
||||
The component renders the tooltip with case details on hover of an Element. Usage:
|
||||
|
||||
```
|
||||
import { Tooltip, CaseStatuses } from '@kbn/cases-components';
|
||||
import type { CaseTooltipContentProps, CaseTooltipProps } from '@kbn/cases-components';
|
||||
|
||||
const tooltipContent: CaseTooltipContentProps = {
|
||||
title: 'Case title',
|
||||
description: 'Case description',
|
||||
createdAt: '2020-02-19T23:06:33.798Z',
|
||||
createdBy: {
|
||||
fullName: 'Elastic User',
|
||||
username: 'elastic',
|
||||
},
|
||||
totalComments: 1,
|
||||
status: CaseStatuses.open,
|
||||
}
|
||||
|
||||
const tooltipProps: CaseTooltipProps = {
|
||||
loading: false,
|
||||
content: tooltipContent,
|
||||
className: 'customClass',
|
||||
};
|
||||
|
||||
<Tooltip {...tooltipProps}>
|
||||
<span>This is a demo span</span>
|
||||
</Tooltip>
|
||||
```
|
||||
|
|
|
@ -9,3 +9,5 @@
|
|||
export { Status } from './src/status/status';
|
||||
export { CaseStatuses } from './src/status/types';
|
||||
export { getStatusConfiguration } from './src/status/config';
|
||||
export { Tooltip } from './src/tooltip/tooltip';
|
||||
export type { CaseTooltipProps, CaseTooltipContentProps } from './src/tooltip/types';
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
|
||||
import { CaseStatuses } from '../status/types';
|
||||
import { Tooltip } from '../tooltip/tooltip';
|
||||
import type { CaseTooltipProps, CaseTooltipContentProps } from '../tooltip/types';
|
||||
|
||||
const sampleText = 'This is a test span element!!';
|
||||
const TestSpan = () => (
|
||||
<a href="https://www.elastic.co/">
|
||||
<span data-test-subj="sample-span">{sampleText}</span>
|
||||
</a>
|
||||
);
|
||||
|
||||
const tooltipContent: CaseTooltipContentProps = {
|
||||
title: 'Unusual process identified',
|
||||
description: 'There was an unusual process while adding alerts to existing case.',
|
||||
createdAt: '2020-02-19T23:06:33.798Z',
|
||||
createdBy: {
|
||||
fullName: 'Elastic User',
|
||||
username: 'elastic',
|
||||
},
|
||||
totalComments: 10,
|
||||
status: CaseStatuses.open,
|
||||
};
|
||||
|
||||
const tooltipProps: CaseTooltipProps = {
|
||||
children: TestSpan,
|
||||
loading: false,
|
||||
content: tooltipContent,
|
||||
};
|
||||
|
||||
const longTitle = `Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
||||
Lorem Ipsum has been the industry standard dummy text ever since the 1500s!! Lorem!!!`;
|
||||
|
||||
const longDescription = `Lorem Ipsum is simply dummy text of the printing and typesetting industry.
|
||||
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer
|
||||
took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries,
|
||||
but also the leap into electronic typesetting, remaining essentially unchanged.`;
|
||||
|
||||
const Template = (args: CaseTooltipProps) => (
|
||||
<I18nProvider>
|
||||
<Tooltip {...args}>
|
||||
<TestSpan />
|
||||
</Tooltip>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
export default {
|
||||
title: 'CaseTooltip',
|
||||
component: Template,
|
||||
} as ComponentMeta<typeof Template>;
|
||||
|
||||
export const Default: ComponentStory<typeof Template> = Template.bind({});
|
||||
Default.args = { ...tooltipProps };
|
||||
|
||||
export const LoadingState: ComponentStory<typeof Template> = Template.bind({});
|
||||
LoadingState.args = { ...tooltipProps, loading: true };
|
||||
|
||||
export const LongTitle: ComponentStory<typeof Template> = Template.bind({});
|
||||
LongTitle.args = { ...tooltipProps, content: { ...tooltipContent, title: longTitle } };
|
||||
|
||||
export const LongDescription: ComponentStory<typeof Template> = Template.bind({});
|
||||
LongDescription.args = {
|
||||
...tooltipProps,
|
||||
content: { ...tooltipContent, description: longDescription },
|
||||
};
|
||||
|
||||
export const InProgressStatus: ComponentStory<typeof Template> = Template.bind({});
|
||||
InProgressStatus.args = {
|
||||
...tooltipProps,
|
||||
content: { ...tooltipContent, status: CaseStatuses['in-progress'] },
|
||||
};
|
||||
|
||||
export const ClosedStatus: ComponentStory<typeof Template> = Template.bind({});
|
||||
ClosedStatus.args = {
|
||||
...tooltipProps,
|
||||
content: { ...tooltipContent, status: CaseStatuses.closed },
|
||||
};
|
||||
|
||||
export const NoUserInfo: ComponentStory<typeof Template> = Template.bind({});
|
||||
NoUserInfo.args = { ...tooltipProps, content: { ...tooltipContent, createdBy: {} } };
|
||||
|
||||
export const FullName: ComponentStory<typeof Template> = Template.bind({});
|
||||
FullName.args = {
|
||||
...tooltipProps,
|
||||
content: { ...tooltipContent, createdBy: { fullName: 'Elastic User' } },
|
||||
};
|
||||
|
||||
export const LongUserName: ComponentStory<typeof Template> = Template.bind({});
|
||||
LongUserName.args = {
|
||||
...tooltipProps,
|
||||
content: { ...tooltipContent, createdBy: { fullName: 'LoremIpsumElasticUser WithALongSurname' } },
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { IconWithCount } from './icon_with_count';
|
||||
|
||||
describe('IconWithCount', () => {
|
||||
it('renders component correctly', () => {
|
||||
const res = render(<IconWithCount count={5} icon={'editorComment'} />);
|
||||
|
||||
expect(res.getByTestId('comment-count-icon')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders count correctly', () => {
|
||||
const res = render(<IconWithCount count={100} icon={'editorComment'} />);
|
||||
|
||||
expect(res.getByText(100)).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
export const IconWithCount = React.memo<{
|
||||
count: number;
|
||||
icon: string;
|
||||
}>(({ count, icon }) => (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none" css={{ marginLeft: 'auto', flexGrow: 0 }}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon
|
||||
css={{ marginRight: '4px' }}
|
||||
size="s"
|
||||
type={icon}
|
||||
data-test-subj="comment-count-icon"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="xs">{count}</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
));
|
||||
|
||||
IconWithCount.displayName = 'IconWithCount';
|
25
packages/kbn-cases-components/src/tooltip/skeleton.tsx
Normal file
25
packages/kbn-cases-components/src/tooltip/skeleton.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFlexItem, EuiLoadingContent, EuiHorizontalRule, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
const SkeletonComponent: React.FC = () => {
|
||||
return (
|
||||
<EuiFlexItem css={{ width: 240 }} data-test-subj="tooltip-loading-content">
|
||||
<EuiLoadingContent lines={1} css={{ width: 70, marginBottom: '12px' }} />
|
||||
<EuiLoadingContent lines={3} />
|
||||
<EuiHorizontalRule margin="xs" />
|
||||
<EuiSpacer size="s" />
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
||||
|
||||
SkeletonComponent.displayName = 'Skeleton';
|
||||
|
||||
export const Skeleton = SkeletonComponent;
|
175
packages/kbn-cases-components/src/tooltip/tooltip.test.tsx
Normal file
175
packages/kbn-cases-components/src/tooltip/tooltip.test.tsx
Normal file
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
|
||||
import { Tooltip } from './tooltip';
|
||||
import { CaseStatuses } from '../status/types';
|
||||
import type { CaseTooltipContentProps, CaseTooltipProps } from './types';
|
||||
|
||||
const elasticUser = {
|
||||
fullName: 'Elastic User',
|
||||
username: 'elastic',
|
||||
};
|
||||
|
||||
const sampleText = 'This is a test span element!!';
|
||||
const TestSpan = () => <span data-test-subj="sample-span">{sampleText}</span>;
|
||||
|
||||
const tooltipContent: CaseTooltipContentProps = {
|
||||
title: 'Another horrible breach!!',
|
||||
description: 'Demo case banana Issue',
|
||||
createdAt: '2020-02-19T23:06:33.798Z',
|
||||
createdBy: elasticUser,
|
||||
totalComments: 1,
|
||||
status: CaseStatuses.open,
|
||||
};
|
||||
|
||||
const tooltipProps: CaseTooltipProps = {
|
||||
children: TestSpan,
|
||||
loading: false,
|
||||
content: tooltipContent,
|
||||
};
|
||||
|
||||
describe('Tooltip', () => {
|
||||
it('renders correctly', async () => {
|
||||
const res = render(
|
||||
<I18nProvider>
|
||||
<Tooltip {...tooltipProps}>
|
||||
<TestSpan />
|
||||
</Tooltip>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
fireEvent.mouseOver(res.getByTestId('sample-span'));
|
||||
expect(await res.findByTestId('cases-components-tooltip')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders custom test subject correctly', async () => {
|
||||
const res = render(
|
||||
<I18nProvider>
|
||||
<Tooltip {...tooltipProps} dataTestSubj="custom-data-test">
|
||||
<TestSpan />
|
||||
</Tooltip>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
fireEvent.mouseOver(res.getByTestId('sample-span'));
|
||||
expect(await res.findByTestId('custom-data-test')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders loading state correctly', async () => {
|
||||
const res = render(
|
||||
<I18nProvider>
|
||||
<Tooltip {...tooltipProps} loading={true}>
|
||||
<TestSpan />
|
||||
</Tooltip>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
fireEvent.mouseOver(res.getByTestId('sample-span'));
|
||||
expect(await res.findByTestId('tooltip-loading-content')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders title correctly', async () => {
|
||||
const res = render(
|
||||
<I18nProvider>
|
||||
<Tooltip {...tooltipProps}>
|
||||
<TestSpan />
|
||||
</Tooltip>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
fireEvent.mouseOver(res.getByTestId('sample-span'));
|
||||
expect(await res.findByText(tooltipContent.title)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders description correctly', async () => {
|
||||
const res = render(
|
||||
<I18nProvider>
|
||||
<Tooltip {...tooltipProps}>
|
||||
<TestSpan />
|
||||
</Tooltip>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
fireEvent.mouseOver(res.getByTestId('sample-span'));
|
||||
expect(await res.findByText(tooltipContent.description)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders icon', async () => {
|
||||
const res = render(
|
||||
<I18nProvider>
|
||||
<Tooltip {...tooltipProps}>
|
||||
<TestSpan />
|
||||
</Tooltip>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
fireEvent.mouseOver(res.getByTestId('sample-span'));
|
||||
expect(await res.findByTestId('comment-count-icon')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders comment count', async () => {
|
||||
const res = render(
|
||||
<I18nProvider>
|
||||
<Tooltip {...tooltipProps}>
|
||||
<TestSpan />
|
||||
</Tooltip>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
fireEvent.mouseOver(res.getByTestId('sample-span'));
|
||||
expect(await res.findByText(tooltipContent.totalComments)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders correct status', async () => {
|
||||
const res = render(
|
||||
<I18nProvider>
|
||||
<Tooltip {...tooltipProps} content={{ ...tooltipContent, status: CaseStatuses.closed }}>
|
||||
<TestSpan />
|
||||
</Tooltip>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
fireEvent.mouseOver(res.getByTestId('sample-span'));
|
||||
expect(await res.findByText('Closed')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders full name when no username available', async () => {
|
||||
const newUser = {
|
||||
fullName: 'New User',
|
||||
};
|
||||
|
||||
const res = render(
|
||||
<I18nProvider>
|
||||
<Tooltip {...tooltipProps} content={{ ...tooltipContent, createdBy: newUser }}>
|
||||
<TestSpan />
|
||||
</Tooltip>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
fireEvent.mouseOver(res.getByTestId('sample-span'));
|
||||
expect(await res.findByTestId('tooltip-username')).toBeInTheDocument();
|
||||
expect(await res.findByText(newUser.fullName)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render username when no username or full name available', () => {
|
||||
const res = render(
|
||||
<I18nProvider>
|
||||
<Tooltip {...tooltipProps} content={{ ...tooltipContent, createdBy: {} }}>
|
||||
<TestSpan />
|
||||
</Tooltip>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
fireEvent.mouseOver(res.getByTestId('sample-span'));
|
||||
expect(res.queryByTestId('tooltip-username')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
32
packages/kbn-cases-components/src/tooltip/tooltip.tsx
Normal file
32
packages/kbn-cases-components/src/tooltip/tooltip.tsx
Normal file
|
@ -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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { EuiToolTip } from '@elastic/eui';
|
||||
|
||||
import { TooltipContent } from './tooltip_content';
|
||||
import type { CaseTooltipProps } from './types';
|
||||
import { Skeleton } from './skeleton';
|
||||
|
||||
const CaseTooltipComponent = React.memo<CaseTooltipProps>((props) => {
|
||||
const { dataTestSubj, children, loading = false, className = '', content } = props;
|
||||
|
||||
return (
|
||||
<EuiToolTip
|
||||
data-test-subj={dataTestSubj ? dataTestSubj : 'cases-components-tooltip'}
|
||||
anchorClassName={className}
|
||||
content={loading ? <Skeleton /> : <TooltipContent {...content} />}
|
||||
>
|
||||
<>{children}</>
|
||||
</EuiToolTip>
|
||||
);
|
||||
});
|
||||
|
||||
CaseTooltipComponent.displayName = 'Tooltip';
|
||||
|
||||
export const Tooltip = memo(CaseTooltipComponent);
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { FormattedRelative } from '@kbn/i18n-react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiText, EuiHorizontalRule } from '@elastic/eui';
|
||||
|
||||
import { Status } from '../status/status';
|
||||
import { CaseStatuses } from '../status/types';
|
||||
import { IconWithCount } from './icon_with_count';
|
||||
import { getTruncatedText } from './utils';
|
||||
import * as i18n from './translations';
|
||||
import type { CaseTooltipContentProps } from './types';
|
||||
|
||||
const TITLE_TRUNCATE_LENGTH = 35;
|
||||
const DESCRIPTION_TRUNCATE_LENGTH = 80;
|
||||
const USER_TRUNCATE_LENGTH = 15;
|
||||
|
||||
const CaseTooltipContentComponent = React.memo<CaseTooltipContentProps>(
|
||||
({ title, description, status, totalComments, createdAt, createdBy }) => (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="xs" direction="column">
|
||||
<EuiFlexGroup gutterSize="xs" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<Status status={status} />
|
||||
</EuiFlexItem>
|
||||
<IconWithCount count={totalComments} icon={'editorComment'} />
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup direction="column" gutterSize="xs">
|
||||
<EuiFlexItem>
|
||||
<EuiText size="relative">
|
||||
<strong>{getTruncatedText(title, TITLE_TRUNCATE_LENGTH)}</strong>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText size="relative">
|
||||
{getTruncatedText(description, DESCRIPTION_TRUNCATE_LENGTH)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
<EuiHorizontalRule margin="xs" />
|
||||
<EuiText size="relative">
|
||||
{status === CaseStatuses.closed ? i18n.CLOSED : i18n.OPENED}{' '}
|
||||
<FormattedRelative value={createdAt} />{' '}
|
||||
{createdBy.username || createdBy.fullName ? (
|
||||
<>
|
||||
{i18n.BY}{' '}
|
||||
<strong data-test-subj="tooltip-username">
|
||||
{getTruncatedText(
|
||||
createdBy.username ?? createdBy.fullName ?? '',
|
||||
USER_TRUNCATE_LENGTH
|
||||
)}
|
||||
</strong>
|
||||
</>
|
||||
) : null}
|
||||
</EuiText>
|
||||
</>
|
||||
)
|
||||
);
|
||||
|
||||
CaseTooltipContentComponent.displayName = 'TooltipContent';
|
||||
|
||||
export const TooltipContent = memo(CaseTooltipContentComponent);
|
21
packages/kbn-cases-components/src/tooltip/translations.ts
Normal file
21
packages/kbn-cases-components/src/tooltip/translations.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const OPENED = i18n.translate('cases.components.tooltip.opened', {
|
||||
defaultMessage: 'Opened',
|
||||
});
|
||||
|
||||
export const CLOSED = i18n.translate('cases.components.tooltip.closed', {
|
||||
defaultMessage: 'Closed',
|
||||
});
|
||||
|
||||
export const BY = i18n.translate('cases.components.tooltip.by', {
|
||||
defaultMessage: 'by',
|
||||
});
|
25
packages/kbn-cases-components/src/tooltip/types.ts
Normal file
25
packages/kbn-cases-components/src/tooltip/types.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { CaseStatuses } from '../status/types';
|
||||
export interface CaseTooltipContentProps {
|
||||
title: string;
|
||||
description: string;
|
||||
status: CaseStatuses;
|
||||
totalComments: number;
|
||||
createdAt: string;
|
||||
createdBy: { username?: string; fullName?: string };
|
||||
}
|
||||
|
||||
export interface CaseTooltipProps {
|
||||
children: React.ReactNode;
|
||||
content: CaseTooltipContentProps;
|
||||
dataTestSubj?: string;
|
||||
className?: string;
|
||||
loading?: boolean;
|
||||
}
|
51
packages/kbn-cases-components/src/tooltip/utils.test.ts
Normal file
51
packages/kbn-cases-components/src/tooltip/utils.test.ts
Normal file
|
@ -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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { getTruncatedText } from './utils';
|
||||
|
||||
describe('getTruncatedText', () => {
|
||||
it('should return truncated text correctly', () => {
|
||||
const sampleText = 'This is a sample text!!';
|
||||
const res = getTruncatedText(sampleText, 4);
|
||||
|
||||
expect(res).toEqual('This...');
|
||||
});
|
||||
|
||||
it('should return original text if text is empty', () => {
|
||||
const res = getTruncatedText('', 4);
|
||||
|
||||
expect(res).toEqual('');
|
||||
});
|
||||
|
||||
it('should return empty text if text is empty', () => {
|
||||
const res = getTruncatedText('', 10);
|
||||
|
||||
expect(res).toEqual('');
|
||||
});
|
||||
|
||||
it('should return original text if truncate length is negative', () => {
|
||||
const sampleText = 'This is a sample text!!';
|
||||
const res = getTruncatedText(sampleText, -4);
|
||||
|
||||
expect(res).toEqual(sampleText);
|
||||
});
|
||||
|
||||
it('should return original text if truncate length is zero', () => {
|
||||
const sampleText = 'This is a sample text!!';
|
||||
const res = getTruncatedText(sampleText, 0);
|
||||
|
||||
expect(res).toEqual(sampleText);
|
||||
});
|
||||
|
||||
it('should return original text if text is smaller than truncate length number', () => {
|
||||
const sampleText = 'This is a sample text!!';
|
||||
const res = getTruncatedText(sampleText, 50);
|
||||
|
||||
expect(res).toEqual(sampleText);
|
||||
});
|
||||
});
|
15
packages/kbn-cases-components/src/tooltip/utils.ts
Normal file
15
packages/kbn-cases-components/src/tooltip/utils.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export const getTruncatedText = (text: string, truncateLength: number): string => {
|
||||
if (truncateLength <= 0 || text.length <= truncateLength) {
|
||||
return text;
|
||||
}
|
||||
|
||||
return text.slice(0, truncateLength).trim().concat('...');
|
||||
};
|
|
@ -14,6 +14,7 @@
|
|||
],
|
||||
"kbn_references": [
|
||||
"@kbn/i18n",
|
||||
"@kbn/i18n-react",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
export const storybookAliases = {
|
||||
apm: 'x-pack/plugins/apm/.storybook',
|
||||
canvas: 'x-pack/plugins/canvas/storybook',
|
||||
cases: 'packages/kbn-cases-components/.storybook',
|
||||
ci_composite: '.ci/.storybook',
|
||||
cloud_chat: 'x-pack/plugins/cloud_integrations/cloud_chat/.storybook',
|
||||
coloring: 'packages/kbn-coloring/.storybook',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue