11import * as path from 'path' ;
22import * as fs from 'fs-extra' ;
33
4- import { window , commands , Uri , ExtensionContext , QuickPickItem , workspace , ViewColumn } from 'vscode' ;
4+ import { window , commands , Uri , ExtensionContext , workspace , ViewColumn } from 'vscode' ;
55import { Credentials } from '../authentication' ;
66import { UserCancellationException } from '../commandRunner' ;
77import { showInformationMessageWithAction } from '../helpers' ;
88import { logger } from '../logging' ;
99import { QueryHistoryManager } from '../query-history' ;
1010import { createGist } from './gh-api/gh-api-client' ;
1111import { RemoteQueriesManager } from './remote-queries-manager' ;
12- import { generateMarkdown } from './remote-queries-markdown-generation' ;
12+ import {
13+ generateMarkdown ,
14+ generateVariantAnalysisMarkdown ,
15+ MarkdownFile ,
16+ } from './remote-queries-markdown-generation' ;
1317import { RemoteQuery } from './remote-query' ;
1418import { AnalysisResults , sumAnalysesResults } from './shared/analysis-result' ;
1519import { pluralize } from '../pure/word' ;
20+ import { VariantAnalysisManager } from './variant-analysis-manager' ;
21+ import { assertNever } from '../pure/helpers-pure' ;
22+ import {
23+ VariantAnalysis ,
24+ VariantAnalysisScannedRepository ,
25+ VariantAnalysisScannedRepositoryResult
26+ } from './shared/variant-analysis' ;
1627
1728/**
18- * Exports the results of the currently-selected remote query.
29+ * Exports the results of the currently-selected remote query or variant analysis .
1930 */
2031export async function exportSelectedRemoteQueryResults ( queryHistoryManager : QueryHistoryManager ) : Promise < void > {
2132 const queryHistoryItem = queryHistoryManager . getCurrentQueryHistoryItem ( ) ;
2233 if ( ! queryHistoryItem || queryHistoryItem . t === 'local' ) {
2334 throw new Error ( 'No variant analysis results currently open. To open results, click an item in the query history view.' ) ;
2435 }
2536
26- if ( ! queryHistoryItem . completed ) {
27- throw new Error ( 'Variant analysis results are not yet available.' ) ;
28- }
29-
3037 if ( queryHistoryItem . t === 'remote' ) {
3138 return commands . executeCommand ( 'codeQL.exportRemoteQueryResults' , queryHistoryItem . queryId ) ;
39+ } else if ( queryHistoryItem . t === 'variant-analysis' ) {
40+ return commands . executeCommand ( 'codeQL.exportVariantAnalysisResults' , queryHistoryItem . variantAnalysis . id ) ;
3241 } else {
33- throw new Error ( 'No variant analysis results currently open. To open results, click an item in the query history view.' ) ;
42+ assertNever ( queryHistoryItem ) ;
3443 }
3544}
3645
3746/**
38- * Exports the results of the given or currently-selected remote query.
47+ * Exports the results of the given remote query.
3948 * The user is prompted to select the export format.
4049 */
4150export async function exportRemoteQueryResults (
@@ -58,32 +67,111 @@ export async function exportRemoteQueryResults(
5867 const query = queryHistoryItem . remoteQuery ;
5968 const analysesResults = remoteQueriesManager . getAnalysesResults ( queryHistoryItem . queryId ) ;
6069
61- const gistOption = {
62- label : '$(ports-open-browser-icon) Create Gist (GitHub)' ,
63- } ;
64- const localMarkdownOption = {
65- label : '$(markdown) Save as markdown' ,
66- } ;
67- const exportFormat = await determineExportFormat ( gistOption , localMarkdownOption ) ;
70+ const exportFormat = await determineExportFormat ( ) ;
71+ if ( ! exportFormat ) {
72+ return ;
73+ }
6874
69- if ( exportFormat === gistOption ) {
70- await exportResultsToGist ( ctx , query , analysesResults ) ;
71- } else if ( exportFormat === localMarkdownOption ) {
72- const queryDirectoryPath = await queryHistoryManager . getQueryHistoryItemDirectory (
73- queryHistoryItem
74- ) ;
75- await exportResultsToLocalMarkdown ( queryDirectoryPath , query , analysesResults ) ;
75+ const exportDirectory = await queryHistoryManager . getQueryHistoryItemDirectory ( queryHistoryItem ) ;
76+
77+ await exportRemoteQueryAnalysisResults ( ctx , exportDirectory , query , analysesResults , exportFormat ) ;
78+ }
79+
80+ export async function exportRemoteQueryAnalysisResults (
81+ ctx : ExtensionContext ,
82+ exportDirectory : string ,
83+ query : RemoteQuery ,
84+ analysesResults : AnalysisResults [ ] ,
85+ exportFormat : 'gist' | 'local' ,
86+ ) {
87+ const description = buildGistDescription ( query , analysesResults ) ;
88+ const markdownFiles = generateMarkdown ( query , analysesResults , exportFormat ) ;
89+
90+ await exportResults ( ctx , exportDirectory , description , markdownFiles , exportFormat ) ;
91+ }
92+
93+ /**
94+ * Exports the results of the given or currently-selected remote query.
95+ * The user is prompted to select the export format.
96+ */
97+ export async function exportVariantAnalysisResults (
98+ ctx : ExtensionContext ,
99+ variantAnalysisManager : VariantAnalysisManager ,
100+ variantAnalysisId : number ,
101+ ) : Promise < void > {
102+ const variantAnalysis = await variantAnalysisManager . getVariantAnalysis ( variantAnalysisId ) ;
103+ if ( ! variantAnalysis ) {
104+ void logger . log ( `Could not find variant analysis with id ${ variantAnalysisId } ` ) ;
105+ throw new Error ( 'There was an error when trying to retrieve variant analysis information' ) ;
76106 }
107+
108+ void logger . log ( `Exporting variant analysis results for variant analysis with id ${ variantAnalysis . id } ` ) ;
109+
110+ const exportFormat = await determineExportFormat ( ) ;
111+ if ( ! exportFormat ) {
112+ return ;
113+ }
114+
115+ async function * getAnalysesResults ( ) : AsyncGenerator < [ VariantAnalysisScannedRepository , VariantAnalysisScannedRepositoryResult ] > {
116+ if ( ! variantAnalysis ?. scannedRepos ) {
117+ return ;
118+ }
119+
120+ for ( const repo of variantAnalysis . scannedRepos ) {
121+ if ( repo . resultCount == 0 ) {
122+ yield [ repo , {
123+ variantAnalysisId : variantAnalysis . id ,
124+ repositoryId : repo . repository . id ,
125+ } ] ;
126+ continue ;
127+ }
128+
129+ let result : VariantAnalysisScannedRepositoryResult ;
130+
131+ if ( ! variantAnalysisManager . areResultsLoaded ( variantAnalysis . id , repo . repository . fullName ) ) {
132+ result = await variantAnalysisManager . loadResultsFromStorage ( variantAnalysis . id , repo . repository . fullName ) ;
133+ } else {
134+ result = await variantAnalysisManager . loadResults ( variantAnalysis . id , repo . repository . fullName ) ;
135+ }
136+
137+ yield [ repo , result ] ;
138+ }
139+ }
140+
141+ const exportDirectory = variantAnalysisManager . getVariantAnalysisStorageLocation ( variantAnalysis . id ) ;
142+
143+ await exportVariantAnalysisAnalysisResults ( ctx , exportDirectory , variantAnalysis , getAnalysesResults ( ) , exportFormat ) ;
144+ }
145+
146+ export async function exportVariantAnalysisAnalysisResults (
147+ ctx : ExtensionContext ,
148+ exportDirectory : string ,
149+ variantAnalysis : VariantAnalysis ,
150+ analysesResults : AsyncIterable < [ VariantAnalysisScannedRepository , VariantAnalysisScannedRepositoryResult ] > ,
151+ exportFormat : 'gist' | 'local' ,
152+ ) {
153+ const description = buildVariantAnalysisGistDescription ( variantAnalysis ) ;
154+ const markdownFiles = await generateVariantAnalysisMarkdown ( variantAnalysis , analysesResults , 'gist' ) ;
155+
156+ await exportResults ( ctx , exportDirectory , description , markdownFiles , exportFormat ) ;
77157}
78158
79159/**
80160 * Determines the format in which to export the results, from the given export options.
81161 */
82- async function determineExportFormat (
83- ...options : { label : string } [ ]
84- ) : Promise < QuickPickItem > {
162+ async function determineExportFormat ( ) : Promise < 'gist' | 'local' | undefined > {
163+ const gistOption = {
164+ label : '$(ports-open-browser-icon) Create Gist (GitHub)' ,
165+ } ;
166+ const localMarkdownOption = {
167+ label : '$(markdown) Save as markdown' ,
168+ } ;
169+
85170 const exportFormat = await window . showQuickPick (
86- options ,
171+ [
172+ gistOption ,
173+ localMarkdownOption ,
174+ ] ,
87175 {
88176 placeHolder : 'Select export format' ,
89177 canPickMany : false ,
@@ -93,20 +181,38 @@ async function determineExportFormat(
93181 if ( ! exportFormat || ! exportFormat . label ) {
94182 throw new UserCancellationException ( 'No export format selected' , true ) ;
95183 }
96- return exportFormat ;
184+
185+ if ( exportFormat === gistOption ) {
186+ return 'gist' ;
187+ }
188+ if ( exportFormat === localMarkdownOption ) {
189+ return 'local' ;
190+ }
191+
192+ return undefined ;
97193}
98194
99- /**
100- * Converts the results of a remote query to markdown and uploads the files as a secret gist.
101- */
102- export async function exportResultsToGist (
195+ export async function exportResults (
103196 ctx : ExtensionContext ,
104- query : RemoteQuery ,
105- analysesResults : AnalysisResults [ ]
106- ) : Promise < void > {
197+ exportDirectory : string ,
198+ description : string ,
199+ markdownFiles : MarkdownFile [ ] ,
200+ exportFormat : 'gist' | 'local' ,
201+ ) {
202+ if ( exportFormat === 'gist' ) {
203+ await exportToGist ( ctx , description , markdownFiles ) ;
204+ } else if ( exportFormat === 'local' ) {
205+ await exportToLocalMarkdown ( exportDirectory , markdownFiles ) ;
206+ }
207+ }
208+
209+ export async function exportToGist (
210+ ctx : ExtensionContext ,
211+ description : string ,
212+ markdownFiles : MarkdownFile [ ]
213+ ) {
107214 const credentials = await Credentials . initialize ( ctx ) ;
108- const description = buildGistDescription ( query , analysesResults ) ;
109- const markdownFiles = generateMarkdown ( query , analysesResults , 'gist' ) ;
215+
110216 // Convert markdownFiles to the appropriate format for uploading to gist
111217 const gistFiles = markdownFiles . reduce ( ( acc , cur ) => {
112218 acc [ `${ cur . fileName } .md` ] = { content : cur . content . join ( '\n' ) } ;
@@ -137,16 +243,25 @@ const buildGistDescription = (query: RemoteQuery, analysesResults: AnalysisResul
137243} ;
138244
139245/**
140- * Converts the results of a remote query to markdown and saves the files locally
141- * in the query directory (where query results and metadata are also saved).
246+ * Builds Gist description
247+ * Ex: Empty Block (Go) x results (y repositories)
142248 */
143- async function exportResultsToLocalMarkdown (
144- queryDirectoryPath : string ,
145- query : RemoteQuery ,
146- analysesResults : AnalysisResults [ ]
249+ const buildVariantAnalysisGistDescription = ( variantAnalysis : VariantAnalysis ) => {
250+ const resultCount = variantAnalysis . scannedRepos ?. reduce ( ( acc , item ) => acc + ( item . resultCount ?? 0 ) , 0 ) ?? 0 ;
251+ const resultLabel = pluralize ( resultCount , 'result' , 'results' ) ;
252+
253+ const repositoryLabel = variantAnalysis . scannedRepos ?. length ? `(${ pluralize ( variantAnalysis . scannedRepos . length , 'repository' , 'repositories' ) } )` : '' ;
254+ return `${ variantAnalysis . query . name } (${ variantAnalysis . query . language } ) ${ resultLabel } ${ repositoryLabel } ` ;
255+ } ;
256+
257+ /**
258+ * Saves the results of an exported query to local markdown files.
259+ */
260+ async function exportToLocalMarkdown (
261+ exportDirectory : string ,
262+ markdownFiles : MarkdownFile [ ] ,
147263) {
148- const markdownFiles = generateMarkdown ( query , analysesResults , 'local' ) ;
149- const exportedResultsPath = path . join ( queryDirectoryPath , 'exported-results' ) ;
264+ const exportedResultsPath = path . join ( exportDirectory , 'exported-results' ) ;
150265 await fs . ensureDir ( exportedResultsPath ) ;
151266 for ( const markdownFile of markdownFiles ) {
152267 const filePath = path . join ( exportedResultsPath , `${ markdownFile . fileName } .md` ) ;
0 commit comments