Skip to content

Commit 3cef925

Browse files
authored
CodeWhisperer: Correct completion type for every completion in user decision events (#3811)
1. Previously we just use the completion type of the first suggestion, as the completion type for all suggestions. Now since this is no longer true, plugin side also needs to make the change to compute the completion type for every suggestion. Since this is per-suggestion level this will be recorded in the userDecision event. 2. Note that we still have 'completionType' field in other events( serviceInvocation, clientComponentLatency, etc) these values no longer make sense for analysis. 3. For actually computing the completionType for each suggestion, the rule is: If a suggestion has multi-lines and they are non-blank lines, it's a block suggestion, otherwise it's a line suggestion.
1 parent 7eda113 commit 3cef925

File tree

10 files changed

+129
-62
lines changed

10 files changed

+129
-62
lines changed

jetbrains-core/src/software/aws/toolkits/jetbrains/services/codewhisperer/editor/CodeWhispererEditorManager.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ class CodeWhispererEditorManager {
5959
PsiDocumentManager.getInstance(project).getPsiFile(document)?.virtualFile,
6060
rangeMarker,
6161
remainingRecommendation,
62-
selectedIndex
62+
selectedIndex,
63+
detail.completionType
6364
)
6465

6566
ApplicationManager.getApplication().messageBus.syncPublisher(

jetbrains-core/src/software/aws/toolkits/jetbrains/services/codewhisperer/model/CodeWhispererModel.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispe
1616
import software.aws.toolkits.jetbrains.services.codewhisperer.service.RequestContext
1717
import software.aws.toolkits.jetbrains.services.codewhisperer.service.ResponseContext
1818
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
19+
import software.aws.toolkits.telemetry.CodewhispererCompletionType
1920
import software.aws.toolkits.telemetry.CodewhispererTriggerType
2021
import software.aws.toolkits.telemetry.Result
2122
import java.util.concurrent.TimeUnit
@@ -63,7 +64,8 @@ data class DetailContext(
6364
val reformatted: Completion,
6465
val isDiscarded: Boolean,
6566
val isTruncatedOnRight: Boolean,
66-
val rightOverlap: String = ""
67+
val rightOverlap: String = "",
68+
val completionType: CodewhispererCompletionType,
6769
)
6870

6971
data class SessionContext(

jetbrains-core/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererRecommendationManager.kt

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import software.amazon.awssdk.services.codewhispererruntime.model.Reference
1616
import software.amazon.awssdk.services.codewhispererruntime.model.Span
1717
import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext
1818
import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationChunk
19+
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getCompletionType
1920
import kotlin.math.max
2021

2122
class CodeWhispererRecommendationManager {
@@ -121,7 +122,15 @@ class CodeWhispererRecommendationManager {
121122
return recommendations.map {
122123
val isDiscardedByUserInput = !it.content().startsWith(userInput) || it.content() == userInput
123124
if (isDiscardedByUserInput) {
124-
return@map DetailContext(requestId, it, it, isDiscarded = true, isTruncatedOnRight = false, rightOverlap = "")
125+
return@map DetailContext(
126+
requestId,
127+
it,
128+
it,
129+
isDiscarded = true,
130+
isTruncatedOnRight = false,
131+
rightOverlap = "",
132+
getCompletionType(it)
133+
)
125134
}
126135

127136
val overlap = findRightContextOverlap(requestContext, it)
@@ -137,7 +146,15 @@ class CodeWhispererRecommendationManager {
137146
.build()
138147
val isDiscardedByUserInputForTruncated = !truncated.content().startsWith(userInput) || truncated.content() == userInput
139148
if (isDiscardedByUserInputForTruncated) {
140-
return@map DetailContext(requestId, it, truncated, isDiscarded = true, isTruncatedOnRight = true, rightOverlap = overlap)
149+
return@map DetailContext(
150+
requestId,
151+
it,
152+
truncated,
153+
isDiscarded = true,
154+
isTruncatedOnRight = true,
155+
rightOverlap = overlap,
156+
getCompletionType(it)
157+
)
141158
}
142159

143160
val reformatted = reformat(requestContext, truncated)
@@ -148,7 +165,8 @@ class CodeWhispererRecommendationManager {
148165
reformatted,
149166
isDiscardedByRightContextTruncationDedupe,
150167
truncated.content().length != it.content().length,
151-
overlap
168+
overlap,
169+
getCompletionType(it)
152170
)
153171
}
154172
}

jetbrains-core/src/software/aws/toolkits/jetbrains/services/codewhisperer/service/CodeWhispererService.kt

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.telemetry.CodeWhis
6565
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CaretMovement
6666
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants
6767
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererConstants.SUPPLEMENTAL_CONTEXT_TIMEOUT
68-
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.checkCompletionType
69-
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.checkEmptyRecommendations
68+
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getCompletionType
7069
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.notifyErrorCodeWhispererUsageLimit
7170
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.promptReAuth
7271
import software.aws.toolkits.jetbrains.services.codewhisperer.util.FileContextProvider
@@ -180,9 +179,7 @@ class CodeWhispererService {
180179
if (response.nextToken().isEmpty()) {
181180
requestContext.latencyContext.paginationAllCompletionsEnd = System.nanoTime()
182181
}
183-
val emptyRecommendations = checkEmptyRecommendations(response.completions())
184-
val completionType = checkCompletionType(response.completions(), emptyRecommendations)
185-
val responseContext = ResponseContext(sessionId, completionType)
182+
val responseContext = ResponseContext(sessionId)
186183
logServiceInvocation(requestId, requestContext, responseContext, response.completions(), latency, null)
187184
lastRecommendationIndex += response.completions().size
188185
ApplicationManager.getApplication().messageBus.syncPublisher(CODEWHISPERER_CODE_COMPLETION_PERFORMED)
@@ -269,7 +266,7 @@ class CodeWhispererService {
269266
}
270267
}
271268
val exceptionType = e::class.simpleName
272-
val responseContext = ResponseContext(sessionId, CodewhispererCompletionType.Unknown)
269+
val responseContext = ResponseContext(sessionId)
273270
CodeWhispererInvocationStatus.getInstance().setInvocationSessionId(sessionId)
274271
logServiceInvocation(requestId, requestContext, responseContext, emptyList(), null, exceptionType)
275272
CodeWhispererTelemetryService.getInstance().sendServiceInvocationEvent(
@@ -373,10 +370,17 @@ class CodeWhispererService {
373370
if (nextStates.recommendationContext.details.isEmpty() && response.nextToken().isEmpty()) {
374371
LOG.debug { "Received just an empty list from this session, requestId: $requestId" }
375372
CodeWhispererTelemetryService.getInstance().sendUserDecisionEvent(
376-
requestId,
377373
requestContext,
378374
responseContext,
379-
Completion.builder().build(),
375+
DetailContext(
376+
requestId,
377+
Completion.builder().build(),
378+
Completion.builder().build(),
379+
false,
380+
false,
381+
"",
382+
CodewhispererCompletionType.Unknown
383+
),
380384
-1,
381385
CodewhispererSuggestionState.Empty,
382386
nextStates.recommendationContext.details.size
@@ -488,7 +492,9 @@ class CodeWhispererService {
488492
responseContext: ResponseContext,
489493
recommendations: List<Completion>
490494
) {
491-
val detailContexts = recommendations.map { DetailContext("", it, it, true, false) }
495+
val detailContexts = recommendations.map {
496+
DetailContext("", it, it, true, false, "", getCompletionType(it))
497+
}
492498
val recommendationContext = RecommendationContext(detailContexts, "", "", VisualPosition(0, 0))
493499

494500
CodeWhispererTelemetryService.getInstance().sendUserDecisionEventForAll(
@@ -719,7 +725,6 @@ data class RequestContext(
719725

720726
data class ResponseContext(
721727
val sessionId: String,
722-
val completionType: CodewhispererCompletionType
723728
)
724729

725730
interface CodeWhispererCodeCompletionServiceListener {

jetbrains-core/src/software/aws/toolkits/jetbrains/services/codewhisperer/telemetry/CodeWhispererTelemetryService.kt

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import com.intellij.openapi.project.Project
1111
import com.intellij.openapi.vfs.VirtualFile
1212
import org.apache.commons.collections4.queue.CircularFifoQueue
1313
import org.jetbrains.annotations.TestOnly
14-
import software.amazon.awssdk.services.codewhispererruntime.model.Completion
1514
import software.aws.toolkits.core.utils.debug
1615
import software.aws.toolkits.core.utils.getLogger
1716
import software.aws.toolkits.jetbrains.services.codewhisperer.model.CodeScanTelemetryEvent
17+
import software.aws.toolkits.jetbrains.services.codewhisperer.model.DetailContext
1818
import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext
1919
import software.aws.toolkits.jetbrains.services.codewhisperer.model.RecommendationContext
2020
import software.aws.toolkits.jetbrains.services.codewhisperer.model.SessionContext
@@ -28,6 +28,7 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.service.ResponseCo
2828
import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhispererSettings
2929
import software.aws.toolkits.jetbrains.services.codewhisperer.util.CodeWhispererUtil.getConnectionStartUrl
3030
import software.aws.toolkits.jetbrains.settings.AwsSettings
31+
import software.aws.toolkits.telemetry.CodewhispererCompletionType
3132
import software.aws.toolkits.telemetry.CodewhispererLanguage
3233
import software.aws.toolkits.telemetry.CodewhispererPreviousSuggestionState
3334
import software.aws.toolkits.telemetry.CodewhispererSuggestionState
@@ -104,7 +105,7 @@ class CodeWhispererTelemetryService {
104105
CodewhispererTelemetry.serviceInvocation(
105106
project = project,
106107
codewhispererAutomatedTriggerType = automatedTriggerType.telemetryType,
107-
codewhispererCompletionType = responseContext.completionType,
108+
codewhispererCompletionType = CodewhispererCompletionType.Line,
108109
codewhispererCursorOffset = offset,
109110
codewhispererLanguage = codewhispererLanguage,
110111
codewhispererLastSuggestionIndex = lastRecommendationIndex,
@@ -126,14 +127,15 @@ class CodeWhispererTelemetryService {
126127
}
127128

128129
fun sendUserDecisionEvent(
129-
requestId: String,
130130
requestContext: RequestContext,
131131
responseContext: ResponseContext,
132-
detail: Completion,
132+
detailContext: DetailContext,
133133
index: Int,
134134
suggestionState: CodewhispererSuggestionState,
135135
numOfRecommendations: Int
136136
) {
137+
val requestId = detailContext.requestId
138+
val recommendation = detailContext.recommendation
137139
val (project, _, triggerTypeInfo) = requestContext
138140
val codewhispererLanguage = requestContext.fileContextInfo.programmingLanguage.toTelemetryType()
139141
val supplementalContext = requestContext.supplementalContext
@@ -143,21 +145,21 @@ class CodeWhispererTelemetryService {
143145
"Index: $index, " +
144146
"State: $suggestionState, " +
145147
"Request ID: $requestId, " +
146-
"Recommendation: ${detail.content()}"
148+
"Recommendation: ${recommendation.content()}"
147149
}
148150
val startUrl = getConnectionStartUrl(requestContext.connection)
149151
val importEnabled = CodeWhispererSettings.getInstance().isImportAdderEnabled()
150152
CodewhispererTelemetry.userDecision(
151153
project = project,
152-
codewhispererCompletionType = responseContext.completionType,
154+
codewhispererCompletionType = detailContext.completionType,
153155
codewhispererLanguage = codewhispererLanguage,
154156
codewhispererPaginationProgress = numOfRecommendations,
155157
codewhispererRequestId = requestId,
156158
codewhispererSessionId = responseContext.sessionId,
157159
codewhispererSuggestionIndex = index,
158-
codewhispererSuggestionReferenceCount = detail.references().size,
159-
codewhispererSuggestionReferences = jacksonObjectMapper().writeValueAsString(detail.references().map { it.licenseName() }.toSet().toList()),
160-
codewhispererSuggestionImportCount = if (importEnabled) detail.mostRelevantMissingImports().size else null,
160+
codewhispererSuggestionReferenceCount = recommendation.references().size,
161+
codewhispererSuggestionReferences = jacksonObjectMapper().writeValueAsString(recommendation.references().map { it.licenseName() }.toSet().toList()),
162+
codewhispererSuggestionImportCount = if (importEnabled) recommendation.mostRelevantMissingImports().size else null,
161163
codewhispererSuggestionState = suggestionState,
162164
codewhispererTriggerType = triggerTypeInfo.triggerType,
163165
credentialStartUrl = startUrl,
@@ -204,6 +206,12 @@ class CodeWhispererTelemetryService {
204206
}
205207

206208
val supplementalContext = requestContext.supplementalContext
209+
val completionType = if (recommendationContext.details.any {
210+
it.completionType == CodewhispererCompletionType.Block
211+
}
212+
) {
213+
CodewhispererCompletionType.Block
214+
} else CodewhispererCompletionType.Line
207215

208216
CodewhispererTelemetry.userTriggerDecision(
209217
project = requestContext.project,
@@ -214,7 +222,7 @@ class CodeWhispererTelemetryService {
214222
codewhispererPartialAcceptanceCount = null,
215223
codewhispererCharactersAccepted = null,
216224
codewhispererCharactersRecommended = null,
217-
codewhispererCompletionType = responseContext.completionType,
225+
codewhispererCompletionType = completionType,
218226
codewhispererLanguage = language.toTelemetryType(),
219227
codewhispererTriggerType = requestContext.triggerTypeInfo.triggerType,
220228
codewhispererAutomatedTriggerType = automatedTriggerType.telemetryType,
@@ -286,13 +294,13 @@ class CodeWhispererTelemetryService {
286294
vFile: VirtualFile?,
287295
range: RangeMarker,
288296
suggestion: String,
289-
selectedIndex: Int
297+
selectedIndex: Int,
298+
completionType: CodewhispererCompletionType
290299
) {
291-
val (sessionId, completionType) = responseContext
292300
val codewhispererLanguage = requestContext.fileContextInfo.programmingLanguage.toTelemetryType()
293301
CodeWhispererUserModificationTracker.getInstance(requestContext.project).enqueue(
294302
AcceptedSuggestionEntry(
295-
time, vFile, range, suggestion, sessionId, requestId, selectedIndex,
303+
time, vFile, range, suggestion, responseContext.sessionId, requestId, selectedIndex,
296304
requestContext.triggerTypeInfo.triggerType, completionType,
297305
codewhispererLanguage, null, null,
298306
requestContext.connection
@@ -312,16 +320,15 @@ class CodeWhispererTelemetryService {
312320
val decisions = mutableListOf<CodewhispererSuggestionState>()
313321

314322
detailContexts.forEachIndexed { index, detailContext ->
315-
val (requestId, detail, _, isDiscarded) = detailContext
316323
val suggestionState = recordSuggestionState(
317324
index,
318325
sessionContext.selectedIndex,
319326
sessionContext.seen.contains(index),
320327
hasUserAccepted,
321-
isDiscarded,
322-
detail.content().isEmpty()
328+
detailContext.isDiscarded,
329+
detailContext.recommendation.content().isEmpty()
323330
)
324-
sendUserDecisionEvent(requestId, requestContext, responseContext, detail, index, suggestionState, detailContexts.size)
331+
sendUserDecisionEvent(requestContext, responseContext, detailContext, index, suggestionState, detailContexts.size)
325332

326333
decisions.add(suggestionState)
327334
}
@@ -381,7 +388,7 @@ class CodeWhispererTelemetryService {
381388
val startUrl = getConnectionStartUrl(requestContext.connection)
382389
CodewhispererTelemetry.perceivedLatency(
383390
project = project,
384-
codewhispererCompletionType = responseContext.completionType,
391+
codewhispererCompletionType = CodewhispererCompletionType.Line,
385392
codewhispererLanguage = codewhispererLanguage,
386393
codewhispererRequestId = requestId,
387394
codewhispererSessionId = responseContext.sessionId,
@@ -409,7 +416,7 @@ class CodeWhispererTelemetryService {
409416
codewhispererPostprocessingLatency = requestContext.latencyContext.getCodeWhispererPostprocessingLatency(),
410417
codewhispererCredentialFetchingLatency = requestContext.latencyContext.getCodeWhispererCredentialFetchingLatency(),
411418
codewhispererTriggerType = requestContext.triggerTypeInfo.triggerType,
412-
codewhispererCompletionType = responseContext.completionType,
419+
codewhispererCompletionType = CodewhispererCompletionType.Line,
413420
codewhispererLanguage = codewhispererLanguage,
414421
credentialStartUrl = startUrl,
415422
codewhispererUserGroup = CodeWhispererUserGroupSettings.getInstance().getUserGroup().name

jetbrains-core/src/software/aws/toolkits/jetbrains/services/codewhisperer/util/CodeWhispererUtil.kt

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -109,24 +109,17 @@ fun VirtualFile.toCodeChunk(path: String): Sequence<Chunk> = sequence {
109109
}
110110

111111
object CodeWhispererUtil {
112-
fun checkCompletionType(
113-
results: List<Completion>,
114-
noRecommendation: Boolean
115-
): CodewhispererCompletionType {
116-
if (noRecommendation) {
117-
return CodewhispererCompletionType.Unknown
118-
}
119-
return if (results[0].content().contains("\n")) {
120-
CodewhispererCompletionType.Block
121-
} else {
122-
CodewhispererCompletionType.Line
112+
fun getCompletionType(completion: Completion): CodewhispererCompletionType {
113+
val content = completion.content()
114+
val nonBlankLines = content.split("\n").count { it.isNotBlank() }
115+
116+
return when {
117+
content.isEmpty() -> CodewhispererCompletionType.Unknown
118+
nonBlankLines > 1 -> CodewhispererCompletionType.Block
119+
else -> CodewhispererCompletionType.Line
123120
}
124121
}
125122

126-
// return true if every recommendation is empty
127-
fun checkEmptyRecommendations(recommendations: List<Completion>): Boolean =
128-
recommendations.all { it.content().isEmpty() }
129-
130123
fun notifyWarnCodeWhispererUsageLimit(project: Project? = null) {
131124
notifyWarn(
132125
message("codewhisperer.notification.usage_limit.warn.title"),

jetbrains-core/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererCodeCoverageTrackerTest.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,19 @@ internal class CodeWhispererCodeCoverageTrackerTestPython : CodeWhispererCodeCov
156156
null,
157157
mock()
158158
)
159-
val responseContext = ResponseContext("sessionId", CodewhispererCompletionType.Block)
159+
val responseContext = ResponseContext("sessionId")
160160
val recommendationContext = RecommendationContext(
161-
listOf(DetailContext("requestId", pythonResponse.completions()[0], pythonResponse.completions()[0], false, false)),
161+
listOf(
162+
DetailContext(
163+
"requestId",
164+
pythonResponse.completions()[0],
165+
pythonResponse.completions()[0],
166+
false,
167+
false,
168+
"",
169+
CodewhispererCompletionType.Block
170+
)
171+
),
162172
"x, y",
163173
"x, y",
164174
mock()

jetbrains-core/tst/software/aws/toolkits/jetbrains/services/codewhisperer/CodeWhispererStateTest.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestU
1010
import software.aws.toolkits.jetbrains.services.codewhisperer.CodeWhispererTestUtil.pythonResponse
1111
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererAutomatedTriggerType
1212
import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererService
13-
import software.aws.toolkits.telemetry.CodewhispererCompletionType
1413
import software.aws.toolkits.telemetry.CodewhispererLanguage
1514
import software.aws.toolkits.telemetry.CodewhispererTriggerType
1615

@@ -52,7 +51,6 @@ class CodeWhispererStateTest : CodeWhispererTestBase() {
5251
assertThat(listOf(actualResponseContext.sessionId)).isEqualTo(
5352
pythonResponse.sdkHttpResponse().headers()[CodeWhispererService.KET_SESSION_ID]
5453
)
55-
assertThat(actualResponseContext.completionType).isEqualTo(CodewhispererCompletionType.Block)
5654
}
5755
}
5856

0 commit comments

Comments
 (0)