Skip to content

Commit 2ca5e32

Browse files
committed
merge in upstream changes
2 parents 5e71f96 + 2b60a35 commit 2ca5e32

File tree

24 files changed

+563
-151
lines changed

24 files changed

+563
-151
lines changed

docs/telemetry.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,3 +293,38 @@ traceId: 'aaaaa-aaaaa-aaaaa-aaaaa-aaaaa'
293293
allowing us to look up `traceId=aaaaa-aaaaa-aaaaa-aaaaa-aaaaa` in our telemetry instance and find all the related events.
294294
295295
For more information visit the OpenTelemetry documentation on traces: https://opentelemetry.io/docs/concepts/signals/traces/
296+
297+
### Manual Trace ID Instrumentation
298+
299+
In certain scenarios you may need to manually instrument disjoint flows to track how a `traceId` propagates through them. e.g.
300+
301+
1. Measuring the time it takes for a message to travel from Amazon Q chat, through VS Code, and back to the customer.
302+
2. Determining the duration for Amazon Q inline to display a message to the user.
303+
304+
In these cases, where there isn't a direct hierarchy of function calls, manual instrumentation of the `traceId` is necessary.
305+
306+
#### Implementation Options
307+
308+
#### 1. When not currently running in a span
309+
310+
If you're not within an active span and you know the `traceId` you want to use:
311+
312+
```javascript
313+
telemetry.withTraceId(() => {
314+
// Code to be executed within this trace
315+
}, 'myTraceId')
316+
```
317+
318+
This method wraps the provided function with the specified traceId
319+
320+
#### 2. When currently running in a span
321+
322+
If you're already executing within a span (e.g., vscode_executeCommand) and you know the traceId you want to use:
323+
324+
```javascript
325+
telemetry.record({
326+
traceId: 'myTraceId',
327+
})
328+
```
329+
330+
This approach records the traceId for the current span and all future spans within the same execution context.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "Security Scan: Fixes an issue that incorrectly removes hardcoded credentials detections from auto scans."
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "Amazon Q Code Transformation: allow users to skip running tests"
4+
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,5 +180,24 @@ describe('securityScanHandler', function () {
180180
assert.equal(codeScanIssueMap.size, 1)
181181
assert.equal(codeScanIssueMap.get('file1.ts')?.length, 2)
182182
})
183+
184+
it('should handle issue filtering with redacted code', () => {
185+
const json = JSON.stringify([
186+
{
187+
filePath: 'file1.ts',
188+
startLine: 1,
189+
endLine: 2,
190+
codeSnippet: [
191+
{ number: 1, content: '**** *' },
192+
{ number: 2, content: '**** *' },
193+
],
194+
},
195+
{ filePath: 'file1.ts', startLine: 3, endLine: 3, codeSnippet: [{ number: 3, content: '**** **' }] },
196+
])
197+
198+
mapToAggregatedList(codeScanIssueMap, json, editor, CodeAnalysisScope.FILE)
199+
assert.strictEqual(codeScanIssueMap.size, 1)
200+
assert.strictEqual(codeScanIssueMap.get('file1.ts')?.length, 1)
201+
})
183202
})
184203
})

packages/amazonq/test/unit/codewhisperer/util/codeParsingUtil.test.ts

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,17 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { extractClasses, extractFunctions, isTestFile, utgLanguageConfigs } from 'aws-core-vscode/codewhisperer'
6+
import * as vscode from 'vscode'
7+
import {
8+
PlatformLanguageId,
9+
extractClasses,
10+
extractFunctions,
11+
isTestFile,
12+
utgLanguageConfigs,
13+
} from 'aws-core-vscode/codewhisperer'
14+
import * as path from 'path'
715
import assert from 'assert'
16+
import { createTestWorkspaceFolder, toFile } from 'aws-core-vscode/test'
817

