Skip to content

Commit d9ee16c

Browse files
Merge branch 'feature/q-lsp' into samgst/q-lsp-q-config-settings
2 parents bb57f74 + 86527a3 commit d9ee16c

File tree

38 files changed

+588
-314
lines changed

38 files changed

+588
-314
lines changed

detekt-rules/src/software/aws/toolkits/gradle/detekt/rules/BannedImportsRule.kt

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class BannedImportsRule : Rule() {
1717
override fun visitImportList(importList: KtImportList) {
1818
super.visitImportList(importList)
1919
importList.imports.forEach { element ->
20-
val importedFqName = element.importedFqName?.asString()
20+
val importedFqName = element.importedFqName?.asString() ?: return
2121
if (importedFqName == "org.assertj.core.api.Assertions") {
2222
report(
2323
CodeSmell(
@@ -28,7 +28,7 @@ class BannedImportsRule : Rule() {
2828
)
2929
}
3030

31-
if (importedFqName?.startsWith("org.hamcrest") == true) {
31+
if (importedFqName.startsWith("org.hamcrest")) {
3232
report(
3333
CodeSmell(
3434
issue,
@@ -38,9 +38,7 @@ class BannedImportsRule : Rule() {
3838
)
3939
}
4040

41-
if (importedFqName?.startsWith("kotlin.test.assert") == true &&
42-
importedFqName.startsWith("kotlin.test.assertNotNull") == false
43-
) {
41+
if (importedFqName.startsWith("kotlin.test.assert") && !importedFqName.startsWith("kotlin.test.assertNotNull")) {
4442
report(
4543
CodeSmell(
4644
issue,
@@ -50,7 +48,17 @@ class BannedImportsRule : Rule() {
5048
)
5149
}
5250

53-
if (importedFqName?.startsWith("org.gradle.internal.impldep") == true) {
51+
if (importedFqName.startsWith("org.junit.jupiter.api.Assertions") || importedFqName.startsWith("org.junit.Assert")) {
52+
report(
53+
CodeSmell(
54+
issue,
55+
Entity.from(element),
56+
message = "Use AssertJ instead of JUnit assertions"
57+
)
58+
)
59+
}
60+
61+
if (importedFqName.startsWith("org.gradle.internal.impldep")) {
5462
report(
5563
CodeSmell(
5664
issue,
@@ -60,7 +68,7 @@ class BannedImportsRule : Rule() {
6068
)
6169
}
6270

63-
if (importedFqName?.contains("kotlinx.coroutines.Dispatchers") == true) {
71+
if (importedFqName.contains("kotlinx.coroutines.Dispatchers")) {
6472
report(
6573
CodeSmell(
6674
issue,

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/common/clients/AmazonQCodeGenerateClient.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import software.amazon.awssdk.services.codewhispererruntime.model.ContentChecksu
1313
import software.amazon.awssdk.services.codewhispererruntime.model.CreateTaskAssistConversationRequest
1414
import software.amazon.awssdk.services.codewhispererruntime.model.CreateTaskAssistConversationResponse
1515
import software.amazon.awssdk.services.codewhispererruntime.model.CreateUploadUrlResponse
16+
import software.amazon.awssdk.services.codewhispererruntime.model.Dimension
1617
import software.amazon.awssdk.services.codewhispererruntime.model.DocV2AcceptanceEvent
1718
import software.amazon.awssdk.services.codewhispererruntime.model.DocV2GenerationEvent
1819
import software.amazon.awssdk.services.codewhispererruntime.model.GetTaskAssistCodeGenerationResponse
@@ -89,6 +90,33 @@ class AmazonQCodeGenerateClient(private val project: Project) {
8990
requestBuilder.userContext(docUserContext)
9091
}
9192

93+
fun sendDocMetricData(operationName: String, result: String): SendTelemetryEventResponse =
94+
bearerClient().sendTelemetryEvent { requestBuilder ->
95+
requestBuilder.telemetryEvent { telemetryEventBuilder ->
96+
telemetryEventBuilder.metricData {
97+
it
98+
.metricName("Operation")
99+
.metricValue(1.0)
100+
.timestamp(Instant.now())
101+
.product("DocGeneration")
102+
.dimensions(
103+
listOf(
104+
Dimension.builder()
105+
.name("operationName")
106+
.value(operationName)
107+
.build(),
108+
Dimension.builder()
109+
.name("result")
110+
.value(result)
111+
.build()
112+
)
113+
)
114+
}
115+
}
116+
requestBuilder.optOutPreference(getTelemetryOptOutPreference())
117+
requestBuilder.userContext(docUserContext)
118+
}
119+
92120
fun createTaskAssistConversation(): CreateTaskAssistConversationResponse = bearerClient().createTaskAssistConversation(
93121
CreateTaskAssistConversationRequest.builder().build()
94122
)

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/common/util/AmazonQCodeGenService.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import software.aws.toolkits.core.utils.getLogger
2020
import software.aws.toolkits.core.utils.warn
2121
import software.aws.toolkits.jetbrains.common.clients.AmazonQCodeGenerateClient
2222
import software.aws.toolkits.jetbrains.common.session.Intent
23-
import software.aws.toolkits.jetbrains.services.amazonqDoc.docServiceError
2423
import software.aws.toolkits.jetbrains.services.amazonqDoc.session.DocGenerationStreamResult
2524
import software.aws.toolkits.jetbrains.services.amazonqDoc.session.ExportDocTaskAssistResultArchiveStreamResult
2625
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ApiException
@@ -34,9 +33,9 @@ import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.MonthlyConvers
3433
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ServiceException
3534
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ZipFileCorruptedException
3635
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl
37-
import software.aws.toolkits.resources.message
3836
import software.aws.toolkits.telemetry.AmazonqTelemetry
3937
import software.aws.toolkits.telemetry.MetricResult
38+
import software.aws.toolkits.jetbrains.services.amazonqDoc.ContentLengthException as DocContentLengthException
4039

4140
class AmazonQCodeGenService(val proxyClient: AmazonQCodeGenerateClient, val project: Project) {
4241
fun createConversation(): String {
@@ -115,7 +114,7 @@ class AmazonQCodeGenService(val proxyClient: AmazonQCodeGenerateClient, val proj
115114

116115
if (e is ValidationException && e.message?.contains("Invalid contentLength") == true) {
117116
if (featureName?.equals("docGeneration") == true) {
118-
throw docServiceError(message("amazonqDoc.exception.content_length_error"))
117+
throw DocContentLengthException(operation = FeatureDevOperation.CreateUploadUrl.toString(), desc = null, cause = e.cause)
119118
}
120119
throw ContentLengthException(operation = FeatureDevOperation.CreateUploadUrl.toString(), desc = null, cause = e.cause)
121120
}
@@ -162,6 +161,9 @@ class AmazonQCodeGenService(val proxyClient: AmazonQCodeGenerateClient, val proj
162161
) {
163162
throw CodeIterationLimitException(operation = FeatureDevOperation.StartTaskAssistCodeGeneration.toString(), desc = null, e.cause)
164163
} else if (e is ValidationException && e.message?.contains("repo size is exceeding the limits") == true) {
164+
if (intent == Intent.DOC) {
165+
throw DocContentLengthException(operation = FeatureDevOperation.CreateUploadUrl.toString(), desc = null, cause = e.cause)
166+
}
165167
throw ContentLengthException(operation = FeatureDevOperation.StartTaskAssistCodeGeneration.toString(), desc = null, cause = e.cause)
166168
} else if (e is ValidationException && e.message?.contains("zipped file is corrupted") == true) {
167169
throw ZipFileCorruptedException(operation = FeatureDevOperation.StartTaskAssistCodeGeneration.toString(), desc = null, e.cause)

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/DocConstants.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,21 @@ const val DIAGRAM_SVG_EXT = "svg"
2121
const val DIAGRAM_DOT_EXT = "dot"
2222
val SUPPORTED_DIAGRAM_EXT_SET: Set<String> = setOf(DIAGRAM_SVG_EXT, DIAGRAM_DOT_EXT)
2323
val SUPPORTED_DIAGRAM_FILE_NAME_SET: Set<String> = SUPPORTED_DIAGRAM_EXT_SET.map { INFRA_DIAGRAM_PREFIX + it }.toSet()
24+
25+
enum class MetricDataOperationName(private val operationName: String) {
26+
StartDocGeneration("StartDocGeneration"),
27+
EndDocGeneration("EndDocGeneration"),
28+
;
29+
30+
override fun toString(): String = operationName
31+
}
32+
33+
enum class MetricDataResult(private val resultName: String) {
34+
Success("Success"),
35+
Fault("Fault"),
36+
Error("Error"),
37+
LlmFailure("LLMFailure"),
38+
;
39+
40+
override fun toString(): String = resultName
41+
}

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/DocExceptions.kt

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,93 @@
33

44
package software.aws.toolkits.jetbrains.services.amazonqDoc
55

6+
import software.aws.toolkits.jetbrains.services.amazonq.project.RepoSizeError
7+
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ClientException
8+
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.LlmException
9+
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ServiceException
610
import software.aws.toolkits.resources.message
711

8-
open class DocException(
9-
override val message: String?,
10-
override val cause: Throwable? = null,
12+
open class DocClientException(
13+
message: String,
14+
operation: String,
15+
desc: String?,
16+
cause: Throwable? = null,
1117
val remainingIterations: Int? = null,
12-
) : RuntimeException()
18+
) : ClientException(message, operation, desc, cause)
1319

14-
class ZipFileError(override val message: String, override val cause: Throwable?) : RuntimeException()
20+
val denyListedErrors = arrayOf("Deserialization error", "Inaccessible host", "UnknownHost")
21+
fun createUserFacingErrorMessage(message: String?): String? =
22+
if (message != null && denyListedErrors.any { message.contains(it) }) "$FEATURE_NAME API request failed" else message
1523

16-
class CodeIterationLimitError(override val message: String, override val cause: Throwable?) : RuntimeException()
24+
class ReadmeTooLargeException(
25+
operation: String,
26+
desc: String?,
27+
cause: Throwable? = null,
28+
remainingIterations: Int? = null,
29+
) : DocClientException(message("amazonqDoc.exception.readme_too_large"), operation, desc, cause, remainingIterations)
1730

18-
internal fun docServiceError(message: String?, cause: Throwable? = null, remainingIterations: Int? = null): Nothing =
19-
throw DocException(message, cause, remainingIterations)
31+
class ReadmeUpdateTooLargeException(
32+
operation: String,
33+
desc: String?,
34+
cause: Throwable? = null,
35+
remainingIterations: Int? = null,
36+
) : DocClientException(message("amazonqDoc.exception.readme_update_too_large"), operation, desc, cause, remainingIterations)
2037

21-
internal fun conversationIdNotFound(): Nothing =
22-
throw DocException(message("amazonqFeatureDev.exception.conversation_not_found"))
38+
class ContentLengthException(
39+
override val message: String = message("amazonqDoc.exception.content_length_error"),
40+
operation: String,
41+
desc: String?,
42+
cause: Throwable? = null,
43+
) :
44+
RepoSizeError, ClientException(message, operation, desc, cause)
2345

24-
val denyListedErrors = arrayOf("Deserialization error", "Inaccessible host", "UnknownHost")
25-
fun createUserFacingErrorMessage(message: String?): String? =
26-
if (message != null && denyListedErrors.any { message.contains(it) }) "$FEATURE_NAME API request failed" else message
46+
class WorkspaceEmptyException(
47+
operation: String,
48+
desc: String?,
49+
cause: Throwable? = null,
50+
) : DocClientException(message("amazonqDoc.exception.workspace_empty"), operation, desc, cause)
51+
52+
class PromptUnrelatedException(
53+
operation: String,
54+
desc: String?,
55+
cause: Throwable? = null,
56+
remainingIterations: Int? = null,
57+
) : DocClientException(message("amazonqDoc.exception.prompt_unrelated"), operation, desc, cause, remainingIterations)
58+
59+
class PromptTooVagueException(
60+
operation: String,
61+
desc: String?,
62+
cause: Throwable? = null,
63+
remainingIterations: Int? = null,
64+
) : DocClientException(message("amazonqDoc.exception.prompt_too_vague"), operation, desc, cause, remainingIterations)
65+
66+
class PromptRefusalException(
67+
operation: String,
68+
desc: String?,
69+
cause: Throwable? = null,
70+
remainingIterations: Int? = null,
71+
) : DocClientException(message("amazonqFeatureDev.exception.prompt_refusal"), operation, desc, cause, remainingIterations)
72+
73+
class GuardrailsException(
74+
operation: String,
75+
desc: String?,
76+
cause: Throwable? = null,
77+
) : DocClientException(message("amazonqDoc.error_text"), operation, desc, cause)
78+
79+
class NoChangeRequiredException(
80+
operation: String,
81+
desc: String?,
82+
cause: Throwable? = null,
83+
) : DocClientException(message("amazonqDoc.exception.no_change_required"), operation, desc, cause)
84+
85+
class EmptyPatchException(operation: String, desc: String?, cause: Throwable? = null) :
86+
LlmException(message("amazonqDoc.error_text"), operation, desc, cause)
87+
88+
class ThrottlingException(
89+
operation: String,
90+
desc: String?,
91+
cause: Throwable? = null,
92+
) : DocClientException(message("amazonqFeatureDev.exception.throttling"), operation, desc, cause)
93+
94+
class DocGenerationException(operation: String, desc: String?, cause: Throwable? = null) :
95+
ServiceException(message("amazonqDoc.error_text"), operation, desc, cause)

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonqDoc/controller/DocController.kt

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ import software.aws.toolkits.jetbrains.services.amazonq.project.RepoSizeError
3434
import software.aws.toolkits.jetbrains.services.amazonq.toolwindow.AmazonQToolWindowFactory
3535
import software.aws.toolkits.jetbrains.services.amazonqDoc.DEFAULT_RETRY_LIMIT
3636
import software.aws.toolkits.jetbrains.services.amazonqDoc.DIAGRAM_SVG_EXT
37-
import software.aws.toolkits.jetbrains.services.amazonqDoc.DocException
37+
import software.aws.toolkits.jetbrains.services.amazonqDoc.DocClientException
3838
import software.aws.toolkits.jetbrains.services.amazonqDoc.FEATURE_NAME
3939
import software.aws.toolkits.jetbrains.services.amazonqDoc.InboundAppMessagesHandler
40-
import software.aws.toolkits.jetbrains.services.amazonqDoc.ZipFileError
40+
import software.aws.toolkits.jetbrains.services.amazonqDoc.MetricDataOperationName
41+
import software.aws.toolkits.jetbrains.services.amazonqDoc.MetricDataResult
4142
import software.aws.toolkits.jetbrains.services.amazonqDoc.cancellingProgressField
4243
import software.aws.toolkits.jetbrains.services.amazonqDoc.createUserFacingErrorMessage
4344
import software.aws.toolkits.jetbrains.services.amazonqDoc.denyListedErrors
@@ -70,6 +71,7 @@ import software.aws.toolkits.jetbrains.services.amazonqDoc.storage.ChatSessionSt
7071
import software.aws.toolkits.jetbrains.services.amazonqDoc.util.getFollowUpOptions
7172
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.CodeIterationLimitException
7273
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.MonthlyConversationLimitError
74+
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.ZipFileCorruptedException
7375
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.CodeReferenceGenerated
7476
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.DeletedFileInfo
7577
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.session.NewFileZipInfo
@@ -592,9 +594,12 @@ class DocController(
592594
messenger.sendUpdatePlaceholder(tabId, message("amazonqFeatureDev.placeholder.provide_code_feedback"))
593595
}
594596

595-
private suspend fun processErrorChatMessage(err: Exception, session: DocSession?, tabId: String, isEnableChatInput: Boolean) {
597+
private suspend fun processErrorChatMessage(err: Exception, session: DocSession?, tabId: String) {
596598
logger.warn(err) { "Encountered ${err.message} for tabId: $tabId" }
597599
messenger.sendUpdatePromptProgress(tabId, null)
600+
val docGenerationMode = docGenerationTasks.getTask(tabId).mode
601+
val isEnableChatInput = docGenerationMode == Mode.EDIT &&
602+
(err as? DocClientException)?.remainingIterations?.let { it > 0 } ?: false
598603

599604
when (err) {
600605
is RepoSizeError -> {
@@ -616,7 +621,7 @@ class DocController(
616621
)
617622
}
618623

619-
is ZipFileError -> {
624+
is ZipFileCorruptedException -> {
620625
messenger.sendError(
621626
tabId = tabId,
622627
errMessage = err.message,
@@ -626,12 +631,11 @@ class DocController(
626631
}
627632

628633
is MonthlyConversationLimitError -> {
629-
messenger.sendUpdatePlaceholder(tabId, newPlaceholder = message("amazonqFeatureDev.placeholder.after_monthly_limit"))
630-
messenger.sendChatInputEnabledMessage(tabId, enabled = true)
631634
messenger.sendMonthlyLimitError(tabId = tabId)
635+
messenger.sendChatInputEnabledMessage(tabId, enabled = false)
632636
}
633637

634-
is DocException -> {
638+
is DocClientException -> {
635639
messenger.sendErrorToUser(
636640
tabId = tabId,
637641
errMessage = err.message,
@@ -765,13 +769,7 @@ class DocController(
765769
)
766770
}
767771
} catch (err: Exception) {
768-
// For non edit mode lock the chat input until they explicitly click one of the follow-ups
769-
var isEnableChatInput = false
770-
if (err is DocException && docGenerationTask.mode == Mode.EDIT) {
771-
isEnableChatInput = err.remainingIterations != null && err.remainingIterations > 0
772-
}
773-
774-
processErrorChatMessage(err, session, tabId, isEnableChatInput)
772+
processErrorChatMessage(err, session, tabId)
775773
}
776774
}
777775

@@ -794,6 +792,10 @@ class DocController(
794792
}
795793

796794
session.send(sessionMessage)
795+
session.sendDocMetricData(
796+
MetricDataOperationName.StartDocGeneration,
797+
MetricDataResult.Success
798+
)
797799

798800
val filePaths: List<NewFileZipInfo> = when (val state = session.sessionState) {
799801
is PrepareDocGenerationState -> state.filePaths ?: emptyList()
@@ -854,7 +856,11 @@ class DocController(
854856
message = IncomingDocMessage.OpenDiff(tabId = followUpMessage.tabId, filePath = filePaths[0].zipFilePath, deleted = false)
855857
)
856858
} catch (err: Exception) {
857-
processErrorChatMessage(err, session, tabId = followUpMessage.tabId, false)
859+
session.sendDocMetricData(
860+
MetricDataOperationName.EndDocGeneration,
861+
session.getMetricResult(err)
862+
)
863+
processErrorChatMessage(err, session, tabId = followUpMessage.tabId)
858864
} finally {
859865
messenger.sendUpdatePlaceholder(
860866
tabId = followUpMessage.tabId,
@@ -873,6 +879,10 @@ class DocController(
873879
messenger.sendChatInputEnabledMessage(tabId = followUpMessage.tabId, enabled = false) // Lock chat input until a follow-up is clicked.
874880
}
875881
}
882+
session.sendDocMetricData(
883+
MetricDataOperationName.EndDocGeneration,
884+
MetricDataResult.Success
885+
)
876886
}
877887

878888
private suspend fun handleEmptyFiles(

0 commit comments

Comments
 (0)