Skip to content

Commit

Permalink
[Enterprise Search] Migrate util and components from ent-search (#76051)
Browse files Browse the repository at this point in the history
* Migrate useDidUpdateEffect hook

Migrates https://github.com/elastic/ent-search/blob/master/app/javascript/shared/utils/useDidUpdateEffect.ts with test added.

* Migrate TruncateContent component

* Migrate TableHeader component

* Remove unused deps

* Fix test name

* Remove custom type in favor of DependencyList

* Add stylesheet for truncated content

* Actually import stylesheet

🤦🏼‍♂️

* Replace legacy mixin

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
scottybollinger and elasticmachine committed Aug 31, 2020
1 parent bfa87d2 commit b13d197
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export { TableHeader } from './table_header';
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import { shallow } from 'enzyme';
import { EuiTableHeader, EuiTableHeaderCell } from '@elastic/eui';

import { TableHeader } from './table_header';

const headerItems = ['foo', 'bar', 'baz'];

describe('TableHeader', () => {
it('renders', () => {
const wrapper = shallow(<TableHeader headerItems={headerItems} />);

expect(wrapper.find(EuiTableHeader)).toHaveLength(1);
expect(wrapper.find(EuiTableHeaderCell)).toHaveLength(3);
});

it('renders extra cell', () => {
const wrapper = shallow(<TableHeader headerItems={headerItems} extraCell />);

expect(wrapper.find(EuiTableHeader)).toHaveLength(1);
expect(wrapper.find(EuiTableHeaderCell)).toHaveLength(4);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';

import { EuiTableHeader, EuiTableHeaderCell } from '@elastic/eui';

interface ITableHeaderProps {
headerItems: string[];
extraCell?: boolean;
}

export const TableHeader: React.FC<ITableHeaderProps> = ({ headerItems, extraCell }) => (
<EuiTableHeader>
{headerItems.map((item, i) => (
<EuiTableHeaderCell key={i}>{item}</EuiTableHeaderCell>
))}
{extraCell && <EuiTableHeaderCell />}
</EuiTableHeader>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export { truncate, truncateBeginning } from './truncate';
export { TruncatedContent } from './truncated_content';
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import { shallow } from 'enzyme';

import { TruncatedContent } from './';

const content = 'foobarbaz';

describe('TruncatedContent', () => {
it('renders with no truncation', () => {
const wrapper = shallow(<TruncatedContent length={4} content="foo" />);

expect(wrapper.find('span.truncated-content')).toHaveLength(0);
expect(wrapper.text()).toEqual('foo');
});

it('renders with truncation at the end', () => {
const wrapper = shallow(<TruncatedContent tooltipType="title" length={4} content={content} />);
const element = wrapper.find('span.truncated-content');

expect(element).toHaveLength(1);
expect(element.prop('title')).toEqual(content);
expect(wrapper.text()).toEqual('foob…');
expect(wrapper.find('span.truncated-content__tooltip')).toHaveLength(0);
});

it('renders with truncation at the beginning', () => {
const wrapper = shallow(
<TruncatedContent tooltipType="title" beginning length={4} content={content} />
);

expect(wrapper.find('span.truncated-content')).toHaveLength(1);
expect(wrapper.text()).toEqual('…rbaz');
});

it('renders with inline tooltip', () => {
const wrapper = shallow(<TruncatedContent beginning length={4} content={content} />);

expect(wrapper.find('span.truncated-content').prop('title')).toEqual('');
expect(wrapper.find('span.truncated-content__tooltip')).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export function truncate(text: string, length: number) {
return `${text.substring(0, length)}…`;
}

export function truncateBeginning(text: string, length: number) {
return `…${text.substring(text.length - length)}`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

.truncated-content {
position: relative;
z-index: 2;
display: inline-block;
white-space: nowrap;

&__tooltip {
position: absolute;
top: 50%;
transform: translateY(-50%);
left: -3px;
margin-top: -1px;
background: $euiColorEmptyShade;
border-radius: 2px;
width: calc(100% + 4px);
height: calc(100% + 4px);
padding: 0 2px;
display: none;
align-items: center;
box-shadow: 0 1px 3px rgba(black, 0.1);
border: 1px solid $euiBorderColor;
width: auto;
white-space: nowrap;

.truncated-content:hover & {
display: flex;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';

import { truncate, truncateBeginning } from './';

import './truncated_content.scss';

interface ITruncatedContentProps {
content: string;
length: number;
beginning?: boolean;
tooltipType?: 'inline' | 'title';
}

export const TruncatedContent: React.FC<ITruncatedContentProps> = ({
content,
length,
beginning = false,
tooltipType = 'inline',
}) => {
if (content.length <= length) return <>{content}</>;

const inline = tooltipType === 'inline';
return (
<span className="truncated-content" title={!inline ? content : ''}>
{beginning ? truncateBeginning(content, length) : truncate(content, length)}
{inline && <span className="truncated-content__tooltip">{content}</span>}
</span>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export { useDidUpdateEffect } from './use_did_update_effect';
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { useState } from 'react';
import { mount } from 'enzyme';

import { EuiLink } from '@elastic/eui';

import { useDidUpdateEffect } from './use_did_update_effect';

const fn = jest.fn();

const TestHook = ({ value }: { value: number }) => {
const [inputValue, setValue] = useState(value);
useDidUpdateEffect(fn, [inputValue]);
return <EuiLink onClick={() => setValue(2)} />;
};

const wrapper = mount(<TestHook value={1} />);

describe('useDidUpdateEffect', () => {
it('should not fire function when value unchanged', () => {
expect(fn).not.toHaveBeenCalled();
});

it('should fire function when value changed', () => {
wrapper.find(EuiLink).simulate('click');
expect(fn).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

/*
* Sometimes we don't want to fire the initial useEffect call.
* This custom Hook only fires after the intial render has completed.
*/
import { useEffect, useRef, DependencyList } from 'react';

export const useDidUpdateEffect = (fn: Function, inputs: DependencyList) => {
const didMountRef = useRef(false);

useEffect(() => {
if (didMountRef.current) {
fn();
} else {
didMountRef.current = true;
}
}, inputs);
};

0 comments on commit b13d197

Please sign in to comment.