import React, { forwardRef, useState } from "react"; import { cl } from "../../utils/helpers"; import { AutoSuggest } from "./AutoSuggest"; import { AutoCompleteOption } from "./AutoSuggest.types"; import { TokenFilterChips } from "./FilterChip"; import type { ExternalOptions, ExternalPropertyDefinitions, ExternalQuery, ExternalToken, InternalPropertyDefinition, InternalPropertyOption, OperationT, } from "./TokenFilter.types"; import { generateAutoCompleteOptions } from "./helpers/generate-autocomplete-options"; import { parseQueryText } from "./helpers/parse-query-text"; type TokenFilterProps = { query: ExternalQuery; onChange: (newQuery: ExternalQuery) => void; className?: string; propertyDefinitions: ExternalPropertyDefinitions; options: ExternalOptions; }; /** * TODO: * - Implement onChange handler to update query state when user selects an autocomplete option. * - Handle token rendering and editing (e.g., show tokens for matched properties/operators/values, allow deleting tokens). * - Writing "stance" still shows status and hostname options */ export const TokenFilter = forwardRef( ({ query, className, propertyDefinitions, options, onChange }, ref) => { const [filterText, setFilterText] = useState(""); const [open, setOpen] = useState(false); const { parsedPropertyDefinitions, parsedPropertyOptions } = derrivedFilterState(propertyDefinitions, options); const queryState = parseQueryText(filterText, parsedPropertyDefinitions); const autoCompleteOptions = generateAutoCompleteOptions( queryState, parsedPropertyDefinitions, parsedPropertyOptions, ); const { addToken, removeToken, updateOperation } = createActionHandlers({ query, onChange, }); const createToken = (newText: string): boolean => { const newQueryState = parseQueryText(newText, parsedPropertyDefinitions); let newToken: ExternalToken | null = null; switch (newQueryState.step) { case "property": { if (newQueryState.value === "") { return false; } newToken = { propertyKey: newQueryState.property.key, operator: newQueryState.operator, value: newQueryState.value, }; break; } case "free-text": { break; } case "operator": { break; } } if (newToken) { addToken(newToken); setFilterText(""); return true; } return false; }; const handleSelectOption = (option: AutoCompleteOption) => { const newQueryState = parseQueryText( option.value, parsedPropertyDefinitions, ); if ( (newQueryState.step === "property" && newQueryState.value === "") || newQueryState.step === "operator" ) { /* Add space after for better formatting */ /* TODO: Handle this scenario better */ setFilterText(`${option.value} `); return false; } setFilterText(option.value); return createToken(option.value); }; return (
); }, ); function derrivedFilterState( propertyDefinitions: ExternalPropertyDefinitions, propteryOptions: ExternalOptions, ): { parsedPropertyDefinitions: InternalPropertyDefinition[]; parsedPropertyOptions: InternalPropertyOption[]; } { const propertyMap = new Map(); for (const property of propertyDefinitions) { propertyMap.set(property.key, { key: property.key, label: property?.label ?? "", groupLabel: property?.groupLabel ?? "", group: property?.group ?? "", operators: property?.operators ?? [], externalProperty: property, }); } const internalOptions: InternalPropertyOption[] = []; for (const option of propteryOptions) { internalOptions.push({ property: propertyMap.get(option.propertyKey) ?? null, value: option.value, label: option.label ?? option.value ?? "", tags: option.tags ?? [], }); } return { parsedPropertyDefinitions: [...propertyMap.values()], parsedPropertyOptions: internalOptions, }; } function createActionHandlers({ query, onChange, }: { query: ExternalQuery; onChange: (newQuery: ExternalQuery) => void; }) { const handleChange = (newQuery: ExternalQuery) => { onChange(newQuery); }; const addToken = (token: ExternalToken) => { handleChange({ ...query, tokens: [...query.tokens, token] }); }; const updateToken = (updateIndex: number, updatedToken: ExternalToken) => { handleChange({ ...query, tokens: query.tokens.map((token, index) => index === updateIndex ? updatedToken : token, ), }); }; const updateOperation = (operation: OperationT) => { handleChange({ ...query, operation }); }; const removeToken = (removeIndex: number) => { handleChange({ ...query, tokens: query.tokens.filter((_, index) => index !== removeIndex), }); }; const removeAllTokens = () => { handleChange({ ...query, tokens: [] }); }; return { addToken, updateToken, updateOperation, removeToken, removeAllTokens, }; }