Skip to content
This repository has been archived by the owner on Apr 18, 2024. It is now read-only.

fix: LEAP-218: Improve performance of search #1601

Merged
merged 26 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
223c5db
fix: LEAP-218: Improve performance of search
juliosgarbi Oct 19, 2023
f2beff5
add new tests to see how it works between the original ui and our own…
juliosgarbi Oct 19, 2023
744079a
working with external input search
juliosgarbi Oct 19, 2023
c721203
implements new search and fix UI
juliosgarbi Oct 23, 2023
2ad7dbb
merge conflicts
juliosgarbi Oct 23, 2023
b498b6e
split taxonomy and taxonomy search
juliosgarbi Oct 24, 2023
9121741
add spacing line in the end of the file
juliosgarbi Oct 24, 2023
c0e72f7
add tests
juliosgarbi Oct 24, 2023
b878457
change the expandedKeys method from its own method to filterDataTree
juliosgarbi Oct 24, 2023
5018d89
fix tests
juliosgarbi Oct 24, 2023
b2584b3
add ff and fix some UX problems
juliosgarbi Oct 26, 2023
1add9e0
remove useless function
juliosgarbi Oct 26, 2023
11bb38f
expand just the mached value
juliosgarbi Oct 27, 2023
31b3416
fix small bugs and add comments
juliosgarbi Oct 30, 2023
9cc0623
stop digging if it doesnt have chindrens
juliosgarbi Oct 30, 2023
880dea7
remove preset ff
juliosgarbi Oct 30, 2023
ee653a6
check if is leaf
juliosgarbi Oct 30, 2023
d233eb8
Merge remote-tracking branch 'origin/master' into fb-leap-218
juliosgarbi Oct 30, 2023
28b1238
Update src/components/NewTaxonomy/NewTaxonomy.tsx
juliosgarbi Oct 30, 2023
5348400
Update src/components/NewTaxonomy/TaxonomySearch.tsx
juliosgarbi Oct 30, 2023
0988f49
Update src/components/NewTaxonomy/TaxonomySearch.tsx
juliosgarbi Oct 30, 2023
b3ee9e5
Update src/components/NewTaxonomy/TaxonomySearch.tsx
juliosgarbi Oct 30, 2023
c3ea39f
Update src/components/NewTaxonomy/TaxonomySearch.tsx
juliosgarbi Oct 30, 2023
e387d22
Update src/components/NewTaxonomy/NewTaxonomy.tsx
juliosgarbi Oct 30, 2023
065b6cc
change changeValue method name to resetValue
juliosgarbi Oct 30, 2023
e96548a
Update src/components/NewTaxonomy/TaxonomySearch.tsx
juliosgarbi Oct 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 52 additions & 3 deletions src/components/NewTaxonomy/NewTaxonomy.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { TreeSelect } from 'antd';
import React, { useCallback, useEffect, useState } from 'react';
import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react';

import { Tooltip } from '../../common/Tooltip/Tooltip';

import './NewTaxonomy.styl';
import { TaxonomySearch, TaxonomySearchRef } from './TaxonomySearch';

type TaxonomyPath = string[];
type onAddLabelCallback = (path: string[]) => any;
Expand All @@ -20,7 +21,7 @@ type TaxonomyItem = {
color?: string,
};

