Allow freeform entries for enum terms
Summary: Solves https://fb.workplace.com/groups/flippersupport/posts/1726178417862809/?comment_id=1726429847837666&reply_comment_id=1730206487460002 Reviewed By: passy Differential Revision: D51497924 fbshipit-source-id: d0737b2b82f29ff8ae654e7cad2ef1daa8244756
This commit is contained in:
committed by
Facebook GitHub Bot
parent
864e296f35
commit
294f39eceb
@@ -64,6 +64,7 @@ export type EnumOperatorConfig = {
|
|||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
enumLabels: EnumLabels;
|
enumLabels: EnumLabels;
|
||||||
|
allowFreeform?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AbsoluteDateOperatorConfig = {
|
export type AbsoluteDateOperatorConfig = {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type PowerSearchEnumSetTermProps = {
|
|||||||
onChange: (value: string[]) => void;
|
onChange: (value: string[]) => void;
|
||||||
enumLabels: EnumLabels;
|
enumLabels: EnumLabels;
|
||||||
defaultValue?: string[];
|
defaultValue?: string[];
|
||||||
|
allowFreeform?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PowerSearchEnumSetTerm: React.FC<PowerSearchEnumSetTermProps> = ({
|
export const PowerSearchEnumSetTerm: React.FC<PowerSearchEnumSetTermProps> = ({
|
||||||
@@ -23,6 +24,7 @@ export const PowerSearchEnumSetTerm: React.FC<PowerSearchEnumSetTermProps> = ({
|
|||||||
onChange,
|
onChange,
|
||||||
enumLabels,
|
enumLabels,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
|
allowFreeform,
|
||||||
}) => {
|
}) => {
|
||||||
const options = React.useMemo(() => {
|
const options = React.useMemo(() => {
|
||||||
return Object.entries(enumLabels).map(([key, label]) => ({
|
return Object.entries(enumLabels).map(([key, label]) => ({
|
||||||
@@ -38,7 +40,7 @@ export const PowerSearchEnumSetTerm: React.FC<PowerSearchEnumSetTermProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
mode="multiple"
|
mode={allowFreeform ? 'tags' : 'multiple'}
|
||||||
autoFocus={!defaultValue}
|
autoFocus={!defaultValue}
|
||||||
style={{minWidth: 100}}
|
style={{minWidth: 100}}
|
||||||
placeholder="..."
|
placeholder="..."
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type PowerSearchEnumTermProps = {
|
|||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
enumLabels: EnumLabels;
|
enumLabels: EnumLabels;
|
||||||
defaultValue?: string;
|
defaultValue?: string;
|
||||||
|
allowFreeform?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PowerSearchEnumTerm: React.FC<PowerSearchEnumTermProps> = ({
|
export const PowerSearchEnumTerm: React.FC<PowerSearchEnumTermProps> = ({
|
||||||
@@ -23,6 +24,7 @@ export const PowerSearchEnumTerm: React.FC<PowerSearchEnumTermProps> = ({
|
|||||||
onChange,
|
onChange,
|
||||||
enumLabels,
|
enumLabels,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
|
allowFreeform,
|
||||||
}) => {
|
}) => {
|
||||||
const [editing, setEditing] = React.useState(!defaultValue);
|
const [editing, setEditing] = React.useState(!defaultValue);
|
||||||
|
|
||||||
@@ -72,6 +74,7 @@ export const PowerSearchEnumTerm: React.FC<PowerSearchEnumTermProps> = ({
|
|||||||
if (editing) {
|
if (editing) {
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
|
mode={allowFreeform ? 'tags' : undefined}
|
||||||
autoFocus
|
autoFocus
|
||||||
style={{width}}
|
style={{width}}
|
||||||
placeholder="..."
|
placeholder="..."
|
||||||
@@ -100,7 +103,7 @@ export const PowerSearchEnumTerm: React.FC<PowerSearchEnumTermProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Button onClick={() => setEditing(true)}>
|
<Button onClick={() => setEditing(true)}>
|
||||||
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
{/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */}
|
||||||
{enumLabels[defaultValue!]}
|
{enumLabels[defaultValue!] ?? defaultValue}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ export const PowerSearchTerm: React.FC<PowerSearchTermProps> = ({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
enumLabels={searchTerm.operator.enumLabels}
|
enumLabels={searchTerm.operator.enumLabels}
|
||||||
|
allowFreeform={searchTerm.operator.allowFreeform}
|
||||||
defaultValue={searchTerm.searchValue}
|
defaultValue={searchTerm.searchValue}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -131,6 +132,7 @@ export const PowerSearchTerm: React.FC<PowerSearchTermProps> = ({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
enumLabels={searchTerm.operator.enumLabels}
|
enumLabels={searchTerm.operator.enumLabels}
|
||||||
|
allowFreeform={searchTerm.operator.allowFreeform}
|
||||||
defaultValue={searchTerm.searchValue}
|
defaultValue={searchTerm.searchValue}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -123,42 +123,51 @@ export const dataTablePowerSearchOperators = {
|
|||||||
valueType: 'FLOAT',
|
valueType: 'FLOAT',
|
||||||
}),
|
}),
|
||||||
// { [enumValue]: enumLabel }
|
// { [enumValue]: enumLabel }
|
||||||
enum_is: (enumLabels: EnumLabels) => ({
|
enum_is: (enumLabels: EnumLabels, allowFreeform?: boolean) => ({
|
||||||
label: 'is',
|
label: 'is',
|
||||||
key: 'enum_is',
|
key: 'enum_is',
|
||||||
valueType: 'ENUM',
|
valueType: 'ENUM',
|
||||||
enumLabels,
|
enumLabels,
|
||||||
|
allowFreeform,
|
||||||
}),
|
}),
|
||||||
enum_is_nullish_or: (enumLabels: EnumLabels) => ({
|
enum_is_nullish_or: (enumLabels: EnumLabels, allowFreeform?: boolean) => ({
|
||||||
label: 'is nullish or',
|
label: 'is nullish or',
|
||||||
key: 'enum_is_nullish_or',
|
key: 'enum_is_nullish_or',
|
||||||
valueType: 'ENUM',
|
valueType: 'ENUM',
|
||||||
enumLabels,
|
enumLabels,
|
||||||
|
allowFreeform,
|
||||||
}),
|
}),
|
||||||
enum_is_not: (enumLabels: EnumLabels) => ({
|
enum_is_not: (enumLabels: EnumLabels, allowFreeform?: boolean) => ({
|
||||||
label: 'is not',
|
label: 'is not',
|
||||||
key: 'enum_is_not',
|
key: 'enum_is_not',
|
||||||
valueType: 'ENUM',
|
valueType: 'ENUM',
|
||||||
enumLabels,
|
enumLabels,
|
||||||
|
allowFreeform,
|
||||||
}),
|
}),
|
||||||
// TODO: Support logical operations (AND, OR, NOT) to combine primitive operators instead of adding new complex operators!
|
// TODO: Support logical operations (AND, OR, NOT) to combine primitive operators instead of adding new complex operators!
|
||||||
enum_set_is_nullish_or_any_of: (enumLabels: EnumLabels) => ({
|
enum_set_is_nullish_or_any_of: (
|
||||||
|
enumLabels: EnumLabels,
|
||||||
|
allowFreeform?: boolean,
|
||||||
|
) => ({
|
||||||
label: 'is nullish or any of',
|
label: 'is nullish or any of',
|
||||||
key: 'enum_set_is_nullish_or_any_of',
|
key: 'enum_set_is_nullish_or_any_of',
|
||||||
valueType: 'ENUM_SET',
|
valueType: 'ENUM_SET',
|
||||||
enumLabels,
|
enumLabels,
|
||||||
|
allowFreeform,
|
||||||
}),
|
}),
|
||||||
enum_set_is_any_of: (enumLabels: EnumLabels) => ({
|
enum_set_is_any_of: (enumLabels: EnumLabels, allowFreeform?: boolean) => ({
|
||||||
label: 'is any of',
|
label: 'is any of',
|
||||||
key: 'enum_set_is_any_of',
|
key: 'enum_set_is_any_of',
|
||||||
valueType: 'ENUM_SET',
|
valueType: 'ENUM_SET',
|
||||||
enumLabels,
|
enumLabels,
|
||||||
|
allowFreeform,
|
||||||
}),
|
}),
|
||||||
enum_set_is_none_of: (enumLabels: EnumLabels) => ({
|
enum_set_is_none_of: (enumLabels: EnumLabels, allowFreeform?: boolean) => ({
|
||||||
label: 'is none of',
|
label: 'is none of',
|
||||||
key: 'enum_set_is_none_of',
|
key: 'enum_set_is_none_of',
|
||||||
valueType: 'ENUM_SET',
|
valueType: 'ENUM_SET',
|
||||||
enumLabels,
|
enumLabels,
|
||||||
|
allowFreeform,
|
||||||
}),
|
}),
|
||||||
is_nullish: () => ({
|
is_nullish: () => ({
|
||||||
label: 'is nullish',
|
label: 'is nullish',
|
||||||
|
|||||||
@@ -146,8 +146,18 @@ type DataTableInput<T = any> =
|
|||||||
};
|
};
|
||||||
|
|
||||||
type PowerSearchSimplifiedConfig =
|
type PowerSearchSimplifiedConfig =
|
||||||
| {type: 'enum'; enumLabels: EnumLabels; inferEnumOptionsFromData?: false}
|
| {
|
||||||
| {type: 'enum'; enumLabels?: never; inferEnumOptionsFromData: true}
|
type: 'enum';
|
||||||
|
enumLabels: EnumLabels;
|
||||||
|
inferEnumOptionsFromData?: false;
|
||||||
|
allowFreeform?: boolean;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'enum';
|
||||||
|
enumLabels?: never;
|
||||||
|
inferEnumOptionsFromData: true;
|
||||||
|
allowFreeform?: boolean;
|
||||||
|
}
|
||||||
| {type: 'int'}
|
| {type: 'int'}
|
||||||
| {type: 'float'}
|
| {type: 'float'}
|
||||||
| {type: 'string'}
|
| {type: 'string'}
|
||||||
@@ -163,6 +173,12 @@ type PowerSearchExtendedConfig = {
|
|||||||
* See https://fburl.com/code/0waicx6p
|
* See https://fburl.com/code/0waicx6p
|
||||||
*/
|
*/
|
||||||
inferEnumOptionsFromData?: boolean;
|
inferEnumOptionsFromData?: boolean;
|
||||||
|
/**
|
||||||
|
* Allows freeform entries for enum column types. Makes most sense together with `inferEnumOptionsFromData`.
|
||||||
|
* If `inferEnumOptionsFromData=true`, then it is `true` by default.
|
||||||
|
* See use-case https://fburl.com/workplace/0kx6fkhm
|
||||||
|
*/
|
||||||
|
allowFreeform?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const powerSearchConfigIsExtendedConfig = (
|
const powerSearchConfigIsExtendedConfig = (
|
||||||
@@ -418,10 +434,12 @@ export function DataTable<T extends object>(
|
|||||||
inferredPowerSearchEnumLabelsForColumn &&
|
inferredPowerSearchEnumLabelsForColumn &&
|
||||||
column.powerSearchConfig.inferEnumOptionsFromData
|
column.powerSearchConfig.inferEnumOptionsFromData
|
||||||
) {
|
) {
|
||||||
|
const allowFreeform = column.powerSearchConfig.allowFreeform ?? true;
|
||||||
columnPowerSearchOperators = columnPowerSearchOperators.map(
|
columnPowerSearchOperators = columnPowerSearchOperators.map(
|
||||||
(operator) => ({
|
(operator) => ({
|
||||||
...operator,
|
...operator,
|
||||||
enumLabels: inferredPowerSearchEnumLabelsForColumn,
|
enumLabels: inferredPowerSearchEnumLabelsForColumn,
|
||||||
|
allowFreeform,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -474,18 +492,30 @@ export function DataTable<T extends object>(
|
|||||||
}
|
}
|
||||||
case 'enum': {
|
case 'enum': {
|
||||||
let enumLabels: EnumLabels;
|
let enumLabels: EnumLabels;
|
||||||
|
let allowFreeform = column.powerSearchConfig.allowFreeform;
|
||||||
|
|
||||||
if (column.powerSearchConfig.inferEnumOptionsFromData) {
|
if (column.powerSearchConfig.inferEnumOptionsFromData) {
|
||||||
enumLabels = inferredPowerSearchEnumLabels[column.key] ?? {};
|
enumLabels = inferredPowerSearchEnumLabels[column.key] ?? {};
|
||||||
|
// Fallback to `true` by default when we use inferred labels
|
||||||
|
if (allowFreeform === undefined) {
|
||||||
|
allowFreeform = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
enumLabels = column.powerSearchConfig.enumLabels;
|
enumLabels = column.powerSearchConfig.enumLabels;
|
||||||
}
|
}
|
||||||
|
|
||||||
columnPowerSearchOperators = [
|
columnPowerSearchOperators = [
|
||||||
dataTablePowerSearchOperators.enum_set_is_any_of(enumLabels),
|
dataTablePowerSearchOperators.enum_set_is_any_of(
|
||||||
dataTablePowerSearchOperators.enum_set_is_none_of(enumLabels),
|
enumLabels,
|
||||||
|
allowFreeform,
|
||||||
|
),
|
||||||
|
dataTablePowerSearchOperators.enum_set_is_none_of(
|
||||||
|
enumLabels,
|
||||||
|
allowFreeform,
|
||||||
|
),
|
||||||
dataTablePowerSearchOperators.enum_set_is_nullish_or_any_of(
|
dataTablePowerSearchOperators.enum_set_is_nullish_or_any_of(
|
||||||
enumLabels,
|
enumLabels,
|
||||||
|
allowFreeform,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user