Skip to content

Commit 62d3ec1

Browse files
authored
feat(amazonq): generate scanName from workspace config aws#6679
## Problem All code scans are run with a scanName as a UUID which makes it impossible to track the bug lifecycle of projects. We are not leveraging the bug tracking feature which allows grouping together different scan runs of the same project by passing in the same scanName between runs. ## Solution Calculate the scanName using ClientID, the current workspace configuration (project root(s) or active file), and code analysis scope.
1 parent 6d550e6 commit 62d3ec1

File tree

6 files changed

+70
-6
lines changed

6 files changed

+70
-6
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "/review: Code reviews are now created with additional workspace context to enable grouping of related scans in the backend"
4+
}

packages/amazonq/test/unit/codewhisperer/service/securityScanHandler.test.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ import {
1515
ListCodeScanFindingsResponse,
1616
pollScanJobStatus,
1717
SecurityScanTimedOutError,
18+
generateScanName,
1819
} from 'aws-core-vscode/codewhisperer'
19-
import { timeoutUtils } from 'aws-core-vscode/shared'
20+
import { getStringHash, timeoutUtils } from 'aws-core-vscode/shared'
2021
import assert from 'assert'
2122
import sinon from 'sinon'
2223
import * as vscode from 'vscode'
@@ -320,4 +321,37 @@ describe('securityScanHandler', function () {
320321
await assert.rejects(() => pollPromise, SecurityScanTimedOutError)
321322
})
322323
})
324+
325+
describe('generateScanName', function () {
326+
const clientId = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
327+
328+
it('generates scan name for FILE_AUTO scope', function () {
329+
const result = generateScanName(['/some/root/path'], CodeAnalysisScope.FILE_AUTO, '/path/to/some/file')
330+
assert.strictEqual(result, getStringHash(`${clientId}::/path/to/some/file::FILE_AUTO`))
331+
})
332+
333+
it('generates scan name for FILE_ON_DEMAND scope', function () {
334+
const result = generateScanName(['/some/root/path'], CodeAnalysisScope.FILE_ON_DEMAND, '/path/to/some/file')
335+
assert.strictEqual(result, getStringHash(`${clientId}::/path/to/some/file::FILE_ON_DEMAND`))
336+
})
337+
338+
it('generates scan name for PROJECT scope with a single project root', function () {
339+
const result = generateScanName(['/some/root/path'], CodeAnalysisScope.PROJECT)
340+
assert.strictEqual(result, getStringHash(`${clientId}::/some/root/path::PROJECT`))
341+
})
342+
343+
it('generates scan name for PROJECT scope with multiple project roots', function () {
344+
const result = generateScanName(['/some/root/pathB', '/some/root/pathA'], CodeAnalysisScope.PROJECT)
345+
assert.strictEqual(result, getStringHash(`${clientId}::/some/root/pathA,/some/root/pathB::PROJECT`))
346+
})
347+
348+
it('does not exceed 126 characters', function () {
349+
let reallyDeepFilePath = ''
350+
for (let i = 0; i < 100; i++) {
351+
reallyDeepFilePath += '/some/deep/path'
352+
}
353+
const result = generateScanName(['/some/root/path'], CodeAnalysisScope.FILE_ON_DEMAND, reallyDeepFilePath)
354+
assert.ok(result.length <= 126)
355+
})
356+
})
323357
})

