diff --git a/package-lock.json b/package-lock.json index df9a88a1..6a7685ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,9 @@ "@babel/eslint-parser": "^7.22.7", "@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/free-brands-svg-icons": "^6.4.0", + "@fortawesome/free-solid-svg-icons": "^7.2.0", "@fortawesome/pro-regular-svg-icons": "^6.4.0", + "@fortawesome/pro-solid-svg-icons": "^6.7.1", "@fortawesome/react-fontawesome": "^0.2.0", "@testing-library/cypress": "^10.0.1", "@testing-library/jest-dom": "^5.16.5", @@ -8757,6 +8759,27 @@ "node": ">=6" } }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "7.2.0", + "resolved": "https://npm.fontawesome.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-7.2.0.tgz", + "integrity": "sha512-YTVITFGN0/24PxzXrwqCgnyd7njDuzp5ZvaCx5nq/jg55kUYd94Nj8UTchBdBofi/L0nwRfjGOg0E41d2u9T1w==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "7.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons/node_modules/@fortawesome/fontawesome-common-types": { + "version": "7.2.0", + "resolved": "https://npm.fontawesome.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-7.2.0.tgz", + "integrity": "sha512-IpR0bER9FY25p+e7BmFH25MZKEwFHTfRAfhOyJubgiDnoJNsSvJ7nigLraHtp4VOG/cy8D7uiV0dLkHOne5Fhw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/@fortawesome/pro-regular-svg-icons": { "version": "6.4.0", "resolved": "https://npm.fontawesome.com/@fortawesome/pro-regular-svg-icons/-/6.4.0/pro-regular-svg-icons-6.4.0.tgz", @@ -8768,6 +8791,25 @@ "node": ">=6" } }, + "node_modules/@fortawesome/pro-solid-svg-icons": { + "version": "6.7.1", + "resolved": "https://npm.fontawesome.com/@fortawesome/pro-solid-svg-icons/-/6.7.1/pro-solid-svg-icons-6.7.1.tgz", + "integrity": "sha512-YMehuODXC+puZRJigjfB+hwCeiUsfEfiDXJV0W1iSbrqX9xBbDl0Ovbnai3EUlwIVWpgfoWew6Cx207wwVAEnA==", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/pro-solid-svg-icons/node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.7.1", + "resolved": "https://npm.fontawesome.com/@fortawesome/fontawesome-common-types/-/6.7.1/fontawesome-common-types-6.7.1.tgz", + "integrity": "sha512-gbDz3TwRrIPT3i0cDfujhshnXO9z03IT1UKRIVi/VEjpNHtSBIP2o5XSm+e816FzzCFEzAxPw09Z13n20PaQJQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/@fortawesome/react-fontawesome": { "version": "0.2.0", "resolved": "https://npm.fontawesome.com/@fortawesome/react-fontawesome/-/0.2.0/react-fontawesome-0.2.0.tgz", diff --git a/package.json b/package.json index b830ec3e..75f9243f 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,15 @@ { "name": "neuronbridge", - "version": "3.4.0", + "version": "3.5.0", "private": true, "dependencies": { "@ant-design/icons": "^5.1.4", "@babel/eslint-parser": "^7.22.7", "@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/free-brands-svg-icons": "^6.4.0", + "@fortawesome/free-solid-svg-icons": "^7.2.0", "@fortawesome/pro-regular-svg-icons": "^6.4.0", + "@fortawesome/pro-solid-svg-icons": "^6.7.1", "@fortawesome/react-fontawesome": "^0.2.0", "@testing-library/cypress": "^10.0.1", "@testing-library/jest-dom": "^5.16.5", diff --git a/public/RELEASENOTES.md b/public/RELEASENOTES.md index 6909f125..32f84f8e 100644 --- a/public/RELEASENOTES.md +++ b/public/RELEASENOTES.md @@ -1,3 +1,44 @@ +## VERSION 3.5.0 - 2026-04-17 + +### Major Features + + - **Curated Matches of Split-GAL4 Lines to Cell Types**: Search results now include a curated matches section that displays expert-annotated associations between Split-GAL4 lines and cell types + - Results are shown in a collapsible, paginated table with confidence level (Confident/Candidate), anatomical region, and source columns + - Curated matches appear above computed matches with a bookmark indicator when results are available + - See the [Curated Results section of the help page](/help#curated_results) for details on confidence levels and data sources. + + - **Line Name Links**: Line names in search result metadata are now clickable links that navigate to a new search for that line + +### UI/UX Improvements + + - **Updated Search Examples**: The example search terms have been refreshed to showcase curated matches functionality + - **Help Documentation**: Added a new help section explaining the curated matches feature + +--- + +## VERSION 3.4.0 - 2026-04-17 + +### Major Features + + - **Aligned Volume Download for Custom Searches**: Custom search results now include a download link for the aligned volume, making it easier to retrieve aligned data for further analysis + - The "View in 3D" button is now available for custom uploads when an aligned volume exists + + - **Searched Libraries Display**: The Color Depth Search step now shows which libraries were searched along with result counts, providing better visibility into search coverage + +### UI/UX Improvements + + - **External Links Open in New Tabs**: All links to external sources now open in a new tab, preventing users from losing their place on the site + +### Bug Fixes & Improvements + + - **Search & Navigation**: + - Improved search result filtering and duplicate removal logic + - Fixed external EM links to use id instead of library + - Fixed the "View" button to be clickable anywhere on the button area + - Updated search examples for accuracy + +--- + ## VERSION 3.3.1 - 2024-09-22 ### Major Features diff --git a/src/App.css b/src/App.css index 90b7a7a7..70bb13e3 100644 --- a/src/App.css +++ b/src/App.css @@ -74,3 +74,11 @@ body { list-style: none; padding: 0; } + +/* hides the anchor offset on any page that needs it */ +.anchorOffset { + display: block; + position: relative; + top: -80px; + visibility: hidden; +} diff --git a/src/components/CuratedResults.jsx b/src/components/CuratedResults.jsx new file mode 100644 index 00000000..52cf43e9 --- /dev/null +++ b/src/components/CuratedResults.jsx @@ -0,0 +1,83 @@ +import PropTypes from "prop-types"; +import { Table, Typography } from "antd"; +import { Link } from "react-router-dom"; + +const { Paragraph } = Typography; + +const columns = [ + { + title: "Line Name", + dataIndex: "name", + key: "name", + render: (name) => {name}, + }, + { + title: "Confidence", + dataIndex: "confidence", + key: "confidence", + }, + { + title: "Anatomical Region", + dataIndex: "anatomicalRegion", + key: "anatomicalRegion", + }, + { + title: "Source", + dataIndex: "source", + key: "source", + }, + { + title: "Cell Type / Neuron ID", + dataIndex: "matched", + key: "matched", + // if the match is a cell type, then we add a wildcard to the search + // to get all the sub cell types. + render: (matched, row) => {matched}, + }, +]; + +export default function CuratedResults({ results, loadError }) { + // if we didn't find anything, then don't display anything. + if (results.length === 0) { + return null; + } + + let pagination = { + position: ["bottomLeft"], + pageSizeOptions: ["2", "5", "10", "15", "20"], + defaultPageSize: 2, + showSizeChanger: true, + showQuickJumper: true + }; + + if (results.length < 3) { + pagination = false; + } + + if (loadError) { + return ( + <> + + There was a problem retrieving the curated matches. + + Reloading the page may resolve the issue. + + If this problem persists, please contact us at{" "} + + neuronbridge@janelia.hhmi.org + + . Please provide the search term used and any other relevant details. + + + ); + } + + return ( + + ); +} + +CuratedResults.propTypes = { + results: PropTypes.arrayOf(PropTypes.object).isRequired, + loadError: PropTypes.bool.isRequired, +}; diff --git a/src/components/Help/HelpButton.jsx b/src/components/Help/HelpButton.jsx index ea748feb..fe87296d 100644 --- a/src/components/Help/HelpButton.jsx +++ b/src/components/Help/HelpButton.jsx @@ -6,10 +6,17 @@ import { AppContext } from "../../containers/AppContext"; export default function HelpButton({target, text, onClick}) { const { appState, setAppState } = useContext(AppContext); - const handleHelp = () => { + const handleHelp = (event) => { + // call the onClick callback if it is provided if (onClick) { - onClick(); + onClick(event); } + // Toggle help if the button is clicked again + if (appState.helpTarget === target) { + setAppState({ ...appState, showHelp: !appState.showHelp }); + return; + } + // Show help for the target setAppState({ ...appState, showHelp: true, helpTarget: target }); } diff --git a/src/components/Help/HelpContents.jsx b/src/components/Help/HelpContents.jsx index 8ec9ce4d..8a009b60 100644 --- a/src/components/Help/HelpContents.jsx +++ b/src/components/Help/HelpContents.jsx @@ -34,29 +34,24 @@ export default function HelpContents({ scroll }) { SearchInput: useRef(), UploadAlignment: useRef(), UploadSearch: useRef(), + CuratedResults: useRef(), }; // use Effect to scroll to target set in the appState? useEffect(() => { if (scroll) { - if (refLookup[appState.helpTarget]) { - if (refLookup[appState.helpTarget].current) { - helpContentRef.current.parentElement.scrollTop = - refLookup[appState.helpTarget].current.offsetTop - 60; - refLookup[appState.helpTarget].current.classList.add("highlighted"); - window.setTimeout(() => { - if ( - refLookup[appState.helpTarget] && - refLookup[appState.helpTarget].current - ) { - refLookup[appState.helpTarget].current.classList.remove( - "highlighted" - ); - } - }, 3000); + const observer = new IntersectionObserver(([entry]) => { + if (entry.isIntersecting) { + refLookup[appState.helpTarget].current.scrollIntoView({ behavior: "instant" }); + observer.disconnect(); } + }); + observer.observe(helpContentRef.current); + return () => { + observer.disconnect(); } } + return () => {}; }, [appState.helpTarget, refLookup, scroll]); const handleResultsPerLine = (count) => { @@ -325,6 +320,33 @@ export default function HelpContents({ scroll }) {