type AntTaxonomyItem = {
export type AntTaxonomyItem = {
title: string | JSX.Element,
value: string,
key: string,
Expand Down Expand Up @@ -54,6 +55,7 @@ type TaxonomyProps = {
onDeleteLabel?: onDeleteLabelCallback,
options: TaxonomyOptions,
isEditable?: boolean,
defaultSearch?: boolean,
};

type TaxonomyExtendedOptions = TaxonomyOptions & {
Expand Down Expand Up @@ -107,14 +109,18 @@ const NewTaxonomy = ({
selected,
onChange,
onLoadData,
defaultSearch = true,
// @todo implement user labels
// onAddLabel,
// onDeleteLabel,
options,
// @todo implement readonly mode
// isEditable = true,
}: TaxonomyProps) => {
const refInput = useRef<TaxonomySearchRef>(null);
const [treeData, setTreeData] = useState<AntTaxonomyItem[]>([]);
const [filteredTreeData, setFilteredTreeData] = useState<AntTaxonomyItem[]>([]);
const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
const separator = options.pathSeparator;
const style = { minWidth: options.minWidth ?? 200, maxWidth: options.maxWidth };
const dropdownWidth = options.dropdownWidth === undefined ? true : +options.dropdownWidth;
Expand All @@ -133,14 +139,57 @@ const NewTaxonomy = ({
return onLoadData?.(node.value.split(separator));
}, []);

const handleSearch = useCallback((list: AntTaxonomyItem[], expandedKeys: React.Key[]) => {
setFilteredTreeData(list);
setExpandedKeys(expandedKeys);
}, []);

const renderDropdown = useCallback((origin: ReactNode) => {
if (!defaultSearch) {
return (
<TaxonomySearch
ref={refInput}
treeData={treeData}
origin={origin}
onChange={handleSearch}
/>
);
} else {
return (
<>
{origin}
</>
);
}
}, [treeData]);

const handleDropdownChange = useCallback((open: boolean) => {
if (open) {
// handleDropdownChange is being called before the dropdown is rendered, 100ms is the time that we have to wait to dropdown be rendered
setTimeout(() => {
refInput.current?.focus();
}, 100);
} else {
refInput.current?.changeValue();
}
}, [refInput]);

return (
<TreeSelect
treeData={treeData}
treeData={defaultSearch ? treeData : filteredTreeData}
value={displayed}
labelInValue={true}
onChange={items => onChange(null, items.map(item => item.value.split(separator)))}
loadData={loadData}
treeCheckable
showSearch={defaultSearch}
showArrow={!defaultSearch}
dropdownRender={renderDropdown}
onDropdownVisibleChange={handleDropdownChange}
treeExpandedKeys={expandedKeys}
onTreeExpand={(expandedKeys: React.Key[]) => {
setExpandedKeys(expandedKeys);
}}
treeCheckStrictly
showCheckedStrategy={TreeSelect.SHOW_ALL}
treeExpandAction="click"
Expand Down
8 changes: 8 additions & 0 deletions src/components/NewTaxonomy/TaxonomySearch.styl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.taxonomy-search-input
width calc(100% - 8px)
height 40px
border-radius 4px
border 1px solid rgba(137, 128, 152, 0.16)
background url("data:image/svg+xml;base64, PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE1Ljc1NSAxNC4yNTVIMTQuOTY1TDE0LjY4NSAxMy45ODVDMTUuNjY1IDEyLjg0NSAxNi4yNTUgMTEuMzY1IDE2LjI1NSA5Ljc1NUMxNi4yNTUgNi4xNjUgMTMuMzQ1IDMuMjU1IDkuNzU1IDMuMjU1QzYuMTY1IDMuMjU1IDMuMjU1IDYuMTY1IDMuMjU1IDkuNzU1QzMuMjU1IDEzLjM0NSA2LjE2NSAxNi4yNTUgOS43NTUgMTYuMjU1QzExLjM2NSAxNi4yNTUgMTIuODQ1IDE1LjY2NSAxMy45ODUgMTQuNjg1TDE0LjI1NSAxNC45NjVWMTUuNzU1TDE5LjI1NSAyMC43NDVMMjAuNzQ1IDE5LjI1NUwxNS43NTUgMTQuMjU1Wk05Ljc1NSAxNC4yNTVDNy4yNjUwMSAxNC4yNTUgNS4yNTUgMTIuMjQ1IDUuMjU1IDkuNzU1QzUuMjU1IDcuMjY1MDEgNy4yNjUwMSA1LjI1NSA5Ljc1NSA1LjI1NUMxMi4yNDUgNS4yNTUgMTQuMjU1IDcuMjY1MDEgMTQuMjU1IDkuNzU1QzE0LjI1NSAxMi4yNDUgMTIuMjQ1IDE0LjI1NSA5Ljc1NSAxNC4yNTVaIiBmaWxsPSIjMDk2REQ5Ii8+Cjwvc3ZnPgo=") center left 4px no-repeat #FAFAFA;
padding 4px 4px 4px 32px
margin 0 4px 14px 4px
133 changes: 133 additions & 0 deletions src/components/NewTaxonomy/TaxonomySearch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React, {
ChangeEvent,
KeyboardEvent,
ReactNode,
useCallback,
useEffect,
useImperativeHandle,
useRef,
useState
} from 'react';

import './TaxonomySearch.styl';
import { Block } from '../../utils/bem';
import { AntTaxonomyItem } from './NewTaxonomy';
import { debounce } from 'lodash';

type TaxonomySearchProps = {
treeData: AntTaxonomyItem[],
origin: ReactNode,
onChange: (list: AntTaxonomyItem[], expandedKeys: React.Key[]) => void,
}

export type TaxonomySearchRef = {
changeValue: () => void,
focus: () => void,
}

const TaxonomySearch = React.forwardRef<TaxonomySearchRef, TaxonomySearchProps>(({
origin,
treeData,
onChange,
}, ref) => {
useImperativeHandle(ref, (): TaxonomySearchRef => {
return {
changeValue() {
setInputValue('');
onChange(treeData, []);
},
focus() {
return inputRef.current?.focus();
},
};
});

const inputRef = useRef<HTMLInputElement>();
const [inputValue, setInputValue] = useState('');

useEffect(() => {
const _filteredData = filterTreeData(treeData, inputValue);

onChange(_filteredData.filteredDataTree, _filteredData.expandedKeys);
}, [treeData]);

//To filter the treeData items that match with the searchValue
const filterTreeNode = useCallback((searchValue: string, treeNode: AntTaxonomyItem) => {
const lowerSearchValue = String(searchValue).toLowerCase();
const lowerResultValue = typeof treeNode.title === 'object' ? treeNode.title.props.children.props.children : treeNode.title;

if (!lowerSearchValue) {
return false;
}

return String(lowerResultValue).toLowerCase().includes(lowerSearchValue);
}, []);

// It's running recursively through treeData and its children filtering the content that match with the search value
const filterTreeData = useCallback((treeData: AntTaxonomyItem[], searchValue: string) => {
const _expandedKeys: React.Key[] = [];

if (!searchValue) {
return {
filteredDataTree: treeData,
expandedKeys: _expandedKeys,
};
}

const dig = (list: AntTaxonomyItem[], keepAll = false) => {
return list.reduce<AntTaxonomyItem[]>((total, dataNode) => {
const children = dataNode['children'];

const match = keepAll || filterTreeNode(searchValue, dataNode);
const childList = dig(children || [], match);

if (match || childList.length) {
if (!keepAll)
_expandedKeys.push(dataNode.key);

total.push({
...dataNode,
isLeaf: undefined,
children: childList,
});
}

return total;
}, []);
};

return {
filteredDataTree: dig(treeData),
expandedKeys: _expandedKeys,
};
}, []);

const handleSearch = useCallback(debounce(async (e: ChangeEvent<HTMLInputElement>) => {
const _filteredData = filterTreeData(treeData, e.target.value);

onChange(_filteredData.filteredDataTree, _filteredData.expandedKeys);
}, 300), [treeData]);

return (
<>
<Block
ref={inputRef}
value={inputValue}
tag={'input'}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value);
handleSearch(e);
}}
onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Backspace' || e.key === 'Delete') e.stopPropagation();
}}
placeholder={'Search'}
data-testid={'taxonomy-search'}
name={'taxonomy-search-input'}
/>
{origin}
</>
);
});

