Skip to content

Commit a808ef5

Browse files
authored
Merge branch 'main' into transformPupTesting
2 parents ed89600 + 1bd99f4 commit a808ef5

File tree

17 files changed

+655
-22
lines changed

17 files changed

+655
-22
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type" : "bugfix",
3+
"description" : "/review: normalize relative file path before unzipping"
4+
}

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/CodeWhispererUTGChatManager.kt

+16-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import software.aws.toolkits.core.utils.debug
2929
import software.aws.toolkits.core.utils.error
3030
import software.aws.toolkits.core.utils.getLogger
3131
import software.aws.toolkits.core.utils.info
32+
import software.aws.toolkits.jetbrains.core.credentials.sono.isInternalUser
3233
import software.aws.toolkits.jetbrains.services.amazonq.clients.AmazonQStreamingClient
3334
import software.aws.toolkits.jetbrains.services.amazonqCodeTest.controller.CodeTestChatHelper
3435
import software.aws.toolkits.jetbrains.services.amazonqCodeTest.messages.Button
@@ -559,12 +560,25 @@ class CodeWhispererUTGChatManager(val project: Project, private val cs: Coroutin
559560
e is JsonParseException -> message("testgen.error.generic_technical_error_message")
560561
else -> message("testgen.error.generic_error_message")
561562
}
562-
563+
val buttonList = mutableListOf<Button>()
564+
if (isInternalUser(getStartUrl(project))) {
565+
buttonList.add(
566+
Button(
567+
"utg_feedback",
568+
message("testgen.button.feedback"),
569+
keepCardAfterClick = true,
570+
position = "outside",
571+
status = "info",
572+
icon = "comment"
573+
),
574+
)
575+
}
563576
codeTestChatHelper.addAnswer(
564577
CodeTestChatMessageContent(
565578
message = errorMessage,
566579
type = ChatMessageType.Answer,
567-
canBeVoted = false
580+
canBeVoted = false,
581+
buttons = buttonList
568582
)
569583
)
570584

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqCodeTest/controller/CodeTestChatController.kt

