@@ -2,20 +2,18 @@ import * as vscode from "vscode";
22import * as uuid from "uuid" ;
33import { convertArrayToCSV } from "convert-array-to-csv" ;
44import { ViolationOverview } from "./ViolationOverviewPanel" ;
5- import { ScanResult } from "@flow-scanner/lightning-flow-scanner-core" ;
5+ import { ScanResult , exportSarif } from "@flow-scanner/lightning-flow-scanner-core" ;
66
77export class ScanOverview {
88 public static currentPanel : ScanOverview | undefined ;
99 public static readonly viewType = "report" ;
10+
1011 private readonly _panel : vscode . WebviewPanel ;
1112 private readonly _extensionUri : vscode . Uri ;
1213 private _disposables : vscode . Disposable [ ] = [ ] ;
13- private isDownloading = false ;
14+ private _lastScanResults : ScanResult [ ] = [ ] ;
1415
15- public static createOrShow (
16- extensionUri : vscode . Uri ,
17- scanResults : ScanResult [ ]
18- ) {
16+ public static createOrShow ( extensionUri : vscode . Uri , scanResults : ScanResult [ ] ) {
1917 const column = vscode . window . activeTextEditor
2018 ? vscode . window . activeTextEditor . viewColumn
2119 : undefined ;
@@ -38,11 +36,8 @@ export class ScanOverview {
3836 ] ,
3937 }
4038 ) ;
41- ScanOverview . currentPanel = new ScanOverview (
42- panel ,
43- extensionUri ,
44- scanResults
45- ) ;
39+
40+ ScanOverview . currentPanel = new ScanOverview ( panel , extensionUri , scanResults ) ;
4641 }
4742
4843 public static kill ( ) {
@@ -66,51 +61,47 @@ export class ScanOverview {
6661 this . _panel . dispose ( ) ;
6762 while ( this . _disposables . length ) {
6863 const x = this . _disposables . pop ( ) ;
69- if ( x ) {
70- x . dispose ( ) ;
71- }
64+ if ( x ) x . dispose ( ) ;
7265 }
7366 }
7467
7568 private async _update ( scanResults : ScanResult [ ] ) {
69+ this . _lastScanResults = scanResults ;
7670 const webview = this . _panel . webview ;
7771 this . _panel . webview . html = this . _getHtmlForWebview ( webview ) ;
72+
7873 webview . onDidReceiveMessage ( async ( data ) => {
7974 switch ( data . type ) {
8075 case "goToFile" : {
81- if ( ! data . value ) {
82- return ;
83- }
76+ if ( ! data . value ) return ;
8477 vscode . workspace . openTextDocument ( data . value . path ) . then ( ( doc ) => {
8578 vscode . window . showTextDocument ( doc ) ;
8679 } ) ;
8780 break ;
8881 }
82+
8983 case "viewAll" : {
90- if ( ! data . value ) {
91- return ;
92- }
84+ if ( ! data . value ) return ;
9385 ViolationOverview . createOrShow ( this . _extensionUri , data . value , "All" ) ;
9486 break ;
9587 }
88+
9689 case "goToDetails" : {
97- if ( ! data . value ) {
98- return ;
99- }
90+ if ( ! data . value ) return ;
10091 ViolationOverview . createOrShow (
10192 this . _extensionUri ,
10293 [ data . value ] ,
10394 data . value . flow . label
10495 ) ;
10596 break ;
10697 }
98+
10799 case "onError" : {
108- if ( ! data . value ) {
109- return ;
110- }
100+ if ( ! data . value ) return ;
111101 vscode . window . showErrorMessage ( data . value ) ;
112102 break ;
113103 }
104+
114105 case "init-view" : {
115106 if ( scanResults ) {
116107 webview . postMessage ( {
@@ -120,18 +111,69 @@ export class ScanOverview {
120111 }
121112 return ;
122113 }
114+
123115 case "download" : {
124- let saveResult = await vscode . window . showSaveDialog ( {
125- filters : {
126- csv : [ ".csv" ] ,
127- } ,
128- } ) ;
129- const csv = convertArrayToCSV ( data . value ) ;
130- await vscode . workspace . fs . writeFile ( saveResult , Buffer . from ( csv ) ) ;
131- await vscode . window . showInformationMessage (
132- "Downloaded file: " + saveResult . fsPath
133- ) ;
134- }
116+ if ( ! data . value || ! Array . isArray ( data . value ) || data . value . length === 0 ) {
117+ await vscode . window . showInformationMessage (
118+ "No results found. Please make sure to complete a scan before downloading."
119+ ) ;
120+ return ;
121+ }
122+
123+ const formatChoice = await vscode . window . showQuickPick (
124+ [
125+ { label : "CSV" , description : "Comma-separated values" , value : "csv" } ,
126+ { label : "SARIF" , description : "Static Analysis Results Interchange Format" , value : "sarif" }
127+ ] ,
128+ {
129+ placeHolder : "Select export format (Esc to cancel)" ,
130+ canPickMany : false ,
131+ ignoreFocusOut : false ,
132+ }
133+ ) ;
134+
135+ if ( ! formatChoice ) return ;
136+
137+ const chosenFormat = formatChoice . value ;
138+ const filterKey = chosenFormat === "sarif" ? "sarif" : "csv" ;
139+ const filterExt = chosenFormat === "sarif" ? ".sarif" : ".csv" ;
140+
141+ const defaultUri = vscode . workspace . workspaceFolders ?. [ 0 ] ?. uri ;
142+ const saveResult = await vscode . window . showSaveDialog ( {
143+ defaultUri,
144+ filters : { [ filterKey ] : [ filterExt ] } ,
145+ title : `Save ${ chosenFormat . toUpperCase ( ) } file` ,
146+ } ) ;
147+
148+ if ( ! saveResult ) return ;
149+
150+ try {
151+ let content : string ;
152+
153+ if ( chosenFormat === "sarif" ) {
154+ // ---- SARIF: use the *original* scan results (they have a real Flow with fsPath)
155+ const originalResults : ScanResult [ ] = this . _lastScanResults ?? [ ] ;
156+ if ( originalResults . length === 0 ) {
157+ await vscode . window . showWarningMessage ( "No original scan data available for SARIF export." ) ;
158+ return ;
159+ }
160+ content = exportSarif ( originalResults ) ;
161+ } else {
162+ // ---- CSV: keep using the web-view payload (already flattened)
163+ content = convertArrayToCSV ( data . value ) ;
164+ }
165+
166+ await vscode . workspace . fs . writeFile ( saveResult , Buffer . from ( content , "utf-8" ) ) ;
167+ await vscode . window . showInformationMessage (
168+ `Downloaded ${ chosenFormat . toUpperCase ( ) } file: ${ saveResult . fsPath } `
169+ ) ;
170+ } catch ( err : any ) {
171+ await vscode . window . showErrorMessage (
172+ `Failed to export ${ chosenFormat . toUpperCase ( ) } : ${ err ?. message ?? err } `
173+ ) ;
174+ }
175+ break ;
176+ }
135177 }
136178 } ) ;
137179 }
@@ -141,11 +183,7 @@ export class ScanOverview {
141183 vscode . Uri . joinPath ( this . _extensionUri , "out/compiled" , "ScanOverview.js" )
142184 ) ;
143185 const cssUri = webview . asWebviewUri (
144- vscode . Uri . joinPath (
145- this . _extensionUri ,
146- "out/compiled" ,
147- "ScanOverview.css"
148- )
186+ vscode . Uri . joinPath ( this . _extensionUri , "out/compiled" , "ScanOverview.css" )
149187 ) ;
150188 const tabulatorStyles = webview . asWebviewUri (
151189 vscode . Uri . joinPath ( this . _extensionUri , "media" , "tabulator.css" )
@@ -157,24 +195,25 @@ export class ScanOverview {
157195 vscode . Uri . joinPath ( this . _extensionUri , "media" , "vscode.css" )
158196 ) ;
159197 const nonce = uuid . v4 ( ) ;
198+
160199 return `
161200 <!DOCTYPE html>
162- <html lang="en">
163- <head>
164- <meta charset="UTF-8">
201+ <html lang="en">
202+ <head>
203+ <meta charset="UTF-8">
165204 <meta http-equiv="Content-Security-Policy" content="img-src https: data:; style-src 'unsafe-inline' ${ webview . cspSource } ; script-src 'nonce-${ nonce } ';">
166- <meta name="viewport" content="width=device-width, initial-scale=1.0">
205+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
167206 <link href="${ tabulatorStyles } " rel="stylesheet">
168207 <link href="${ stylesResetUri } " rel="stylesheet">
169208 <link href="${ stylesMainUri } " rel="stylesheet">
170209 <link href="${ cssUri } " rel="stylesheet">
171210 <script nonce="${ nonce } ">
172- const tsvscode = acquireVsCodeApi();
211+ const tsvscode = acquireVsCodeApi();
173212 </script>
174- </head>
213+ </head>
175214 <body>
176215 <script src="${ scriptUri } " nonce="${ nonce } "></script>
177- </body>
178- </html>`;
216+ </body>
217+ </html>`;
179218 }
180- }
219+ }
0 commit comments