Skip to content

Commit bfeb122

Browse files
authored
Merge branch 'feature/q-lsp-chat' into samgst/q-lsp-chat-webview-not-available
2 parents 1d93c3f + bc95823 commit bfeb122

File tree

10 files changed

+172
-63
lines changed

10 files changed

+172
-63
lines changed

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/ActionRegistrar.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class ActionRegistrar {
2929

3030
fun reportMessageClick(command: EditorContextCommand, project: Project) {
3131
if (command == EditorContextCommand.GenerateUnitTests) {
32-
AsyncChatUiListener.notifyPartialMessageUpdate(Gson().toJson(TestCommandMessage()))
32+
AsyncChatUiListener.notifyPartialMessageUpdate(project, Gson().toJson(TestCommandMessage()))
3333
} else {
3434
// new agentic chat route
3535
ApplicationManager.getApplication().executeOnPooledThread {
@@ -45,7 +45,7 @@ class ActionRegistrar {
4545
val params = SendToPromptParams(selection = codeSelection, triggerType = TriggerType.CONTEXT_MENU)
4646
uiMessage = FlareUiMessage(command = SEND_TO_PROMPT, params = params)
4747
}
48-
AsyncChatUiListener.notifyPartialMessageUpdate(uiMessage)
48+
AsyncChatUiListener.notifyPartialMessageUpdate(project, uiMessage)
4949
}
5050
}
5151
}

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/cwc/commands/codescan/actions/ExplainCodeIssueAction.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package software.aws.toolkits.jetbrains.services.cwc.commands.codescan.actions
55

66
import com.intellij.openapi.actionSystem.ActionManager
7+
import com.intellij.openapi.actionSystem.ActionUpdateThread
78
import com.intellij.openapi.actionSystem.AnAction
89
import com.intellij.openapi.actionSystem.AnActionEvent
910
import com.intellij.openapi.actionSystem.DataKey
@@ -18,7 +19,14 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.SendT
1819
import software.aws.toolkits.jetbrains.services.amazonq.lsp.model.aws.chat.TriggerType
1920

2021
class ExplainCodeIssueAction : AnAction(), DumbAware {
22+
override fun getActionUpdateThread() = ActionUpdateThread.BGT
23+
24+
override fun update(e: AnActionEvent) {
25+
e.presentation.isEnabledAndVisible = e.project != null
26+
}
27+
2128
override fun actionPerformed(e: AnActionEvent) {
29+
val project = e.project ?: return
2230
val issueDataKey = DataKey.create<MutableMap<String, String>>("amazonq.codescan.explainissue")
2331
val issueContext = e.getData(issueDataKey) ?: return
2432

@@ -50,7 +58,7 @@ class ExplainCodeIssueAction : AnAction(), DumbAware {
5058
)
5159

5260
val uiMessage = FlareUiMessage(SEND_TO_PROMPT, params)
53-
AsyncChatUiListener.notifyPartialMessageUpdate(uiMessage)
61+
AsyncChatUiListener.notifyPartialMessageUpdate(project, uiMessage)
5462
}
5563
}
5664
}

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC
315315

