Skip to content

Commit 8bea19a

Browse files
committed
dashboard: Add search box and link to URL
Adds an input form for searching job names. Searches are appended to the URL. Fixes #4 Signed-off-by: Anna Finn <[email protected]>
1 parent 9b993f0 commit 8bea19a

File tree

2 files changed

+138
-12
lines changed

2 files changed

+138
-12
lines changed

components/searchForm.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export const SearchForm = ({ handleSearch }) => {
2+
return (
3+
<div className="flex flex-col items-center md:text-base text-xs">
4+
<div className="flex min-[1126px]:justify-end justify-center w-full">
5+
<form className="p-2 bg-gray-700 rounded-md flex flex-row" onSubmit={(e) => handleSearch(e)}>
6+
<div>
7+
<label className="block text-white">Match Mode:</label>
8+
<select name="matchMode" className="px-1 h-fit rounded-lg">
9+
<option value="or">Match Any</option>
10+
<option value="and">Match All</option>
11+
</select>
12+
</div>
13+
<div className="mx-2">
14+
<label className="block text-white">Search Text:</label>
15+
<input type="text" name="value" required></input>
16+
</div>
17+
<button type="submit" className="bg-blue-500 text-white px-4 rounded-3xl">Submit</button>
18+
</form>
19+
</div>
20+
</div>
21+
);
22+
};

pages/index.js

Lines changed: 116 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,23 @@ import { useEffect, useState } from "react";
22
import { DataTable } from "primereact/datatable";
33
import { Column } from "primereact/column";
44
import { weatherTemplate, getWeatherIndex } from "../components/weatherTemplate";
5+
import { basePath } from "../next.config.js";
6+
import { SearchForm } from "../components/searchForm";
57

68

79
export default function Home() {
8-
const [loading, setLoading] = useState(true);
9-
const [jobs, setJobs] = useState([]);
10-
const [rows, setRows] = useState([]);
11-
const [expandedRows, setExpandedRows] = useState([]);
10+
const [loading, setLoading] = useState(true);
11+
const [jobs, setJobs] = useState([]);
12+
const [rows, setRows] = useState([]);
13+
const [expandedRows, setExpandedRows] = useState([]);
14+
const [keepSearch, setKeepSearch] = useState(true);
1215

1316
useEffect(() => {
1417
const fetchData = async () => {
1518
let data = {};
1619

1720
if (process.env.NODE_ENV === "development") {
18-
data = (await import("../job_stats.json")).default;
21+
data = (await import("../localData/job_stats.json")).default;
1922
} else {
2023
const response = await fetch(
2124
"https://raw.githubusercontent.com/kata-containers/kata-containers.github.io" +
@@ -41,15 +44,59 @@ export default function Home() {
4144
fetchData();
4245
}, []);
4346

47+
// Filters the jobs s.t. all values must be contained in the name.
48+
const matchAll = (filteredJobs, values) => {
49+
return filteredJobs.filter((job) => {
50+
const jobName = job.name.toLowerCase();
51+
return values.every((val) => {
52+
const decodedValue = decodeURIComponent(val).toLowerCase();
53+
return jobName.includes(decodedValue);
54+
});
55+
});
56+
};
57+
58+
// Filters the jobs s.t. at least one value must be contained in the name.
59+
const matchAny = (filteredJobs, values) => {
60+
return filteredJobs.filter((job) => {
61+
const jobName = job.name.toLowerCase();
62+
return values.some((val) => {
63+
const decodedValue = decodeURIComponent(val).toLowerCase();
64+
return jobName.includes(decodedValue);
65+
});
66+
});
67+
};
68+
69+
4470
useEffect(() => {
4571
setLoading(true);
72+
let filteredJobs = jobs;
73+
74+
//Filter based on the URL.
75+
const urlParams = new URLSearchParams(window.location.search);
76+
switch(urlParams.get("matchMode")) {
77+
case "and":
78+
filteredJobs = matchAll(filteredJobs, urlParams.getAll("value"));
79+
break;
80+
case "or":
81+
filteredJobs = matchAny(filteredJobs, urlParams.getAll("value"));
82+
break;
83+
default:
84+
break;
85+
}
86+
87+
88+
//Set the rows for the table.
89+
setRows(
90+
filteredJobs.map((job) => ({
91+
name : job.name,
92+
runs : job.runs,
93+
fails : job.fails,
94+
skips : job.skips,
95+
required : job.required,
96+
weather : getWeatherIndex(job),
97+
}))
98+
);
4699

47-
// Create rows to set into table.
48-
const rows = jobs.map((job) => ({
49-
...job,
50-
weather: getWeatherIndex(job),
51-
}));
52-
setRows(rows);
53100
setLoading(false);
54101
}, [jobs]);
55102

@@ -66,6 +113,11 @@ export default function Home() {
66113
setExpandedRows(updatedExpandedRows);
67114
};
68115

116+
const buttonClass = (active) => `tab md:px-4 px-2 py-2 border-2
117+
${active ? "border-blue-500 bg-blue-500 text-white"
118+
: "border-gray-300 bg-white hover:bg-gray-100"}`;
119+
120+
69121
// Template for rendering the Name column as a clickable item
70122
const nameTemplate = (rowData) => {
71123
return (
@@ -120,6 +172,39 @@ export default function Home() {
120172
);
121173
};
122174

175+
// Apply search terms to the URL and reload the page.
176+
const handleSearch= (e) => {
177+
// Prevent the default behavior so that we can keep search terms.
178+
e.preventDefault();
179+
const matchMode = e.target.matchMode.value;
180+
const value = e.target.value.value.trimEnd();
181+
if (value) {
182+
// Append the new matchMode regardless of if search terms were kept.
183+
const path = new URLSearchParams();
184+
path.append("matchMode", matchMode);
185+
if (keepSearch) {
186+
// If keepSearch is true, add existing parameters in the URL.
187+
const urlParams = new URLSearchParams(window.location.search);
188+
urlParams.getAll("value").forEach((val) => {
189+
path.append("value", val);
190+
});
191+
}
192+
//Add the search term from the form and redirect.
193+
path.append("value", value);
194+
window.location.assign(`${basePath}/?${path.toString()}`);
195+
}
196+
};
197+
198+
// Clear the search parameters, but only if they exist.
199+
const clearSearch = () => {
200+
const urlParts = window.location.href.split("?");
201+
if(urlParts[1] !== undefined){
202+
window.location.assign(urlParts[0]);
203+
}
204+
}
205+
206+
207+
123208
const renderTable = () => (
124209
<DataTable
125210
value={rows}
@@ -178,9 +263,28 @@ export default function Home() {
178263
"m-0 h-full p-4 overflow-x-hidden overflow-y-auto bg-surface-ground font-normal text-text-color antialiased select-text"
179264
}
180265
>
266+
<div className="space-x-2 mx-auto">
267+
<button
268+
className={buttonClass()}
269+
onClick={() => clearSearch()}>
270+
Clear Search
271+
</button>
272+
<button
273+
className={buttonClass(keepSearch)}
274+
onClick={() => setKeepSearch(!keepSearch)}>
275+
Keep URL Search Terms
276+
</button>
277+
</div>
278+
279+
<SearchForm handleSearch={handleSearch} />
280+
281+
<div className="mt-1 text-center md:text-lg text-base">
282+
Total Rows: {rows.length}
283+
</div>
284+
181285
<div>{renderTable()}</div>
182286
<div className="mt-4 text-lg">Total Rows: {rows.length}</div>
183287
</main>
184288
</div>
185289
);
186-
}
290+
}

0 commit comments

Comments
 (0)