From c6f1c6852e6d3d0e81c18e8ad92f24a2a0801cc0 Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Tue, 26 Nov 2024 16:39:13 -0500 Subject: [PATCH 01/34] feat: Adds basic example of curated results table. This table contains some hard coded results and shows what it could look like when populated with data. Next steps are to add the data lookup code and tweak the layout of the table. --- package-lock.json | 20 ++++++ package.json | 1 + src/components/CuratedResults.jsx | 102 ++++++++++++++++++++++++++++++ src/components/UnifiedSearch.jsx | 2 + 4 files changed, 125 insertions(+) create mode 100644 src/components/CuratedResults.jsx diff --git a/package-lock.json b/package-lock.json index df9a88a1..31950e85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/free-brands-svg-icons": "^6.4.0", "@fortawesome/pro-regular-svg-icons": "^6.4.0", + "@fortawesome/pro-solid-svg-icons": "^6.4.0", "@fortawesome/react-fontawesome": "^0.2.0", "@testing-library/cypress": "^10.0.1", "@testing-library/jest-dom": "^5.16.5", @@ -8768,6 +8769,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..28816c6d 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/free-brands-svg-icons": "^6.4.0", "@fortawesome/pro-regular-svg-icons": "^6.4.0", + "@fortawesome/pro-solid-svg-icons": "^6.4.0", "@fortawesome/react-fontawesome": "^0.2.0", "@testing-library/cypress": "^10.0.1", "@testing-library/jest-dom": "^5.16.5", diff --git a/src/components/CuratedResults.jsx b/src/components/CuratedResults.jsx new file mode 100644 index 00000000..a59a402d --- /dev/null +++ b/src/components/CuratedResults.jsx @@ -0,0 +1,102 @@ +import { useState, useEffect } from "react"; +import PropTypes from "prop-types"; +import { Card, Table } from "antd"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faBookmark } from "@fortawesome/pro-solid-svg-icons"; +import { Link } from "react-router-dom"; + +import HelpButton from "./Help/HelpButton"; + +const columns = [ + { + title: "Line Name", + dataIndex: "name", + key: "name", + }, + { + title: "Confidence", + dataIndex: "confidence", + key: "confidence", + }, + { + title: "Anatomical Region", + dataIndex: "anatomicalRegion", + key: "anatomicalRegion", + }, + { + title: "Cell Type", + dataIndex: "cellType", + key: "cellType", + render: (cellType) => {cellType}, + }, +]; + +export default function CuratedResults({ searchTerm }) { + const [results, setResults] = useState([]); + + // use the searchTerm to fetch curated results from DynamoDB? + useEffect(() => { + // fetch results from DynamoDB + setResults([ + { + key: "1", + name: "MB018B", + confidence: "candidate", + anatomicalRegion: "Brain", + cellType: "KCg-d", + }, + { + key: "2", + name: "MB018B", + confidence: "candiate", + anatomicalRegion: "Brain", + cellType: "KCg-m", + }, + { + key: "3", + name: "MB018B", + confidence: "confident", + anatomicalRegion: "Brain", + cellType: "MBON13", + }, + ]); + }, [searchTerm]); + + // if we found anything, then display it in a table above the rest of the results. + // if we're still waiting for the fetch to complete, then display a loading spinner. + // if there was an error, then display an error message. + + // if the user has not yet entered a search term, then don't display anything. + if (!searchTerm) { + return null; + } + + // if we didn't find anything, then don't display anything. + if (results.length === 0) { + return null; + } + + return ( + } + style={{ marginBottom: "2em", position: "relative" }} + > + + + + ); +} + +CuratedResults.propTypes = { + searchTerm: PropTypes.string.isRequired, +}; diff --git a/src/components/UnifiedSearch.jsx b/src/components/UnifiedSearch.jsx index 0737f300..6bec1253 100644 --- a/src/components/UnifiedSearch.jsx +++ b/src/components/UnifiedSearch.jsx @@ -5,6 +5,7 @@ import { Spin, message, Typography } from "antd"; import SearchInput from "./SearchInput"; import UnifiedSearchResults from "./UnifiedSearchResults"; +import CuratedResults from "./CuratedResults"; import NoSearch from "./NoSearch"; import { AppContext } from "../containers/AppContext"; import { setResultsFullUrlPaths } from "../libs/utils"; @@ -291,6 +292,7 @@ export default function UnifiedSearch() { Loading... ) : ""} {loadError ? searchError : ""} + {searchTerm ? : ""} {byLineResult && byBodyResult && !lineLoading && !bodyLoading ? ( <> Date: Tue, 3 Dec 2024 17:10:38 -0500 Subject: [PATCH 02/34] feat: Adds Curated Results section to help. This adds a new section to the help page that will describe the curated results section of the search. --- src/App.css | 8 ++++++ src/components/CuratedResults.jsx | 2 +- src/components/Help/HelpContents.jsx | 41 ++++++++++++++++++---------- src/components/HelpPage.css | 7 +---- src/components/UnifiedSearch.jsx | 38 ++++++++++++++++++-------- 5 files changed, 63 insertions(+), 33 deletions(-) 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 index a59a402d..a03b711d 100644 --- a/src/components/CuratedResults.jsx +++ b/src/components/CuratedResults.jsx @@ -79,7 +79,7 @@ export default function CuratedResults({ searchTerm }) { return ( } + extra={} style={{ marginBottom: "2em", position: "relative" }} > { 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) => { @@ -324,6 +319,22 @@ export default function HelpContents({ scroll }) {

+ + + #curated_results + + Curated Results +

+ Curated results show detailed information about... +

+ + + 0 && !searchDataset.includes(":")) { + if ( + searchDataset && + searchDataset.length > 0 && + !searchDataset.includes(":") + ) { bodyCombined.results = bodyCombined.results.filter((item) => { - const [dataset, ,bodyid] = item.publishedName.split(":"); + const [dataset, version, 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. } else if (searchDataset && searchDataset.length > 0) { bodyCombined.results = bodyCombined.results.filter((item) => item.publishedName.match(searchRegex), @@ -287,12 +292,23 @@ export default function UnifiedSearch() {
{!searchTerm ? : ""} + {searchTerm ? ( + <> + +

Computed Image Matches

+ + ) : ( + "" + )} {(lineLoading || bodyLoading) && !loadError ? (
- Loading... -
) : ""} + Loading... +
+ ) : ( + "" + )} {loadError ? searchError : ""} - {searchTerm ? : ""} + {byLineResult && byBodyResult && !lineLoading && !bodyLoading ? ( <> byBodyResult.results.length ? (

- There are additional matches for your search term in different datasets. To view them - search for ‘ + There are additional matches for your search term in different + datasets. To view them search for ‘ {searchBodyIdOrName} From b51686aa78557a820ddcc37a58ae1c1070ae370a Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Wed, 4 Dec 2024 10:36:39 -0500 Subject: [PATCH 03/34] feat: Adds API endpoint fetch to CuratedResults. Adds the endpoint that returns the curated matches from our DynamoDB. --- src/components/CuratedResults.jsx | 100 ++++++++++++++++++++++-------- src/mocks/handlers.js | 65 +++++++++++++++++-- 2 files changed, 134 insertions(+), 31 deletions(-) diff --git a/src/components/CuratedResults.jsx b/src/components/CuratedResults.jsx index a03b711d..a7dbbe1f 100644 --- a/src/components/CuratedResults.jsx +++ b/src/components/CuratedResults.jsx @@ -1,17 +1,21 @@ import { useState, useEffect } from "react"; import PropTypes from "prop-types"; -import { Card, Table } from "antd"; +import { Card, Table, Typography } from "antd"; +import { Auth, API } from "aws-amplify"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faBookmark } from "@fortawesome/pro-solid-svg-icons"; import { Link } from "react-router-dom"; import HelpButton from "./Help/HelpButton"; +const { Paragraph } = Typography; + const columns = [ { title: "Line Name", dataIndex: "name", key: "name", + render: (name) => {name}, }, { title: "Confidence", @@ -31,35 +35,57 @@ const columns = [ }, ]; +function filterAndSortCuratedMatches(matches) { + // strip duplicates if cellType and name are the same + const deduped = matches.filter( + (v, i, a) => + a.findIndex((t) => t.cellType === v.cellType && t.name === v.name) === + i, + ); + // sort by name and then confidence, where confident comes before candidate. + deduped.sort((a, b) => { + if (a.name < b.name) { + return -1; + } + if (a.name > b.name) { + return 1; + } + if (a.confidence === "confident" && b.confidence === "candidate") { + return -1; + } + if (a.confidence === "candidate" && b.confidence === "confident") { + return 1; + } + return 0; + }); + return deduped; +} + export default function CuratedResults({ searchTerm }) { const [results, setResults] = useState([]); + const [loadError, setLoadError] = useState(false); // use the searchTerm to fetch curated results from DynamoDB? useEffect(() => { - // fetch results from DynamoDB - setResults([ - { - key: "1", - name: "MB018B", - confidence: "candidate", - anatomicalRegion: "Brain", - cellType: "KCg-d", - }, - { - key: "2", - name: "MB018B", - confidence: "candiate", - anatomicalRegion: "Brain", - cellType: "KCg-m", - }, - { - key: "3", - name: "MB018B", - confidence: "confident", - anatomicalRegion: "Brain", - cellType: "MBON13", - }, - ]); + if (searchTerm !== "") { + Auth.currentCredentials().then(() => { + setLoadError(false); + API.get("SearchAPI", "/curated_matches", { + queryStringParameters: { + search_term: searchTerm, + }, + }) + .then((response) => { + if (response.curated_matches.length > 0) { + const sorted = filterAndSortCuratedMatches(response.curated_matches); + setResults(sorted); + } + }) + .catch((error) => { + setLoadError(error); + }); + }); + } }, [searchTerm]); // if we found anything, then display it in a table above the rest of the results. @@ -76,9 +102,31 @@ export default function CuratedResults({ searchTerm }) { return null; } + if (loadError) { + return ( + } + style={{ marginBottom: "2em" }} + > + + 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 + + . + + + ); + } + return ( } style={{ marginBottom: "2em", position: "relative" }} > diff --git a/src/mocks/handlers.js b/src/mocks/handlers.js index db2f85a4..f05c52e7 100644 --- a/src/mocks/handlers.js +++ b/src/mocks/handlers.js @@ -18,19 +18,74 @@ 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)) + "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://cmcg8hqwd1.execute-api.us-east-1.amazonaws.com/curated_matches", + (req, res, ctx) => { + console.log(req.url.searchParams.get("q")); + return res( + ctx.status(200), + ctx.json({ + curated_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", + }, + ], + }), + ); + }, ), rest.get( "https://janelia-neuronbridge-data-dev.s3.us-east-1.amazonaws.com/v3_3_2/refs.json", From f489f263e7f3b40de1b6ed4f3221b7fdc159a466 Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Tue, 10 Dec 2024 16:23:31 -0500 Subject: [PATCH 04/34] feat: Fixes up curated matches processing. Now that the API has been created, the result processing needed to be fixed, because the response from the API differed from the one that was being used during testing. --- src/components/CuratedResults.jsx | 43 +++++++++--- src/mocks/handlers.js | 108 +++++++++++++++--------------- 2 files changed, 87 insertions(+), 64 deletions(-) diff --git a/src/components/CuratedResults.jsx b/src/components/CuratedResults.jsx index a7dbbe1f..820c693d 100644 --- a/src/components/CuratedResults.jsx +++ b/src/components/CuratedResults.jsx @@ -36,24 +36,45 @@ const columns = [ ]; function filterAndSortCuratedMatches(matches) { + // expand the matches. + const expanded = matches.flatMap((match) => { + if (match.itemType === "line_name") { + return match.matches.map((m) => ({ + name: match.name, + confidence: m.annotation, + anatomicalRegion: m.region, + cellType: m.cell_type, + })); + } + if (match.itemType === "cell_type") { + return match.matches.map((m) => ({ + name: m.line, + confidence: m.annotation, + anatomicalRegion: m.region, + cellType: match.name, + })); + } + return []; + }); + // strip duplicates if cellType and name are the same - const deduped = matches.filter( + const deduped = expanded.filter( (v, i, a) => - a.findIndex((t) => t.cellType === v.cellType && t.name === v.name) === - i, + a.findIndex((t) => t.cellType === v.cellType && t.name === v.name) === i, ); // sort by name and then confidence, where confident comes before candidate. deduped.sort((a, b) => { - if (a.name < b.name) { + if (a.confidence === "Confident" && b.confidence === "Candidate") { return -1; } - if (a.name > b.name) { + if (a.confidence === "Candidate" && b.confidence === "Confident") { return 1; } - if (a.confidence === "confident" && b.confidence === "candidate") { + + if (a.name < b.name) { return -1; } - if (a.confidence === "candidate" && b.confidence === "confident") { + if (a.name > b.name) { return 1; } return 0; @@ -72,12 +93,12 @@ export default function CuratedResults({ searchTerm }) { setLoadError(false); API.get("SearchAPI", "/curated_matches", { queryStringParameters: { - search_term: searchTerm, + q: searchTerm, }, }) .then((response) => { - if (response.curated_matches.length > 0) { - const sorted = filterAndSortCuratedMatches(response.curated_matches); + if (response.matches.length > 0) { + const sorted = filterAndSortCuratedMatches(response.matches); setResults(sorted); } }) @@ -118,7 +139,7 @@ export default function CuratedResults({ searchTerm }) { neuronbridge@janelia.hhmi.org - . + . Please provide the search term used and any other relevant details. ); diff --git a/src/mocks/handlers.js b/src/mocks/handlers.js index f05c52e7..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"; @@ -28,63 +28,65 @@ export const handlers = [ }, ), - rest.get( + /* 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://cmcg8hqwd1.execute-api.us-east-1.amazonaws.com/curated_matches", (req, res, ctx) => { - console.log(req.url.searchParams.get("q")); - return res( - ctx.status(200), - ctx.json({ - curated_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", - }, - ], - }), - ); + 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( From 8df3ec9788912f81a231d746c4dd7a2539348eb5 Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Wed, 11 Dec 2024 10:54:24 -0500 Subject: [PATCH 05/34] feat: Places curated matches in a collapsible element This makes it possible to hide the curated matches and view the computed matches, if the curated matches are not what you are looking for. --- src/components/CuratedResults.jsx | 111 +--------------- src/components/Help/HelpButton.jsx | 11 +- src/components/UnifiedSearch.jsx | 198 +++++++++++++++++++++++------ 3 files changed, 170 insertions(+), 150 deletions(-) diff --git a/src/components/CuratedResults.jsx b/src/components/CuratedResults.jsx index 820c693d..0f44f4fa 100644 --- a/src/components/CuratedResults.jsx +++ b/src/components/CuratedResults.jsx @@ -1,9 +1,5 @@ -import { useState, useEffect } from "react"; import PropTypes from "prop-types"; import { Card, Table, Typography } from "antd"; -import { Auth, API } from "aws-amplify"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faBookmark } from "@fortawesome/pro-solid-svg-icons"; import { Link } from "react-router-dom"; import HelpButton from "./Help/HelpButton"; @@ -35,89 +31,7 @@ const columns = [ }, ]; -function filterAndSortCuratedMatches(matches) { - // expand the matches. - const expanded = matches.flatMap((match) => { - if (match.itemType === "line_name") { - return match.matches.map((m) => ({ - name: match.name, - confidence: m.annotation, - anatomicalRegion: m.region, - cellType: m.cell_type, - })); - } - if (match.itemType === "cell_type") { - return match.matches.map((m) => ({ - name: m.line, - confidence: m.annotation, - anatomicalRegion: m.region, - cellType: match.name, - })); - } - return []; - }); - - // strip duplicates if cellType and name are the same - const deduped = expanded.filter( - (v, i, a) => - a.findIndex((t) => t.cellType === v.cellType && t.name === v.name) === i, - ); - // 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 CuratedResults({ searchTerm }) { - const [results, setResults] = useState([]); - const [loadError, setLoadError] = useState(false); - - // use the searchTerm to fetch curated results from DynamoDB? - useEffect(() => { - if (searchTerm !== "") { - Auth.currentCredentials().then(() => { - setLoadError(false); - API.get("SearchAPI", "/curated_matches", { - queryStringParameters: { - q: searchTerm, - }, - }) - .then((response) => { - if (response.matches.length > 0) { - const sorted = filterAndSortCuratedMatches(response.matches); - setResults(sorted); - } - }) - .catch((error) => { - setLoadError(error); - }); - }); - } - }, [searchTerm]); - - // if we found anything, then display it in a table above the rest of the results. - // if we're still waiting for the fetch to complete, then display a loading spinner. - // if there was an error, then display an error message. - - // if the user has not yet entered a search term, then don't display anything. - if (!searchTerm) { - return null; - } - +export default function CuratedResults({ results, loadError }) { // if we didn't find anything, then don't display anything. if (results.length === 0) { return null; @@ -145,27 +59,10 @@ export default function CuratedResults({ searchTerm }) { ); } - return ( - } - style={{ marginBottom: "2em", position: "relative" }} - > - -

- - ); + return
; } CuratedResults.propTypes = { - searchTerm: PropTypes.string.isRequired, + results: PropTypes.array.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/UnifiedSearch.jsx b/src/components/UnifiedSearch.jsx index 960731b3..96429e15 100644 --- a/src/components/UnifiedSearch.jsx +++ b/src/components/UnifiedSearch.jsx @@ -1,13 +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/pro-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; @@ -24,6 +27,53 @@ 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) => ({ + name: match.name, + confidence: m.annotation, + anatomicalRegion: m.region, + cellType: m.cell_type, + })); + } + if (match.itemType === "cell_type") { + return match.matches.map((m) => ({ + name: m.line, + confidence: m.annotation, + anatomicalRegion: m.region, + cellType: match.name, + })); + } + return []; + }); + + // strip duplicates if cellType and name are the same + const deduped = expanded.filter( + (v, i, a) => + a.findIndex((t) => t.cellType === v.cellType && t.name === v.name) === i, + ); + // 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") || ""; @@ -35,6 +85,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 @@ -46,6 +100,7 @@ export default function UnifiedSearch() { ":", ); + // load the computed matches useEffect(() => { function readMetaData(metaData, combinedResults, setResults) { return new Promise((resolve, reject) => { @@ -273,6 +328,29 @@ 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); + }); + }); + } + }, [searchTerm]); + const searchError = (
System Error @@ -288,51 +366,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 = [ + { + key: "2", + label: "Computed Image Matches", + children: computedMatches, + }, + ]; + + if (curatedResults.length > 0) { + items.unshift({ + key: "1", + label: "Curated Matches", + children: curatedMatches, + extra: ( + { + event.stopPropagation(); + }} + /> + ), + }); + } + return (
- {!searchTerm ? : ""} - {searchTerm ? ( - <> - -

Computed Image Matches

- + {!searchTerm ? ( + ) : ( - "" - )} - {(lineLoading || bodyLoading) && !loadError ? ( -
- Loading... +
+ { curatedResults.length > 0 ? (): null} +
- ) : ( - "" - )} - {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} - - ’ - -

- ) : ( - "" - )} - - ) : ( - "" )}
); From 81dcfbb35f675122ac245905f14fee9eb2de4ad2 Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Wed, 11 Dec 2024 15:05:28 -0500 Subject: [PATCH 06/34] feat: adds pagination to the curate matches table. --- src/components/CuratedResults.jsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/CuratedResults.jsx b/src/components/CuratedResults.jsx index 0f44f4fa..2193c9b0 100644 --- a/src/components/CuratedResults.jsx +++ b/src/components/CuratedResults.jsx @@ -37,6 +37,17 @@ export default function CuratedResults({ results, loadError }) { return null; } + let pagination = { + position: ['topLeft'], + showSizeChanger: true, + showQuickJumper: true, + showTotal: (total) => `Total ${total} items`, + }; + + if (results.length < 6) { + pagination = false; + } + if (loadError) { return ( ; + return
; } CuratedResults.propTypes = { From 0004e2dc34394b6c99fff40b7576181895a90a10 Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Wed, 11 Dec 2024 15:08:37 -0500 Subject: [PATCH 07/34] feat: curated matches sets page size to 5 --- src/components/CuratedResults.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/CuratedResults.jsx b/src/components/CuratedResults.jsx index 2193c9b0..758c8c65 100644 --- a/src/components/CuratedResults.jsx +++ b/src/components/CuratedResults.jsx @@ -39,6 +39,8 @@ export default function CuratedResults({ results, loadError }) { let pagination = { position: ['topLeft'], + pageSizeOptions: ['5', '10', '15', '20'], + defaultPageSize: 5, showSizeChanger: true, showQuickJumper: true, showTotal: (total) => `Total ${total} items`, From f0a346dfb93b5885eaf1e011efcb19e5c01bd49b Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Tue, 17 Dec 2024 08:54:05 -0500 Subject: [PATCH 08/34] fix: removes card border around curated matches error --- src/components/CuratedResults.jsx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/components/CuratedResults.jsx b/src/components/CuratedResults.jsx index 758c8c65..744476d3 100644 --- a/src/components/CuratedResults.jsx +++ b/src/components/CuratedResults.jsx @@ -1,9 +1,7 @@ import PropTypes from "prop-types"; -import { Card, Table, Typography } from "antd"; +import { Table, Typography } from "antd"; import { Link } from "react-router-dom"; -import HelpButton from "./Help/HelpButton"; - const { Paragraph } = Typography; const columns = [ @@ -52,11 +50,7 @@ export default function CuratedResults({ results, loadError }) { if (loadError) { return ( - } - style={{ marginBottom: "2em" }} - > + <> There was a problem retrieving the curated matches. @@ -68,7 +62,7 @@ export default function CuratedResults({ results, loadError }) { . Please provide the search term used and any other relevant details. - + ); } From 02557e0a313817ced6c5d3930858a544500f02e0 Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Tue, 17 Dec 2024 08:54:47 -0500 Subject: [PATCH 09/34] fix: Changes title of curated matches container. --- src/components/UnifiedSearch.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/UnifiedSearch.jsx b/src/components/UnifiedSearch.jsx index 96429e15..a89f8351 100644 --- a/src/components/UnifiedSearch.jsx +++ b/src/components/UnifiedSearch.jsx @@ -417,7 +417,7 @@ export default function UnifiedSearch() { if (curatedResults.length > 0) { items.unshift({ key: "1", - label: "Curated Matches", + label: "Curated Matches of Split-GAL4 Lines to Cell Types", children: curatedMatches, extra: ( Date: Tue, 17 Dec 2024 09:07:24 -0500 Subject: [PATCH 10/34] fix: Constrains maximum width of search results. To prevent them from being spread too far across wide screens. --- src/components/UnifiedSearch.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/UnifiedSearch.jsx b/src/components/UnifiedSearch.jsx index a89f8351..4599ac69 100644 --- a/src/components/UnifiedSearch.jsx +++ b/src/components/UnifiedSearch.jsx @@ -431,7 +431,7 @@ export default function UnifiedSearch() { } return ( -
+
{!searchTerm ? ( From 9060790b9d8561d007c1bf0865450f53cdb80f3e Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Tue, 17 Dec 2024 09:14:00 -0500 Subject: [PATCH 11/34] feat: Adds "search" link to Neuron type/instance The type and instance text of a match now links back to a search using that text. --- src/components/SkeletonMeta.jsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) 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}

); } From e75f4d8be926a0cc20c3ad97748bb65563a7eac5 Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Tue, 17 Dec 2024 09:17:16 -0500 Subject: [PATCH 12/34] fix: Removes collapse box around computed matches. --- src/components/UnifiedSearch.jsx | 40 ++++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/components/UnifiedSearch.jsx b/src/components/UnifiedSearch.jsx index 4599ac69..295a6322 100644 --- a/src/components/UnifiedSearch.jsx +++ b/src/components/UnifiedSearch.jsx @@ -406,13 +406,7 @@ export default function UnifiedSearch() { ) : null; - const items = [ - { - key: "2", - label: "Computed Image Matches", - children: computedMatches, - }, - ]; + const items = []; if (curatedResults.length > 0) { items.unshift({ @@ -431,23 +425,29 @@ export default function UnifiedSearch() { } return ( -
+
{!searchTerm ? ( ) : ( -
- { curatedResults.length > 0 ? (): null} - +
+ {curatedResults.length > 0 ? ( + + ) : null} + {curatedResults.length > 0 ? ( + + ) : null} +
+ {computedMatches}
)}
From 60579bbb6d9b4df1b03a2689765598c6475a0aa6 Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Tue, 7 Jan 2025 11:22:35 -0500 Subject: [PATCH 13/34] feat: Adds "Source" column to curated results. --- src/components/CuratedResults.jsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/CuratedResults.jsx b/src/components/CuratedResults.jsx index 744476d3..bd765377 100644 --- a/src/components/CuratedResults.jsx +++ b/src/components/CuratedResults.jsx @@ -21,6 +21,11 @@ const columns = [ dataIndex: "anatomicalRegion", key: "anatomicalRegion", }, + { + title: "Source", + dataIndex: "source", + key: "source", + }, { title: "Cell Type", dataIndex: "cellType", From 7e436f2392b96b73b6b5a953e9f720ce1e2f2054 Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Tue, 7 Jan 2025 11:23:05 -0500 Subject: [PATCH 14/34] fix: sets default to 2 items per page for curated results. --- src/components/CuratedResults.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/CuratedResults.jsx b/src/components/CuratedResults.jsx index bd765377..b575a3ef 100644 --- a/src/components/CuratedResults.jsx +++ b/src/components/CuratedResults.jsx @@ -42,8 +42,8 @@ export default function CuratedResults({ results, loadError }) { let pagination = { position: ['topLeft'], - pageSizeOptions: ['5', '10', '15', '20'], - defaultPageSize: 5, + pageSizeOptions: ['2', '5', '10', '15', '20'], + defaultPageSize: 2, showSizeChanger: true, showQuickJumper: true, showTotal: (total) => `Total ${total} items`, From 9f241285b26c46865f990dd5d79313d53446f03f Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Tue, 7 Jan 2025 11:23:43 -0500 Subject: [PATCH 15/34] fix: Changes 'Search' button to 'Query' button. This plus the change of 'Results' to 'Query Results' is supposed to indicate that the user is querying the meta data and does not get a "search" result until they click on the Color Depth Search Results button or the PatchPerPixMatch Results button. --- src/components/SearchInput.jsx | 4 ++-- src/components/UnifiedSearchResults.jsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/SearchInput.jsx b/src/components/SearchInput.jsx index 10aa9748..1486f6dd 100644 --- a/src/components/SearchInput.jsx +++ b/src/components/SearchInput.jsx @@ -77,9 +77,9 @@ export default function SearchInput({ searchTerm, examples, uploads, help }) { > { if (!dropDownOpen) { 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}` } />
From b54e4e89c25e40a2404da076515a0a7f667aaf09 Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Tue, 7 Jan 2025 13:50:02 -0500 Subject: [PATCH 16/34] fix: hides pagination when curated results <= 2. Removes the pagination controls if there two or fewer results as the default page size has been changed to two items per page. --- package-lock.json | 2 +- package.json | 2 +- src/components/CuratedResults.jsx | 17 +++++++++-------- src/components/UnifiedSearch.jsx | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 31950e85..40445ea2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/free-brands-svg-icons": "^6.4.0", "@fortawesome/pro-regular-svg-icons": "^6.4.0", - "@fortawesome/pro-solid-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/package.json b/package.json index 28816c6d..1aa3a38e 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/free-brands-svg-icons": "^6.4.0", "@fortawesome/pro-regular-svg-icons": "^6.4.0", - "@fortawesome/pro-solid-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/src/components/CuratedResults.jsx b/src/components/CuratedResults.jsx index b575a3ef..cc090e0a 100644 --- a/src/components/CuratedResults.jsx +++ b/src/components/CuratedResults.jsx @@ -41,17 +41,16 @@ export default function CuratedResults({ results, loadError }) { } let pagination = { - position: ['topLeft'], - pageSizeOptions: ['2', '5', '10', '15', '20'], + position: ["topLeft"], + pageSizeOptions: ["2", "5", "10", "15", "20"], defaultPageSize: 2, showSizeChanger: true, - showQuickJumper: true, - showTotal: (total) => `Total ${total} items`, + showQuickJumper: true }; - if (results.length < 6) { + if (results.length < 3) { pagination = false; - } + } if (loadError) { return ( @@ -71,10 +70,12 @@ export default function CuratedResults({ results, loadError }) { ); } - return
; + return ( +
+ ); } CuratedResults.propTypes = { - results: PropTypes.array.isRequired, + results: PropTypes.arrayOf(PropTypes.object).isRequired, loadError: PropTypes.bool.isRequired, }; diff --git a/src/components/UnifiedSearch.jsx b/src/components/UnifiedSearch.jsx index 295a6322..28735845 100644 --- a/src/components/UnifiedSearch.jsx +++ b/src/components/UnifiedSearch.jsx @@ -411,7 +411,7 @@ export default function UnifiedSearch() { if (curatedResults.length > 0) { items.unshift({ key: "1", - label: "Curated Matches of Split-GAL4 Lines to Cell Types", + label: `Curated Matches of Split-GAL4 Lines to Cell Types (${curatedResults.length} items)`, children: curatedMatches, extra: ( Date: Wed, 8 Jan 2025 11:07:12 -0500 Subject: [PATCH 17/34] testing: fix SearchInput tests after button name change --- src/components/__tests__/SearchInput.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From b0ee4fc30042a1d8bb170547cd3df78400ac7e91 Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Wed, 15 Jan 2025 11:10:52 -0500 Subject: [PATCH 18/34] feat: Adds EM annotations to curated matches table --- src/components/CuratedResults.jsx | 2 +- src/components/UnifiedSearch.jsx | 36 +++++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/components/CuratedResults.jsx b/src/components/CuratedResults.jsx index cc090e0a..b069ada3 100644 --- a/src/components/CuratedResults.jsx +++ b/src/components/CuratedResults.jsx @@ -27,7 +27,7 @@ const columns = [ key: "source", }, { - title: "Cell Type", + title: "Cell Type / Neuron ID", dataIndex: "cellType", key: "cellType", render: (cellType) => {cellType}, diff --git a/src/components/UnifiedSearch.jsx b/src/components/UnifiedSearch.jsx index 28735845..19597c39 100644 --- a/src/components/UnifiedSearch.jsx +++ b/src/components/UnifiedSearch.jsx @@ -31,28 +31,50 @@ function filterAndSortCuratedMatches(matches) { // expand the matches. const expanded = matches.flatMap((match) => { if (match.itemType === "line_name") { + return match.matches.map((m) => { + const cellType = m.cell_type || `${m.dataset}:${m.body_id}`; + return { + name: match.name, + confidence: m.annotation, + anatomicalRegion: m.region, + cellType, + source: m.annotator, + }; + }); + } + if (match.itemType === "cell_type") { return match.matches.map((m) => ({ - name: match.name, + name: m.line, confidence: m.annotation, anatomicalRegion: m.region, - cellType: m.cell_type, + cellType: match.name, + source: m.annotator, })); } - if (match.itemType === "cell_type") { + if (match.itemType === "body_id") { return match.matches.map((m) => ({ name: m.line, confidence: m.annotation, anatomicalRegion: m.region, - cellType: match.name, + cellType:`${m.dataset}:${match.name}`, + source: m.annotator, })); } return []; }); - // strip duplicates if cellType and name are the same + // strip duplicates if deep comparison is the same. const deduped = expanded.filter( - (v, i, a) => - a.findIndex((t) => t.cellType === v.cellType && t.name === v.name) === i, + (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) => { From d9d102663b49329e2c75812d427c271fd253ef1b Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Wed, 15 Jan 2025 17:32:10 -0500 Subject: [PATCH 19/34] fix: reduce allowed search term length to 2 from 3 This needed to be done, so that people could search for cell types with short names, like "EL". --- src/components/Search.jsx | 6 +++--- src/components/UnifiedSearch.jsx | 28 +++++++++++++++++++++++----- 2 files changed, 26 insertions(+), 8 deletions(-) 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/UnifiedSearch.jsx b/src/components/UnifiedSearch.jsx index 19597c39..40f28632 100644 --- a/src/components/UnifiedSearch.jsx +++ b/src/components/UnifiedSearch.jsx @@ -156,24 +156,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, @@ -338,7 +339,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); + }); }); } }, [ @@ -368,6 +379,13 @@ export default function UnifiedSearch() { }) .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"), + }); + }); }); } From d882022e345e07f63f62a04ff20a15d58decd6eb Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Thu, 27 Feb 2025 09:41:34 -0500 Subject: [PATCH 20/34] fix: Adds a wildcard to the end of all curated cell_types In order to address some parent type/subtype uncertainty during the curation process, we are linking to the wildcard search for a cell_type so that all results are returned when clicking on it. --- src/components/CuratedResults.jsx | 13 ++++++++++--- src/components/UnifiedSearch.jsx | 9 ++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/CuratedResults.jsx b/src/components/CuratedResults.jsx index b069ada3..b7d55911 100644 --- a/src/components/CuratedResults.jsx +++ b/src/components/CuratedResults.jsx @@ -28,9 +28,16 @@ const columns = [ }, { title: "Cell Type / Neuron ID", - dataIndex: "cellType", - key: "cellType", - render: (cellType) => {cellType}, + dataIndex: "matched", + key: "matched", + render: (matched, row) => { + // if the match is a cell type, then we add a wildcard to the search + // to get all the sub cell types. + if (row.type === 'cell_type') { + return {matched}; + } + return {matched}; + } }, ]; diff --git a/src/components/UnifiedSearch.jsx b/src/components/UnifiedSearch.jsx index 40f28632..9e2eea04 100644 --- a/src/components/UnifiedSearch.jsx +++ b/src/components/UnifiedSearch.jsx @@ -37,7 +37,8 @@ function filterAndSortCuratedMatches(matches) { name: match.name, confidence: m.annotation, anatomicalRegion: m.region, - cellType, + matched: cellType, + type: "line_name", source: m.annotator, }; }); @@ -47,7 +48,8 @@ function filterAndSortCuratedMatches(matches) { name: m.line, confidence: m.annotation, anatomicalRegion: m.region, - cellType: match.name, + matched: match.name, + type: "cell_type", source: m.annotator, })); } @@ -56,7 +58,8 @@ function filterAndSortCuratedMatches(matches) { name: m.line, confidence: m.annotation, anatomicalRegion: m.region, - cellType:`${m.dataset}:${match.name}`, + matched:`${m.dataset}:${match.name}`, + type: "body_id", source: m.annotator, })); } From d523bf237ea087130b151a44a8c9a8d26f5d33ee Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Thu, 27 Feb 2025 11:03:41 -0500 Subject: [PATCH 21/34] fix: Adds addWildCard attribute to curated matches This indicates if a wildcard should be added to the result when clicking on it and adding it to the search bar. --- src/components/CuratedResults.jsx | 11 +++-------- src/components/UnifiedSearch.jsx | 8 ++++++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/components/CuratedResults.jsx b/src/components/CuratedResults.jsx index b7d55911..843af9f5 100644 --- a/src/components/CuratedResults.jsx +++ b/src/components/CuratedResults.jsx @@ -30,14 +30,9 @@ const columns = [ title: "Cell Type / Neuron ID", dataIndex: "matched", key: "matched", - render: (matched, row) => { - // if the match is a cell type, then we add a wildcard to the search - // to get all the sub cell types. - if (row.type === 'cell_type') { - return {matched}; - } - return {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}, }, ]; diff --git a/src/components/UnifiedSearch.jsx b/src/components/UnifiedSearch.jsx index 9e2eea04..c8e7ba5e 100644 --- a/src/components/UnifiedSearch.jsx +++ b/src/components/UnifiedSearch.jsx @@ -32,13 +32,15 @@ function filterAndSortCuratedMatches(matches) { const expanded = matches.flatMap((match) => { if (match.itemType === "line_name") { return match.matches.map((m) => { - const cellType = m.cell_type || `${m.dataset}:${m.body_id}`; + 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: cellType, + matched, type: "line_name", + addWildard, source: m.annotator, }; }); @@ -50,6 +52,7 @@ function filterAndSortCuratedMatches(matches) { anatomicalRegion: m.region, matched: match.name, type: "cell_type", + addWildard: "*", source: m.annotator, })); } @@ -60,6 +63,7 @@ function filterAndSortCuratedMatches(matches) { anatomicalRegion: m.region, matched:`${m.dataset}:${match.name}`, type: "body_id", + addWildard: "", source: m.annotator, })); } From 67f06045a3084194cafc9bfc1dde86dc67b7dc95 Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Mon, 3 Mar 2025 10:15:40 -0500 Subject: [PATCH 22/34] fix: removes incorrect "Additional matches ...." message When performing a wildcard search, there are times when the Additional matches in different datasets" message would appear when it was not supposed to. This is because the search could return multiple hits for the wildcard, eg LHPD2c7* would return a hit for LHPD2c7 and LHPD2c7_R, these contain 4 and 2 bodyIds respectively, with two of the bodyIds overlapping. The code filtered out the duplicates at a later stage, but did not do so, before the check that displays the "Additional matches ...." message. This adds a check to remove all bodyIds that have been seen before and prevents the message from being shown at the wrong time. --- src/components/UnifiedSearch.jsx | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/components/UnifiedSearch.jsx b/src/components/UnifiedSearch.jsx index c8e7ba5e..63b6ef08 100644 --- a/src/components/UnifiedSearch.jsx +++ b/src/components/UnifiedSearch.jsx @@ -214,6 +214,8 @@ export default function UnifiedSearch() { Auth.currentCredentials().then(() => { setLoadError(false); + + API.get("SearchAPI", "/published_names", { queryStringParameters: { q: searchBodyIdOrName }, }) @@ -222,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) { @@ -267,7 +271,14 @@ export default function UnifiedSearch() { ) { return match.bodyIDs.map((body) => { publishedNames.add(body); - const bodyID = body.split(":").at(-1); + const [bodyID] = Object.entries(body)[0]; + // 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) => @@ -291,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) { @@ -319,13 +334,14 @@ export default function UnifiedSearch() { !searchDataset.includes(":") ) { bodyCombined.results = bodyCombined.results.filter((item) => { - const [dataset, version, 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. + // 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), From 0d7a077869643f606ebebbf30dbb5dbba03be5fd Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Wed, 12 Mar 2025 10:14:45 -0400 Subject: [PATCH 23/34] fix: Moves curated matches pagination to table bottom. Moves the pagination controls to the bottom of the results table, to make it more obvious that there are more than 2 results. --- src/components/CuratedResults.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CuratedResults.jsx b/src/components/CuratedResults.jsx index 843af9f5..52cf43e9 100644 --- a/src/components/CuratedResults.jsx +++ b/src/components/CuratedResults.jsx @@ -43,7 +43,7 @@ export default function CuratedResults({ results, loadError }) { } let pagination = { - position: ["topLeft"], + position: ["bottomLeft"], pageSizeOptions: ["2", "5", "10", "15", "20"], defaultPageSize: 2, showSizeChanger: true, From d267c7dd40e80277915cba462ca5f95023ac6a74 Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Wed, 12 Mar 2025 10:23:05 -0400 Subject: [PATCH 24/34] fix: Makes curated match count bold to highlight it. The idea is to draw more attention to the result count, so people don't think there are only 2 results. --- src/components/UnifiedSearch.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/UnifiedSearch.jsx b/src/components/UnifiedSearch.jsx index 63b6ef08..63d9eda2 100644 --- a/src/components/UnifiedSearch.jsx +++ b/src/components/UnifiedSearch.jsx @@ -474,7 +474,7 @@ export default function UnifiedSearch() { if (curatedResults.length > 0) { items.unshift({ key: "1", - label: `Curated Matches of Split-GAL4 Lines to Cell Types (${curatedResults.length} items)`, + label:

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

, children: curatedMatches, extra: ( Date: Thu, 3 Apr 2025 16:10:14 -0400 Subject: [PATCH 25/34] added help text for curated matches from Geoffrey --- src/components/Help/HelpContents.jsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/Help/HelpContents.jsx b/src/components/Help/HelpContents.jsx index 78ba1529..8a009b60 100644 --- a/src/components/Help/HelpContents.jsx +++ b/src/components/Help/HelpContents.jsx @@ -330,11 +330,22 @@ export default function HelpContents({ scroll }) { Curated Results

- Curated results show detailed information about... + 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: +

    +
  • Annotations generated as part of split-GAL4 publications, and included with releases posted to https://splitgal4.janelia.org.
  • +
  • Scoring of PatchPerPixMatch EM search results for a subset of the cell type lines published in Meissner et al., 2025. These scores and additional details may be published in a forthcoming standalone paper or addendum to the above paper.
  • +
- - + Results have one of three confidence levels: +
    +
  • Confident: >95% confidence, based on detailed examination of all potential associations, similar to criteria for publication.
  • +
  • Probable: 70-95% confidence, an association that seems correct but has not been as fully validated.
  • +
  • Candidate: 30-70% confidence, where more work is needed for validation and there are often multiple candidates.
  • +
+

Date: Thu, 3 Apr 2025 16:10:35 -0400 Subject: [PATCH 26/34] updated paper reference --- src/components/References.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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
From 7cd4b37caa80d5fe0b45eef8ea479e30b8d1343c Mon Sep 17 00:00:00 2001 From: Konrad Rokicki Date: Thu, 3 Apr 2025 16:37:49 -0400 Subject: [PATCH 27/34] updated search examples to include more curated matches --- src/components/SearchInput.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/SearchInput.jsx b/src/components/SearchInput.jsx index 1486f6dd..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}`; From 0c182aab6fe5d80d21a9612540dadbe6b54ccacd Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Tue, 14 Apr 2026 09:46:29 -0400 Subject: [PATCH 28/34] fix: restore bodyID string parsing broken during rebase Object.entries() was incorrectly used on a string, causing metadata fetches to fail and returning no search results. Also switches faBookmark import to free-solid-svg-icons. --- src/components/UnifiedSearch.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/UnifiedSearch.jsx b/src/components/UnifiedSearch.jsx index 63d9eda2..4fed357a 100644 --- a/src/components/UnifiedSearch.jsx +++ b/src/components/UnifiedSearch.jsx @@ -3,7 +3,7 @@ import { useLocation, Link } from "react-router-dom"; import { Storage, Auth, API } from "aws-amplify"; import { Collapse, Spin, message, Typography } from "antd"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faBookmark } from "@fortawesome/pro-solid-svg-icons"; +import { faBookmark } from "@fortawesome/free-solid-svg-icons"; import SearchInput from "./SearchInput"; import UnifiedSearchResults from "./UnifiedSearchResults"; @@ -271,7 +271,7 @@ export default function UnifiedSearch() { ) { return match.bodyIDs.map((body) => { publishedNames.add(body); - const [bodyID] = Object.entries(body)[0]; + 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* From 3bb4238212c538444713ef99db1499227e952a5b Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Tue, 14 Apr 2026 09:46:54 -0400 Subject: [PATCH 29/34] feat: add @fortawesome/free-solid-svg-icons dependency --- package-lock.json | 22 ++++++++++++++++++++++ package.json | 1 + 2 files changed, 23 insertions(+) diff --git a/package-lock.json b/package-lock.json index 40445ea2..6a7685ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@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", @@ -8758,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", diff --git a/package.json b/package.json index 1aa3a38e..a9914bd8 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@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", From 3d5734dfc314f8c5a64f5c6468cd761b055c719d Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Fri, 17 Apr 2026 09:12:01 -0400 Subject: [PATCH 30/34] feat: make line name a link to search page in LineMeta --- src/components/LineMeta.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LineMeta.jsx b/src/components/LineMeta.jsx index 319d71b3..afc521fb 100644 --- a/src/components/LineMeta.jsx +++ b/src/components/LineMeta.jsx @@ -54,7 +54,7 @@ export default function LineMeta({ attributes, compact, fromSearch }) { return (

Line Name: - {publishedName} + {publishedName}

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

Line Name: - {publishedName} + {publishedName}

{attributes.normalizedScore ? (

From 59614a2291aa2691d8987855741842381ed30f3b Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Fri, 17 Apr 2026 09:23:52 -0400 Subject: [PATCH 31/34] chore: bump version to 3.5.0 and add 3.4.0 release notes --- package.json | 2 +- public/RELEASENOTES.md | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a9914bd8..75f9243f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "neuronbridge", - "version": "3.4.0", + "version": "3.5.0", "private": true, "dependencies": { "@ant-design/icons": "^5.1.4", diff --git a/public/RELEASENOTES.md b/public/RELEASENOTES.md index 6909f125..493b09b1 100644 --- a/public/RELEASENOTES.md +++ b/public/RELEASENOTES.md @@ -1,3 +1,40 @@ +## VERSION 3.4.0 - 2025-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 + - The selected channel from mask selection is persisted to the search record + + - **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 + - **External Link Icons**: An external link icon has been added to all external links, making it clear when a link will navigate away from NeuronBridge + - **Additional Data Source Links**: Two additional links have been added to the landing page data sources list + +### Bug Fixes & Improvements + + - **Search & Navigation**: + - Improved search result filtering and duplicate removal logic + - Fixed the default search library selection in the custom search form + - 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 + + - **Stability & Error Handling**: + - Added null check for error responses in the Results catch handler + - Added cacheControl headers to references.json fetch for improved caching + +### Content Updates + + - Added CITATION.cff for Zenodo integration + - Added reference for Male CNS + - Updated site meta description + +--- + ## VERSION 3.3.1 - 2024-09-22 ### Major Features From 83f31d8242157d5949f8bfcf39044bac6777b6c3 Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Fri, 17 Apr 2026 09:33:04 -0400 Subject: [PATCH 32/34] docs: add 3.5.0 release notes --- public/RELEASENOTES.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/public/RELEASENOTES.md b/public/RELEASENOTES.md index 493b09b1..8e0b0c29 100644 --- a/public/RELEASENOTES.md +++ b/public/RELEASENOTES.md @@ -1,3 +1,33 @@ +## 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 + - Includes EM body ID annotations alongside cell type matches + - Curated matches appear above computed matches with a bookmark indicator when results are available + - Supports searching by line name, cell type, or body ID with wildcard expansion + + - **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 + + - **Search Input Updates**: The search button has been renamed from "Search" to "Query" and the minimum search term length has been reduced from 3 to 2 characters + - **Updated Search Examples**: The example search terms have been refreshed to showcase curated matches functionality + - **Constrained Results Width**: Search results are now constrained to a maximum width for improved readability + - **Help Documentation**: Added a new help section explaining the curated matches feature + +### Curated Results + +See the [Curated Results section of the help page](/help#curated_results) for details on confidence levels and data sources. + +### Content Updates + + - Updated paper reference + - Added help text for curated matches + +--- + ## VERSION 3.4.0 - 2025-04-17 ### Major Features From a7b604109664a156e96ed4ab67539913bd94a2bf Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Fri, 17 Apr 2026 09:33:07 -0400 Subject: [PATCH 33/34] fix: scroll to hash anchor on mount instead of page top --- src/components/ScrollToTopOnMount.jsx | 8 ++++++++ 1 file changed, 8 insertions(+) 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); }, []); From 0de5732cd81299562c987cc88d849a716bef0e7f Mon Sep 17 00:00:00 2001 From: Jody Clements Date: Fri, 17 Apr 2026 09:43:47 -0400 Subject: [PATCH 34/34] chore: Updated release notes to remove unnecessary details --- public/RELEASENOTES.md | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/public/RELEASENOTES.md b/public/RELEASENOTES.md index 8e0b0c29..32f84f8e 100644 --- a/public/RELEASENOTES.md +++ b/public/RELEASENOTES.md @@ -4,65 +4,39 @@ - **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 - - Includes EM body ID annotations alongside cell type matches - Curated matches appear above computed matches with a bookmark indicator when results are available - - Supports searching by line name, cell type, or body ID with wildcard expansion + - 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 - - **Search Input Updates**: The search button has been renamed from "Search" to "Query" and the minimum search term length has been reduced from 3 to 2 characters - **Updated Search Examples**: The example search terms have been refreshed to showcase curated matches functionality - - **Constrained Results Width**: Search results are now constrained to a maximum width for improved readability - **Help Documentation**: Added a new help section explaining the curated matches feature -### Curated Results - -See the [Curated Results section of the help page](/help#curated_results) for details on confidence levels and data sources. - -### Content Updates - - - Updated paper reference - - Added help text for curated matches - --- -## VERSION 3.4.0 - 2025-04-17 +## 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 - - The selected channel from mask selection is persisted to the search record - **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 - - **External Link Icons**: An external link icon has been added to all external links, making it clear when a link will navigate away from NeuronBridge - - **Additional Data Source Links**: Two additional links have been added to the landing page data sources list ### Bug Fixes & Improvements - **Search & Navigation**: - Improved search result filtering and duplicate removal logic - - Fixed the default search library selection in the custom search form - 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 - - **Stability & Error Handling**: - - Added null check for error responses in the Results catch handler - - Added cacheControl headers to references.json fetch for improved caching - -### Content Updates - - - Added CITATION.cff for Zenodo integration - - Added reference for Male CNS - - Updated site meta description - --- ## VERSION 3.3.1 - 2024-09-22