[Discover] Don't truncate long field descriptions if user expanded them previously (#188841)

- Closes https://github.com/elastic/kibana/issues/188508

## Summary

This is a follow up PR for
https://github.com/elastic/kibana/pull/187160#pullrequestreview-2181768414.
It stores the toggle state in local storage under
`fieldDescription:truncateByDefault`.

To test, open field popover from a sidebar to see ECS field description
or a custom field description. By default it should be truncated. But
after expanding (by clicking on the short description), it will expand
automatically when opening another field popover next time. Please note
that it does not affect field descriptions in DocViewer popover as they
are always shown in full length there.

### Checklist

- [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
This commit is contained in:
Julia Rechkunova 2024-07-24 08:52:13 +02:00 committed by GitHub
parent 145f65275c
commit db6886fb03
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 57 additions and 4 deletions

View file

@ -10,8 +10,27 @@ import React from 'react';
import { FieldDescription } from './field_description';
import { render, screen } from '@testing-library/react';
import { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
import { SHOULD_TRUNCATE_FIELD_DESCRIPTION_LOCALSTORAGE_KEY } from './field_description';
const mockSetLocalStorage = jest.fn();
const mockLocalStorageKey = SHOULD_TRUNCATE_FIELD_DESCRIPTION_LOCALSTORAGE_KEY;
let mockTestInitialLocalStorageValue: boolean | undefined;
jest.mock('react-use/lib/useLocalStorage', () => {
return jest.fn((key: string, initialValue: number) => {
if (key !== mockLocalStorageKey) {
throw new Error(`Unexpected key: ${key}`);
}
return [mockTestInitialLocalStorageValue ?? initialValue, mockSetLocalStorage];
});
});
describe('FieldDescription', () => {
afterEach(() => {
mockSetLocalStorage.mockReset();
mockTestInitialLocalStorageValue = undefined;
});
it('should render correctly when no custom description', async () => {
render(<FieldDescription field={{ name: 'bytes', type: 'number' }} />);
const desc = screen.queryByTestId('fieldDescription-bytes');
@ -35,8 +54,23 @@ describe('FieldDescription', () => {
expect(screen.queryByTestId('fieldDescription-bytes')).toHaveTextContent(
`${customDescription}View less`
);
expect(mockSetLocalStorage).toHaveBeenCalledWith(false);
screen.queryByTestId('toggleFieldDescription-bytes')?.click();
expect(screen.queryByTestId('fieldDescription-bytes')).toHaveTextContent(customDescription);
expect(mockSetLocalStorage).toHaveBeenCalledWith(true);
});
it('should render correctly with a long custom description and do not truncate it by default as per local storage', async () => {
mockTestInitialLocalStorageValue = false;
const customDescription = 'test this long desc '.repeat(8).trim();
render(<FieldDescription field={{ name: 'bytes', type: 'number', customDescription }} />);
expect(screen.queryByTestId('fieldDescription-bytes')).toHaveTextContent(
`${customDescription}View less`
);
expect(mockSetLocalStorage).not.toHaveBeenCalled();
screen.queryByTestId('toggleFieldDescription-bytes')?.click();
expect(screen.queryByTestId('fieldDescription-bytes')).toHaveTextContent(customDescription);
expect(mockSetLocalStorage).toHaveBeenCalledWith(true);
});
it('should render a long custom description without truncation', async () => {

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import React, { useState } from 'react';
import React, { useCallback, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { Markdown } from '@kbn/shared-ux-markdown';
import {
@ -19,6 +19,11 @@ import {
import { css } from '@emotion/react';
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
import { esFieldTypeToKibanaFieldType } from '@kbn/field-types';
import useLocalStorage from 'react-use/lib/useLocalStorage';
const SHOULD_TRUNCATE_FIELD_DESCRIPTION_BY_DEFAULT = true;
export const SHOULD_TRUNCATE_FIELD_DESCRIPTION_LOCALSTORAGE_KEY =
'fieldDescription:truncateByDefault';
const MAX_VISIBLE_LENGTH = 110;
@ -81,10 +86,24 @@ const EcsFieldDescriptionFallback: React.FC<
export const FieldDescriptionContent: React.FC<
FieldDescriptionContentProps & { ecsFieldDescription?: string }
> = ({ field, color, truncate = true, ecsFieldDescription, Wrapper }) => {
const [shouldTruncateByDefault, setShouldTruncateByDefault] = useLocalStorage<boolean>(
SHOULD_TRUNCATE_FIELD_DESCRIPTION_LOCALSTORAGE_KEY,
SHOULD_TRUNCATE_FIELD_DESCRIPTION_BY_DEFAULT
);
const { euiTheme } = useEuiTheme();
const customDescription = (field?.customDescription || ecsFieldDescription || '').trim();
const isTooLong = Boolean(truncate && customDescription.length > MAX_VISIBLE_LENGTH);
const [isTruncated, setIsTruncated] = useState<boolean>(isTooLong);
const [isTruncated, setIsTruncated] = useState<boolean>(
(shouldTruncateByDefault ?? SHOULD_TRUNCATE_FIELD_DESCRIPTION_BY_DEFAULT) && isTooLong
);
const truncateFieldDescription = useCallback(
(nextValue) => {
setIsTruncated(nextValue);
setShouldTruncateByDefault(nextValue);
},
[setIsTruncated, setShouldTruncateByDefault]
);
if (!customDescription) {
return null;
@ -100,7 +119,7 @@ export const FieldDescriptionContent: React.FC<
defaultMessage: 'View full field description',
})}
className="eui-textBreakWord eui-textLeft"
onClick={() => setIsTruncated(false)}
onClick={() => truncateFieldDescription(false)}
css={css`
padding: 0;
margin: 0;
@ -130,7 +149,7 @@ export const FieldDescriptionContent: React.FC<
size="xs"
flush="both"
data-test-subj={`toggleFieldDescription-${field.name}`}
onClick={() => setIsTruncated(true)}
onClick={() => truncateFieldDescription(true)}
>
{i18n.translate('fieldUtils.fieldDescription.viewLessButton', {
defaultMessage: 'View less',