+ + #curated_results + + Curated Results +

+ Curated results are based on human evaluations of EM cell types labeled in LM images of split-GAL4 lines. In general they should be more accurate than the average NeuronBridge image search hit. The name of the primary person evaluating the cell type is listed. +

+

+ Curated results come from two sources: +

+ + Results have one of three confidence levels: + +

+ Line Name: - {publishedName} + {publishedName}

); } @@ -64,7 +64,7 @@ export default function LineMeta({ attributes, compact, fromSearch }) {

Line Name: - {publishedName} + {publishedName}

{attributes.normalizedScore ? (

diff --git a/src/components/References.jsx b/src/components/References.jsx index d2876f14..f872e491 100644 --- a/src/components/References.jsx +++ b/src/components/References.jsx @@ -188,7 +188,7 @@ export default function References() {

Raw (uncurated) Split-GAL4 Lines
- Meissner et al., 2024 + Meissner et al., 2025
FlyWire
diff --git a/src/components/ScrollToTopOnMount.jsx b/src/components/ScrollToTopOnMount.jsx index 774b12db..8cce2ba1 100644 --- a/src/components/ScrollToTopOnMount.jsx +++ b/src/components/ScrollToTopOnMount.jsx @@ -2,6 +2,14 @@ import { useEffect } from "react"; function ScrollToTopOnMount() { useEffect(() => { + const { hash } = window.location; + if (hash) { + const element = document.getElementById(hash.slice(1)); + if (element) { + element.scrollIntoView(); + return; + } + } window.scrollTo(0, 0); }, []); diff --git a/src/components/Search.jsx b/src/components/Search.jsx index e8194dcc..055fb7a9 100644 --- a/src/components/Search.jsx +++ b/src/components/Search.jsx @@ -24,14 +24,14 @@ function Search() { if (!searchTerm) { return; } - if (searchTerm.length < 3) { + if (searchTerm.length < 2) { message.error({ duration: 0, - content: "Searches must have a minimum of 3 characters.", + content: "Searches must have a minimum of 2 characters.", key: "searchminimum", onClick: () => message.destroy("searchminimum"), }); - setResults({ error: "Searches must have a minimum of 3 characters." }); + setResults({ error: "Searches must have a minimum of 2 characters." }); return; } if (searchTerm.match(/\*(\*|\.)\*/)) { diff --git a/src/components/SearchInput.jsx b/src/components/SearchInput.jsx index 10aa9748..11a8dc59 100644 --- a/src/components/SearchInput.jsx +++ b/src/components/SearchInput.jsx @@ -48,9 +48,8 @@ export default function SearchInput({ searchTerm, examples, uploads, help }) { setSearch(searchText); }; - const exampleIds = process.env.REACT_APP_LEVEL && process.env.REACT_APP_LEVEL.match(/pre$/i) - ? ["1537331894","720575940630770042","512929","AN09B007","MBON05","*adt*","R33C10","VT002996"] - : ["1537331894","720575940630770042","512929","AN09B007","MBON05","*adt*","R33C10","VT002996"]; + const ids = ["1537331894","SS65417","MBON05","ER3a_a","12191","SLP374","720575940630770042","*adt*"]; + const exampleIds = process.env.REACT_APP_LEVEL && process.env.REACT_APP_LEVEL.match(/pre$/i) ? ids : ids; const exampleLinks = exampleIds.map((id, i) => { const url = `/search?q=${id}`; @@ -77,9 +76,9 @@ export default function SearchInput({ searchTerm, examples, uploads, help }) { > { if (!dropDownOpen) { diff --git a/src/components/SkeletonMeta.jsx b/src/components/SkeletonMeta.jsx index ac3e5855..38273c0b 100644 --- a/src/components/SkeletonMeta.jsx +++ b/src/components/SkeletonMeta.jsx @@ -18,11 +18,22 @@ function NeuronTypeOrAnnotation({ attributes }) { ); } - const neuronTypeAndInstance = `${neuronType || "-"} / ${neuronInstance || "-"}`; + const typeLink = neuronType ? ( + {neuronType} + ) : ( + "-" + ); + + const instanceLink = neuronInstance ? ( + {neuronInstance} + ) : ( + "-" + ); + return (

Neuron Type / Instance: -
{neuronTypeAndInstance} +
{typeLink} / {instanceLink}

); } diff --git a/src/components/UnifiedSearch.jsx b/src/components/UnifiedSearch.jsx index 0737f300..4fed357a 100644 --- a/src/components/UnifiedSearch.jsx +++ b/src/components/UnifiedSearch.jsx @@ -1,12 +1,16 @@ import React, { useState, useEffect, useContext } from "react"; import { useLocation, Link } from "react-router-dom"; import { Storage, Auth, API } from "aws-amplify"; -import { Spin, message, Typography } from "antd"; +import { Collapse, Spin, message, Typography } from "antd"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faBookmark } from "@fortawesome/free-solid-svg-icons"; import SearchInput from "./SearchInput"; import UnifiedSearchResults from "./UnifiedSearchResults"; +import CuratedResults from "./CuratedResults"; import NoSearch from "./NoSearch"; import { AppContext } from "../containers/AppContext"; +import HelpButton from "./Help/HelpButton"; import { setResultsFullUrlPaths } from "../libs/utils"; const { Title, Paragraph } = Typography; @@ -23,6 +27,82 @@ function splitOnLastOccurrence(str, substring) { return [before, after]; } +function filterAndSortCuratedMatches(matches) { + // expand the matches. + const expanded = matches.flatMap((match) => { + if (match.itemType === "line_name") { + return match.matches.map((m) => { + const matched = m.cell_type || `${m.dataset}:${m.body_id}`; + const addWildard = m.cell_type ? "*" : ""; + return { + name: match.name, + confidence: m.annotation, + anatomicalRegion: m.region, + matched, + type: "line_name", + addWildard, + source: m.annotator, + }; + }); + } + if (match.itemType === "cell_type") { + return match.matches.map((m) => ({ + name: m.line, + confidence: m.annotation, + anatomicalRegion: m.region, + matched: match.name, + type: "cell_type", + addWildard: "*", + source: m.annotator, + })); + } + if (match.itemType === "body_id") { + return match.matches.map((m) => ({ + name: m.line, + confidence: m.annotation, + anatomicalRegion: m.region, + matched:`${m.dataset}:${match.name}`, + type: "body_id", + addWildard: "", + source: m.annotator, + })); + } + return []; + }); + + // strip duplicates if deep comparison is the same. + const deduped = expanded.filter( + (value, index, self) => + index === + self.findIndex( + (t) => + t.name === value.name && + t.confidence === value.confidence && + t.anatomicalRegion === value.anatomicalRegion && + t.cellType === value.cellType && + t.source === value.source, + ), + ); + // sort by name and then confidence, where confident comes before candidate. + deduped.sort((a, b) => { + if (a.confidence === "Confident" && b.confidence === "Candidate") { + return -1; + } + if (a.confidence === "Candidate" && b.confidence === "Confident") { + return 1; + } + + if (a.name < b.name) { + return -1; + } + if (a.name > b.name) { + return 1; + } + return 0; + }); + return deduped; +} + export default function UnifiedSearch() { const query = useQuery(); const searchTerm = query.get("q") || ""; @@ -34,6 +114,10 @@ export default function UnifiedSearch() { const [loadError, setLoadError] = useState(false); const [bodyLoading, setBodyLoading] = useState(false); const [foundItems, setFoundItems] = useState(0); + + const [curatedResults, setCuratedResults] = useState([]); + const [curatedError, setCuratedError] = useState(false); + const { appState } = useContext(AppContext); // strip out the dataset from the body id if present and run the @@ -45,6 +129,7 @@ export default function UnifiedSearch() { ":", ); + // load the computed matches useEffect(() => { function readMetaData(metaData, combinedResults, setResults) { return new Promise((resolve, reject) => { @@ -66,7 +151,7 @@ export default function UnifiedSearch() { }); } - if (appState.dataConfig.loaded && loadedTerm !== searchTerm) { + if (appState.dataConfig.loaded && loadedTerm !== searchTerm) { setLoadedTerm(searchTerm); setByLineResults(null); setByBodyResults(null); @@ -78,24 +163,25 @@ export default function UnifiedSearch() { // don't let people search with strings shorter than 3 characters. // This returns too many results. - if (searchTerm.length < 3) { + if (searchTerm.length < 2) { message.error({ duration: 0, - content: "Searches must have a minimum of 3 characters.", + content: "Searches must have a minimum of 2 characters.", key: "searchminimum", onClick: () => message.destroy("searchminimum"), }); setByLineResults({ - error: "Searches must have a minimum of 3 characters.", + error: "Searches must have a minimum of 2 characters.", results: [], }); setByBodyResults({ - error: "Searches must have a minimum of 3 characters.", + error: "Searches must have a minimum of 2 characters.", results: [], }); return; } + if (searchTerm.match(/\*(\*|\.)\*/)) { message.error({ duration: 0, @@ -128,6 +214,8 @@ export default function UnifiedSearch() { Auth.currentCredentials().then(() => { setLoadError(false); + + API.get("SearchAPI", "/published_names", { queryStringParameters: { q: searchBodyIdOrName }, }) @@ -136,6 +224,8 @@ export default function UnifiedSearch() { const bodyCombined = { results: [] }; const publishedNames = new Set(); + const seenIds = new Set(); + const allItems = items.names .sort((a, b) => { if (a.searchKey === b.searchKey) { @@ -182,6 +272,13 @@ export default function UnifiedSearch() { return match.bodyIDs.map((body) => { publishedNames.add(body); const bodyID = body.split(":").at(-1); + // skip if we have already seen this bodyID in one of the other + // matches. This happens when we use wildcard searches that return + // results for both the neuron type and the neuron instance. eg: LHPD2c7* + if (seenIds.has(bodyID)) { + return Promise.resolve(); + } + seenIds.add(bodyID); const byBodyUrl = `${appState.dataVersion}/metadata/by_body/${bodyID}.json`; return Storage.get(byBodyUrl, storageOptions) .then((metaData) => @@ -205,6 +302,10 @@ export default function UnifiedSearch() { // once all the items have loaded, we can clean up. allPromisses.then(() => { + // Set the foundItems count to the total number of items found + // in the search results. This is used to determine if we need to + // show the 'additional matches' message. + setFoundItems(bodyCombined.results.length); // remove duplicates from the combined results. This can happen if we are // loading data from a partial neurontype string, eg: WED01 if (bodyCombined.results.length > 0) { @@ -227,14 +328,20 @@ export default function UnifiedSearch() { // have a colon in it. A missing colon means it does not require a match to // the dataset version, so the version is removed from the publishedName // in the match and then checked against the search term. - if (searchDataset && searchDataset.length > 0 && !searchDataset.includes(":")) { + if ( + searchDataset && + searchDataset.length > 0 && + !searchDataset.includes(":") + ) { bodyCombined.results = bodyCombined.results.filter((item) => { - const [dataset, ,bodyid] = item.publishedName.split(":"); + const [dataset, , bodyid] = item.publishedName.split(":"); const noVersion = `${dataset}:${bodyid}`; return noVersion.match(searchRegex); }); - // filter out items that don't match the original searchTerm if a - // dataset and version was used. + // filter out items that don't match the original searchTerm if a + // dataset and version was used. For example, if the search term + // was 'manc:v1.2.1:12191' then we want to filter out any results + // that don't match the full search term, such as 'manc:v1.0:12191' } else if (searchDataset && searchDataset.length > 0) { bodyCombined.results = bodyCombined.results.filter((item) => item.publishedName.match(searchRegex), @@ -255,7 +362,17 @@ export default function UnifiedSearch() { setBodyLoading(false); }); }) - .catch((e) => setLoadError(e)); + .catch((e) => { + message.error({ + duration: 0, + content: e?.response?.data?.error || "There was a problem contacting the search service.", + key: "curated_error", + onClick: () => message.destroy("curated_error"), + }); + + + setLoadError(true); + }); }); } }, [ @@ -267,6 +384,36 @@ export default function UnifiedSearch() { searchDataset, ]); + useEffect(() => { + if (searchTerm !== "") { + setCuratedResults([]); + Auth.currentCredentials().then(() => { + setCuratedError(false); + API.get("SearchAPI", "/curated_matches", { + queryStringParameters: { + q: searchTerm, + }, + }) + .then((response) => { + if (response.matches.length > 0) { + const sorted = filterAndSortCuratedMatches(response.matches); + setCuratedResults(sorted); + } + }) + .catch((error) => { + setCuratedError(error); + message.error({ + duration: 0, + content: error?.response?.data?.error || "There was a problem contacting the search service.", + key: "curated_error", + onClick: () => message.destroy("curated_error"), + }); + + }); + }); + } + }, [searchTerm]); + const searchError = (
System Error @@ -282,39 +429,89 @@ export default function UnifiedSearch() {
); + let computedMatches = null; + + if (loadError) { + computedMatches = searchError; + } else if (byLineResult && byBodyResult && !lineLoading && !bodyLoading) { + computedMatches = ( + <> + + {foundItems > byBodyResult.results.length ? ( +

+ + There are additional matches for your search term in different + datasets. To view them search for ‘ + + {searchBodyIdOrName} + + ’ + +

+ ) : ( + "" + )} + + ); + } else if (lineLoading || (bodyLoading && !loadError)) { + computedMatches = ( +
+ Loading... +
+ ); + } + + const curatedMatches = searchTerm ? ( + + ) : null; + + const items = []; + + if (curatedResults.length > 0) { + items.unshift({ + key: "1", + label:

Curated Matches of Split-GAL4 Lines to Cell Types ({curatedResults.length} items)

, + children: curatedMatches, + extra: ( + { + event.stopPropagation(); + }} + /> + ), + }); + } + return ( -
+
- {!searchTerm ? : ""} - {(lineLoading || bodyLoading) && !loadError ? ( -
- Loading... -
) : ""} - {loadError ? searchError : ""} - {byLineResult && byBodyResult && !lineLoading && !bodyLoading ? ( - <> - - {foundItems > byBodyResult.results.length ? ( -

- - There are additional matches for your search term in different datasets. To view them - search for ‘ - - {searchBodyIdOrName} - - ’ - -

- ) : ( - "" - )} - + {!searchTerm ? ( + ) : ( - "" +
+ {curatedResults.length > 0 ? ( + + ) : null} + {curatedResults.length > 0 ? ( + + ) : null} +
+ {computedMatches} +
)}
); diff --git a/src/components/UnifiedSearchResults.jsx b/src/components/UnifiedSearchResults.jsx index 11cddb96..b7445e9b 100644 --- a/src/components/UnifiedSearchResults.jsx +++ b/src/components/UnifiedSearchResults.jsx @@ -151,7 +151,7 @@ export default function UnifiedSearchResults(props) { onChange={(newPage) => handlePageChange(newPage)} total={resultsList.length} showTotal={(total, range) => - `Results ${range[0]}-${range[1]} of ${total}` + `Query Results ${range[0]}-${range[1]} of ${total}` } /> @@ -169,7 +169,7 @@ export default function UnifiedSearchResults(props) { onChange={(newPage) => handlePageChange(newPage)} total={resultsList.length} showTotal={(total, range) => - `Results ${range[0]}-${range[1]} of ${total}` + `Query Results ${range[0]}-${range[1]} of ${total}` } />
diff --git a/src/components/__tests__/SearchInput.jsx b/src/components/__tests__/SearchInput.jsx index 14fc238a..48b65105 100644 --- a/src/components/__tests__/SearchInput.jsx +++ b/src/components/__tests__/SearchInput.jsx @@ -12,7 +12,7 @@ describe("SearchInput: unit tests", () => { ); - expect(getByText('Search')); + expect(getByText('Query')); }); /* had to disable the accessibility test, because it throws an error due to diff --git a/src/mocks/handlers.js b/src/mocks/handlers.js index db2f85a4..6d5e9a57 100644 --- a/src/mocks/handlers.js +++ b/src/mocks/handlers.js @@ -1,8 +1,8 @@ import { rest } from "msw"; import publishedNames12288 from "./published_names_12288.json"; -import devPreConfig from "./dev_pre_config.json"; -import refsJSON from "./refs.json"; /* +import refsJSON from "./refs.json"; +import devPreConfig from "./dev_pre_config.json"; import metadata1537331894 from "./1537331894_metadata.json"; import LH173metadata from "./LH173_metadata.json"; import R16F12metadata from "./R16F12_metadata.json"; @@ -18,19 +18,76 @@ import results2988247185125302403 from "./2988247185125302403.json"; /* eslint-disable-next-line import/prefer-default-export */ export const handlers = [ - rest.get( + rest.get( "https://tj19qkjsxh.execute-api.us-east-1.amazonaws.com/published_names", (req, res, ctx) => { - if (req.url.searchParams.get('q') === '12288') { + if (req.url.searchParams.get("q") === "12288") { return res(ctx.status(200), ctx.json(publishedNames12288)); } return req.passthrough(); - } + }, ), + /* rest.get( + "https://janelia-neuronbridge-data-devpre.s3.us-east-1.amazonaws.com/v3_0_0/config.json", + (req, res, ctx) => res(ctx.status(200), ctx.json(devPreConfig)), + ), rest.get( - "https://janelia-neuronbridge-data-devpre.s3.us-east-1.amazonaws.com/v3_0_0/config.json", - (req, res, ctx) => res(ctx.status(200), ctx.json(devPreConfig)) + "https://cmcg8hqwd1.execute-api.us-east-1.amazonaws.com/curated_matches", + (req, res, ctx) => { + if (req.url.searchParams.get("search_term") === "MB018B") { + return res( + ctx.status(200), + ctx.json({ + matches: [ + { + search_term: "MB018B", + name: "MB018B", + confidence: "candidate", + anatomicalRegion: "Brain", + cellType: "KCg-d", + }, + { + search_term: "MB018B", + name: "MB018B", + confidence: "candidate", + anatomicalRegion: "Brain", + cellType: "KCg-m", + }, + { + search_term: "KCg-m", + name: "MB018B", + confidence: "candidate", + anatomicalRegion: "Brain", + cellType: "KCg-m", + }, + { + search_term: "MB018B", + name: "MB018B", + confidence: "confident", + anatomicalRegion: "Brain", + cellType: "MBON13", + }, + { + search_term: "KCg-F", + name: "MB016G", + confidence: "candidate", + anatomicalRegion: "VNC", + cellType: "KCg-F", + }, + { + search_term: "MB017C", + name: "MB017C", + confidence: "confident", + anatomicalRegion: "Brain", + cellType: "KCg-F", + }, + ], + }), + ); + } + return req.passthrough() + }, ), rest.get( "https://janelia-neuronbridge-data-dev.s3.us-east-1.amazonaws.com/v3_3_2/refs.json",