Skip to content

Commit d222b88

Browse files
committed
experimental search
1 parent cbc686b commit d222b88

File tree

8 files changed

+4008
-227
lines changed

8 files changed

+4008
-227
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## June 12, 2025
44
- Added dandi-index and implemented advanced search
5+
- Added .env.local template for configuring PubNub keys in local development
56

67
## June 5, 2025
78
- Modernized Python package structure with pyproject.toml configuration. Removed legacy setup.py, setup.cfg, and setup.cfg.j2 files

package-lock.json

Lines changed: 3636 additions & 213 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"numcodecs": "^0.3.1",
2727
"pako": "^2.1.0",
2828
"plotly.js-dist-min": "^3.0.0",
29+
"pubnub": "^9.6.0",
2930
"react": "^18.3.1",
3031
"react-dom": "^18.3.1",
3132
"react-draggable": "^4.4.6",

src/pages/DandiPage/DandiPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import doDandiSemanticSearch from "./doDandiSemanticSearch";
2525
import { useNeurodataTypesIndex } from "./hooks/useNeurodataTypesIndex";
2626
import { useDandisetNotebooks } from "./hooks/useDandisetNotebooks";
2727
import { doNeurodataTypesSearch } from "./services/doNeurodataTypesSearch";
28-
import { ExperimentalSearchPanel } from "./components/ExperimentalSearchPanel";
28+
import { ExperimentalSearchPanel } from "./experimentalSearch/ExperimentalSearchPanel";
2929

3030
type DandiPageProps = {
3131
width: number;

src/pages/DandiPage/DandisetSearchResult.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
Tooltip,
88
} from "@mui/material";
99
import { MenuBook } from "@mui/icons-material";
10-
import { DandisetAdvancedSearchResult } from "./dandi-types";
10+
import { DandisetNeurodataTypesSearchResult } from "./dandi-types";
1111
import { useNavigate } from "react-router-dom";
1212

