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

Commit

Permalink
add ff and fix some UX problems
Browse files Browse the repository at this point in the history
  • Loading branch information
juliosgarbi committed Oct 26, 2023
1 parent 5018d89 commit b2584b3
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 35 deletions.
62 changes: 42 additions & 20 deletions src/components/NewTaxonomy/NewTaxonomy.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { TreeSelect } from 'antd';
import React, { ReactNode, 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 } from './TaxonomySearch';
import { TaxonomySearch, TaxonomySearchRef } from './TaxonomySearch';

type TaxonomyPath = string[];
type onAddLabelCallback = (path: string[]) => any;
Expand Down Expand Up @@ -55,6 +55,7 @@ type TaxonomyProps = {
onDeleteLabel?: onDeleteLabelCallback,
options: TaxonomyOptions,
isEditable?: boolean,
defaultSearch?: boolean,
};

type TaxonomyExtendedOptions = TaxonomyOptions & {
Expand Down Expand Up @@ -108,16 +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<string[] | undefined>();
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 @@ -132,42 +135,61 @@ const NewTaxonomy = ({
setTreeData(convert(items, { ...options, maxUsagesReached }, value));
}, [items, maxUsagesReached]);

useEffect(() => {
setFilteredTreeData(treeData);
}, [treeData]);

const loadData = useCallback(async (node: any) => {
return onLoadData?.(node.value.split(separator));
}, []);

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

const renderDropdown = useCallback((origin: ReactNode) => {
return(
<TaxonomySearch
treeData={treeData}
origin={origin}
onChange={handleSearch}
/>
);
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={filteredTreeData}
treeData={defaultSearch ? treeData : filteredTreeData}
value={displayed}
labelInValue={true}
onChange={items => onChange(null, items.map(item => item.value.split(separator)))}
loadData={loadData}
treeCheckable
filterTreeNode={false}
showSearch={false}
showArrow={true}
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
81 changes: 66 additions & 15 deletions src/components/NewTaxonomy/TaxonomySearch.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import React, { ChangeEvent, KeyboardEvent, ReactNode, useCallback } from 'react';
import React, {
ChangeEvent,
KeyboardEvent,
ReactNode,
useCallback,
useEffect,
useImperativeHandle,
useRef,
useState
} from 'react';

import './TaxonomySearch.styl';
import { Block } from '../../utils/bem';
Expand All @@ -8,46 +17,81 @@ import { debounce } from 'lodash';
type TaxonomySearchProps = {
treeData: AntTaxonomyItem[],
origin: ReactNode,
onChange: (list: AntTaxonomyItem[], expandedKeys: string[] | undefined) => void,
onChange: (list: AntTaxonomyItem[], expandedKeys: React.Key[]) => void,
}

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

const TaxonomySearch = React.forwardRef<TaxonomySearchRef, TaxonomySearchProps>(({
origin,
treeData,
onChange,
}: TaxonomySearchProps) => {
}, 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(treeNode['title']).toLowerCase().includes(lowerSearchValue);
return String(lowerResultValue).toLowerCase().includes(lowerSearchValue);
}, []);

const fillLegacyProps = (dataNode: AntTaxonomyItem) => {
if (!dataNode) {
return dataNode;
}

return { ...dataNode };
};

// 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: string[] = [];
const _expandedKeys: React.Key[] = [];

if (!searchValue) {
return {
filteredDataTree:treeData,
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 match = keepAll || filterTreeNode(searchValue, fillLegacyProps(dataNode));
const childList = dig(children || [], match);

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

total.push({
...dataNode,
isLeaf: undefined,
Expand All @@ -60,8 +104,8 @@ const TaxonomySearch = ({
};

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

Expand All @@ -71,19 +115,26 @@ const TaxonomySearch = ({
onChange(_filteredData.filteredDataTree, _filteredData.expandedKeys);
}, 300), [treeData]);

return(
return (
<>
<Block
ref={inputRef}
value={inputValue}
tag={'input'}
onChange={handleSearch}
onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => e.stopPropagation()}
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
2 changes: 2 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 @@ -178,6 +179,7 @@ Object.entries(taxonomies).forEach(([title, Taxonomy]) => {
if (Taxonomy.isNew) {
LabelStudio.addFeatureFlagsOnPageLoad({
[FF_TAXONOMY_ASYNC]: true,
[FF_LEAP_218]: true,
});
}
});
Expand Down

0 comments on commit b2584b3

Please sign in to comment.