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:
Andrey Goncharov
2023-11-21 11:40:23 -08:00
committed by Facebook GitHub Bot
parent 864e296f35
commit 294f39eceb
6 changed files with 59 additions and 12 deletions

View File

@@ -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 = {

View File

@@ -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="..."

View File

@@ -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>
); );
}; };

View File

@@ -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}
/> />
); );

View File

@@ -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',

View File

@@ -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;