Skip to content

Commit

Permalink
feat(filter-picker): support list type (#1739)
Browse files Browse the repository at this point in the history
* feat(filter-picker): support `list` type

* fix(property-selector): fix PreviewCard style

* fix(filter-picker): 修复列表型事件属性,过滤条件,input框宽度不为100%

Co-authored-by: maxin <maxin@growingio.com>
Co-authored-by: Danny <danny@DannydeMacBook-Pro.local>
  • Loading branch information
3 people committed Dec 28, 2021
1 parent 87eff12 commit e178062
Show file tree
Hide file tree
Showing 15 changed files with 340 additions and 48 deletions.
24 changes: 24 additions & 0 deletions src/legacy/filter-picker/FilterPicker.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,30 @@ Default.args = {
valueType: null,
__typename: 'Dimension',
},
{
associatedKey: null,
description: '',
groupId: 'event',
groupName: '事件变量',
id: 'var_list_1',
isSystem: false,
key: 'var_list_1',
name: '列表型属性1_update',
type: 'var',
valueType: 'list',
},
{
associatedKey: null,
description: '',
groupId: 'event',
groupName: '事件变量',
id: 'var_list_2',
isSystem: false,
key: 'var_list_2',
name: '列表型属性2',
type: 'var',
valueType: 'list',
},
{
id: 'p',
name: '页面',
Expand Down
18 changes: 10 additions & 8 deletions src/legacy/filter-picker/FilterPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ import React, { useMemo, useState } from 'react';
import { FilterOutlined } from '@gio-design/icons';
import { useLocale } from '@gio-design/utils';
import FilterOverlay from './components/FilterOverlay/index';
import { FilterPickerProps, FilterValueType } from './interfaces';
import { FilterPickerProps, FilterValueType, operationsOptionType } from './interfaces';
import Button from '../../button'; // new
import Dropdown from '../dropdown';
import defaultLocaleTextObject from './locales/zh-CN';
import './style';
import { defaultOperationsOption } from './components/FilterList/Expression/FilterCondition';

const defaultOperationsOption: operationsOptionType = {
string: ['=', '!=', 'in', 'not in', 'like', 'not like', 'hasValue', 'noValue'],
int: ['=', '!=', '>', '>=', '<', '<=', 'between', 'not between', 'hasValue', 'noValue'],
double: ['=', '!=', '>', '>=', '<', '<=', 'between', 'not between', 'hasValue', 'noValue'],
date: ['=', '!=', '>', '<', 'relativeBetween', 'relativeCurrent', 'between', 'not between', 'hasValue', 'noValue'],
list: ['hasAll', 'not hasAll', 'empty', 'not empty'],
};

export type TextObject = typeof defaultLocaleTextObject & { code: 'zh-CN' | 'en-US' };

Expand Down Expand Up @@ -81,12 +88,7 @@ const FilterPicker = (props: FilterPickerProps) => {
disabled={disabled}
>
{children || (
<Button.IconButton
data-testid="filter-picker"
{...rest}
size="small"
type={!localVisible ? 'text' : 'secondary'}
>
<Button.IconButton data-testid="filter-picker" size="small" active={localVisible} type="secondary" {...rest}>
<FilterOutlined size="14px" />
</Button.IconButton>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useContext, useEffect, useState } from 'react';
import NumberAttrSelect from './components/NumberAttrSelect';
import DateAttrSelect from './components/DateAttrSelect';
import StringAttrSelect from './components/StringAttrSelect/index';
import ListAttrSelect from './components/ListAttrSelect';
import Footer from '../../../Footer';
import './attrSelect.less';
import {
Expand All @@ -13,7 +14,7 @@ import {
useSelectOptions,
AttributeMap,
} from './interfaces';
import { operationsOptionType, titleGroup } from '../../../../interfaces';
import { operationsOptionType, titleGroup, ListValue } from '../../../../interfaces';
import Checkbox from '../../../../../../checkbox'; // new
import Select from '../../../../../../select'; // new
import Divider from '../../../../../../divider';
Expand All @@ -23,7 +24,7 @@ interface FilterAttrOverlayProps {
valueType: attributeValue;
onSubmit: (v: FilterValueType) => void;
onCancel: () => void;
op: StringValue | NumberValue | DateValue;
op: StringValue | NumberValue | DateValue | ListValue;
curryDimensionValueRequest: (dimension: string, keyword: string) => Promise<any> | undefined;
values: string[];
exprKey: string;
Expand All @@ -36,7 +37,7 @@ function FilterAttrOverlay(props: FilterAttrOverlayProps) {
const selectOptions = useSelectOptions();
const { valueType, onSubmit, onCancel, op, curryDimensionValueRequest, values, exprKey, operationsOption, numType } =
props;
const [operationValue, setOperationValue] = useState<StringValue | NumberValue | DateValue>(op);
const [operationValue, setOperationValue] = useState<StringValue | NumberValue | DateValue | ListValue>(op);
const [attrValue, setAttrValue] = useState<string[]>(values);
const [checked, setChecked] = useState<boolean>(valueType === 'date' && (op === '>=' || op === '<='));

Expand All @@ -45,6 +46,7 @@ function FilterAttrOverlay(props: FilterAttrOverlayProps) {
int: t.int,
date: t.date,
double: t.double,
list: t.list,
};

useEffect(() => {
Expand All @@ -64,7 +66,7 @@ function FilterAttrOverlay(props: FilterAttrOverlayProps) {
}
}
}
if (values?.[0] === ' ') {
if (values?.[0] === ' ' && valueType !== 'list') {
setOperationValue(op === '!=' ? 'hasValue' : 'noValue');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand All @@ -90,9 +92,9 @@ function FilterAttrOverlay(props: FilterAttrOverlayProps) {
// 相对现在(relativeCurrent) => (relativeTime)
// 相对区间(relativeBetween) => (relativeTime)
const parseValue = (
v: StringValue | NumberValue | DateValue,
v: StringValue | NumberValue | DateValue | ListValue,
check: boolean
): StringValue | NumberValue | DateValue => {
): StringValue | NumberValue | DateValue | ListValue => {
const includesMap: { [key: string]: DateValue } = {
'>': '>=',
'<': '<=',
Expand All @@ -115,7 +117,13 @@ function FilterAttrOverlay(props: FilterAttrOverlayProps) {
const submit = () => {
const filterValue: FilterValueType = {
op: parseValue(operationValue, checked),
values: operationValue !== 'hasValue' && operationValue !== 'noValue' ? attrValue : [' '],
values:
operationValue !== 'hasValue' &&
operationValue !== 'noValue' &&
operationValue !== 'empty' &&
operationValue !== 'not empty'
? attrValue
: [' '],
};
onSubmit(filterValue);
};
Expand Down Expand Up @@ -152,6 +160,17 @@ function FilterAttrOverlay(props: FilterAttrOverlayProps) {
exprKey={exprKey}
/>
);
case AttributeMap.list:
return (
<ListAttrSelect
valueType={attr}
attrSelect={selectValue}
attrChange={setAttrValue}
curryDimensionValueRequest={curryDimensionValueRequest}
values={attrValue}
exprKey={exprKey}
/>
);
default:
return (
<NumberAttrSelect
Expand All @@ -167,12 +186,14 @@ function FilterAttrOverlay(props: FilterAttrOverlayProps) {
return (
<div className="filter-attr_select-box">
<div>
<div className="filter-attr_select-title">{titleMap[valueType] || t.string}</div>
<div className="filter-attr_select-title">{titleMap[valueType] ?? valueType}</div>
<Select
options={
operationsOption
? selectOptions?.[valueType]?.filter((opItem) =>
operationsOption?.[valueType].includes(opItem.value as any)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
operationsOption?.[valueType].includes(opItem.value)
)
: selectOptions?.[valueType]
}
Expand All @@ -194,8 +215,14 @@ function FilterAttrOverlay(props: FilterAttrOverlayProps) {
<Footer
onSubmit={submit}
onCancel={cancel}
// 当values为空,同时不时无值,有值状态下,确认按钮disable
comfirmStatus={operationValue !== 'hasValue' && operationValue !== 'noValue' && !attrValue.length}
// 当 values 为空,同时不是无值,有值状态下,确认按钮 disable
comfirmStatus={
operationValue !== 'empty' &&
operationValue !== 'not empty' &&
operationValue !== 'hasValue' &&
operationValue !== 'noValue' &&
!attrValue.length
}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import React, { useContext, useEffect, useState } from 'react';
import { Input, Loading } from '../../../../../../../..';
import List from '../../../../../../../list-pro';
import { FilterPickerContext } from '../../../../../../FilterPicker';
import { attributeValue } from '../../interfaces';

interface ListAttrSelectProps {
valueType: attributeValue;
attrChange: (v: any) => void;
curryDimensionValueRequest: (dimension: string, keyword: string) => Promise<any> | undefined;
values: string[];
exprKey: string;
attrSelect: string;
}

type checkOptionsItem = {
value: string;
label: string;
};

let timer: any = null;

function ListAttrSelect(props: ListAttrSelectProps) {
const { valueType, curryDimensionValueRequest, attrChange, values = [], exprKey, attrSelect } = props;
const [inputValue, setInputValue] = useState<string>('');
const [checkValue, setCheckValue] = useState<string[]>(values);
const { textObject } = useContext(FilterPickerContext);
// check-options
const [checkOptions, setCheckOptions] = useState<checkOptionsItem[]>([]);
// 存放本次自由输入的值
const [inputCheckList, setInputCheckList] = useState<string[]>([]);
// 副本,用来保存values的值,因为values里面包含一部分上次自由输入的值
// 暂存values的值,用来区分本次自有输入和上次自由输入的值
const [defaultList, setDefaultList] = useState<string[]>(values);
const [loadingStatue, setLoadingStatue] = useState<boolean>(true);

useEffect(() => {
setCheckValue(values);
setDefaultList(values);
// setInputValue(values?.length ? values.join(',') : '');
}, [values]);

const changInputValue = (v: React.ChangeEvent<HTMLInputElement>) => {
// 输入规则为:
// 当正常输入时,作为搜索关键字,进行查询,返回搜索结果,用户进行选择
// 当输入最后一个字符为英文逗号时,算自由输入,将用户输入的字符串直接作为选项,展示”自由输入:(string)“并为选中状态
// 多个英文逗号分隔时,为多个自由输入,展示多条”自由输入:(string)“并选中
// 当清空输入框内容时,如果有自由输入选项,自由输入选项保留,不清空,并始终保持选中状态,除非用户勾选取消
const valueList = v.target.value.split(',');
const filterValueList = valueList.filter((ele: string) => !!ele);
// 本次输入的自由输入+当前编辑情况下曾经输入过的自由输入记录
const checkList = Array.from(new Set([...filterValueList, ...inputCheckList]));

// 当input输入字符串,存在英文逗号时,将字符串以英文逗号为分割点,
if (valueList.length > 1 && valueList[valueList.length - 1] === '') {
// 以逗号结尾时,前面的字符作为自由输入的结果
const res = Array.from(new Set([...checkList, ...checkValue]));
setInputCheckList(checkList);
setCheckOptions(
checkList.map((ele: string) => ({
label: `${textObject.freeInput}${ele}`,
value: ele,
}))
);
setCheckValue(res);
attrChange(res);
} else {
setLoadingStatue(true);
const filterCheckedList: string[] = checkValue.filter((ele: string) => !checkList.includes(ele));

if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
curryDimensionValueRequest?.(exprKey, valueList[valueList.length - 1] || '')?.then((res: string[]) => {
if (res.length) {
setCheckOptions([
// 所有的自由输入选项
...inputCheckList
.filter((ele: string) => checkList.includes(ele))
.map((ele: string) => ({ label: `${textObject.freeInput}${ele}`, value: ele })),
// 已选中的,过滤掉自由输入的选项
...Array.from(new Set([...filterCheckedList, ...res])).map((ele: string) => ({ label: ele, value: ele })),
]);
} else {
setCheckOptions([
// 所有的自由输入选项
...inputCheckList
.filter((ele: string) => checkList.includes(ele))
.map((ele: string) => ({ label: `${textObject.freeInput}${ele}`, value: ele })),
]);
}
setLoadingStatue(false);
});
}, 500);
}

setInputValue(v.target.value);
};

const changeCheckValue = (checkedValue: checkOptionsItem) => {
if (!checkValue.includes(checkedValue.value)) {
setCheckValue([...checkValue, checkedValue.value]);
attrChange([...checkValue, checkedValue.value]);
} else {
const filter = checkValue.filter((ele: string) => ele !== checkedValue.value);
setCheckValue(filter);
attrChange(filter);
}
};
// 初始化check-options
useEffect(() => {
curryDimensionValueRequest?.(exprKey, '')?.then((res: string[]) => {
res.length &&
setCheckOptions(
Array.from(new Set([...defaultList, ...res])).map((ele: string) => ({
label: ele,
value: ele,
}))
);
setLoadingStatue(false);
});
}, [valueType, exprKey, attrSelect, curryDimensionValueRequest, defaultList]);

switch (attrSelect) {
case 'hasAll':
case 'not hasAll':
return (
<div style={{ height: '330px' }}>
<Input placeholder={textObject.pleaseEnter} value={inputValue} onChange={changInputValue} style={{width: '100%'}}/>
{loadingStatue ? (
<div
style={{
width: '100%',
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<Loading />
</div>
) : (
<List
isMultiple
stateless
value={checkValue}
dataSource={checkOptions}
width={293}
height={270}
onClick={changeCheckValue}
/>
)}
</div>
);

default:
return null;
}
}

export default ListAttrSelect;
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,16 @@ import parseValuesToText from './utils';
import FilterAttrOverlay from './FilterAttrOverlay';
import { attributeValue, FilterValueType, StringValue, NumberValue, DateValue } from './interfaces';
import Selector from '../../../../../selector-pro';
import { operationsOptionType } from '../../../../interfaces';
import { ListValue, operationsOptionType } from '../../../../interfaces';
import Tooltip from '../../../../../../tooltip'; // new
import { FilterPickerContext, TextObject } from '../../../../FilterPicker';
import defaultLocaleTextObject from '../../../../locales/zh-CN';

export const defaultOperationsOption: operationsOptionType = {
string: ['=', '!=', 'in', 'not in', 'like', 'not like', 'hasValue', 'noValue'],
int: ['=', '!=', '>', '>=', '<', '<=', 'between', 'not between', 'hasValue', 'noValue'],
double: ['=', '!=', '>', '>=', '<', '<=', 'between', 'not between', 'hasValue', 'noValue'],
date: ['=', '!=', '>', '<', 'relativeBetween', 'relativeCurrent', 'between', 'not between', 'hasValue', 'noValue'],
};

interface FilterConditionProps {
valueType: attributeValue;
onSubmit: (v: FilterValueType) => void;
onCancel: () => void;
op: StringValue | NumberValue | DateValue;
op: StringValue | NumberValue | DateValue | ListValue;
dimensionValueRequest?: (data: any) => Promise<any>;
timeRange: string;
measurements: any[];
Expand Down Expand Up @@ -102,7 +95,7 @@ function FilterCondition(props: FilterConditionProps) {
curryDimensionValueRequest={curryDimensionValueRequest}
values={values}
exprKey={exprKey}
operationsOption={{ ...defaultOperationsOption, ...operationsOption }}
operationsOption={operationsOption}
numType={numType}
/>
);
Expand Down

1 comment on commit e178062

@vercel
Copy link

@vercel vercel bot commented on e178062 Dec 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.