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' ;
15- import { RemoteQueryHistoryItem } from './remote-query-history-item' ;
1619import { 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' ;
1727
1828/**
19- * Exports the results of the given or currently-selected remote query.
29+ * Exports the results of the currently-selected remote query or variant analysis.
30+ */
31+ export async function exportSelectedRemoteQueryResults ( queryHistoryManager : QueryHistoryManager ) : Promise < void > {
32+ const queryHistoryItem = queryHistoryManager . getCurrentQueryHistoryItem ( ) ;
33+ if ( ! queryHistoryItem || queryHistoryItem . t === 'local' ) {
34+ throw new Error ( 'No variant analysis results currently open. To open results, click an item in the query history view.' ) ;
35+ }
36+
37+ if ( queryHistoryItem . t === 'remote' ) {
38+ return commands . executeCommand ( 'codeQL.exportRemoteQueryResults' , queryHistoryItem . queryId ) ;
39+ } else if ( queryHistoryItem . t === 'variant-analysis' ) {
40+ return commands . executeCommand ( 'codeQL.exportVariantAnalysisResults' , queryHistoryItem . variantAnalysis . id ) ;
41+ } else {
42+ assertNever ( queryHistoryItem ) ;
43+ }
44+ }
45+
46+ /**
47+ * Exports the results of the given remote query.
2048 * The user is prompted to select the export format.
2149 */
2250export async function exportRemoteQueryResults (
2351 queryHistoryManager : QueryHistoryManager ,
2452 remoteQueriesManager : RemoteQueriesManager ,
2553 ctx : ExtensionContext ,
26- queryId ? : string ,
54+ queryId : string ,
2755) : Promise < void > {
28- let queryHistoryItem : RemoteQueryHistoryItem ;
29- if ( queryId ) {
30- const query = queryHistoryManager . getRemoteQueryById ( queryId ) ;
31- if ( ! query ) {
32- void logger . log ( `Could not find query with id ${ queryId } ` ) ;
33- throw new Error ( 'There was an error when trying to retrieve variant analysis information' ) ;
34- }
35- queryHistoryItem = query ;
36- } else {
37- const query = queryHistoryManager . getCurrentQueryHistoryItem ( ) ;
38- if ( ! query || query . t !== 'remote' ) {
39- throw new Error ( 'No variant analysis results currently open. To open results, click an item in the query history view.' ) ;
40- }
41- queryHistoryItem = query ;
56+ const queryHistoryItem = queryHistoryManager . getRemoteQueryById ( queryId ) ;
57+ if ( ! queryHistoryItem ) {
58+ void logger . log ( `Could not find query with id ${ queryId } ` ) ;
59+ throw new Error ( 'There was an error when trying to retrieve variant analysis information' ) ;
4260 }
4361
4462 if ( ! queryHistoryItem . completed ) {
@@ -49,32 +67,107 @@ export async function exportRemoteQueryResults(
4967 const query = queryHistoryItem . remoteQuery ;
5068 const analysesResults = remoteQueriesManager . getAnalysesResults ( queryHistoryItem . queryId ) ;
5169
52- const gistOption = {
53- label : '$(ports-open-browser-icon) Create Gist (GitHub)' ,
54- } ;
55- const localMarkdownOption = {
56- label : '$(markdown) Save as markdown' ,
57- } ;
58- const exportFormat = await determineExportFormat ( gistOption , localMarkdownOption ) ;
70+ const exportFormat = await determineExportFormat ( ) ;
71+ if ( ! exportFormat ) {
72+ return ;
73+ }
5974
60- if ( exportFormat === gistOption ) {
61- await exportResultsToGist ( ctx , query , analysesResults ) ;
62- } else if ( exportFormat === localMarkdownOption ) {
63- const queryDirectoryPath = await queryHistoryManager . getQueryHistoryItemDirectory (
64- queryHistoryItem
65- ) ;
66- 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' ) ;
67106 }
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+ const result = await variantAnalysisManager . loadResults ( variantAnalysis . id , repo . repository . fullName , {
130+ skipCacheStore : true ,
131+ } ) ;
132+
133+ yield [ repo , result ] ;
134+ }
135+ }
136+
137+ const exportDirectory = variantAnalysisManager . getVariantAnalysisStorageLocation ( variantAnalysis . id ) ;
138+
139+ await exportVariantAnalysisAnalysisResults ( ctx , exportDirectory , variantAnalysis , getAnalysesResults ( ) , exportFormat ) ;
140+ }
141+
142+ export async function exportVariantAnalysisAnalysisResults (
143+ ctx : ExtensionContext ,
144+ exportDirectory : string ,
145+ variantAnalysis : VariantAnalysis ,
146+ analysesResults : AsyncIterable < [ VariantAnalysisScannedRepository , VariantAnalysisScannedRepositoryResult ] > ,
147+ exportFormat : 'gist' | 'local' ,
148+ ) {
149+ const description = buildVariantAnalysisGistDescription ( variantAnalysis ) ;
150+ const markdownFiles = await generateVariantAnalysisMarkdown ( variantAnalysis , analysesResults , 'gist' ) ;
151+
152+ await exportResults ( ctx , exportDirectory , description , markdownFiles , exportFormat ) ;
68153}
69154
70155/**
71156 * Determines the format in which to export the results, from the given export options.
72157 */
73- async function determineExportFormat (
74- ...options : { label : string } [ ]
75- ) : Promise < QuickPickItem > {
158+ async function determineExportFormat ( ) : Promise < 'gist' | 'local' | undefined > {
159+ const gistOption = {
160+ label : '$(ports-open-browser-icon) Create Gist (GitHub)' ,
161+ } ;
162+ const localMarkdownOption = {
163+ label : '$(markdown) Save as markdown' ,
164+ } ;
165+
76166 const exportFormat = await window . showQuickPick (
77- options ,
167+ [
168+ gistOption ,
169+ localMarkdownOption ,
170+ ] ,
78171 {
79172 placeHolder : 'Select export format' ,
80173 canPickMany : false ,
@@ -84,20 +177,38 @@ async function determineExportFormat(
84177 if ( ! exportFormat || ! exportFormat . label ) {
85178 throw new UserCancellationException ( 'No export format selected' , true ) ;
86179 }
87- return exportFormat ;
180+
181+ if ( exportFormat === gistOption ) {
182+ return 'gist' ;
183+ }
184+ if ( exportFormat === localMarkdownOption ) {
185+ return 'local' ;
186+ }
187+
188+ return undefined ;
88189}
89190
90- /**
91- * Converts the results of a remote query to markdown and uploads the files as a secret gist.
92- */
93- export async function exportResultsToGist (
191+ export async function exportResults (
94192 ctx : ExtensionContext ,
95- query : RemoteQuery ,
96- analysesResults : AnalysisResults [ ]
97- ) : Promise < void > {
193+ exportDirectory : string ,
194+ description : string ,
195+ markdownFiles : MarkdownFile [ ] ,
196+ exportFormat : 'gist' | 'local' ,
197+ ) {
198+ if ( exportFormat === 'gist' ) {
199+ await exportToGist ( ctx , description , markdownFiles ) ;
200+ } else if ( exportFormat === 'local' ) {
201+ await exportToLocalMarkdown ( exportDirectory , markdownFiles ) ;
202+ }
203+ }
204+
205+ export async function exportToGist (
206+ ctx : ExtensionContext ,
207+ description : string ,
208+ markdownFiles : MarkdownFile [ ]
209+ ) {
98210 const credentials = await Credentials . initialize ( ctx ) ;
99- const description = buildGistDescription ( query , analysesResults ) ;
100- const markdownFiles = generateMarkdown ( query , analysesResults , 'gist' ) ;
211+
101212 // Convert markdownFiles to the appropriate format for uploading to gist
102213 const gistFiles = markdownFiles . reduce ( ( acc , cur ) => {
103214 acc [ `${ cur . fileName } .md` ] = { content : cur . content . join ( '\n' ) } ;
@@ -128,16 +239,25 @@ const buildGistDescription = (query: RemoteQuery, analysesResults: AnalysisResul
128239} ;
129240
130241/**
131- * Converts the results of a remote query to markdown and saves the files locally
132- * in the query directory (where query results and metadata are also saved).
242+ * Builds Gist description
243+ * Ex: Empty Block (Go) x results (y repositories)
133244 */
134- async function exportResultsToLocalMarkdown (
135- queryDirectoryPath : string ,
136- query : RemoteQuery ,
137- analysesResults : AnalysisResults [ ]
245+ const buildVariantAnalysisGistDescription = ( variantAnalysis : VariantAnalysis ) => {
246+ const resultCount = variantAnalysis . scannedRepos ?. reduce ( ( acc , item ) => acc + ( item . resultCount ?? 0 ) , 0 ) ?? 0 ;
247+ const resultLabel = pluralize ( resultCount , 'result' , 'results' ) ;
248+
249+ const repositoryLabel = variantAnalysis . scannedRepos ?. length ? `(${ pluralize ( variantAnalysis . scannedRepos . length , 'repository' , 'repositories' ) } )` : '' ;
250+ return `${ variantAnalysis . query . name } (${ variantAnalysis . query . language } ) ${ resultLabel } ${ repositoryLabel } ` ;
251+ } ;
252+
253+ /**
254+ * Saves the results of an exported query to local markdown files.
255+ */
256+ async function exportToLocalMarkdown (
257+ exportDirectory : string ,
258+ markdownFiles : MarkdownFile [ ] ,
138259) {
139- const markdownFiles = generateMarkdown ( query , analysesResults , 'local' ) ;
140- const exportedResultsPath = path . join ( queryDirectoryPath , 'exported-results' ) ;
260+ const exportedResultsPath = path . join ( exportDirectory , 'exported-results' ) ;
141261 await fs . ensureDir ( exportedResultsPath ) ;
142262 for ( const markdownFile of markdownFiles ) {
143263 const filePath = path . join ( exportedResultsPath , `${ markdownFile . fileName } .md` ) ;
0 commit comments