918
describe('RegexValidationForPython', () => {
1019
it('should extract all function names from a python file content', () => {
@@ -57,6 +66,99 @@ describe('RegexValidationForJava', () => {
5766
})
5867

5968
describe('isTestFile', () => {
69+
let testWsFolder: string
70+
beforeEach(async function () {
71+
testWsFolder = (await createTestWorkspaceFolder()).uri.fsPath
72+
})
73+
74+
it('validate by file path', async function () {
75+
const langs = new Map<string, string>([
76+
['java', '.java'],
77+
['python', '.py'],
78+
['typescript', '.ts'],
79+
['javascript', '.js'],
80+
['typescriptreact', '.tsx'],
81+
['javascriptreact', '.jsx'],
82+
])
83+
const testFilePathsWithoutExt = [
84+
'/test/MyClass',
85+
'/test/my_class',
86+
'/tst/MyClass',
87+
'/tst/my_class',
88+
'/tests/MyClass',
89+
'/tests/my_class',
90+
]
91+
92+
const srcFilePathsWithoutExt = [
93+
'/src/MyClass',
94+
'MyClass',
95+
'foo/bar/MyClass',
96+
'foo/my_class',
97+
'my_class',
98+
'anyFolderOtherThanTest/foo/myClass',
99+
]
100+
101+
for (const [languageId, ext] of langs) {
102+
const testFilePaths = testFilePathsWithoutExt.map((it) => it + ext)
103+
for (const testFilePath of testFilePaths) {
104+
const actual = await isTestFile(testFilePath, { languageId: languageId })
105+
assert.strictEqual(actual, true)
106+
}
107+
108+
const srcFilePaths = srcFilePathsWithoutExt.map((it) => it + ext)
109+
for (const srcFilePath of srcFilePaths) {
110+
const actual = await isTestFile(srcFilePath, { languageId: languageId })
111+
assert.strictEqual(actual, false)
112+
}
113+
}
114+
})
115+
116+
async function assertIsTestFile(
117+
fileNames: string[],
118+
config: { languageId: PlatformLanguageId },
119+
expected: boolean
120+
) {
121+
for (const fileName of fileNames) {
122+
const p = path.join(testWsFolder, fileName)
123+
await toFile('', p)
124+
const document = await vscode.workspace.openTextDocument(p)
125+
const actual = await isTestFile(document.uri.fsPath, { languageId: config.languageId })
126+
assert.strictEqual(actual, expected)
127+
}
128+
}
129+
130+
it('validate by file name', async function () {
131+
const camelCaseSrc = ['Foo.java', 'Bar.java', 'Baz.java']
132+
await assertIsTestFile(camelCaseSrc, { languageId: 'java' }, false)
133+
134+
const camelCaseTst = ['FooTest.java', 'BarTests.java']
135+
await assertIsTestFile(camelCaseTst, { languageId: 'java' }, true)
136+
137+
const snakeCaseSrc = ['foo.py', 'bar.py']
138+
await assertIsTestFile(snakeCaseSrc, { languageId: 'python' }, false)
139+
140+
const snakeCaseTst = ['test_foo.py', 'bar_test.py']
141+
await assertIsTestFile(snakeCaseTst, { languageId: 'python' }, true)
142+
143+
const javascriptSrc = ['Foo.js', 'bar.js']
144+
await assertIsTestFile(javascriptSrc, { languageId: 'javascript' }, false)
145+
146+
const javascriptTst = ['Foo.test.js', 'Bar.spec.js']
147+
await assertIsTestFile(javascriptTst, { languageId: 'javascript' }, true)
148+
149+
const typescriptSrc = ['Foo.ts', 'bar.ts']
150+
await assertIsTestFile(typescriptSrc, { languageId: 'typescript' }, false)
151+
152+
const typescriptTst = ['Foo.test.ts', 'Bar.spec.ts']
153+
await assertIsTestFile(typescriptTst, { languageId: 'typescript' }, true)
154+
155+
const jsxSrc = ['Foo.jsx', 'Bar.jsx']
156+
await assertIsTestFile(jsxSrc, { languageId: 'javascriptreact' }, false)
157+
158+
const jsxTst = ['Foo.test.jsx', 'Bar.spec.jsx']
159+
await assertIsTestFile(jsxTst, { languageId: 'javascriptreact' }, true)
160+
})
161+
60162
it('should return true if the file name matches the test filename pattern - Java', async () => {
61163
const filePaths = ['/path/to/MyClassTest.java', '/path/to/TestMyClass.java', '/path/to/MyClassTests.java']
62164
const language = 'java'

packages/amazonq/test/unit/codewhisperer/util/utgUtils.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,31 @@ describe('shouldFetchUtgContext', () => {
4444
assert.strictEqual(utgUtils.shouldFetchUtgContext('c', UserGroup.CrossFile), undefined)
4545
})
4646
})
47+
48+
describe('guessSrcFileName', function () {
49+
it('should return undefined if no matching regex', function () {
50+
assert.strictEqual(utgUtils.guessSrcFileName('Foo.java', 'java'), undefined)
51+
assert.strictEqual(utgUtils.guessSrcFileName('folder1/foo.py', 'python'), undefined)
52+
assert.strictEqual(utgUtils.guessSrcFileName('Bar.js', 'javascript'), undefined)
53+
})
54+
55+
it('java', function () {
56+
assert.strictEqual(utgUtils.guessSrcFileName('FooTest.java', 'java'), 'Foo.java')
57+
assert.strictEqual(utgUtils.guessSrcFileName('FooTests.java', 'java'), 'Foo.java')
58+
})
59+
60+
it('python', function () {
61+
assert.strictEqual(utgUtils.guessSrcFileName('test_foo.py', 'python'), 'foo.py')
62+
assert.strictEqual(utgUtils.guessSrcFileName('foo_test.py', 'python'), 'foo.py')
63+
})
64+
65+
it('typescript', function () {
66+
assert.strictEqual(utgUtils.guessSrcFileName('Foo.test.ts', 'typescript'), 'Foo.ts')
67+
assert.strictEqual(utgUtils.guessSrcFileName('Foo.spec.ts', 'typescript'), 'Foo.ts')
68+
})
69+
70+
it('javascript', function () {
71+
assert.strictEqual(utgUtils.guessSrcFileName('Foo.test.js', 'javascript'), 'Foo.js')
72+
assert.strictEqual(utgUtils.guessSrcFileName('Foo.spec.js', 'javascript'), 'Foo.js')
73+
})
74+
})

packages/core/src/amazonqGumby/activation.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import * as vscode from 'vscode'
77
import { Commands } from '../shared/vscode/commands2'
88
import { TransformationHubViewProvider } from '../codewhisperer/service/transformByQ/transformationHubViewProvider'
99
import { ExtContext } from '../shared/extensions'
10-
import { stopTransformByQ } from '../codewhisperer/commands/startTransformByQ'
10+
import {
11+
cleanupTransformationJob,
12+
postTransformationJob,
13+
stopTransformByQ,
14+
} from '../codewhisperer/commands/startTransformByQ'
1115
import { transformByQState } from '../codewhisperer/models/model'
1216
import { ProposedTransformationExplorer } from '../codewhisperer/service/transformByQ/transformationResultsViewProvider'
1317
import { CodeTransformTelemetryState } from './telemetry/codeTransformTelemetryState'
@@ -48,7 +52,9 @@ export async function activate(context: ExtContext) {
4852

4953
Commands.register('aws.amazonq.stopTransformationInHub', async (cancelSrc: CancelActionPositions) => {
5054
if (transformByQState.isRunning()) {
51-
void stopTransformByQ(transformByQState.getJobId(), cancelSrc)
55+
await stopTransformByQ(transformByQState.getJobId(), cancelSrc)
56+
await postTransformationJob()
57+
await cleanupTransformationJob()
5258
}
5359
}),
5460

packages/core/src/amazonqGumby/chat/controller/controller.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,12 @@ export class GumbyController {
303303
})
304304
this.messenger.sendJobFinishedMessage(message.tabID, CodeWhispererConstants.jobCancelledChatMessage)
305305
break
306+
case ButtonActions.CONFIRM_SKIP_TESTS_FORM:
307+
await this.handleSkipTestsSelection(message)
308+
break
309+
case ButtonActions.CANCEL_SKIP_TESTS_FORM:
310+
this.messenger.sendJobFinishedMessage(message.tabID, CodeWhispererConstants.jobCancelledChatMessage)
311+
break
306312
case ButtonActions.VIEW_TRANSFORMATION_HUB:
307313
await vscode.commands.executeCommand(GumbyCommands.FOCUS_TRANSFORMATION_HUB, CancelActionPositions.Chat)
308314
this.messenger.sendJobSubmittedMessage(message.tabID)
@@ -334,6 +340,23 @@ export class GumbyController {
334340
}
335341
}
336342

343+
private async handleSkipTestsSelection(message: any) {
344+
const skipTestsSelection = message.formSelectedValues['GumbyTransformSkipTestsForm']
345+
if (skipTestsSelection === CodeWhispererConstants.skipUnitTestsMessage) {
346+
transformByQState.setCustomBuildCommand(CodeWhispererConstants.skipUnitTestsBuildCommand)
347+
} else {
348+
transformByQState.setCustomBuildCommand(CodeWhispererConstants.doNotSkipUnitTestsBuildCommand)
349+
}
350+
telemetry.codeTransform_submitSelection.emit({
351+
codeTransformSessionId: CodeTransformTelemetryState.instance.getSessionId(),
352+
userChoice: skipTestsSelection,
353+
result: MetadataResult.Pass,
354+
})
355+
this.messenger.sendSkipTestsSelectionMessage(skipTestsSelection, message.tabID)
356+
// perform local build
357+
await this.validateBuildWithPromptOnError(message)
358+
}
359+
337360
// prompt user to pick project and specify source JDK version
338361
private async handleUserProjectSelection(message: any) {
339362
await telemetry.codeTransform_submitSelection.run(async () => {
@@ -365,7 +388,8 @@ export class GumbyController {
365388
}
366389

367390
await processTransformFormInput(pathToProject, fromJDKVersion, toJDKVersion)
368-
await this.validateBuildWithPromptOnError(message)
391+
392+
await this.messenger.sendSkipTestsPrompt(message.tabID)
369393
})
370394
}
371395

packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,45 @@ export class Messenger {
109109
this.dispatcher.sendAuthenticationUpdate(new AuthenticationUpdateMessage(gumbyEnabled, authenticatingTabIDs))
110110
}
111111

112+
public async sendSkipTestsPrompt(tabID: string) {
113+
const formItems: ChatItemFormItem[] = []
114+
formItems.push({
115+
id: 'GumbyTransformSkipTestsForm',
116+
type: 'select',
117+
title: CodeWhispererConstants.skipUnitTestsFormTitle,
118+
mandatory: true,
119+
options: [
120+
{
121+
value: CodeWhispererConstants.runUnitTestsMessage,
122+
label: CodeWhispererConstants.runUnitTestsMessage,
123+
},
124+
{
125+
value: CodeWhispererConstants.skipUnitTestsMessage,
126+
label: CodeWhispererConstants.skipUnitTestsMessage,
127+
},
128+
],
129+
})
130+
131+
this.dispatcher.sendAsyncEventProgress(
132+
new AsyncEventProgressMessage(tabID, {
133+
inProgress: true,
134+
message: CodeWhispererConstants.skipUnitTestsFormMessage,
135+
})
136+
)
137+
138+
this.dispatcher.sendChatPrompt(
139+
new ChatPrompt(
140+
{
141+
message: 'Q Code Transformation',
142+
formItems: formItems,
143+
},
144+
'TransformSkipTestsForm',
145+
tabID,
146+
false
147+
)
148+
)
149+
}
150+
112151
public async sendProjectPrompt(projects: TransformationCandidateProject[], tabID: string) {
113152
const projectFormOptions: { value: any; label: string }[] = []
114153
const detectedJavaVersions = new Array<JDKVersion | undefined>()
@@ -125,7 +164,7 @@ export class Messenger {
125164
formItems.push({
126165
id: 'GumbyTransformProjectForm',
127166
type: 'select',
128-
title: 'Choose a project to transform',
167+
title: CodeWhispererConstants.chooseProjectFormTitle,
129168
mandatory: true,
130169

131170
options: projectFormOptions,
@@ -134,7 +173,7 @@ export class Messenger {
134173
formItems.push({
135174
id: 'GumbyTransformJdkFromForm',
136175
type: 'select',
137-
title: 'Choose the source code version',
176+
title: CodeWhispererConstants.chooseSourceVersionFormTitle,
138177
mandatory: true,
139178
options: [
140179
{
@@ -155,7 +194,7 @@ export class Messenger {
155194
formItems.push({
156195
id: 'GumbyTransformJdkToForm',
157196
type: 'select',
158-
title: 'Choose the target code version',
197+
title: CodeWhispererConstants.chooseTargetVersionFormTitle,
159198
mandatory: true,
160199
options: [
161200
{
@@ -407,7 +446,7 @@ export class Messenger {
407446
projectName: string,
408447
fromJDKVersion: JDKVersion,
409448
toJDKVersion: JDKVersion,
410-
tabID: any
449+
tabID: string
411450
) {
412451
const message = `### Transformation details
413452
-------------
@@ -421,6 +460,11 @@ export class Messenger {
421460
this.dispatcher.sendChatMessage(new ChatMessage({ message, messageType: 'prompt' }, tabID))
422461
}
423462

463+
public sendSkipTestsSelectionMessage(skipTestsSelection: string, tabID: string) {
464+
const message = `Okay, I will ${skipTestsSelection.toLowerCase()} when building your project.`
465+
this.dispatcher.sendChatMessage(new ChatMessage({ message, messageType: 'ai-prompt' }, tabID))
466+
}
467+
424468
public sendHumanInTheLoopInitialMessage(tabID: string, codeSnippet: string) {
425469
let message = `I was not able to upgrade all dependencies. To resolve it, I will try to find an updated depedency in your local Maven repository. I will need additional information from you to continue.`
426470

packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ export enum ButtonActions {
1414
STOP_TRANSFORMATION_JOB = 'gumbyStopTransformationJob',
1515
VIEW_TRANSFORMATION_HUB = 'gumbyViewTransformationHub',
1616
CONFIRM_TRANSFORMATION_FORM = 'gumbyTransformFormConfirm',
17+
CONFIRM_SKIP_TESTS_FORM = 'gumbyTransformSkipTestsFormConfirm',
1718
CANCEL_TRANSFORMATION_FORM = 'gumbyTransformFormCancel',
19+
CANCEL_SKIP_TESTS_FORM = 'gumbyTransformSkipTestsFormCancel',
1820
CONFIRM_DEPENDENCY_FORM = 'gumbyTransformDependencyFormConfirm',
1921
CANCEL_DEPENDENCY_FORM = 'gumbyTransformDependencyFormCancel',
2022
CONFIRM_JAVA_HOME_FORM = 'gumbyJavaHomeFormConfirm',

0 commit comments

Comments
 (0)