packages/core/src/codewhisperer/commands/startSecurityScan.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
listScanResults,
1919
throwIfCancelled,
2020
getLoggerForScope,
21+
generateScanName,
2122
} from '../service/securityScanHandler'
2223
import { runtimeLanguageContext } from '../util/runtimeLanguageContext'
2324
import {
@@ -38,7 +39,6 @@ import path from 'path'
3839
import { ZipMetadata, ZipUtil } from '../util/zipUtil'
3940
import { debounce } from 'lodash'
4041
import { once } from '../../shared/utilities/functionUtils'
41-
import { randomUUID } from '../../shared/crypto'
4242
import { CodeAnalysisScope, ProjectSizeExceededErrorMessage, SecurityScanStep } from '../models/constants'
4343
import {
4444
CodeScanJobFailedError,
@@ -185,7 +185,7 @@ export async function startSecurityScan(
185185
}
186186
let artifactMap: ArtifactMap = {}
187187
const uploadStartTime = performance.now()
188-
const scanName = randomUUID()
188+
const scanName = generateScanName(projectPaths, scope, fileName)
189189
try {
190190
artifactMap = await getPresignedUrlAndUpload(client, zipMetadata, scope, scanName)
191191
} finally {

packages/core/src/codewhisperer/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,12 @@ export { DocumentChangedSource, KeyStrokeHandler, DefaultDocumentChangedType } f
7272
export { ReferenceLogViewProvider } from './service/referenceLogViewProvider'
7373
export { LicenseUtil } from './util/licenseUtil'
7474
export { SecurityIssueProvider } from './service/securityIssueProvider'
75-
export { listScanResults, mapToAggregatedList, pollScanJobStatus } from './service/securityScanHandler'
75+
export {
76+
listScanResults,
77+
mapToAggregatedList,
78+
pollScanJobStatus,
79+
generateScanName,
80+
} from './service/securityScanHandler'
7681
export { CodeWhispererCodeCoverageTracker } from './tracker/codewhispererCodeCoverageTracker'
7782
export { TelemetryHelper } from './util/telemetryHelper'
7883
export { LineSelection, LineTracker } from './tracker/lineTracker'

packages/core/src/codewhisperer/service/securityScanHandler.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ import { runtimeLanguageContext } from '../util/runtimeLanguageContext'
4646
import { FeatureUseCase } from '../models/constants'
4747
import { UploadTestArtifactToS3Error } from '../../amazonqTest/error'
4848
import { ChatSessionManager } from '../../amazonqTest/chat/storages/chatSession'
49+
import { getStringHash } from '../../shared/utilities/textUtilities'
50+
import { getClientId } from '../../shared/telemetry/util'
51+
import globals from '../../shared/extensionGlobals'
4952

5053
export async function listScanResults(
5154
client: DefaultCodeWhispererClient,
@@ -417,3 +420,21 @@ function getPollingTimeoutMsForScope(scope: CodeWhispererConstants.CodeAnalysisS
417420
? CodeWhispererConstants.expressScanTimeoutMs
418421
: CodeWhispererConstants.standardScanTimeoutMs
419422
}
423+
424+
/**
425+
* Generates a scanName that unique identifies a user's workspace configuration for a Q code review.
426+
*
427+
* @param projectPaths List of project root paths
428+
* @param scope {@link CodeWhispererConstants.CodeAnalysisScope} Scope of files included in the code review
429+
* @param fileName File name of the file being reviewed, or pass undefined for workspace review
430+
* @returns A string hash that uniquely identifies the workspace configuration
431+
*/
432+
export function generateScanName(
433+
projectPaths: string[],
434+
scope: CodeWhispererConstants.CodeAnalysisScope,
435+
fileName?: string
436+
) {
437+
const clientId = getClientId(globals.globalState)
438+
const projectId = fileName ?? projectPaths.sort((a, b) => a.localeCompare(b)).join(',')
439+
return getStringHash(`${clientId}::${projectId}::${scope}`)
440+
}

packages/core/src/testE2E/codewhisperer/securityScan.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ import {
1818
createScanJob,
1919
pollScanJobStatus,
2020
listScanResults,
21+
generateScanName,
2122
} from '../../codewhisperer/service/securityScanHandler'
2223
import { makeTemporaryToolkitFolder } from '../../shared/filesystemUtilities'
2324
import fs from '../../shared/fs/fs'
2425
import { ZipUtil } from '../../codewhisperer/util/zipUtil'
25-
import { randomUUID } from '../../shared/crypto'
2626

2727
const filePromptWithSecurityIssues = `from flask import app
2828
@@ -95,7 +95,7 @@ describe('CodeWhisperer security scan', async function () {
9595
const projectPaths = zipUtil.getProjectPaths()
9696
const scope = CodeWhispererConstants.CodeAnalysisScope.PROJECT
9797
const zipMetadata = await zipUtil.generateZip(uri, scope)
98-
const codeScanName = randomUUID()
98+
const codeScanName = generateScanName(projectPaths, scope)
9999

100100
let artifactMap
101101
try {

0 commit comments

Comments
 (0)