316316
override fun sendChatUpdate(params: LSPAny): CompletableFuture<Unit> {
317317
AsyncChatUiListener.notifyPartialMessageUpdate(
318+
project,
318319
FlareUiMessage(
319320
command = CHAT_SEND_UPDATE,
320321
params = params,

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,12 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.textdocument.TextDoc
7777
import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolderUtil.createWorkspaceFolders
7878
import software.aws.toolkits.jetbrains.services.amazonq.lsp.workspace.WorkspaceServiceHandler
7979
import software.aws.toolkits.jetbrains.services.amazonq.profile.QDefaultServiceConfig
80+
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl
8081
import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata
8182
import software.aws.toolkits.jetbrains.settings.LspSettings
8283
import software.aws.toolkits.jetbrains.utils.notifyInfo
8384
import software.aws.toolkits.resources.message
85+
import software.aws.toolkits.telemetry.Telemetry
8486
import java.io.IOException
8587
import java.io.OutputStreamWriter
8688
import java.io.PipedInputStream
@@ -526,20 +528,36 @@ private class AmazonQServerInstance(private val project: Project, private val cs
526528
* may fail to start in that case. The caller should handle potential runtime initialization failures.
527529
*/
528530
private fun getNodeRuntimePath(nodePath: Path): Path {
531+
val resolveNodeMetric = { isBundled: Boolean, success: Boolean ->
532+
Telemetry.languageserver.setup.use {
533+
it.id("q")
534+
it.metadata("languageServerSetupStage", "resolveNode")
535+
it.metadata("credentialStartUrl", getStartUrl(project))
536+
it.setAttribute("isBundledNode", isBundled)
537+
it.success(success)
538+
}
539+
}
540+
529541
if (Files.exists(nodePath) && Files.isExecutable(nodePath)) {
542+
resolveNodeMetric(true, true)
530543
return nodePath
531544
}
545+
532546
// use alternative node runtime if it is not found
533547
LOG.warn { "Node Runtime download failed. Fallback to user specified node runtime " }
534548
// attempt to use user provided node runtime path
535549
val nodeRuntime = LspSettings.getInstance().getNodeRuntimePath()
536550
if (!nodeRuntime.isNullOrEmpty()) {
537551
LOG.info { "Using node from $nodeRuntime " }
552+
553+
resolveNodeMetric(false, true)
538554
return Path.of(nodeRuntime)
539555
} else {
540556
val localNode = locateNodeCommand()
541557
if (localNode != null) {
542558
LOG.info { "Using node from ${localNode.toAbsolutePath()}" }
559+
560+
resolveNodeMetric(false, true)
543561
return localNode
544562
}
545563
notifyInfo(
@@ -557,6 +575,8 @@ private class AmazonQServerInstance(private val project: Project, private val cs
557575
) { _, notification -> notification.expire() }
558576
)
559577
)
578+
579+
resolveNodeMetric(false, false)
560580
return nodePath
561581
}
562582
}

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ import software.aws.toolkits.core.utils.getLogger
1717
import software.aws.toolkits.core.utils.info
1818
import software.aws.toolkits.core.utils.warn
1919
import software.aws.toolkits.jetbrains.core.saveFileFromUrl
20+
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl
2021
import software.aws.toolkits.resources.AwsCoreBundle
22+
import software.aws.toolkits.telemetry.LanguageServerSetupStage
23+
import software.aws.toolkits.telemetry.Telemetry
2124
import java.nio.file.Files
2225
import java.nio.file.Path
2326
import java.nio.file.Paths
@@ -106,33 +109,33 @@ class ArtifactHelper(private val lspArtifactsPath: Path = DEFAULT_ARTIFACT_PATH,
106109
}
107110

108111
suspend fun tryDownloadLspArtifacts(project: Project, targetVersion: Version, target: VersionTarget): Path? {
109-
val temporaryDownloadPath = Files.createTempDirectory("lsp-dl")
110-
val downloadPath = lspArtifactsPath.resolve(targetVersion.serverVersion.toString())
112+
val destinationPath = lspArtifactsPath.resolve(targetVersion.serverVersion.toString())
111113

112114
while (currentAttempt.get() < maxDownloadAttempts) {
113115
currentAttempt.incrementAndGet()
114116
logger.info { "Attempt ${currentAttempt.get()} of $maxDownloadAttempts to download LSP artifacts" }
117+
val temporaryDownloadPath = Files.createTempDirectory("lsp-dl")
115118

116119
try {
117120
return withBackgroundProgress(
118121
project,
119122
AwsCoreBundle.message("amazonqFeatureDev.placeholder.downloading_and_extracting_lsp_artifacts"),
120123
cancellable = true
121124
) {
122-
if (downloadLspArtifacts(temporaryDownloadPath, target) && !target.contents.isNullOrEmpty()) {
123-
moveFilesFromSourceToDestination(temporaryDownloadPath, downloadPath)
125+
if (downloadLspArtifacts(project, temporaryDownloadPath, target) && !target.contents.isNullOrEmpty()) {
126+
moveFilesFromSourceToDestination(temporaryDownloadPath, destinationPath)
124127
target.contents
125128
.mapNotNull { it.filename }
126-
.forEach { filename -> extractZipFile(downloadPath.resolve(filename), downloadPath) }
127-
logger.info { "Successfully downloaded and moved LSP artifacts to $downloadPath" }
129+
.forEach { filename -> extractZipFile(destinationPath.resolve(filename), destinationPath) }
130+
logger.info { "Successfully downloaded and moved LSP artifacts to $destinationPath" }
128131

129132
val thirdPartyLicenses = targetVersion.thirdPartyLicenses
130133
logger.info {
131-
"Installing Amazon Q Language Server v${targetVersion.serverVersion} to: $downloadPath. " +
134+
"Installing Amazon Q Language Server v${targetVersion.serverVersion} to: $destinationPath. " +
132135
if (thirdPartyLicenses == null) "" else "Attribution notice can be found at $thirdPartyLicenses"
133136
}
134137

135-
return@withBackgroundProgress downloadPath
138+
return@withBackgroundProgress destinationPath
136139
}
137140

138141
return@withBackgroundProgress null
@@ -146,15 +149,15 @@ class ArtifactHelper(private val lspArtifactsPath: Path = DEFAULT_ARTIFACT_PATH,
146149
else -> { logger.error(e) { "Failed to download/move LSP artifacts on attempt ${currentAttempt.get()}" } }
147150
}
148151
temporaryDownloadPath.toFile().deleteRecursively()
149-
downloadPath.toFile().deleteRecursively()
152+
destinationPath.toFile().deleteRecursively()
150153
}
151154
}
152155
logger.error { "Failed to download LSP artifacts after $maxDownloadAttempts attempts" }
153156
return null
154157
}
155158

156159
@VisibleForTesting
157-
internal fun downloadLspArtifacts(downloadPath: Path, target: VersionTarget?): Boolean {
160+
internal fun downloadLspArtifacts(project: Project, downloadPath: Path, target: VersionTarget?): Boolean {
158161
if (target == null || target.contents.isNullOrEmpty()) {
159162
logger.warn { "No target contents available for download" }
160163
return false
@@ -171,7 +174,7 @@ class ArtifactHelper(private val lspArtifactsPath: Path = DEFAULT_ARTIFACT_PATH,
171174
logger.warn { "No hash available for ${content.filename}" }
172175
return@forEach
173176
}
174-
downloadAndValidateFile(content.url, filePath, contentHash)
177+
downloadAndValidateFile(project, content.url, filePath, contentHash)
175178
}
176179
validateDownloadedFiles(downloadPath, target.contents)
177180
} catch (e: Exception) {
@@ -182,18 +185,46 @@ class ArtifactHelper(private val lspArtifactsPath: Path = DEFAULT_ARTIFACT_PATH,
182185
return true
183186
}
184187

185-
private fun downloadAndValidateFile(url: String, filePath: Path, expectedHash: String) {
188+
private fun downloadAndValidateFile(project: Project, url: String, filePath: Path, expectedHash: String) {
189+
val recordDownload = { runnable: () -> Unit ->
190+
Telemetry.languageserver.setup.use { telemetry ->
191+
telemetry.id("q")
192+
telemetry.languageServerSetupStage(LanguageServerSetupStage.GetServer)
193+
telemetry.metadata("credentialStartUrl", getStartUrl(project))
194+
telemetry.success(true)
195+
196+
try {
197+
runnable()
198+
} catch (t: Throwable) {
199+
telemetry.success(false)
200+
telemetry.recordException(t)
201+
}
202+
}
203+
}
204+
186205
try {
187206
if (!filePath.exists()) {
188207
logger.info { "Downloading file: ${filePath.fileName}" }
189-
saveFileFromUrl(url, filePath, ProgressManager.getInstance().progressIndicator)
208+
recordDownload { saveFileFromUrl(url, filePath, ProgressManager.getInstance().progressIndicator) }
190209
}
191210
if (!validateFileHash(filePath, expectedHash)) {
192211
logger.warn { "Hash mismatch for ${filePath.fileName}, re-downloading" }
193212
filePath.deleteIfExists()
194-
saveFileFromUrl(url, filePath)
195-
if (!validateFileHash(filePath, expectedHash)) {
196-
throw LspException("Hash mismatch after re-download for ${filePath.fileName}", LspException.ErrorCode.HASH_MISMATCH)
213+
recordDownload { saveFileFromUrl(url, filePath) }
214+
215+
Telemetry.languageserver.setup.use {
216+
it.id("q")
217+
it.languageServerSetupStage(LanguageServerSetupStage.Validate)
218+
it.metadata("credentialStartUrl", getStartUrl(project))
219+
it.success(true)
220+
221+
if (!validateFileHash(filePath, expectedHash)) {
222+
it.success(false)
223+
224+
val exception = LspException("Hash mismatch after re-download for ${filePath.fileName}", LspException.ErrorCode.HASH_MISMATCH)
225+
it.recordException(exception)
226+
throw exception
227+
}
197228
}
198229
}
199230
} catch (e: Exception) {

0 commit comments

Comments
 (0)