From a8f5fecc2bdfad05c187d15567115838e32ebc45 Mon Sep 17 00:00:00 2001 From: Andrey Goncharov Date: Wed, 8 Nov 2023 02:08:25 -0800 Subject: [PATCH] Infer enum labels Reviewed By: LukeDefeo Differential Revision: D51067952 fbshipit-source-id: ed39d3ab037a2169120187bf20bf4a023488c025 --- .../flipper-plugin/src/__tests__/api.node.tsx | 1 + .../src/ui/PowerSearch/PowerSearchConfig.tsx | 7 +- .../ui/PowerSearch/PowerSearchEnumSetTerm.tsx | 3 +- .../ui/PowerSearch/PowerSearchEnumTerm.tsx | 7 +- .../src/ui/PowerSearch/index.tsx | 9 +- .../data-table/DataTableWithPowerSearch.tsx | 99 ++++++++++++++++++- 6 files changed, 118 insertions(+), 8 deletions(-) diff --git a/desktop/flipper-plugin/src/__tests__/api.node.tsx b/desktop/flipper-plugin/src/__tests__/api.node.tsx index a42f69b26..695740797 100644 --- a/desktop/flipper-plugin/src/__tests__/api.node.tsx +++ b/desktop/flipper-plugin/src/__tests__/api.node.tsx @@ -121,6 +121,7 @@ test('Correct top level API exposed', () => { "ElementSearchResultSet", "ElementsInspectorElement", "ElementsInspectorProps", + "EnumLabels", "FieldConfig", "FileDescriptor", "FileEncoding", diff --git a/desktop/flipper-plugin/src/ui/PowerSearch/PowerSearchConfig.tsx b/desktop/flipper-plugin/src/ui/PowerSearch/PowerSearchConfig.tsx index f2775d0b1..b262ca63f 100644 --- a/desktop/flipper-plugin/src/ui/PowerSearch/PowerSearchConfig.tsx +++ b/desktop/flipper-plugin/src/ui/PowerSearch/PowerSearchConfig.tsx @@ -55,11 +55,16 @@ export type FloatOperatorConfig = { precision?: number; }; +/** + * { value: label } + */ +export type EnumLabels = {[key: string | number]: string | number}; + export type EnumOperatorConfig = { valueType: EnumFilterValueType; key: string; label: string; - enumLabels: {[key: string]: string}; + enumLabels: EnumLabels; }; export type AbsoluteDateOperatorConfig = { diff --git a/desktop/flipper-plugin/src/ui/PowerSearch/PowerSearchEnumSetTerm.tsx b/desktop/flipper-plugin/src/ui/PowerSearch/PowerSearchEnumSetTerm.tsx index 3aad64df0..6c2e08129 100644 --- a/desktop/flipper-plugin/src/ui/PowerSearch/PowerSearchEnumSetTerm.tsx +++ b/desktop/flipper-plugin/src/ui/PowerSearch/PowerSearchEnumSetTerm.tsx @@ -9,11 +9,12 @@ import {Select} from 'antd'; import React from 'react'; +import {EnumLabels} from './PowerSearchConfig'; type PowerSearchEnumSetTermProps = { onCancel: () => void; onChange: (value: string[]) => void; - enumLabels: {[key: string]: string}; + enumLabels: EnumLabels; defaultValue?: string[]; }; diff --git a/desktop/flipper-plugin/src/ui/PowerSearch/PowerSearchEnumTerm.tsx b/desktop/flipper-plugin/src/ui/PowerSearch/PowerSearchEnumTerm.tsx index 7d8fb9b73..679a9c906 100644 --- a/desktop/flipper-plugin/src/ui/PowerSearch/PowerSearchEnumTerm.tsx +++ b/desktop/flipper-plugin/src/ui/PowerSearch/PowerSearchEnumTerm.tsx @@ -9,11 +9,12 @@ import {Button, Select} from 'antd'; import React from 'react'; +import {EnumLabels} from './PowerSearchConfig'; type PowerSearchEnumTermProps = { onCancel: () => void; onChange: (value: string) => void; - enumLabels: {[key: string]: string}; + enumLabels: EnumLabels; defaultValue?: string; }; @@ -38,8 +39,8 @@ export const PowerSearchEnumTerm: React.FC = ({ let longestOptionLabelWidth = 0; Object.values(enumLabels).forEach((label) => { - if (label.length > longestOptionLabelWidth) { - longestOptionLabelWidth = label.length; + if (label.toString().length > longestOptionLabelWidth) { + longestOptionLabelWidth = label.toString().length; } }); diff --git a/desktop/flipper-plugin/src/ui/PowerSearch/index.tsx b/desktop/flipper-plugin/src/ui/PowerSearch/index.tsx index cf6b50c1f..79521d914 100644 --- a/desktop/flipper-plugin/src/ui/PowerSearch/index.tsx +++ b/desktop/flipper-plugin/src/ui/PowerSearch/index.tsx @@ -13,6 +13,7 @@ import { PowerSearchConfig, FieldConfig, OperatorConfig, + EnumLabels, } from './PowerSearchConfig'; import {PowerSearchContainer} from './PowerSearchContainer'; import { @@ -31,7 +32,13 @@ import {theme} from '../theme'; import {SearchOutlined} from '@ant-design/icons'; import {getFlipperLib} from 'flipper-plugin-core'; -export {PowerSearchConfig, OperatorConfig, FieldConfig, SearchExpressionTerm}; +export { + PowerSearchConfig, + EnumLabels, + OperatorConfig, + FieldConfig, + SearchExpressionTerm, +}; type PowerSearchProps = { config: PowerSearchConfig; diff --git a/desktop/flipper-plugin/src/ui/data-table/DataTableWithPowerSearch.tsx b/desktop/flipper-plugin/src/ui/data-table/DataTableWithPowerSearch.tsx index d7dd34108..63f1620c0 100644 --- a/desktop/flipper-plugin/src/ui/data-table/DataTableWithPowerSearch.tsx +++ b/desktop/flipper-plugin/src/ui/data-table/DataTableWithPowerSearch.tsx @@ -67,6 +67,7 @@ import { FieldConfig, OperatorConfig, SearchExpressionTerm, + EnumLabels, } from '../PowerSearch'; import { dataTablePowerSearchOperatorProcessorConfig, @@ -154,7 +155,16 @@ export type DataTableColumn = { powerSearchConfig?: | OperatorConfig[] | 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 { @@ -263,6 +273,73 @@ export function DataTable( [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>({}); + 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 = + {}; + + 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 res: PowerSearchConfig = {fields: {}}; @@ -290,6 +367,20 @@ export function DataTable( } else { columnPowerSearchOperators = column.powerSearchConfig.operators; useWholeRow = !!column.powerSearchConfig.useWholeRow; + + const inferredPowerSearchEnumLabelsForColumn = + inferredPowerSearchEnumLabels[column.key]; + if ( + inferredPowerSearchEnumLabelsForColumn && + column.powerSearchConfig.inferEnumOptionsFromData + ) { + columnPowerSearchOperators = columnPowerSearchOperators.map( + (operator) => ({ + ...operator, + enumLabels: inferredPowerSearchEnumLabelsForColumn, + }), + ); + } } const columnFieldConfig: FieldConfig = { @@ -305,7 +396,11 @@ export function DataTable( } return res; - }, [columns, props.enablePowerSearchWholeRowSearch]); + }, [ + columns, + props.enablePowerSearchWholeRowSearch, + inferredPowerSearchEnumLabels, + ]); const renderingConfig = useMemo>(() => { let startIndex = 0;