From a87143123bd85d248030fcdd7db170fb0697b158 Mon Sep 17 00:00:00 2001 From: Ian Bolton Date: Mon, 14 Oct 2024 10:16:01 -0400 Subject: [PATCH] Reinstate search filter Add divider Auto open the file and incident when stepping through the wizard Fix compact prop Signed-off-by: Ian Bolton --- vscode/src/webview/components/App.tsx | 22 +- .../components/GuidedApproachWizard.tsx | 34 ++- .../components/ViolationIncidentsList.tsx | 221 +++++++++--------- 3 files changed, 163 insertions(+), 114 deletions(-) diff --git a/vscode/src/webview/components/App.tsx b/vscode/src/webview/components/App.tsx index e85e1877..a482f3f8 100644 --- a/vscode/src/webview/components/App.tsx +++ b/vscode/src/webview/components/App.tsx @@ -19,7 +19,7 @@ import { } from "@patternfly/react-core"; import { SearchIcon } from "@patternfly/react-icons"; import { vscode } from "../globals"; -import { RuleSet } from "../types"; +import { Incident, RuleSet } from "../types"; import { mockResults } from "../mockResults"; import GuidedApproachWizard from "./GuidedApproachWizard"; import ProgressIndicator from "./ProgressIndicator"; @@ -33,6 +33,17 @@ const App: React.FC = () => { const [analysisMessage, setAnalysisMessage] = useState(""); const [errorMessage, setErrorMessage] = useState(null); const [isWizardOpen, setIsWizardOpen] = useState(false); + const [focusedIncident, setFocusedIncident] = useState(null); + const [expandedViolations, setExpandedViolations] = useState>(new Set()); + + const handleIncidentSelect = (incident: Incident) => { + setFocusedIncident(incident); + vscode.postMessage({ + command: "openFile", + file: incident.uri, + line: incident.lineNumber, + }); + }; useEffect(() => { setAnalysisResults(mockResults as RuleSet[]); @@ -152,7 +163,14 @@ const App: React.FC = () => { {isAnalyzing ? ( ) : hasViolations ? ( - + ) : ( diff --git a/vscode/src/webview/components/GuidedApproachWizard.tsx b/vscode/src/webview/components/GuidedApproachWizard.tsx index bd002a10..cbe89eff 100644 --- a/vscode/src/webview/components/GuidedApproachWizard.tsx +++ b/vscode/src/webview/components/GuidedApproachWizard.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from "react"; +import React, { useMemo, useState, useEffect } from "react"; import { Wizard, WizardNav, @@ -28,6 +28,7 @@ const GuidedApproachWizard: React.FC = ({ violations, const [activeStepIndex, setActiveStepIndex] = useState(0); const [selectedIncident, setSelectedIncident] = useState(null); const [quickFix, setQuickFix] = useState(null); + const [expandedViolations, setExpandedViolations] = useState>(new Set()); const generateQuickFix = (violation: Violation, incident: Incident) => { vscode.postMessage({ @@ -39,6 +40,28 @@ const GuidedApproachWizard: React.FC = ({ violations, }); }; + const handleIncidentClick = (incident: Incident) => { + setSelectedIncident(incident); + vscode.postMessage({ + command: "openFile", + file: incident.uri, + line: incident.lineNumber, + }); + }; + + // Auto-select the first incident when the wizard opens or when navigating to a new step + useEffect(() => { + if (violations[activeStepIndex] && violations[activeStepIndex].incidents.length > 0) { + const firstIncident = violations[activeStepIndex].incidents[0]; + setSelectedIncident(firstIncident); + handleIncidentClick(firstIncident); + setExpandedViolations(new Set([violations[activeStepIndex].description])); + } else { + setSelectedIncident(null); + } + setQuickFix(null); + }, [activeStepIndex, violations]); + // Define the wizard steps const steps: WizardBasicStep[] = useMemo(() => { return violations.map((violation, index) => ({ @@ -50,8 +73,10 @@ const GuidedApproachWizard: React.FC = ({ violations, @@ -73,15 +98,12 @@ const GuidedApproachWizard: React.FC = ({ violations, ) : ( - - Select an incident to see details and generate a QuickFix. - + No incidents found for this violation. )} {quickFix && ( diff --git a/vscode/src/webview/components/ViolationIncidentsList.tsx b/vscode/src/webview/components/ViolationIncidentsList.tsx index 52716365..e5072923 100644 --- a/vscode/src/webview/components/ViolationIncidentsList.tsx +++ b/vscode/src/webview/components/ViolationIncidentsList.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback } from "react"; +import React, { useState, useCallback, useMemo } from "react"; import { Violation, Incident } from "../types"; import { ExpandableSection, @@ -18,54 +18,47 @@ import { MenuToggle, Label, MenuToggleElement, + InputGroup, + Divider, } from "@patternfly/react-core"; -import { SortAmountDownIcon } from "@patternfly/react-icons"; -import { vscode } from "../globals"; +import { SortAmountDownIcon, TimesIcon } from "@patternfly/react-icons"; + +type SortOption = "description" | "incidentCount" | "severity"; interface ViolationIncidentsListProps { violations: Violation[]; focusedIncident?: Incident | null; - onIncidentSelect?: (incident: Incident | null) => void; + onIncidentSelect: (incident: Incident) => void; compact?: boolean; + expandedViolations: Set; + setExpandedViolations: React.Dispatch>>; } -type SortOption = "description" | "incidentCount" | "severity"; - const ViolationIncidentsList: React.FC = ({ violations, focusedIncident, onIncidentSelect, compact = false, + expandedViolations, + setExpandedViolations, }) => { - const [expandedViolations, setExpandedViolations] = useState>(new Set()); const [searchTerm, setSearchTerm] = useState(""); const [sortBy, setSortBy] = useState("description"); const [isSortSelectOpen, setIsSortSelectOpen] = useState(false); - const toggleViolation = useCallback((violationId: string) => { - setExpandedViolations((prev) => { - const newSet = new Set(prev); - if (newSet.has(violationId)) { - newSet.delete(violationId); - } else { - newSet.add(violationId); - } - return newSet; - }); - }, []); - - const handleIncidentClick = useCallback( - (incident: Incident) => { - if (onIncidentSelect) { - onIncidentSelect(incident); - } - vscode.postMessage({ - command: "openFile", - file: incident.uri, - line: incident.lineNumber, + const toggleViolation = useCallback( + (violationId: string) => { + setExpandedViolations((prev) => { + const newSet = new Set(prev); + if (newSet.has(violationId)) { + newSet.delete(violationId); + } else { + newSet.add(violationId); + } + return newSet; }); }, - [onIncidentSelect], + [setExpandedViolations], ); const getHighestSeverity = (incidents: Incident[]): string => { @@ -77,47 +70,47 @@ const ViolationIncidentsList: React.FC = ({ }, "low"); }; - // const filteredAndSortedViolations = useMemo(() => { - // let result = violations; + const filteredAndSortedViolations = useMemo(() => { + let result = violations; - // // Filter - // if (searchTerm) { - // const lowercaseSearchTerm = searchTerm.toLowerCase(); - // result = result.filter((violation) => { - // const matchingIncidents = violation.incidents.filter( - // (incident) => - // incident.message.toLowerCase().includes(lowercaseSearchTerm) || - // incident.uri.toLowerCase().includes(lowercaseSearchTerm), - // ); + // Filter + if (searchTerm) { + const lowercaseSearchTerm = searchTerm.toLowerCase(); + result = result.filter((violation) => { + const matchingIncidents = violation.incidents.filter( + (incident) => + incident.message.toLowerCase().includes(lowercaseSearchTerm) || + incident.uri.toLowerCase().includes(lowercaseSearchTerm), + ); - // return ( - // matchingIncidents.length > 0 || - // violation.description.toLowerCase().includes(lowercaseSearchTerm) - // ); - // }); - // } + return ( + matchingIncidents.length > 0 || + violation.description.toLowerCase().includes(lowercaseSearchTerm) + ); + }); + } - // // Sort - // result.sort((a, b) => { - // switch (sortBy) { - // case "description": - // return a.description.localeCompare(b.description); - // case "incidentCount": - // return b.incidents.length - a.incidents.length; - // case "severity": - // const severityOrder = { high: 3, medium: 2, low: 1 }; - // const aMaxSeverity = - // severityOrder[getHighestSeverity(a.incidents) as keyof typeof severityOrder]; - // const bMaxSeverity = - // severityOrder[getHighestSeverity(b.incidents) as keyof typeof severityOrder]; - // return bMaxSeverity - aMaxSeverity; - // default: - // return 0; - // } - // }); + // Sort + result.sort((a, b) => { + switch (sortBy) { + case "description": + return a.description.localeCompare(b.description); + case "incidentCount": + return b.incidents.length - a.incidents.length; + case "severity": + const severityOrder = { high: 3, medium: 2, low: 1 }; + const aMaxSeverity = + severityOrder[getHighestSeverity(a.incidents) as keyof typeof severityOrder]; + const bMaxSeverity = + severityOrder[getHighestSeverity(b.incidents) as keyof typeof severityOrder]; + return bMaxSeverity - aMaxSeverity; + default: + return 0; + } + }); - // return result; - // }, [violations, searchTerm, sortBy]); + return result; + }, [violations, searchTerm, sortBy]); const renderViolation = useCallback( (violation: Violation) => { @@ -177,36 +170,41 @@ const ViolationIncidentsList: React.FC = ({ isExpanded={isExpanded} > - {violation.incidents.map((incident) => ( - - - - - - - - - {incident.severity} - - - + {violation.incidents.map((incident, index) => ( + + {index > 0 && } + + + + + + + + + {incident.severity} + + + + ))} @@ -214,7 +212,7 @@ const ViolationIncidentsList: React.FC = ({ ); }, - [expandedViolations, handleIncidentClick, toggleViolation, focusedIncident], + [expandedViolations, toggleViolation, focusedIncident], ); const onSortToggle = () => { @@ -232,19 +230,30 @@ const ViolationIncidentsList: React.FC = ({ ); + const clearSearch = () => { + setSearchTerm(""); + }; + return ( - setSearchTerm(value)} - /> + + setSearchTerm(value)} + /> + {searchTerm && ( + + )} +