1+
12import { useEffect , useState } from "react" ;
23import { DataTable } from "primereact/datatable" ;
34import { Column } from "primereact/column" ;
45import { weatherTemplate , getWeatherIndex } from "../components/weatherTemplate" ;
6+ import { basePath } from "../next.config.js" ;
57
68
79export default function Home ( ) {
810 const [ loading , setLoading ] = useState ( true ) ;
911 const [ jobs , setJobs ] = useState ( [ ] ) ;
1012 const [ rows , setRows ] = useState ( [ ] ) ;
1113 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 ( "../data/ 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,55 @@ 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 , urlParams ) => {
49+ const values = urlParams . getAll ( "value" ) ;
50+ return filteredJobs . filter ( ( job ) => {
51+ const jobName = job . name . toLowerCase ( ) ;
52+ return values . every ( ( val ) => {
53+ const decodedValue = decodeURIComponent ( val ) . toLowerCase ( ) ;
54+ return jobName . includes ( decodedValue ) ;
55+ } ) ;
56+ } ) ;
57+ } ;
58+
59+ // Filters the jobs s.t. at least one value must be contained in the name.
60+ const matchAny = ( filteredJobs , urlParams ) => {
61+ const values = urlParams . getAll ( "value" ) ;
62+ return filteredJobs . filter ( ( job ) => {
63+ const jobName = job . name . toLowerCase ( ) ;
64+ return values . some ( ( val ) => {
65+ const decodedValue = decodeURIComponent ( val ) . toLowerCase ( ) ;
66+ return jobName . includes ( decodedValue ) ;
67+ } ) ;
68+ } ) ;
69+ } ;
70+
71+
4472 useEffect ( ( ) => {
4573 setLoading ( true ) ;
74+ let filteredJobs = jobs ;
75+
76+ //Filter based on the URL.
77+ const urlParams = new URLSearchParams ( window . location . search ) ;
78+ if ( urlParams . get ( "matchMode" ) === "and" ) {
79+ filteredJobs = matchAll ( filteredJobs , urlParams ) ;
80+ } else if ( urlParams . get ( "matchMode" ) === "or" ) {
81+ filteredJobs = matchAny ( filteredJobs , urlParams ) ;
82+ }
83+
84+ //Set the rows for the table.
85+ setRows (
86+ filteredJobs . map ( ( job ) => ( {
87+ name : job . name ,
88+ runs : job . runs ,
89+ fails : job . fails ,
90+ skips : job . skips ,
91+ required : job . required ,
92+ weather : getWeatherIndex ( job ) ,
93+ } ) )
94+ ) ;
4695
47- // Create rows to set into table.
48- const rows = jobs . map ( ( job ) => ( {
49- ...job ,
50- weather : getWeatherIndex ( job ) ,
51- } ) ) ;
52- setRows ( rows ) ;
5396 setLoading ( false ) ;
5497 } , [ jobs ] ) ;
5598
@@ -66,6 +109,11 @@ export default function Home() {
66109 setExpandedRows ( updatedExpandedRows ) ;
67110 } ;
68111
112+ const buttonClass = ( active ) => `tab md:px-4 px-2 py-2 border-2
113+ ${ active ? "border-blue-500 bg-blue-500 text-white"
114+ : "border-gray-300 bg-white hover:bg-gray-100" } `;
115+
116+
69117 // Template for rendering the Name column as a clickable item
70118 const nameTemplate = ( rowData ) => {
71119 return (
@@ -120,6 +168,39 @@ export default function Home() {
120168 ) ;
121169 } ;
122170
171+ // Apply search terms to the URL and reload the page.
172+ const handleSearch = ( e ) => {
173+ // Prevent the default behavior so that we can keep search terms.
174+ e . preventDefault ( ) ;
175+ const matchMode = e . target . matchMode . value ;
176+ const value = e . target . value . value . trimEnd ( ) ;
177+ if ( value ) {
178+ // Append the new matchMode regardless of if search terms were kept.
179+ const path = new URLSearchParams ( ) ;
180+ path . append ( "matchMode" , matchMode ) ;
181+ if ( keepSearch ) {
182+ // If keepSearch is true, add existing parameters in the URL.
183+ const urlParams = new URLSearchParams ( window . location . search ) ;
184+ urlParams . getAll ( "value" ) . forEach ( ( val ) => {
185+ path . append ( "value" , val ) ;
186+ } ) ;
187+ }
188+ //Add the search term from the form and redirect.
189+ path . append ( "value" , encodeURIComponent ( value ) ) ;
190+ window . location . assign ( `${ basePath } /?${ path . toString ( ) } ` ) ;
191+ }
192+ } ;
193+
194+ // Clear the search parameters, but only if they exist.
195+ const clearSearch = ( ) => {
196+ const urlParts = window . location . href . split ( "?" ) ;
197+ if ( urlParts [ 1 ] !== undefined ) {
198+ window . location . assign ( urlParts [ 0 ] ) ;
199+ }
200+ }
201+
202+
203+
123204 const renderTable = ( ) => (
124205 < DataTable
125206 value = { rows }
@@ -178,9 +259,46 @@ export default function Home() {
178259 "m-0 h-full p-4 overflow-x-hidden overflow-y-auto bg-surface-ground font-normal text-text-color antialiased select-text"
179260 }
180261 >
262+ < div className = "space-x-2 mx-auto" >
263+ < button
264+ className = { buttonClass ( ) }
265+ onClick = { ( ) => clearSearch ( ) } >
266+ Clear Search
267+ </ button >
268+ < button
269+ className = { buttonClass ( keepSearch ) }
270+ onClick = { ( ) => setKeepSearch ( ! keepSearch ) } >
271+ Keep URL Search Terms
272+ </ button >
273+ </ div >
274+
275+ < div className = "flex flex-col items-center md:text-base text-xs" >
276+ < div className = "flex min-[1126px]:justify-end justify-center w-full" >
277+ < form className = "p-2 bg-gray-700 rounded-md flex flex-row" onSubmit = { ( e ) => handleSearch ( e ) } >
278+ < div >
279+ < label className = "block text-white" > Match Mode:</ label >
280+ < select name = "matchMode" className = "px-1 h-fit rounded-lg" >
281+ < option value = "or" > Match Any</ option >
282+ < option value = "and" > Match All</ option >
283+ </ select >
284+ </ div >
285+ < div className = "mx-2" >
286+ < label className = "block text-white" > Search Text:</ label >
287+ < input type = "text" name = "value" required > </ input >
288+ </ div >
289+ < button type = "submit" className = "bg-blue-500 text-white px-4 rounded-3xl" > Submit</ button >
290+ </ form >
291+ </ div >
292+ </ div >
293+
294+ < div className = "mt-1 text-center md:text-lg text-base" >
295+ Total Rows: { rows . length }
296+ </ div >
297+
298+
181299 < div > { renderTable ( ) } </ div >
182300 < div className = "mt-4 text-lg" > Total Rows: { rows . length } </ div >
183301 </ main >
184302 </ div >
185303 ) ;
186- }
304+ }
0 commit comments