Skip to content

Commit c24fba8

Browse files
committed
feat: add a shareable URL with prefilled form and auto query execution; refs #40
1 parent 5e2601b commit c24fba8

File tree

4 files changed

+96
-28
lines changed

4 files changed

+96
-28
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"axios": "1.8.2",
2626
"dayjs": "^1.11.10",
2727
"jwt-decode": "^3.1.2",
28+
"pako": "^2.1.0",
2829
"query-string": "^8.1.0",
2930
"react": "^18.2.0",
3031
"react-dom": "^18.2.0",
@@ -40,6 +41,7 @@
4041
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
4142
"@trivago/prettier-plugin-sort-imports": "^4.2.0",
4243
"@types/node": "^20.5.7",
44+
"@types/pako": "^2.0.3",
4345
"@typescript-eslint/eslint-plugin": "^5.31.0",
4446
"@typescript-eslint/parser": "^5.31.0",
4547
"eslint": "^8.21.0",

src/pages/SearchPage.tsx

Lines changed: 79 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { generateSchemaWithDatabaseEnum } from "./searchformSchema";
2+
import ArrowCircleRightIcon from "@mui/icons-material/ArrowCircleRight";
23
import {
34
Typography,
45
Container,
@@ -14,6 +15,7 @@ import SubjectCard from "components/SearchPage/SubjectCard";
1415
import { Colors } from "design/theme";
1516
import { useAppDispatch } from "hooks/useAppDispatch";
1617
import { useAppSelector } from "hooks/useAppSelector";
18+
import pako from "pako";
1719
import React from "react";
1820
import { useState, useEffect, useMemo } from "react";
1921
import {
@@ -40,6 +42,41 @@ const SearchPage: React.FC = () => {
4042
>([]);
4143
const [skip, setSkip] = useState(0);
4244
const [page, setPage] = useState(1);
45+
const [queryLink, setQueryLink] = useState("");
46+
47+
// parse query from url on page load
48+
useEffect(() => {
49+
const hash = window.location.hash;
50+
if (hash.startsWith("#query=")) {
51+
const encoded = hash.replace("#query=", "");
52+
try {
53+
const decoded = pako.inflate(
54+
Uint8Array.from(atob(encoded), (c) => c.charCodeAt(0)),
55+
{ to: "string" }
56+
);
57+
const parsed = JSON.parse(decoded);
58+
setFormData(parsed);
59+
const requestData = { ...parsed, skip: 0 };
60+
setSkip(0);
61+
setHasSearched(true);
62+
dispatch(fetchMetadataSearchResults(requestData)).then((res: any) => {
63+
if (res.payload) {
64+
setResults(res.payload);
65+
}
66+
});
67+
} catch (e) {
68+
console.error("Failed to parse query from URL", e);
69+
}
70+
}
71+
}, [dispatch]);
72+
73+
// generate a direct link to the query
74+
const updateQueryLink = (queryData: Record<string, any>) => {
75+
const deflated = pako.deflate(JSON.stringify(queryData));
76+
const encoded = btoa(String.fromCharCode(...deflated));
77+
const link = `${window.location.origin}${window.location.pathname}#query=${encoded}`;
78+
setQueryLink(link);
79+
};
4380

4481
// setting pagination
4582
const itemsPerPage = 10;
@@ -51,10 +88,6 @@ const SearchPage: React.FC = () => {
5188
setPage(value);
5289
};
5390

54-
// const paginatedResults = Array.isArray(searchResults)
55-
// ? searchResults.slice((page - 1) * itemsPerPage, page * itemsPerPage)
56-
// : [];
57-
5891
const paginatedResults = Array.isArray(results)
5992
? results.slice((page - 1) * itemsPerPage, page * itemsPerPage)
6093
: [];
@@ -168,6 +201,16 @@ const SearchPage: React.FC = () => {
168201
? activeStyle
169202
: {}
170203
: hiddenStyle,
204+
session_name: showSubjectFilters
205+
? formData["session_name"]
206+
? activeStyle
207+
: {}
208+
: hiddenStyle,
209+
run_name: showSubjectFilters
210+
? formData["run_name"]
211+
? activeStyle
212+
: {}
213+
: hiddenStyle,
171214

172215
"ui:submitButtonOptions": {
173216
props: {
@@ -209,18 +252,18 @@ const SearchPage: React.FC = () => {
209252
};
210253

211254
// print the result in dev tool
212-
if (Array.isArray(searchResults)) {
213-
searchResults.forEach((item, idx) => {
214-
try {
215-
const parsed = JSON.parse(item.json);
216-
console.log(`Result #${idx}:`, { ...item, parsedJson: parsed });
217-
} catch (e) {
218-
console.error(`Failed to parse JSON for item #${idx}`, e);
219-
}
220-
});
221-
} else {
222-
console.warn("searchResults is not an array:", searchResults);
223-
}
255+
// if (Array.isArray(searchResults)) {
256+
// searchResults.forEach((item, idx) => {
257+
// try {
258+
// const parsed = JSON.parse(item.json);
259+
// console.log(`Result #${idx}:`, { ...item, parsedJson: parsed });
260+
// } catch (e) {
261+
// console.error(`Failed to parse JSON for item #${idx}`, e);
262+
// }
263+
// });
264+
// } else {
265+
// console.warn("searchResults is not an array:", searchResults);
266+
// }
224267

225268
// determine the results are subject-level or dataset-level
226269
let isDataset: boolean | null = null;
@@ -249,7 +292,6 @@ const SearchPage: React.FC = () => {
249292

250293
// submit function
251294
const handleSubmit = ({ formData }: any) => {
252-
// dispatch(fetchMetadataSearchResults(formData));
253295
const requestData = { ...formData, skip: 0 };
254296
setFormData(requestData);
255297
setSkip(0);
@@ -258,6 +300,7 @@ const SearchPage: React.FC = () => {
258300
});
259301
setHasSearched(true);
260302
setPage(1);
303+
updateQueryLink(formData);
261304
};
262305

263306
// reset function
@@ -313,6 +356,21 @@ const SearchPage: React.FC = () => {
313356
minWidth: "35%",
314357
}}
315358
>
359+
{queryLink && (
360+
<Box mt={2}>
361+
<a
362+
href={queryLink}
363+
target="_blank"
364+
rel="noopener noreferrer"
365+
style={{ textDecoration: "none", color: Colors.purple }}
366+
>
367+
<Box component="span" display="inline-flex" alignItems="center">
368+
Direct Link to This Query
369+
<ArrowCircleRightIcon sx={{ marginLeft: 0.5 }} />
370+
</Box>
371+
</a>
372+
</Box>
373+
)}
316374
<Box
317375
sx={{
318376
display: "flex",
@@ -397,15 +455,14 @@ const SearchPage: React.FC = () => {
397455
Loading search results...
398456
</Typography>
399457
</Box>
400-
) : Array.isArray(results) ? ( // change searchResults into results
458+
) : Array.isArray(results) ? (
401459
<>
402460
<Typography
403461
variant="h6"
404462
sx={{ borderBottom: "1px solid lightgray", mb: 2 }}
405463
>
406-
{results.length > 0 //change searchResults into results
464+
{results.length > 0
407465
? `Showing ${results.length} ${
408-
//change searchResults into results
409466
isDataset ? "Datasets" : "Subjects"
410467
}`
411468
: `No matching ${
@@ -424,7 +481,6 @@ const SearchPage: React.FC = () => {
424481

425482
<Box textAlign="center" mt={2} mb={2}>
426483
<Pagination
427-
// count={Math.ceil(searchResults.length / itemsPerPage)}
428484
count={Math.ceil(results.length / itemsPerPage)}
429485
page={page}
430486
onChange={handlePageChange}
@@ -451,8 +507,7 @@ const SearchPage: React.FC = () => {
451507
</Box>
452508

453509
{results.length > 0 &&
454-
paginatedResults.length > 0 && //change searchResults into results
455-
// searchResults.slice(0, visibleCount)
510+
paginatedResults.length > 0 &&
456511
paginatedResults.map((item, idx) => {
457512
try {
458513
const parsedJson = JSON.parse(item.json);
@@ -489,7 +544,7 @@ const SearchPage: React.FC = () => {
489544
</>
490545
) : (
491546
<Typography sx={{ color: Colors.error }}>
492-
{results?.msg === "empty output" //change searchResults into results
547+
{results?.msg === "empty output"
493548
? "No results found based on your criteria. Please adjust the filters and try again."
494549
: "Something went wrong. Please try again later."}
495550
</Typography>

src/pages/searchformSchema.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ export const baseSchema: JSONSchema7 = {
107107
title: "Task keywords",
108108
type: "string",
109109
},
110+
type_name: {
111+
title: "Data type keywords",
112+
type: "string",
113+
},
110114
session_name: {
111115
title: "Session keywords",
112116
type: "string",
@@ -115,10 +119,7 @@ export const baseSchema: JSONSchema7 = {
115119
title: "Run keywords",
116120
type: "string",
117121
},
118-
type_name: {
119-
title: "Data type keywords",
120-
type: "string",
121-
},
122+
122123
// count: {
123124
// title: "Only return total counts",
124125
// type: "boolean",

yarn.lock

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2460,6 +2460,11 @@
24602460
dependencies:
24612461
undici-types "~6.19.2"
24622462

2463+
"@types/pako@^2.0.3":
2464+
version "2.0.3"
2465+
resolved "https://registry.yarnpkg.com/@types/pako/-/pako-2.0.3.tgz#b6993334f3af27c158f3fe0dfeeba987c578afb1"
2466+
integrity sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==
2467+
24632468
"@types/parse-json@^4.0.0":
24642469
version "4.0.2"
24652470
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"
@@ -7908,6 +7913,11 @@ package-json-from-dist@^1.0.0:
79087913
resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505"
79097914
integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==
79107915

7916+
pako@^2.1.0:
7917+
version "2.1.0"
7918+
resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86"
7919+
integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==
7920+
79117921
param-case@^3.0.4:
79127922
version "3.0.4"
79137923
resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"

0 commit comments

Comments
 (0)