Skip to content

Commit

Permalink
Component search upgrades (#7314)
Browse files Browse the repository at this point in the history
* renaming + new fields in AdvancedSearchFilters

* removes forwardRef from SearchLabeledInput

* Revert "removes forwardRef from SearchLabeledInput"

This reverts commit 7449183.

* autocomplete

* working Type

* switch to state

* field rename

* options in autoComplete based on const

* Updated snapshots (#7390)

Co-authored-by: vder <[email protected]>

---------

Co-authored-by: Piotr Fałdrowicz <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: vder <[email protected]>
  • Loading branch information
4 people authored Jan 8, 2025
1 parent 2623924 commit 9728145
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 138 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions designer/client/cypress/e2e/search.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe("Search Panel View", () => {
cy.get("[data-testid=search-panel]").find("input[name='type']").should("have.value", "sink");

cy.get("[data-testid=search-panel]").find("input[name='type']").click();
cy.realType(",processor");
cy.realType(",dynamicService");

cy.get("[data-testid=search-panel]").find("button[type='submit']").click();

Expand All @@ -61,13 +61,13 @@ describe("Search Panel View", () => {
cy.get("[data-testid=search-panel]").find("svg[id='advanced-search-icon']").click();
cy.get("[data-testid=search-panel]").find("input[name='type']").click();

cy.realType("sink,processor");
cy.realType("sink,dynamicSe");

cy.get("[data-testid=search-panel]").find("button[type='submit']").click();

cy.get("[data-testid=search-panel]")
.find("input[data-selector='NODES_IN_SCENARIO']")
.should("have.value", "type:(sink,processor) se");
.should("have.value", "type:(sink,dynamicSe) se");

cy.get("[data-testid=search-panel]").contains("dynamicService");
cy.get("[data-testid=search-panel]").contains("sendSms");
Expand All @@ -76,7 +76,7 @@ describe("Search Panel View", () => {
it("should filter nodes when setting up multiple selectors using form", () => {
cy.get("[data-testid=search-panel]").find("svg[id='advanced-search-icon']").click();

cy.get("[data-testid=search-panel]").find("input[name='id']").click();
cy.get("[data-testid=search-panel]").find("input[name='name']").click();
cy.realType("bounded,dynamic,send,enricher");

cy.get("[data-testid=search-panel]").find("input[name='type']").click();
Expand All @@ -86,7 +86,7 @@ describe("Search Panel View", () => {

cy.get("[data-testid=search-panel]")
.find("input[data-selector='NODES_IN_SCENARIO']")
.should("have.value", "id:(bounded,dynamic,send,enricher) type:(sink,enricher) ");
.should("have.value", "name:(bounded,dynamic,send,enricher) type:(sink,enricher)");

cy.get("[data-testid=search-panel]").contains("enricher");
cy.get("[data-testid=search-panel]").contains("sendSms");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Autocomplete, FormControl } from "@mui/material";
import { nodeInput } from "../graph/node-modal/NodeDetailsContent/NodeTableStyled";
import React from "react";

export const SearchLabeledAutocomplete = ({ children, name, options, value, setFilterFields }) => {
function handleChange(_, value) {
setFilterFields((prev) => ({ ...prev, [name]: [value] }));
}

return (
<FormControl sx={{ display: "flex", flexDirection: "column", m: 0, gap: 1, width: "100%" }}>
{children}
<Autocomplete
freeSolo
options={options}
value={value.join(",")}
onChange={handleChange}
onInputChange={handleChange}
className={nodeInput}
renderInput={(params) => (
<div ref={params.InputProps.ref}>
<input name={name} {...params.inputProps} className={nodeInput} />
</div>
)}
/>
</FormControl>
);
};

SearchLabeledAutocomplete.displayName = "SearchLabeledAutocomplete";
15 changes: 7 additions & 8 deletions designer/client/src/components/sidePanels/SearchLabeledInput.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { forwardRef, PropsWithChildren } from "react";
import React from "react";
import { FormControl } from "@mui/material";
import { nodeInput } from "../graph/node-modal/NodeDetailsContent/NodeTableStyled";
import React from "react";

export type SearchLabeledInputProps = PropsWithChildren<{
name: string;
}>;
export const SearchLabeledInput = ({ children, name, value, setFilterFields }) => {
function handleChange(event) {
setFilterFields((prev) => ({ ...prev, [name]: event.target.value.split(",") }));
}

export const SearchLabeledInput = forwardRef<HTMLInputElement, SearchLabeledInputProps>(({ children, ...props }, ref) => {
return (
<FormControl sx={{ display: "flex", flexDirection: "column", m: 0, gap: 1, width: "100%" }}>
{children}
<input ref={ref} {...props} className={nodeInput} />
<input name={name} value={value} className={nodeInput} onChange={handleChange} />
</FormControl>
);
});
};

SearchLabeledInput.displayName = "SearchLabeledInput";
Original file line number Diff line number Diff line change
@@ -1,99 +1,85 @@
import React, { MutableRefObject, useEffect, useMemo, useRef, useState } from "react";
import { Button, Box, Typography } from "@mui/material";
import React, { useEffect, useMemo } from "react";
import { Box, Button, Typography } from "@mui/material";
import { useTranslation } from "react-i18next";
import { SearchLabeledInput } from "../../sidePanels/SearchLabeledInput";
import { SearchLabel } from "../../sidePanels/SearchLabel";
import { resolveSearchQuery, searchQueryToString, selectorByName } from "./utils";
import { SearchQuery } from "./SearchResults";
import { resolveSearchQuery } from "./utils";

const transformInput = (input: string, fieldName: string) => {
return input === "" ? "" : `${fieldName}:(${input})`;
};

function extractSimpleSearchQuery(text: string): string {
const regex = /(\w+):\(([^)]*)\)/g;
let match: RegExpExecArray | null;
let lastIndex = 0;

while ((match = regex.exec(text)) !== null) {
lastIndex = regex.lastIndex;
}

const rest = text.slice(lastIndex).trim();

return rest;
}
import { SearchLabeledAutocomplete } from "../../sidePanels/SearchLabeledAutocomplete";
import { useSelector } from "react-redux";
import { getScenario } from "../../../reducers/selectors/graph";
import NodeUtils from "../../graph/NodeUtils";
import { uniq } from "lodash";
import { getComponentGroups } from "../../../reducers/selectors/settings";

export function AdvancedSearchFilters({
filterFields,
setFilterFields,
filter,
setFilter,
setCollapsedHandler,
refForm,
}: {
filterFields: SearchQuery;
setFilterFields: React.Dispatch<React.SetStateAction<SearchQuery>>;
filter: string;
setFilter: React.Dispatch<React.SetStateAction<string>>;
setCollapsedHandler: React.Dispatch<React.SetStateAction<boolean>>;
refForm: MutableRefObject<HTMLFormElement>;
}) {
const { t } = useTranslation();
//const refForm = useRef<HTMLFormElement>(null);
const componentsGroups = useSelector(getComponentGroups);
const { scenarioGraph } = useSelector(getScenario);
const allNodes = NodeUtils.nodesFromScenarioGraph(scenarioGraph);

const displayNames = useMemo(
() => ({
id: t("panels.search.field.id", "Name"),
name: t("panels.search.field.id", "Name"),
description: t("panels.search.field.description", "Description"),
type: t("panels.search.field.type", "Type"),
paramName: t("panels.search.field.paramName", "Label"),
paramValue: t("panels.search.field.paramValue", "Value"),
outputValue: t("panels.search.field.outputValue", "Output"),
edgeExpression: t("panels.search.field.edgeExpression", "Edge"),
label: t("panels.search.field.paramName", "Label"),
value: t("panels.search.field.paramValue", "Value"),
output: t("panels.search.field.outputValue", "Output"),
edge: t("panels.search.field.edgeExpression", "Edge"),
}),
[t],
);

//Here be dragons: direct DOM manipulation
useEffect(() => {
if (refForm.current) {
const searchQuery = resolveSearchQuery(filter);
const formElements = refForm.current.elements;
const componentLabels = useMemo(() => {
return new Set(
componentsGroups.flatMap((componentGroup) => componentGroup.components).map((component) => component.label.toLowerCase()),
);
}, []);

const nodeTypes = useMemo(() => {
const availableTypes = allNodes
.flatMap((node) =>
selectorByName("type")
.flatMap((s) => s.selector(node))
.filter((item) => item !== undefined),
)
.map((selectorResult) => (typeof selectorResult === "string" ? selectorResult : selectorResult?.expression))
.filter((type) => componentLabels.has(type.toLowerCase()));

return uniq(availableTypes);
}, [allNodes]);

Array.from(formElements).forEach((element: HTMLInputElement) => {
if (element.name in searchQuery) {
element.value = (searchQuery[element.name] || []).join(",");
} else {
element.value = "";
}
});
}
useEffect(() => {
const searchQuery = resolveSearchQuery(filter);
setFilterFields(searchQuery);
}, [filter]);

const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();

const formData = new FormData(event.currentTarget);

const transformedInputs = Array.from(formData.entries())
.map(([fieldName, fieldValue]) => {
const input = (fieldValue as string) || "";
return transformInput(input, fieldName);
})
.filter((input) => input !== "");

const finalText = transformedInputs.join(" ").trim() + " " + extractSimpleSearchQuery(filter);

setFilter(finalText);
setFilter(searchQueryToString(filterFields));
setCollapsedHandler(false);
};

const handleClear = () => {
setFilter(extractSimpleSearchQuery(filter));

refForm.current.reset();
setFilter(filterFields?.plainQuery);
};

return (
<Box
ref={refForm}
component="form"
onSubmit={handleSubmit}
sx={{
Expand All @@ -105,28 +91,37 @@ export function AdvancedSearchFilters({
}}
>
<Typography fontWeight="bold">{t("search.panel.advancedFilters.label", "Advanced Search")}</Typography>
<SearchLabeledInput name="id">
<SearchLabel label={displayNames["id"]} />
</SearchLabeledInput>
<SearchLabeledInput name="description">
<SearchLabel label={displayNames["description"]} />
<SearchLabeledInput name="name" value={filterFields?.name || []} setFilterFields={setFilterFields}>
<SearchLabel label={displayNames["name"]} />
</SearchLabeledInput>
<SearchLabeledInput name="paramName">
<SearchLabel label={displayNames["paramName"]} />
<SearchLabeledInput name="value" value={filterFields?.value || []} setFilterFields={setFilterFields}>
<SearchLabel label={displayNames["value"]} />
</SearchLabeledInput>
<SearchLabeledInput name="paramValue">
<SearchLabel label={displayNames["paramValue"]} />
<SearchLabeledAutocomplete name="type" options={nodeTypes} value={filterFields?.type || []} setFilterFields={setFilterFields}>
<SearchLabel label={displayNames["type"]} />
</SearchLabeledAutocomplete>
<SearchLabeledInput name="label" value={filterFields?.label || []} setFilterFields={setFilterFields}>
<SearchLabel label={displayNames["label"]} />
</SearchLabeledInput>
<SearchLabeledInput name="outputValue">
<SearchLabel label={displayNames["outputValue"]} />
<SearchLabeledInput name="description" value={filterFields?.description || []} setFilterFields={setFilterFields}>
<SearchLabel label={displayNames["description"]} />
</SearchLabeledInput>
<SearchLabeledInput name="type">
<SearchLabel label={displayNames["type"]} />
<SearchLabeledInput name="output" value={filterFields?.output || []} setFilterFields={setFilterFields}>
<SearchLabel label={displayNames["output"]} />
</SearchLabeledInput>
<SearchLabeledInput name="edgeExpression">
<SearchLabel label={displayNames["edgeExpression"]} />
<SearchLabeledInput name="edge" value={filterFields?.edge || []} setFilterFields={setFilterFields}>
<SearchLabel label={displayNames["edge"]} />
</SearchLabeledInput>
<Box sx={{ display: "flex", flexDirection: "row", justifyContent: "space-between", width: "100%", mt: 2, mb: 1 }}>
<Box
sx={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
width: "100%",
mt: 2,
mb: 1,
}}
>
<Button sx={{ width: "45%" }} size="small" variant="outlined" onClick={handleClear}>
{t("search.panel.advancedFilters.clearButton.label", "Clear")}
</Button>
Expand Down
11 changes: 6 additions & 5 deletions designer/client/src/components/toolbars/search/SearchPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@ import { AdvancedOptionsIcon, SearchIcon } from "../../table/SearchFilter";
import { Focusable } from "../../themed/InputWithIcon";
import { ToolbarPanelProps } from "../../toolbarComponents/DefaultToolbarPanel";
import { ToolbarWrapper } from "../../toolbarComponents/toolbarWrapper/ToolbarWrapper";
import { SearchResults } from "./SearchResults";
import { SearchQuery, SearchResults } from "./SearchResults";
import { SearchInputWithIcon } from "../../themed/SearchInput";
import { EventTrackingSelector, getEventTrackingProps } from "../../../containers/event-tracking";
import { Collapse } from "@mui/material";
import { AdvancedSearchFilters } from "./AdvancedSearchFilters";
import { SearchPanelStyled, TipPanelStyled } from "../../tips/Styled";
import { SearchPanelStyled } from "../../tips/Styled";

export function SearchPanel(props: ToolbarPanelProps): ReactElement {
const { t } = useTranslation();
const [filterFields, setFilterFields] = useState<SearchQuery>({});
const [filter, setFilter] = useState<string>("");
const refForm = useRef<HTMLFormElement>(null);
const clearFilter = useCallback(() => {
setFilter("");
refForm.current.reset();
setFilterFields({});
}, []);
const [advancedOptionsCollapsed, setAdvancedOptionsCollapsed] = useState(false);

Expand All @@ -44,7 +44,8 @@ export function SearchPanel(props: ToolbarPanelProps): ReactElement {
<Collapse in={advancedOptionsCollapsed} timeout="auto" unmountOnExit={false}>
<SearchPanelStyled>
<AdvancedSearchFilters
refForm={refForm}
filterFields={filterFields}
setFilterFields={setFilterFields}
filter={filter}
setFilter={setFilter}
setCollapsedHandler={setAdvancedOptionsCollapsed}
Expand Down
10 changes: 5 additions & 5 deletions designer/client/src/components/toolbars/search/SearchResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import { resetSelection } from "../../../actions/nk";
import { useWindows } from "../../../windowManager";

export type SearchQuery = {
id?: string[];
name?: string[];
description?: string[];
type?: string[];
paramName?: string[];
paramValue?: string[];
outputValue?: string[];
edgeExpression?: string[];
label?: string[];
value?: string[];
output?: string[];
edge?: string[];
plainQuery?: string;
};

Expand Down
Loading

0 comments on commit 9728145

Please sign in to comment.