+44-9
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.intellij.diff.DiffManagerEx
99
import com.intellij.diff.requests.SimpleDiffRequest
1010
import com.intellij.ide.BrowserUtil
1111
import com.intellij.openapi.application.ApplicationManager
12+
import com.intellij.openapi.application.runInEdt
1213
import com.intellij.openapi.fileEditor.FileEditorManager
1314
import com.intellij.openapi.project.Project
1415
import com.intellij.openapi.project.guessProjectDir
@@ -53,6 +54,7 @@ import software.aws.toolkits.jetbrains.core.AwsClientManager
5354
import software.aws.toolkits.jetbrains.core.coroutines.EDT
5455
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager
5556
import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection
57+
import software.aws.toolkits.jetbrains.core.credentials.sono.isInternalUser
5658
import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext
5759
import software.aws.toolkits.jetbrains.services.amazonq.auth.AuthController
5860
import software.aws.toolkits.jetbrains.services.amazonq.project.RelevantDocument
@@ -92,6 +94,7 @@ import software.aws.toolkits.jetbrains.services.cwc.editor.context.ExtractionTri
9294
import software.aws.toolkits.jetbrains.services.cwc.editor.context.file.FileContext
9395
import software.aws.toolkits.jetbrains.services.cwc.messages.ChatMessageType
9496
import software.aws.toolkits.jetbrains.services.telemetry.TelemetryService
97+
import software.aws.toolkits.jetbrains.ui.feedback.TestGenFeedbackDialog
9598
import software.aws.toolkits.jetbrains.utils.notifyError
9699
import software.aws.toolkits.resources.message
97100
import software.aws.toolkits.telemetry.AmazonqTelemetry
@@ -168,13 +171,14 @@ class CodeTestChatController(
168171
override suspend fun processStartTestGen(message: IncomingCodeTestMessage.StartTestGen) {
169172
codeTestChatHelper.setActiveCodeTestTabId(message.tabId)
170173
val session = codeTestChatHelper.getActiveSession()
174+
if (session.isGeneratingTests) {
175+
return
176+
}
177+
sessionCleanUp(session.tabId)
171178
// check if IDE has active file open, yes return (fileName and filePath) else return null
172179
val project = context.project
173180
val fileInfo = checkActiveFileInIDE(project, message) ?: return
174181
session.programmingLanguage = fileInfo.fileLanguage
175-
if (session.isGeneratingTests === true) {
176-
return
177-
}
178182
session.startTimeOfTestGeneration = Instant.now().toEpochMilli().toDouble()
179183
session.isGeneratingTests = true
180184

@@ -565,7 +569,7 @@ class CodeTestChatController(
565569
session.linesOfCodeGenerated = lineDifference.coerceAtLeast(0)
566570
session.charsOfCodeGenerated = charDifference.coerceAtLeast(0)
567571
session.latencyOfTestGeneration = (Instant.now().toEpochMilli() - session.startTimeOfTestGeneration)
568-
UiTelemetry.click(null as Project?, "unitTestGeneration_viewDiff")
572+
UiTelemetry.click(context.project, "unitTestGeneration_viewDiff")
569573

570574
val buttonList = mutableListOf<Button>()
571575
buttonList.add(
@@ -662,7 +666,7 @@ class CodeTestChatController(
662666
testGenerationEventResponse.responseMetadata().requestId()}"
663667
}
664668

665-
UiTelemetry.click(null as Project?, "unitTestGeneration_acceptDiff")
669+
UiTelemetry.click(context.project, "unitTestGeneration_acceptDiff")
666670

667671
AmazonqTelemetry.utgGenerateTests(
668672
cwsprChatProgrammingLanguage = session.programmingLanguage.languageId,
@@ -691,10 +695,10 @@ class CodeTestChatController(
691695
CodeTestChatMessageContent(
692696
message = message("testgen.message.success"),
693697
type = ChatMessageType.Answer,
694-
canBeVoted = false
698+
canBeVoted = false,
699+
buttons = this.showFeedbackButton()
695700
)
696701
)
697-
sessionCleanUp(session.tabId)
698702
codeTestChatHelper.updateUI(
699703
promptInputDisabledState = false,
700704
promptInputPlaceholder = message("testgen.placeholder.enter_slash_quick_actions"),
@@ -840,7 +844,8 @@ class CodeTestChatController(
840844
CodeTestChatMessageContent(
841845
message = message("testgen.message.success"),
842846
type = ChatMessageType.Answer,
843-
canBeVoted = false
847+
canBeVoted = false,
848+
buttons = this.showFeedbackButton()
844849
)
845850
)
846851
val testGenerationEventResponse = client.sendTestGenerationEvent(
@@ -884,7 +889,10 @@ class CodeTestChatController(
884889
requestId = session.startTestGenerationRequestId,
885890
status = Status.REJECTED,
886891
)
887-
sessionCleanUp(message.tabId)
892+
}
893+
"utg_feedback" -> {
894+
sendFeedback()
895+
UiTelemetry.click(context.project, "unitTestGeneration_provideFeedback")
888896
}
889897
"utg_skip_and_finish" -> {
890898
codeTestChatHelper.addAnswer(
@@ -1373,6 +1381,33 @@ class CodeTestChatController(
13731381
println(message)
13741382
}
13751383

1384+
private fun sendFeedback() {
1385+
runInEdt {
1386+
TestGenFeedbackDialog(
1387+
context.project,
1388+
codeTestChatHelper.getActiveSession().startTestGenerationRequestId,
1389+
codeTestChatHelper.getActiveSession().testGenerationJob
1390+
).show()
1391+
}
1392+
}
1393+
1394+
private fun showFeedbackButton(): MutableList<Button> {
1395+
val buttonList = mutableListOf<Button>()
1396+
if (isInternalUser(getStartUrl(context.project))) {
1397+
buttonList.add(
1398+
Button(
1399+
"utg_feedback",
1400+
message("testgen.button.feedback"),
1401+
keepCardAfterClick = true,
1402+
position = "outside",
1403+
status = "info",
1404+
icon = "comment"
1405+
),
1406+
)
1407+
}
1408+
return buttonList
1409+
}
1410+
13761411
companion object {
13771412
private val LOG = getLogger<CodeTestChatController>()
13781413

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/sessionconfig/CodeScanSessionConfig.kt

+3-6
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.utils.Ama
3030
import software.aws.toolkits.jetbrains.services.codewhisperer.language.CodeWhispererProgrammingLanguage
3131
import software.aws.toolkits.jetbrains.services.codewhisperer.language.languages.CodeWhispererUnknownLanguage
3232
import software.aws.toolkits.jetbrains.services.codewhisperer.language.programmingLanguage
33-
import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhispererTelemetryService
3433
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.CODE_SCAN_CREATE_PAYLOAD_TIMEOUT_IN_SECONDS
3534
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.CodeAnalysisScope
3635
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.DEFAULT_CODE_SCAN_TIMEOUT_IN_SECONDS
3736
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.DEFAULT_PAYLOAD_LIMIT_IN_BYTES
3837
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.EXPRESS_SCAN_TIMEOUT_IN_SECONDS
3938
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.FILE_SCAN_PAYLOAD_SIZE_LIMIT_IN_BYTES
39+
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getNormalizedRelativePath
4040
import software.aws.toolkits.jetbrains.services.codewhisperer.util.GitIgnoreFilteringUtil
4141
import software.aws.toolkits.jetbrains.services.codewhisperer.util.isWithin
4242
import software.aws.toolkits.resources.message
@@ -196,12 +196,9 @@ class CodeScanSessionConfig(
196196
private fun zipFiles(files: List<Path>, codeDiff: String? = null): File = createTemporaryZipFile {
197197
files.forEach { file ->
198198
try {
199-
val relativePath = "${project.name}/${file.relativeTo(projectRoot.toNioPath())}"
200-
if (relativePath.contains("../") || relativePath.contains("..\\")) {
201-
CodeWhispererTelemetryService.getInstance().sendInvalidZipEvent(file, projectRoot.toNioPath(), relativePath)
202-
}
199+
val relativePath = getNormalizedRelativePath(project.name, file.relativeTo(projectRoot.toNioPath()))
203200
LOG.debug { "Selected file for truncation: $file" }
204-
it.putNextEntry(relativePath.toString(), file)
201+
it.putNextEntry(relativePath, file)
205202
} catch (e: Exception) {
206203
cannotFindFile("Zipping error: ${e.message}", file.pathString)
207204
}

plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtil.kt

+5
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ import software.aws.toolkits.resources.message
5656
import software.aws.toolkits.telemetry.CodewhispererCompletionType
5757
import software.aws.toolkits.telemetry.CodewhispererGettingStartedTask
5858
import software.aws.toolkits.telemetry.CredentialSourceId
59+
import java.nio.file.Path
60+
import java.nio.file.Paths
5961

6062
// Controls the condition to send telemetry event to CodeWhisperer service, currently:
6163
// 1. It will be sent for Builder ID users, only if they have optin telemetry sharing.
@@ -329,6 +331,9 @@ object CodeWhispererUtil {
329331
WindowManager.getInstance().setAlphaModeRatio(it, alpha)
330332
}
331333
}
334+
335+
fun getNormalizedRelativePath(projectName: String, relativePath: Path): String =
336+
Paths.get(projectName).resolve(relativePath).normalize().toString()
332337
}
333338

334339
enum class CaretMovement {

plugins/amazonq/codewhisperer/jetbrains-community/tst/software/aws/toolkits/jetbrains/services/codewhisperer/codescan/CodeWhispererProjectCodeScanTest.kt

+12
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ import org.mockito.kotlin.spy
1414
import org.mockito.kotlin.stub
1515
import software.aws.toolkits.jetbrains.services.codewhisperer.codescan.sessionconfig.CodeScanSessionConfig
1616
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
17+
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getNormalizedRelativePath
1718
import software.aws.toolkits.jetbrains.utils.rules.PythonCodeInsightTestFixtureRule
1819
import software.aws.toolkits.jetbrains.utils.rules.addFileToModule
1920
import software.aws.toolkits.jetbrains.utils.rules.addModule
2021
import software.aws.toolkits.telemetry.CodewhispererLanguage
2122
import java.io.BufferedInputStream
23+
import java.nio.file.Paths
2224
import java.util.zip.ZipInputStream
2325
import kotlin.io.path.relativeTo
2426
import kotlin.test.assertNotNull
@@ -108,6 +110,16 @@ class CodeWhispererProjectCodeScanTest : CodeWhispererCodeScanTestBase(PythonCod
108110
assertE2ERunsSuccessfully(sessionConfigSpy, project, totalLines, 10, totalSize, 1)
109111
}
110112

113+
@Test
114+
fun `test getNormalizedRelativePath()`() {
115+
assertThat(
116+
listOf("projectName\\src\\PackageName", "projectName/src/PackageName")
117+
).contains(getNormalizedRelativePath("projectName", Paths.get("src/PackageName")))
118+
assertThat(
119+
listOf("projectName\\src\\Package2", "projectName/src/Package2")
120+
).contains(getNormalizedRelativePath("projectName", Paths.get("src/./Package1/../Package2")))
121+
}
122+
111123
private fun setupCsharpProject() {
112124
val testModule = projectRule.fixture.addModule("testModule")
113125
val testModule2 = projectRule.fixture.addModule("testModule2")

plugins/amazonq/mynah-ui/src/mynah-ui/ui/apps/codeTestChatConnector.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ export class CodeTestChatConnector {
151151
text: '',
152152
options: messageData.followUps,
153153
} : undefined,
154-
canBeVoted: true,
154+
canBeVoted: messageData.canBeVoted ?? false,
155155
fileList: messageData.fileList ? {
156156
rootFolderTitle: messageData.projectRootName,
157157
fileTreeTitle: 'READY FOR REVIEW',
@@ -477,7 +477,20 @@ export class CodeTestChatConnector {
477477
disabled: true
478478
}
479479
]
480-
break;
480+
break
481+
482+
case FormButtonIds.CodeTestProvideFeedback:
483+
answer.buttons = [
484+
{
485+
keepCardAfterClick: true,
486+
text: 'Thanks for providing feedback.',
487+
id: 'utg_provided_feedback',
488+
status: 'success',
489+
position: 'outside',
490+
disabled: true
491+
}
492+
]
493+
break
481494
default:
482495
console.warn(`Unhandled action ID: ${action.id}`);
483496
break;

plugins/amazonq/mynah-ui/src/mynah-ui/ui/forms/constants.ts

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const enum FormButtonIds {
2727
CodeTestStartGeneration = "code_test_start_generation",
2828
CodeTestViewDiff = "utg_view_diff",
2929
CodeTestAccept = "utg_accept",
30+
CodeTestProvideFeedback = "utg_feedback",
3031
CodeTestRegenerate = "utg_regenerate",
3132
CodeTestReject = "utg_reject",
3233
CodeTestBuildAndExecute = "utg_build_and_execute",

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/ui/feedback/FeatureDevFeedbackDialog.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ class FeatureDevFeedbackDialog(project: Project) : FeedbackDialog(project) {
1616
}
1717

1818
init {
19-
title = message("feedback.title.amazonq.feature_dev")
19+
title = message("feedback.title.amazonq.send_feedback")
2020
}
2121
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.ui.feedback
5+
6+
import com.intellij.openapi.project.Project
7+
import software.aws.toolkits.jetbrains.services.telemetry.TelemetryService
8+
import software.aws.toolkits.resources.message
9+
10+
class TestGenFeedbackDialog(
11+
project: Project,
12+
private val requestId: String? = null,
13+
private val jobId: String? = null,
14+
) : FeedbackDialog(project) {
15+
override fun notificationTitle() = message("aws.notification.title.amazonq.test_generation")
16+
override fun feedbackPrompt() = message("feedback.comment.textbox.title.amazonq.test_generation")
17+
override fun productName() = "Amazon Q Test Generation"
18+
override suspend fun sendFeedback() {
19+
TelemetryService.getInstance().sendFeedback(sentiment, "UserComment: $commentText, RequestId: $requestId, TestGenerationJobId: $jobId")
20+
}
21+
22+
init {
23+
title = message("feedback.title.amazonq.send_feedback")
24+
}
25+
}

plugins/core/resources/resources/software/aws/toolkits/resources/MessagesBundle.properties

+4-1
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ aws.notification.sam_cli_not_valid=Error: {0}. Click <a href="">here</a> to reco
234234
aws.notification.title=AWS Toolkit
235235
aws.notification.title.amazonq=Amazon Q
236236
aws.notification.title.amazonq.feature_dev=Amazon Q FeatureDev
237+
aws.notification.title.amazonq.test_generation=Amazon Q Test Generation
237238
aws.notification.title.codewhisperer=Amazon Q
238239
aws.onboarding.getstarted.panel.bottom_text=<a href="https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/auth-access.html">Learn more about authenticating with the Toolkit</a>
239240
aws.onboarding.getstarted.panel.bottom_text_question=Why do these have different authentication requirements?
@@ -1308,6 +1309,7 @@ feedback.comment.textbox.initial.length=2000 characters remaining
13081309
feedback.comment.textbox.title=What do you like about {0}? What can we improve?
13091310
feedback.comment.textbox.title.amazonq=How was your experience with the upgrade of your Java application?
13101311
feedback.comment.textbox.title.amazonq.feature_dev=How has your experience with Amazon Q been? What can we improve?
1312+
feedback.comment.textbox.title.amazonq.test_generation=How has your experience with Amazon Q Test Generation been? What can we improve?
13111313
feedback.connect.with.github.title=Join us on GitHub
13121314
feedback.customer.alert.info=Please don't add personally identifiable information (PII), confidential or sensitive information in your feedback.<br>Remove any PII when sharing file paths, error messages, etc.
13131315
feedback.description=Submit quick feedback about the AWS Toolkit for JetBrains
@@ -1327,7 +1329,7 @@ feedback.submit_success=Thanks for the feedback!
13271329
feedback.submitting=Sharing...
13281330
feedback.title=Share Feedback for {0}...
13291331
feedback.title.amazonq=Send feedback for Code Transformation by Amazon Q
1330-
feedback.title.amazonq.feature_dev=Send feedback for Amazon Q
1332+
feedback.title.amazonq.send_feedback=Send feedback for Amazon Q
13311333
feedback.validation.comment_too_long=Comment is too long.
13321334
feedback.validation.empty_comment=Please provide a comment.
13331335
feedback.validation.no_sentiment=Please select how you're feeling.
@@ -2064,6 +2066,7 @@ sqs.subscribe.sns.validation.empty_topic=Topic must be specified.
20642066
sqs.toolwindow=SQS
20652067
sqs.url.parse_error=Error parsing SQS queue URL
20662068
tags.title=Tags
2069+
testgen.button.feedback=How can we make /test better?
20672070
testgen.error.generic_error_message=Amazon Q encountered an error while generating tests. Try again later.
20682071
testgen.error.generic_technical_error_message=I am experiencing technical difficulties at the moment. Please try again in a few minutes.
20692072
testgen.error.maximum_generations_reach=You've reached the monthly quota for Amazon Q Developer's agent capabilities. You can try again next month. For more information on usage limits, see the <a href=\"https://aws.amazon.com/q/developer/pricing/\" target=\"_blank\">Amazon Q Developer pricing page</a>.

0 commit comments

Comments
 (0)