1313
const formatDate = (dateString: string) => {
@@ -17,7 +17,7 @@ const formatDate = (dateString: string) => {
1717
import { formatBytes } from "@shared/util/formatBytes";
1818

1919
type Props = {
20-
dandiset: DandisetAdvancedSearchResult;
20+
dandiset: DandisetNeurodataTypesSearchResult;
2121
notebookUrls?: string[];
2222
};
2323

src/pages/DandiPage/components/ExperimentalSearchPanel.tsx

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { FunctionComponent, useEffect, useState } from "react";
2+
import { JobRunnerClient } from "./jobRunnerClient";
3+
4+
type ExperimentalSearchPanelProps = {
5+
setDandisetIds: (ids: string[]) => void;
6+
};
7+
8+
type Filter = {
9+
contactPerson?: string | undefined;
10+
};
11+
12+
export const ExperimentalSearchPanel: FunctionComponent<
13+
ExperimentalSearchPanelProps
14+
> = ({ setDandisetIds }) => {
15+
const [jobRunnerClient, setJobRunnerClient] = useState<
16+
JobRunnerClient | undefined
17+
>(undefined);
18+
const [jobRunnerClientIsAlive, setJobRunnerClientIsAlive] =
19+
useState<boolean>(false);
20+
useEffect(() => {
21+
let client: JobRunnerClient;
22+
try {
23+
client = new JobRunnerClient();
24+
} catch (error) {
25+
console.error("Failed to initialize JobRunnerClient:", error);
26+
setJobRunnerClient(undefined);
27+
return;
28+
}
29+
client.onStatusChange(() => {
30+
setJobRunnerClientIsAlive(client.isAlive());
31+
});
32+
setJobRunnerClient(client);
33+
return () => {
34+
client.dispose();
35+
};
36+
}, []);
37+
const [filter, setFilter] = useState<Filter>({
38+
contactPerson: undefined,
39+
});
40+
const { allContactPersons } = useAllContactPersons(jobRunnerClient);
41+
const { dandisetIds } = useSearch(jobRunnerClient, filter);
42+
useEffect(() => {
43+
setDandisetIds(dandisetIds);
44+
}, [dandisetIds, setDandisetIds]);
45+
if (!jobRunnerClient) {
46+
return <div>No job runner is configured.</div>;
47+
}
48+
if (!jobRunnerClientIsAlive) {
49+
return <div>Waiting for job runner to initialize...</div>;
50+
}
51+
console.info({ dandisetIds });
52+
return (
53+
<div>
54+
{allContactPersons && (
55+
<div>
56+
<div style={{ marginBottom: 10 }}>Contact person:</div>
57+
<select
58+
value={filter.contactPerson || ""}
59+
onChange={(e) =>
60+
setFilter({
61+
...filter,
62+
contactPerson: e.target.value || undefined,
63+
})
64+
}
65+
style={{ width: 300, height: 30 }}
66+
>
67+
<option value="">All contact persons</option>
68+
{allContactPersons.map((person) => (
69+
<option key={person} value={person}>
70+
{person}
71+
</option>
72+
))}
73+
</select>
74+
</div>
75+
)}
76+
</div>
77+
);
78+
};
79+
80+
const useAllContactPersons = (jobRunnerClient: JobRunnerClient | undefined) => {
81+
const script = `
82+
const dandisets = await interface.getDandisets();
83+
let contactPersons = [];
84+
for (const dandiset of dandisets) {
85+
contactPersons.push(dandiset.contact_person);
86+
}
87+
// unique and alphabetically sorted
88+
contactPersons = [...new Set(contactPersons)].sort();
89+
interface.print("<>" + JSON.stringify(contactPersons, null, 2) + "</>");
90+
`;
91+
const [allContactPersons, setAllContactPersons] = useState<
92+
string[] | undefined
93+
>(undefined);
94+
useEffect(() => {
95+
if (!jobRunnerClient) return;
96+
const runScript = async () => {
97+
try {
98+
const response = await jobRunnerClient.executeScript(script);
99+
const i1 = response.indexOf("<>");
100+
const i2 = response.lastIndexOf("</>");
101+
if (i1 === -1 || i2 === -1 || i2 <= i1) {
102+
console.error("Invalid response format:", response);
103+
return;
104+
}
105+
const jsonString = response.substring(i1 + 2, i2);
106+
const contactPersons = JSON.parse(jsonString);
107+
if (!Array.isArray(contactPersons)) {
108+
console.error("Parsed response is not an array:", contactPersons);
109+
return;
110+
}
111+
setAllContactPersons(contactPersons);
112+
} catch (error) {
113+
console.error("Error executing script:", error);
114+
}
115+
};
116+
runScript();
117+
}, [jobRunnerClient, script]);
118+
return { allContactPersons };
119+
};
120+
121+
const useSearch = (
122+
jobRunnerClient: JobRunnerClient | undefined,
123+
filter: Filter,
124+
) => {
125+
const [dandisetIds, setDandisetIds] = useState<string[]>([]);
126+
useEffect(() => {
127+
if (!filter.contactPerson || !jobRunnerClient) {
128+
setDandisetIds([]);
129+
return;
130+
}
131+
const script = `
132+
const dandisets = await interface.getDandisets();
133+
const filteredDandisets = dandisets.filter(dandiset => dandiset.contact_person === "${filter.contactPerson}");
134+
if (filteredDandisets.length > 0) {
135+
filteredDandisets.forEach(dandiset => {
136+
interface.print(\`DANDISET: \${dandiset.dandiset_id}\`);
137+
});
138+
} else {
139+
interface.print("No dandisets found with contact person Baker, Cody.");
140+
}
141+
`;
142+
const runScript = async () => {
143+
try {
144+
const response = await jobRunnerClient.executeScript(script);
145+
const lines = response
146+
.split("\n")
147+
.filter((line) => line.startsWith("DANDISET: "));
148+
const ids = lines.map((line) => line.replace("DANDISET: ", "").trim());
149+
setDandisetIds(ids);
150+
} catch (error) {
151+
console.error("Error executing search script:", error);
152+
}
153+
};
154+
runScript();
155+
}, [filter.contactPerson, jobRunnerClient]);
156+
return {
157+
dandisetIds,
158+
};
159+
};

0 commit comments

Comments
 (0)