export { TaxonomySearch };
2 changes: 2 additions & 0 deletions src/tags/control/Taxonomy/Taxonomy.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { parseValue } from '../../../utils/data';
import {
FF_DEV_2007_DEV_2008,
FF_DEV_3617,
FF_LEAP_218,
FF_LSDV_4583,
FF_TAXONOMY_ASYNC,
FF_TAXONOMY_LABELING,
Expand Down Expand Up @@ -612,6 +613,7 @@ const HtxTaxonomy = observer(({ item }) => {
onAddLabel={item.userLabels && item.onAddLabel}
onDeleteLabel={item.userLabels && item.onDeleteLabel}
options={options}
defaultSearch={!isFF(FF_LEAP_218)}
isEditable={!item.isReadOnly()}
/>
) : (
Expand Down
2 changes: 2 additions & 0 deletions src/utils/feature-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,8 @@ export const FF_DBLCLICK_DELAY = 'fflag_fix_front_lsdv_5248_double_click_delay_2
*/
export const FF_TAXONOMY_ASYNC = 'fflag_feat_front_lsdv_5451_async_taxonomy_110823_short';

export const FF_LEAP_218 = 'fflag_fix_front_leap_218_improve_performance_of_taxonomy_search_short';

/**
* Allow to label NER directly with Taxonomy instead of Labels
* @link https://app.launchdarkly.com/default/production/features/fflag_feat_front_lsdv_5452_taxonomy_labeling_110823_short
Expand Down
30 changes: 30 additions & 0 deletions tests/functional/specs/control_tags/taxonomy.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
FF_DEV_2007_DEV_2008,
FF_DEV_2100_A,
FF_DEV_3617,
FF_LEAP_218,
FF_TAXONOMY_ASYNC,
FF_TAXONOMY_LABELING
} from '../../../../src/utils/feature-flags';
Expand Down Expand Up @@ -172,4 +173,33 @@ Object.entries(taxonomies).forEach(([title, Taxonomy]) => {
Taxonomy.hasNoSelected('Book 1 / Chapter 2 / Section 2.1');
});
});

describe('Control Tags - Taxonomy - search', () => {
beforeEach(() => {
if (Taxonomy.isNew) {
LabelStudio.addFeatureFlagsOnPageLoad({
[FF_TAXONOMY_ASYNC]: true,
[FF_LEAP_218]: true,
});
}
});

it('should input search and filter treeData', () => {
if (!Taxonomy.isNew) return;

LabelStudio.params()
.config(buildDynamicTaxonomyConfig({ showFullPath: true }))
.data(taxonomyDataWithSimilarAliases)
.withResult([taxonomyResultWithSimilarAliases])
.init();

Taxonomy.open();
cy.get('[data-testid="taxonomy-search"]').type('Section 3.3');
Taxonomy.dropdown.contains('.ant-select-tree-treenode', 'Section 3.3').should('be.visible');
cy.get('[data-testid="taxonomy-search"]').clear();
cy.get('[data-testid="taxonomy-search"]').type('Section 7.0');
Taxonomy.dropdown.contains('No Data').should('be.visible');
Taxonomy.hasSelected('Book 1 / Chapter 2 / Section 2.1');
});
});
});