-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: adds support for globus.search.facet to generate filter UI base…
…d on configured facets and responses from the index (#28)
- Loading branch information
1 parent
ffb58a9
commit c0cce3b
Showing
9 changed files
with
422 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
"use context"; | ||
import React, { | ||
createContext, | ||
useReducer, | ||
useContext, | ||
type Dispatch, | ||
} from "react"; | ||
import { Static, getAttribute } from "../../static"; | ||
import { GFacetResult } from "./page"; | ||
|
||
const FACETS = getAttribute("globus.search.facets", []); | ||
type Facet = NonNullable< | ||
Static["data"]["attributes"]["globus"]["search"]["facets"] | ||
>[0]; | ||
|
||
export function getFacetFieldNameByName(name: string) { | ||
let match = FACETS.find((facet: Facet) => facet.name === name)?.field_name; | ||
if (!match) { | ||
match = FACETS.find( | ||
(facet: Facet) => facet.field_name === name, | ||
)?.field_name; | ||
} | ||
return match; | ||
} | ||
|
||
type SearchState = { | ||
facetFilters: Record<string, any>; | ||
}; | ||
|
||
type SearchAction = | ||
| { | ||
type: "set_facet_filter"; | ||
payload: { | ||
facet: GFacetResult; | ||
value: string[]; | ||
}; | ||
} | ||
| { | ||
type: "reset_facet_filters"; | ||
}; | ||
|
||
function searchReducer(state: SearchState, action: SearchAction) { | ||
switch (action.type) { | ||
case "set_facet_filter": { | ||
const fieldName = getFacetFieldNameByName(action.payload.facet.name); | ||
let filter; | ||
if (action.payload.value.length !== 0) { | ||
filter = { | ||
type: "match_any", | ||
field_name: fieldName, | ||
values: action.payload.value, | ||
}; | ||
} | ||
return { | ||
...state, | ||
facetFilters: { ...state.facetFilters, [fieldName]: filter }, | ||
}; | ||
} | ||
case "reset_facet_filters": | ||
return { ...state, facetFilters: initialState.facetFilters }; | ||
default: | ||
return state; | ||
} | ||
} | ||
|
||
const initialState: SearchState = { | ||
facetFilters: {}, | ||
}; | ||
|
||
const SearchContext = createContext(initialState); | ||
const SearchDispatchContext = createContext<Dispatch<SearchAction>>(() => {}); | ||
|
||
export default function SearchProvider({ | ||
children, | ||
}: React.PropsWithChildren<{}>) { | ||
const [search, dispatch] = useReducer(searchReducer, initialState); | ||
return ( | ||
<SearchContext.Provider value={search}> | ||
<SearchDispatchContext.Provider value={dispatch}> | ||
{children} | ||
</SearchDispatchContext.Provider> | ||
</SearchContext.Provider> | ||
); | ||
} | ||
|
||
export function useSearch() { | ||
return useContext(SearchContext); | ||
} | ||
|
||
export function useSearchDispatch() { | ||
return useContext(SearchDispatchContext); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
"use client"; | ||
import { | ||
HStack, | ||
InputGroup, | ||
Input, | ||
Button, | ||
Box, | ||
Stat, | ||
StatLabel, | ||
StatNumber, | ||
VStack, | ||
} from "@chakra-ui/react"; | ||
import React, { FormEvent, useState } from "react"; | ||
import SearchFacets from "./SearchFacets"; | ||
import { search as gsearch } from "@globus/sdk"; | ||
import { useSearch } from "../app/search-provider"; | ||
import { GSearchResult } from "../app/page"; | ||
import { getAttribute } from "../../static"; | ||
import ResultListing from "./ResultListing"; | ||
|
||
const SEARCH_INDEX = getAttribute("globus.search.index"); | ||
const FACETS = getAttribute("globus.search.facets", []); | ||
|
||
export function Search() { | ||
const search = useSearch(); | ||
const inputRef = React.useRef<HTMLInputElement>(null); | ||
const formRef = React.useRef<HTMLFormElement>(null); | ||
const [result, setResult] = useState<undefined | GSearchResult>(); | ||
|
||
const handleSearchSubmit = async (e: FormEvent<HTMLFormElement>) => { | ||
e.preventDefault(); | ||
const response = await gsearch.query.post(SEARCH_INDEX, { | ||
payload: { | ||
q: e.currentTarget.q.value, | ||
facets: FACETS, | ||
filters: Object.values(search.facetFilters), | ||
}, | ||
}); | ||
const results = await response.json(); | ||
setResult(results); | ||
}; | ||
|
||
return ( | ||
<> | ||
<form ref={formRef} onSubmit={handleSearchSubmit}> | ||
<HStack p={4}> | ||
<InputGroup size="md"> | ||
<Input | ||
name="q" | ||
type="search" | ||
placeholder="Start your search here..." | ||
ref={inputRef} | ||
/> | ||
</InputGroup> | ||
<Button colorScheme="brand" type="submit"> | ||
Search | ||
</Button> | ||
</HStack> | ||
<SearchFacets result={result} px={4} /> | ||
<Box> | ||
<Box p={4}> | ||
{result && result.total > 0 && ( | ||
<> | ||
<Stat size="sm"> | ||
<StatLabel>Results</StatLabel> | ||
<StatNumber>{result.total} datasets found</StatNumber> | ||
</Stat> | ||
<VStack py={2} spacing={5} align="stretch"> | ||
{result.gmeta?.map((gmeta, i) => ( | ||
<ResultListing key={i} gmeta={gmeta} /> | ||
))} | ||
</VStack> | ||
</> | ||
)} | ||
{result && result.total === 0 && <Box>No datasets found.</Box>} | ||
</Box> | ||
</Box> | ||
</form> | ||
</> | ||
); | ||
} |
Empty file.
Oops, something went wrong.