Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(search): Search data fields for all component types #3476

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions editor.planx.uk/src/hooks/useSearch.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import Fuse, { IFuseOptions } from "fuse.js";
import Fuse, { FuseOptionKey, IFuseOptions } from "fuse.js";
import { useEffect, useMemo, useState } from "react";

interface UseSearchProps<T extends object> {
list: T[];
keys: string[];
keys: Array<FuseOptionKey<T>>;
}

export interface SearchResult<T extends object> {
item: T;
key: string;
matchIndices?: [number, number][];
matchIndices: [number, number][];
refIndex: number;
}

export type SearchResults<T extends object> = SearchResult<T>[];
Expand Down Expand Up @@ -39,15 +40,20 @@ export const useSearch = <T extends object>({
useEffect(() => {
const fuseResults = fuse.search(pattern);
setResults(
fuseResults.map((result) => ({
item: result.item,
key: result.matches?.[0].key || "",
// We only display the first match
matchIndices:
(result.matches?.[0].indices as [number, number][]) || undefined,
})),
fuseResults.map((result) => {
// Required type narrowing for FuseResult
if (!result.matches) throw Error("Matches missing from FuseResults");

return {
item: result.item,
key: result.matches?.[0].key || "",
// We only display the first match
matchIndices: result.matches[0].indices as [number, number][],
refIndex: result.matches[0]?.refIndex || 0,
};
}),
);
}, [pattern]);
}, [pattern, fuse]);

return { results, search: setPattern };
};
73 changes: 65 additions & 8 deletions editor.planx.uk/src/pages/FlowEditor/components/Sidebar/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Container from "@mui/material/Container";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton";
import { styled, Theme } from "@mui/material/styles";
import { styled } from "@mui/material/styles";
import Typography from "@mui/material/Typography";
import { ComponentType, IndexedNode } from "@opensystemslab/planx-core/types";
import { ICONS } from "@planx/components/ui";
Expand All @@ -14,7 +14,6 @@ import { capitalize, get } from "lodash";
import { SLUGS } from "pages/FlowEditor/data/types";
import { useStore } from "pages/FlowEditor/lib/store";
import React, { useEffect } from "react";
import { useNavigation } from "react-navi";
import { FONT_WEIGHT_BOLD, FONT_WEIGHT_SEMI_BOLD } from "theme";
import ChecklistItem from "ui/shared/ChecklistItem";
import Input from "ui/shared/Input";
Expand Down Expand Up @@ -76,7 +75,7 @@ const Headline: React.FC<HeadlineProps> = ({ text, matchIndices, variant }) => {
{text.split("").map((char, index) => (
<Typography
component="span"
variant="data"
variant={variant}
key={`headline-character-${index}`}
sx={(theme) => ({
fontWeight: isHighlighted(index) ? FONT_WEIGHT_BOLD : "regular",
Expand Down Expand Up @@ -104,7 +103,7 @@ const SearchResultCard: React.FC<{ result: SearchResult<IndexedNode> }> = ({
let Icon = ICONS[item.type];
// TODO: Generate display key from key
let displayKey = "Data";
const headline = get(item, key).toString() || "";
let headline = get(item, key)?.toString() || "";

// For Answer nodes, update display values to match the parent question
if (item.type === ComponentType.Answer) {
Expand All @@ -114,6 +113,40 @@ const SearchResultCard: React.FC<{ result: SearchResult<IndexedNode> }> = ({
displayKey = "Option (data)";
}

if (item.type === ComponentType.FileUploadAndLabel) {
headline =
(item["data"]?.["fileTypes"] as [])[result.refIndex]["fn"] || "";
displayKey = "File type (data)";
}

if (item.type === ComponentType.Calculate) {
if (result.key === "formula") {
headline = item.data!.formula;
displayKey = "Formula";
}
if (result.key === "data.output") {
headline = item.data!.output;
displayKey = "Output (data)";
}
// never?
}

if (item.type === ComponentType.List) {
if (result.key === "data.schema.fields.data.fn") {
headline = (item.data as any).schema.fields[result.refIndex].data.fn;
displayKey = "Data";
}
if (result.key === "data.schema.fields.data.options.data.val") {
// Fuse.js flattens deeply nested arrays when using refIndex
const options = (item.data as any).schema.fields.flatMap((field: any) => field.data.options)
console.log({options})
headline = options[result.refIndex].data.val;
displayKey = "Option (data)";
}
// never?

}

return {
Icon,
componentType,
Expand Down Expand Up @@ -175,7 +208,6 @@ const SearchResultCard: React.FC<{ result: SearchResult<IndexedNode> }> = ({
const ExternalPortalList: React.FC = () => {
const externalPortals = useStore((state) => state.externalPortals);
const hasExternalPortals = Object.keys(externalPortals).length;
const { navigate } = useNavigation();

if (!hasExternalPortals) return null;

Expand All @@ -202,7 +234,21 @@ const ExternalPortalList: React.FC = () => {

interface SearchNodes {
input: string;
facets: ["data.fn", "data.val"];
facets: [
"data.fn",
"data.val",
// FUAL
"data.fileTypes.fn",
// Calculate
"data.output",
{
name: "formula",
getFn: (node: IndexedNode) => string[],
},
// List
"data.schema.fields.data.fn",
"data.schema.fields.data.options.data.val",
];
}

const Search: React.FC = () => {
Expand All @@ -213,10 +259,21 @@ const Search: React.FC = () => {

useEffect(() => {
if (!orderedFlow) setOrderedFlow();
}, [setOrderedFlow]);
}, [setOrderedFlow, orderedFlow]);

const formik = useFormik<SearchNodes>({
initialValues: { input: "", facets: ["data.fn", "data.val"] },
initialValues: {
input: "",
facets: [
"data.fn",
"data.val",
"data.fileTypes.fn",
"data.output",
{ name: "formula", getFn: (node: IndexedNode) => Object.keys(node.data?.defaults || {}) },
"data.schema.fields.data.fn",
"data.schema.fields.data.options.data.val",
]
},
onSubmit: ({ input }) => {
search(input);
},
Expand Down
Loading