Skip to content

Commit

Permalink
Merge master into feature/sdkv3
Browse files Browse the repository at this point in the history
  • Loading branch information
aws-toolkit-automation authored Mar 3, 2025
2 parents 2086649 + 62d3ec1 commit 25ccd97
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "Feature",
"description": "/review: Code reviews are now created with additional workspace context to enable grouping of related scans in the backend"
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import {
ListCodeScanFindingsResponse,
pollScanJobStatus,
SecurityScanTimedOutError,
generateScanName,
} from 'aws-core-vscode/codewhisperer'
import { timeoutUtils } from 'aws-core-vscode/shared'
import { getStringHash, timeoutUtils } from 'aws-core-vscode/shared'
import assert from 'assert'
import sinon from 'sinon'
import * as vscode from 'vscode'
Expand Down Expand Up @@ -320,4 +321,37 @@ describe('securityScanHandler', function () {
await assert.rejects(() => pollPromise, SecurityScanTimedOutError)
})
})

describe('generateScanName', function () {
const clientId = 'ffffffff-ffff-ffff-ffff-ffffffffffff'

it('generates scan name for FILE_AUTO scope', function () {
const result = generateScanName(['/some/root/path'], CodeAnalysisScope.FILE_AUTO, '/path/to/some/file')
assert.strictEqual(result, getStringHash(`${clientId}::/path/to/some/file::FILE_AUTO`))
})

it('generates scan name for FILE_ON_DEMAND scope', function () {
const result = generateScanName(['/some/root/path'], CodeAnalysisScope.FILE_ON_DEMAND, '/path/to/some/file')
assert.strictEqual(result, getStringHash(`${clientId}::/path/to/some/file::FILE_ON_DEMAND`))
})

it('generates scan name for PROJECT scope with a single project root', function () {
const result = generateScanName(['/some/root/path'], CodeAnalysisScope.PROJECT)
assert.strictEqual(result, getStringHash(`${clientId}::/some/root/path::PROJECT`))
})

it('generates scan name for PROJECT scope with multiple project roots', function () {
const result = generateScanName(['/some/root/pathB', '/some/root/pathA'], CodeAnalysisScope.PROJECT)
assert.strictEqual(result, getStringHash(`${clientId}::/some/root/pathA,/some/root/pathB::PROJECT`))
})

it('does not exceed 126 characters', function () {
let reallyDeepFilePath = ''
for (let i = 0; i < 100; i++) {
reallyDeepFilePath += '/some/deep/path'
}
const result = generateScanName(['/some/root/path'], CodeAnalysisScope.FILE_ON_DEMAND, reallyDeepFilePath)
assert.ok(result.length <= 126)
})
})
})
4 changes: 2 additions & 2 deletions packages/core/src/codewhisperer/commands/startSecurityScan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
listScanResults,
throwIfCancelled,
getLoggerForScope,
generateScanName,
} from '../service/securityScanHandler'
import { runtimeLanguageContext } from '../util/runtimeLanguageContext'
import {
Expand All @@ -38,7 +39,6 @@ import path from 'path'
import { ZipMetadata, ZipUtil } from '../util/zipUtil'
import { debounce } from 'lodash'
import { once } from '../../shared/utilities/functionUtils'
import { randomUUID } from '../../shared/crypto'
import { CodeAnalysisScope, ProjectSizeExceededErrorMessage, SecurityScanStep } from '../models/constants'
import {
CodeScanJobFailedError,
Expand Down Expand Up @@ -185,7 +185,7 @@ export async function startSecurityScan(
}
let artifactMap: ArtifactMap = {}
const uploadStartTime = performance.now()
const scanName = randomUUID()
const scanName = generateScanName(projectPaths, scope, fileName)
try {
artifactMap = await getPresignedUrlAndUpload(client, zipMetadata, scope, scanName)
} finally {
Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/codewhisperer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,12 @@ export { DocumentChangedSource, KeyStrokeHandler, DefaultDocumentChangedType } f
export { ReferenceLogViewProvider } from './service/referenceLogViewProvider'
export { LicenseUtil } from './util/licenseUtil'
export { SecurityIssueProvider } from './service/securityIssueProvider'
export { listScanResults, mapToAggregatedList, pollScanJobStatus } from './service/securityScanHandler'
export {
listScanResults,
mapToAggregatedList,
pollScanJobStatus,
generateScanName,
} from './service/securityScanHandler'
export { CodeWhispererCodeCoverageTracker } from './tracker/codewhispererCodeCoverageTracker'
export { TelemetryHelper } from './util/telemetryHelper'
export { LineSelection, LineTracker } from './tracker/lineTracker'
Expand Down
21 changes: 21 additions & 0 deletions packages/core/src/codewhisperer/service/securityScanHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ import { runtimeLanguageContext } from '../util/runtimeLanguageContext'
import { FeatureUseCase } from '../models/constants'
import { UploadTestArtifactToS3Error } from '../../amazonqTest/error'
import { ChatSessionManager } from '../../amazonqTest/chat/storages/chatSession'
import { getStringHash } from '../../shared/utilities/textUtilities'
import { getClientId } from '../../shared/telemetry/util'
import globals from '../../shared/extensionGlobals'

export async function listScanResults(
client: DefaultCodeWhispererClient,
Expand Down Expand Up @@ -417,3 +420,21 @@ function getPollingTimeoutMsForScope(scope: CodeWhispererConstants.CodeAnalysisS
? CodeWhispererConstants.expressScanTimeoutMs
: CodeWhispererConstants.standardScanTimeoutMs
}

/**
* Generates a scanName that unique identifies a user's workspace configuration for a Q code review.
*
* @param projectPaths List of project root paths
* @param scope {@link CodeWhispererConstants.CodeAnalysisScope} Scope of files included in the code review
* @param fileName File name of the file being reviewed, or pass undefined for workspace review
* @returns A string hash that uniquely identifies the workspace configuration
*/
export function generateScanName(
projectPaths: string[],
scope: CodeWhispererConstants.CodeAnalysisScope,
fileName?: string
) {
const clientId = getClientId(globals.globalState)
const projectId = fileName ?? projectPaths.sort((a, b) => a.localeCompare(b)).join(',')
return getStringHash(`${clientId}::${projectId}::${scope}`)
}
4 changes: 2 additions & 2 deletions packages/core/src/testE2E/codewhisperer/securityScan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ import {
createScanJob,
pollScanJobStatus,
listScanResults,
generateScanName,
} from '../../codewhisperer/service/securityScanHandler'
import { makeTemporaryToolkitFolder } from '../../shared/filesystemUtilities'
import fs from '../../shared/fs/fs'
import { ZipUtil } from '../../codewhisperer/util/zipUtil'
import { randomUUID } from '../../shared/crypto'

const filePromptWithSecurityIssues = `from flask import app
Expand Down Expand Up @@ -95,7 +95,7 @@ describe('CodeWhisperer security scan', async function () {
const projectPaths = zipUtil.getProjectPaths()
const scope = CodeWhispererConstants.CodeAnalysisScope.PROJECT
const zipMetadata = await zipUtil.generateZip(uri, scope)
const codeScanName = randomUUID()
const codeScanName = generateScanName(projectPaths, scope)

let artifactMap
try {
Expand Down

0 comments on commit 25ccd97

Please sign in to comment.