Skip to content

Commit c210861

Browse files
committed
status fix
1 parent d6d1c9a commit c210861

5 files changed

Lines changed: 82 additions & 102 deletions

File tree

web-report/src/AppProvider.tsx

Lines changed: 19 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,18 @@ type WindowWithFileSystemApi = Window & {
2828
}) => Promise<FileSystemApiHandle>;
2929
};
3030

31+
export type StatusFilterState = "inactive" | "active" | "removed";
32+
export type StatusFilterMap = Record<number, StatusFilterState>;
33+
3134
type AppContextType = {
3235
data: WebFuzzingCommonsReport | null;
3336
loading: boolean;
3437
error: string | null;
3538
testFiles: Record<string, string>;
3639
loadTestFile: (path: string) => Promise<void>;
3740
transformedReport: ITransformedReport[];
38-
filterEndpoints: (activeFilters: Record<number, string>) => ITransformedReport[];
41+
statusFilters: StatusFilterMap;
42+
setStatusFilters: (filters: StatusFilterMap) => void;
3943
filteredEndpoints: ITransformedReport[];
4044
invalidReportErrors: ZodIssue[] | null;
4145
lowCodeMode: boolean;
@@ -322,80 +326,47 @@ export const AppProvider = ({ children }: AppProviderProps) => {
322326

323327
const clearReviewMessage = useCallback(() => setReviewMessage(null), []);
324328

325-
const [filteredEndpoints, setFilteredEndpoints] = useState(transformedReport);
329+
const [statusFilters, setStatusFilters] = useState<StatusFilterMap>({});
326330

327-
useEffect(() => {
328-
if (data) {
329-
setFilteredEndpoints(transformedReport);
330-
}
331-
}, [data, transformedReport]);
332-
333-
const filterEndpoints = (activeFilters: Record<number, string>) => {
334-
// Filter the endpoints based on the active filters
335-
const filtered = transformedReport.filter(endpoint => {
336-
// If no filters are active, show all endpoints
337-
if (Object.keys(activeFilters).length === 0) {
338-
return true;
339-
}
331+
const filteredEndpoints = useMemo(() => {
332+
const activeFilters = statusFilters;
333+
return transformedReport.filter(endpoint => {
334+
if (Object.keys(activeFilters).length === 0) return true;
340335

341-
// Check if any status code or fault code is marked as "removed"
342336
const hasRemovedStatusCode = endpoint.httpStatusCodes.some(code =>
343337
activeFilters[code.code] === "removed"
344338
);
345339
const hasRemovedFaultCode = endpoint.faults.some(code =>
346340
activeFilters[-code.code] === "removed"
347341
);
348-
349-
// Check if any status code or fault code is marked as "active"
350342
const hasActiveStatusCode = endpoint.httpStatusCodes.some(code =>
351343
activeFilters[code.code] === "active"
352344
);
353345
const hasActiveFaultCode = endpoint.faults.some(code =>
354346
activeFilters[-code.code] === "active"
355347
);
356348

357-
const hasActiveFilter = activeFilters && Object.values(activeFilters).some((value) => value === "active");
358-
const hasRemovedFilter = activeFilters && Object.values(activeFilters).some((value) => value === "removed");
349+
const hasActiveFilter = Object.values(activeFilters).some(v => v === "active");
350+
const hasRemovedFilter = Object.values(activeFilters).some(v => v === "removed");
359351

360-
if (!hasActiveFilter && !hasRemovedFilter) {
361-
// If no filters are active, show all endpoints
362-
return true;
363-
}
352+
if (!hasActiveFilter && !hasRemovedFilter) return true;
364353

365354
if (hasActiveFilter) {
366-
if (hasRemovedFilter) {
367-
// If there are both active and removed filters, check if the endpoint matches any of them
368-
if(hasRemovedFaultCode || hasRemovedStatusCode) {
369-
return false;
370-
}
371-
372-
return !!(hasActiveStatusCode || hasActiveFaultCode);
373-
374-
} else {
375-
// If there are only active filters, check if the endpoint matches any of them
376-
return !!(hasActiveStatusCode || hasActiveFaultCode);
377-
378-
}
379-
} else if (hasRemovedFilter) {
380-
// If there are only removed filters, check if the endpoint matches any of them
381-
return !(hasRemovedStatusCode || hasRemovedFaultCode);
382-
383-
} else {
384-
// If there are no active or removed filters, show all endpoints
385-
return true;
355+
if (hasRemovedFilter && (hasRemovedStatusCode || hasRemovedFaultCode)) return false;
356+
return hasActiveStatusCode || hasActiveFaultCode;
386357
}
358+
return !(hasRemovedStatusCode || hasRemovedFaultCode);
387359
});
388-
setFilteredEndpoints(filtered)
389-
return filtered;
390-
}
360+
}, [statusFilters, transformedReport]);
391361

392362
const value: AppContextType = {
393363
data,
394364
loading,
395365
error,
396366
testFiles,
397367
transformedReport,
398-
filterEndpoints,
368+
statusFilters,
369+
setStatusFilters,
399370
filteredEndpoints,
400371
invalidReportErrors,
401372
lowCodeMode,

web-report/src/components/EndpointAccordion.tsx

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,14 @@ export const EndpointAccordion: React.FC<IEndpointAccordionProps> = ({
3737
}
3838
);
3939

40-
const [selectedCode, setSelectedCode] = useState<number | string>(sortedStatusCodes[0]?.code || 0);
41-
const [isFault, setIsFault] = useState(false);
40+
type Selection = {code: number | string; isFault: boolean};
41+
const [selection, setSelection] = useState<Selection | null>(null);
4242

43-
const selectedTestCases = statusCodes.find((code) => code.code === selectedCode)?.testCases || [];
44-
const selectedFaultTestCases = faults.find((code) => code.code === selectedCode)?.testCases || [];
43+
const toggleSelection = (code: number | string, fault: boolean) => {
44+
setSelection(prev =>
45+
prev && prev.code === code && prev.isFault === fault ? null : {code, isFault: fault}
46+
);
47+
};
4548

4649
const sortedFaults = faults.sort((a, b) => {
4750
const codeA = Number(a.code);
@@ -51,13 +54,22 @@ export const EndpointAccordion: React.FC<IEndpointAccordionProps> = ({
5154
}
5255
return codeA - codeB;
5356
});
54-
const getSelectedStyle = (code: number | string, fault: boolean) => {
5557

56-
const isSelected = selectedCode === code && isFault === fault;
58+
const getSelectedStyle = (code: number | string, fault: boolean) => {
59+
const isSelected = selection?.code === code && selection?.isFault === fault;
5760
return isSelected ? "ring-2 ring-offset-2 ring-offset-white ring-blue-400 shadow-md" : "";
58-
5961
}
6062

63+
const selectedGroup = selection
64+
? (selection.isFault
65+
? sortedFaults.find(f => f.code === selection.code)
66+
: sortedStatusCodes.find(s => s.code === selection.code))
67+
: null;
68+
69+
const nonEmptyStatusGroups = sortedStatusCodes.filter(c => c.testCases.length > 0);
70+
const nonEmptyFaultGroups = sortedFaults.filter(f => f.testCases.length > 0);
71+
const hasAnyTestCases = nonEmptyStatusGroups.length > 0 || nonEmptyFaultGroups.length > 0;
72+
6173
const faultColors = ["bg-red-300", "bg-red-500", "bg-red-700"];
6274
return (
6375
<AccordionItem value={value} className="border-2 border-black mb-4 overflow-hidden" data-testid={endpoint}>
@@ -82,10 +94,7 @@ export const EndpointAccordion: React.FC<IEndpointAccordionProps> = ({
8294
<div className="flex flex-wrap gap-2">
8395
{
8496
sortedStatusCodes.map((code, index) => (
85-
<Badge key={index} onClick={() => {
86-
setSelectedCode(code.code);
87-
setIsFault(false);
88-
}}
97+
<Badge key={index} onClick={() => toggleSelection(code.code, false)}
8998
className={`${getColor(code.code, true, false)} ${getSelectedStyle(code.code, false)} ${getHoverColor(code.code, false)} cursor-pointer text-white font-mono text-base border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]`}>
9099
{code.code == -1 ? "NO-RESPONSE" : `H${code.code}`}
91100
</Badge>
@@ -103,10 +112,7 @@ export const EndpointAccordion: React.FC<IEndpointAccordionProps> = ({
103112
<div className="flex flex-wrap gap-2">
104113
{
105114
sortedFaults.map((fault, index) => (
106-
<Badge key={index} onClick={() => {
107-
setSelectedCode(fault.code)
108-
setIsFault(true);
109-
}}
115+
<Badge key={index} onClick={() => toggleSelection(fault.code, true)}
110116
className={`${faultColors[index % faultColors.length]} ${getSelectedStyle(fault.code, true)} hover:bg-red-400 cursor-pointer text-white text-base font-mono border-2 border-black shadow-[2px_2px_0px_0px_rgba(0,0,0,1)]`}>
111117
F{fault.code}
112118
</Badge>
@@ -118,15 +124,30 @@ export const EndpointAccordion: React.FC<IEndpointAccordionProps> = ({
118124
}
119125
</div>
120126
</div>
121-
<div className="text-xs text-gray-500 mt-1">Click to show test cases.</div>
127+
<div className="text-xs text-gray-500 mt-1">
128+
{selection ? "Click the highlighted code again to clear the filter." : "Showing all test cases. Click a code to filter."}
129+
</div>
122130

123-
{
124-
(selectedTestCases.length > 0 || selectedFaultTestCases.length > 0) &&
131+
{hasAnyTestCases && (
125132
<div className="mt-6">
126-
<TestCases addTestTab={addTestTab} isFault={isFault} code={selectedCode}
127-
testCases={selectedTestCases.length > 0 && !isFault ? selectedTestCases : selectedFaultTestCases}/>
133+
{selection && selectedGroup && selectedGroup.testCases.length > 0 && (
134+
<TestCases addTestTab={addTestTab} isFault={selection.isFault} code={selection.code}
135+
testCases={selectedGroup.testCases}/>
136+
)}
137+
{!selection && (
138+
<>
139+
{nonEmptyStatusGroups.map(c => (
140+
<TestCases key={`s-${c.code}`} addTestTab={addTestTab} isFault={false}
141+
code={c.code} testCases={c.testCases}/>
142+
))}
143+
{nonEmptyFaultGroups.map(f => (
144+
<TestCases key={`f-${f.code}`} addTestTab={addTestTab} isFault={true}
145+
code={f.code} testCases={f.testCases}/>
146+
))}
147+
</>
148+
)}
128149
</div>
129-
}
150+
)}
130151
</AccordionContent>
131152
</AccordionItem>
132153
)

web-report/src/components/StatusCodeFilterButton.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
import { useState } from "react"
21
import {Badge} from "@/components/ui/badge.tsx";
32

43
type FilterState = "inactive" | "active" | "removed"
54

65
interface StatusCodeFilterButtonProps {
76
code: number
8-
initialState?: FilterState
7+
state: FilterState
98
onChange: (code: number, state: FilterState) => void,
109
isFault?: boolean
1110
}
1211

13-
export function StatusCodeFilterButton({ code, initialState = "inactive", onChange, isFault }: StatusCodeFilterButtonProps) {
14-
const [state, setState] = useState<FilterState>(initialState)
12+
export function StatusCodeFilterButton({ code, state, onChange, isFault }: StatusCodeFilterButtonProps) {
1513

1614
const getBackgroundColor = () => {
1715
if(isFault) {
@@ -40,7 +38,6 @@ export function StatusCodeFilterButton({ code, initialState = "inactive", onChan
4038

4139
const toggleState = () => {
4240
const newState: FilterState = state === "inactive" ? "active" : state === "active" ? "removed" : "inactive"
43-
setState(newState)
4441
onChange(code, newState)
4542
}
4643

web-report/src/components/StatusCodeFilters.tsx

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {useState} from "react"
1+
import {useState, useMemo} from "react"
22
import {StatusCodeFilterButton} from "./StatusCodeFilterButton"
33
import {ITransformedReport} from "@/lib/utils.tsx";
44
import {StatusCodeModal} from "@/components/StatusCodeModal.tsx";
@@ -7,36 +7,27 @@ type FilterState = "inactive" | "active" | "removed"
77

88
interface StatusCodeFiltersProps {
99
data: ITransformedReport[]
10+
filters: Record<number, FilterState>
1011
onFiltersChange: (activeFilters: Record<number, FilterState>) => void
1112
}
1213

13-
export function StatusCodeFilters({data, onFiltersChange}: StatusCodeFiltersProps) {
14+
export function StatusCodeFilters({data, filters, onFiltersChange}: StatusCodeFiltersProps) {
1415

15-
// Extract all unique status codes from endpoints
16-
const allStatusCodes = [...new Map(
17-
data.flatMap(endpoint => endpoint.httpStatusCodes)
18-
.map(item => [item.code, item])
19-
).values()].sort((a, b) => a.code - b.code);
16+
const allStatusCodes = useMemo(() =>
17+
[...new Map(
18+
data.flatMap(endpoint => endpoint.httpStatusCodes)
19+
.map(item => [item.code, item])
20+
).values()].sort((a, b) => a.code - b.code),
21+
[data]
22+
);
2023

21-
const allFaultCodes = [...new Set(data.map(item => {
22-
return item.faults.map(fault => fault.code)
23-
}).flat())].sort((a, b) => a - b);
24+
const allFaultCodes = useMemo(() =>
25+
[...new Set(data.flatMap(item => item.faults.map(fault => fault.code)))].sort((a, b) => a - b),
26+
[data]
27+
);
2428

25-
const initialFilters = allStatusCodes.reduce((acc, code) => {
26-
acc[code.code] = "inactive"
27-
return acc
28-
}, {} as Record<number, FilterState>)
29-
allFaultCodes.forEach(code => {
30-
initialFilters[-code] = "inactive"
31-
})
32-
const [filters, setFilters] = useState<Record<number, FilterState>>(initialFilters)
33-
34-
// Handle filter state change
3529
const handleFilterChange = (code: number, state: FilterState) => {
36-
const newFilters = {...filters, [code]: state}
37-
38-
setFilters(newFilters)
39-
onFiltersChange(newFilters)
30+
onFiltersChange({...filters, [code]: state});
4031
}
4132

4233
const [isModalOpen, setIsModalOpen] = useState(false)
@@ -51,7 +42,7 @@ export function StatusCodeFilters({data, onFiltersChange}: StatusCodeFiltersProp
5142
<StatusCodeFilterButton
5243
key={code.code}
5344
code={code.code}
54-
initialState={filters[code.code] || "inactive"}
45+
state={filters[code.code] ?? "inactive"}
5546
onChange={handleFilterChange}
5647
/>
5748
))}
@@ -64,7 +55,7 @@ export function StatusCodeFilters({data, onFiltersChange}: StatusCodeFiltersProp
6455
<StatusCodeFilterButton
6556
key={-code}
6657
code={-code}
67-
initialState={filters[-code] || "inactive"}
58+
state={filters[-code] ?? "inactive"}
6859
onChange={handleFilterChange}
6960
isFault={true}
7061
/>

web-report/src/pages/Endpoints.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ interface IProps {
1010

1111
export const Endpoints: React.FC<IProps> = ({addTestTab}) => {
1212

13-
const {transformedReport, filteredEndpoints, filterEndpoints} = useAppContext();
13+
const {transformedReport, filteredEndpoints, statusFilters, setStatusFilters} = useAppContext();
1414

1515
return (
1616
<div className="border-2 border-black p-3 sm:p-6 rounded-none">
17-
<StatusCodeFilters data={transformedReport} onFiltersChange={filterEndpoints}/>
17+
<StatusCodeFilters data={transformedReport} filters={statusFilters} onFiltersChange={setStatusFilters}/>
1818
<div className="flex items-center mb-2">
1919
<h3 className="text-sm font-medium text-gray-700 mr-3"># Endpoints:</h3>
2020
<div className="flex flex-wrap gap-2 font-bold font-mono">

0 commit comments

Comments
 (0)