Infer enum labels

Reviewed By: LukeDefeo

Differential Revision: D51067952

fbshipit-source-id: ed39d3ab037a2169120187bf20bf4a023488c025
This commit is contained in:
Andrey Goncharov
2023-11-08 02:08:25 -08:00
committed by Facebook GitHub Bot
parent 701ae01501
commit a8f5fecc2b
6 changed files with 118 additions and 8 deletions

View File

@@ -121,6 +121,7 @@ test('Correct top level API exposed', () => {
"ElementSearchResultSet", "ElementSearchResultSet",
"ElementsInspectorElement", "ElementsInspectorElement",
"ElementsInspectorProps", "ElementsInspectorProps",
"EnumLabels",
"FieldConfig", "FieldConfig",
"FileDescriptor", "FileDescriptor",
"FileEncoding", "FileEncoding",

View File

@@ -55,11 +55,16 @@ export type FloatOperatorConfig = {
precision?: number; precision?: number;
}; };
/**
* { value: label }
*/
export type EnumLabels = {[key: string | number]: string | number};
export type EnumOperatorConfig = { export type EnumOperatorConfig = {
valueType: EnumFilterValueType; valueType: EnumFilterValueType;
key: string; key: string;
label: string; label: string;
enumLabels: {[key: string]: string}; enumLabels: EnumLabels;
}; };
export type AbsoluteDateOperatorConfig = { export type AbsoluteDateOperatorConfig = {

View File

@@ -9,11 +9,12 @@
import {Select} from 'antd'; import {Select} from 'antd';
import React from 'react'; import React from 'react';
import {EnumLabels} from './PowerSearchConfig';
type PowerSearchEnumSetTermProps = { type PowerSearchEnumSetTermProps = {
onCancel: () => void; onCancel: () => void;
onChange: (value: string[]) => void; onChange: (value: string[]) => void;
enumLabels: {[key: string]: string}; enumLabels: EnumLabels;
defaultValue?: string[]; defaultValue?: string[];
}; };

View File

@@ -9,11 +9,12 @@
import {Button, Select} from 'antd'; import {Button, Select} from 'antd';
import React from 'react'; import React from 'react';
import {EnumLabels} from './PowerSearchConfig';
type PowerSearchEnumTermProps = { type PowerSearchEnumTermProps = {
onCancel: () => void; onCancel: () => void;
onChange: (value: string) => void; onChange: (value: string) => void;
enumLabels: {[key: string]: string}; enumLabels: EnumLabels;
defaultValue?: string; defaultValue?: string;
}; };
@@ -38,8 +39,8 @@ export const PowerSearchEnumTerm: React.FC<PowerSearchEnumTermProps> = ({
let longestOptionLabelWidth = 0; let longestOptionLabelWidth = 0;
Object.values(enumLabels).forEach((label) => { Object.values(enumLabels).forEach((label) => {
if (label.length > longestOptionLabelWidth) { if (label.toString().length > longestOptionLabelWidth) {
longestOptionLabelWidth = label.length; longestOptionLabelWidth = label.toString().length;
} }
}); });

View File

@@ -13,6 +13,7 @@ import {
PowerSearchConfig, PowerSearchConfig,
FieldConfig, FieldConfig,
OperatorConfig, OperatorConfig,
EnumLabels,
} from './PowerSearchConfig'; } from './PowerSearchConfig';
import {PowerSearchContainer} from './PowerSearchContainer'; import {PowerSearchContainer} from './PowerSearchContainer';
import { import {
@@ -31,7 +32,13 @@ import {theme} from '../theme';
import {SearchOutlined} from '@ant-design/icons'; import {SearchOutlined} from '@ant-design/icons';
import {getFlipperLib} from 'flipper-plugin-core'; import {getFlipperLib} from 'flipper-plugin-core';
export {PowerSearchConfig, OperatorConfig, FieldConfig, SearchExpressionTerm}; export {
PowerSearchConfig,
EnumLabels,
OperatorConfig,
FieldConfig,
SearchExpressionTerm,
};
type PowerSearchProps = { type PowerSearchProps = {
config: PowerSearchConfig; config: PowerSearchConfig;

View File

@@ -67,6 +67,7 @@ import {
FieldConfig, FieldConfig,
OperatorConfig, OperatorConfig,
SearchExpressionTerm, SearchExpressionTerm,
EnumLabels,
} from '../PowerSearch'; } from '../PowerSearch';
import { import {
dataTablePowerSearchOperatorProcessorConfig, dataTablePowerSearchOperatorProcessorConfig,
@@ -154,7 +155,16 @@ export type DataTableColumn<T = any> = {
powerSearchConfig?: powerSearchConfig?:
| OperatorConfig[] | OperatorConfig[]
| false | false
| {operators: OperatorConfig[]; useWholeRow?: boolean}; | {
operators: OperatorConfig[];
useWholeRow?: boolean;
/**
* Auto-generate enum options based on the data.
* Requires the column to be set as a secondary "index" (single column, not a compound multi-column index).
* See https://fburl.com/code/0waicx6p
*/
inferEnumOptionsFromData?: boolean;
};
}; };
export interface TableRowRenderContext<T = any> { export interface TableRowRenderContext<T = any> {
@@ -263,6 +273,73 @@ export function DataTable<T extends object>(
[columns], [columns],
); );
// Collecting a hashmap of unique values for every column we infer the power search enum labels for (hashmap of hashmaps).
// It could be a hashmap of sets, but then we would need to convert a set to a hashpmap when rendering enum power search term, so it is just more convenient to make it a hashmap of hashmaps
const [inferredPowerSearchEnumLabels, setInferredPowerSearchEnumLabels] =
React.useState<Record<DataTableColumn['key'], EnumLabels>>({});
React.useEffect(() => {
const columnKeysToInferOptionsFor: string[] = [];
const secondaryIndeciesKeys = new Set(dataSource.secondaryIndicesKeys());
for (const column of columns) {
if (
typeof column.powerSearchConfig === 'object' &&
!Array.isArray(column.powerSearchConfig) &&
column.powerSearchConfig.inferEnumOptionsFromData
) {
if (!secondaryIndeciesKeys.has(column.key)) {
console.warn(
'inferEnumOptionsFromData work only if the same column key is specified as a DataSource secondary index! See https://fburl.com/code/0waicx6p. Missing index definition!',
column.key,
);
continue;
}
columnKeysToInferOptionsFor.push(column.key);
}
}
if (columnKeysToInferOptionsFor.length > 0) {
const getInferredLabels = () => {
const newInferredLabels: Record<DataTableColumn['key'], EnumLabels> =
{};
for (const key of columnKeysToInferOptionsFor) {
newInferredLabels[key] = {};
for (const indexValue of dataSource.getAllIndexValues([
key as keyof T,
]) ?? []) {
// `indexValue` is a stringified JSON in a format of { key: value }
const value = Object.values(JSON.parse(indexValue))[0] as string;
newInferredLabels[key][value] = value;
}
}
return newInferredLabels;
};
setInferredPowerSearchEnumLabels(getInferredLabels());
const unsubscribeIndexUpdates = dataSource.addDataListener(
'siNewIndexValue',
({firstOfKind}) => {
if (firstOfKind) {
setInferredPowerSearchEnumLabels(getInferredLabels());
}
},
);
const unsubscribeDataSourceClear = dataSource.addDataListener(
'clear',
() => {
setInferredPowerSearchEnumLabels(getInferredLabels());
},
);
return () => {
unsubscribeIndexUpdates();
unsubscribeDataSourceClear();
};
}
}, [columns, dataSource]);
const powerSearchConfig: PowerSearchConfig = useMemo(() => { const powerSearchConfig: PowerSearchConfig = useMemo(() => {
const res: PowerSearchConfig = {fields: {}}; const res: PowerSearchConfig = {fields: {}};
@@ -290,6 +367,20 @@ export function DataTable<T extends object>(
} else { } else {
columnPowerSearchOperators = column.powerSearchConfig.operators; columnPowerSearchOperators = column.powerSearchConfig.operators;
useWholeRow = !!column.powerSearchConfig.useWholeRow; useWholeRow = !!column.powerSearchConfig.useWholeRow;
const inferredPowerSearchEnumLabelsForColumn =
inferredPowerSearchEnumLabels[column.key];
if (
inferredPowerSearchEnumLabelsForColumn &&
column.powerSearchConfig.inferEnumOptionsFromData
) {
columnPowerSearchOperators = columnPowerSearchOperators.map(
(operator) => ({
...operator,
enumLabels: inferredPowerSearchEnumLabelsForColumn,
}),
);
}
} }
const columnFieldConfig: FieldConfig = { const columnFieldConfig: FieldConfig = {
@@ -305,7 +396,11 @@ export function DataTable<T extends object>(
} }
return res; return res;
}, [columns, props.enablePowerSearchWholeRowSearch]); }, [
columns,
props.enablePowerSearchWholeRowSearch,
inferredPowerSearchEnumLabels,
]);
const renderingConfig = useMemo<TableRowRenderContext<T>>(() => { const renderingConfig = useMemo<TableRowRenderContext<T>>(() => {
let startIndex = 0; let startIndex = 0;