Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type" : "feature",
"description" : "Adds capability to send new context commands to AB groups"
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage
import software.aws.toolkits.jetbrains.services.amazonq.messages.MessageConnector
import software.aws.toolkits.jetbrains.services.amazonq.onboarding.OnboardingPageInteraction
import software.aws.toolkits.jetbrains.services.amazonq.onboarding.OnboardingPageInteractionType
import software.aws.toolkits.jetbrains.services.amazonq.util.highlightCommand
import software.aws.toolkits.jetbrains.services.amazonq.webview.BrowserConnector
import software.aws.toolkits.jetbrains.services.amazonq.webview.FqnWebviewAdapter
import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.EditorThemeAdapter
Expand Down Expand Up @@ -125,7 +126,8 @@ class AmazonQToolWindow private constructor(
isFeatureDevAvailable = isFeatureDevAvailable(project),
isCodeScanAvailable = isCodeScanAvailable(project),
isCodeTestAvailable = isCodeTestAvailable(project),
isDocAvailable = isDocAvailable(project)
isDocAvailable = isDocAvailable(project),
highlightCommand = highlightCommand()
)

scope.launch {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.amazonq.util

import software.aws.toolkits.jetbrains.services.amazonq.CodeWhispererFeatureConfigService

data class HighlightCommand(val command: String, val description: String)

fun highlightCommand(): HighlightCommand? {
val feature = CodeWhispererFeatureConfigService.getInstance().getHighlightCommandFeature()

if (feature == null || feature.value.stringValue().isEmpty()) return null

return HighlightCommand(feature.value.stringValue(), feature.variation)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@

package software.aws.toolkits.jetbrains.services.amazonq.webview

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import com.intellij.ui.jcef.JBCefJSQuery
import org.cef.CefApp
import software.aws.toolkits.jetbrains.services.amazonq.util.HighlightCommand
import software.aws.toolkits.jetbrains.services.amazonq.util.createBrowser
import software.aws.toolkits.jetbrains.settings.MeetQSettings

Expand All @@ -25,6 +27,7 @@ class Browser(parent: Disposable) : Disposable {
isDocAvailable: Boolean,
isCodeScanAvailable: Boolean,
isCodeTestAvailable: Boolean,
highlightCommand: HighlightCommand?,
) {
// register the scheme handler to route http://mynah/ URIs to the resources/assets directory on classpath
CefApp.getInstance()
Expand All @@ -34,7 +37,7 @@ class Browser(parent: Disposable) : Disposable {
AssetResourceHandler.AssetResourceHandlerFactory(),
)

loadWebView(isCodeTransformAvailable, isFeatureDevAvailable, isDocAvailable, isCodeScanAvailable, isCodeTestAvailable)
loadWebView(isCodeTransformAvailable, isFeatureDevAvailable, isDocAvailable, isCodeScanAvailable, isCodeTestAvailable, highlightCommand)
}

override fun dispose() {
Expand All @@ -55,12 +58,15 @@ class Browser(parent: Disposable) : Disposable {
isDocAvailable: Boolean,
isCodeScanAvailable: Boolean,
isCodeTestAvailable: Boolean,
highlightCommand: HighlightCommand?,
) {
// setup empty state. The message request handlers use this for storing state
// that's persistent between page loads.
jcefBrowser.setProperty("state", "")
// load the web app
jcefBrowser.loadHTML(getWebviewHTML(isCodeTransformAvailable, isFeatureDevAvailable, isDocAvailable, isCodeScanAvailable, isCodeTestAvailable))
jcefBrowser.loadHTML(
getWebviewHTML(isCodeTransformAvailable, isFeatureDevAvailable, isDocAvailable, isCodeScanAvailable, isCodeTestAvailable, highlightCommand)
)
}

/**
Expand All @@ -73,6 +79,7 @@ class Browser(parent: Disposable) : Disposable {
isDocAvailable: Boolean,
isCodeScanAvailable: Boolean,
isCodeTestAvailable: Boolean,
highlightCommand: HighlightCommand?,
): String {
val postMessageToJavaJsCode = receiveMessageQuery.inject("JSON.stringify(message)")

Expand All @@ -92,7 +99,8 @@ class Browser(parent: Disposable) : Disposable {
$isCodeTransformAvailable, // whether /transform is available
$isDocAvailable, // whether /doc is available
$isCodeScanAvailable, // whether /scan is available
$isCodeTestAvailable // whether /test is available
$isCodeTestAvailable, // whether /test is available
${OBJECT_MAPPER.writeValueAsString(highlightCommand)}
);
}
</script>
Expand All @@ -114,5 +122,6 @@ class Browser(parent: Disposable) : Disposable {
companion object {
private const val WEB_SCRIPT_URI = "http://mynah/js/mynah-ui.js"
private const val MAX_ONBOARDING_PAGE_COUNT = 3
private val OBJECT_MAPPER = jacksonObjectMapper()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import software.amazon.awssdk.services.codewhispererruntime.model.ListAvailableC
import software.amazon.awssdk.services.codewhispererruntime.model.ListFeatureEvaluationsRequest
import software.amazon.awssdk.services.codewhispererruntime.model.ListFeatureEvaluationsResponse
import software.amazon.awssdk.services.codewhispererruntime.paginators.ListAvailableCustomizationsIterable
import software.aws.toolkits.core.TokenConnectionSettings
import software.aws.toolkits.core.region.AwsRegion
import software.aws.toolkits.jetbrains.core.MockClientManagerRule
import software.aws.toolkits.jetbrains.core.credentials.LegacyManagedBearerSsoConnection
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager
Expand Down Expand Up @@ -58,6 +60,44 @@ class CodeWhispererFeatureConfigServiceTest {
assertThat(CodeWhispererFeatureConfigService.FEATURE_DEFINITIONS).containsKeys("testFeature")
}

@Test
fun `test highlightCommand returns non-empty`() {
mockClientManagerRule.create<CodeWhispererRuntimeClient>().stub {
on { listFeatureEvaluations(any<ListFeatureEvaluationsRequest>()) } doReturn ListFeatureEvaluationsResponse.builder().featureEvaluations(
listOf(
FeatureEvaluation.builder()
.feature("highlightCommand")
.variation("a new command")
.value(FeatureValue.fromStringValue("@highlight"))
.build()
)
).build()
}

val mockTokenSettings = mock<TokenConnectionSettings> {
on { providerId } doReturn "mock"
on { region } doReturn AwsRegion.GLOBAL
}

val mockSsoConnection = mock<LegacyManagedBearerSsoConnection> {
on { startUrl } doReturn "fake sso url"
on { getConnectionSettings() } doReturn mockTokenSettings
}

projectRule.project.replaceService(
ToolkitConnectionManager::class.java,
mock { on { activeConnectionForFeature(eq(QConnection.getInstance())) } doReturn mockSsoConnection },
disposableRule.disposable
)

runBlocking {
CodeWhispererFeatureConfigService.getInstance().fetchFeatureConfigs(projectRule.project)
}

assertThat(CodeWhispererFeatureConfigService.getInstance().getHighlightCommandFeature()?.value?.stringValue()).isEqualTo("@highlight")
assertThat(CodeWhispererFeatureConfigService.getInstance().getHighlightCommandFeature()?.variation).isEqualTo("a new command")
}

@Test
fun `test customizationArnOverride returns empty for BID users`() {
testCustomizationArnOverrideABHelper(isIdc = false, isInListAvailableCustomizations = false)
Expand All @@ -80,7 +120,7 @@ class CodeWhispererFeatureConfigServiceTest {
on { listFeatureEvaluations(any<ListFeatureEvaluationsRequest>()) } doReturn ListFeatureEvaluationsResponse.builder().featureEvaluations(
listOf(
FeatureEvaluation.builder()
.feature(CodeWhispererFeatureConfigService.CUSTOMIZATION_ARN_OVERRIDE_NAME)
.feature("customizationArnOverride")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the other feature consts were made private so moved this to private which required the hardcoded value

Copy link
Contributor

@Will-ShaoHua Will-ShaoHua Dec 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i feel it's not necessary to make the experiemtn names private tho, we should be good as long as they are const.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's easier to restructure and refactor featureConfigService to simply use enum as experiment, but it's not blocker for the PR

.variation("customization-name")
.value(FeatureValue.fromStringValue("test arn"))
.build()
Expand Down
6 changes: 4 additions & 2 deletions plugins/amazonq/mynah-ui/src/mynah-ui/ui/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
MynahUI,
MynahUIDataModel,
NotificationType,
ProgressField,
ProgressField, QuickActionCommand,
ReferenceTrackerInformation
} from '@aws/mynah-ui-chat'
import './styles/dark.scss'
Expand Down Expand Up @@ -40,7 +40,8 @@ export const createMynahUI = (
codeTransformInitEnabled: boolean,
docInitEnabled: boolean,
codeScanEnabled: boolean,
codeTestEnabled: boolean
codeTestEnabled: boolean,
highlightCommand?: QuickActionCommand,
) => {
let disclaimerCardActive = !disclaimerAcknowledged

Expand Down Expand Up @@ -87,6 +88,7 @@ export const createMynahUI = (
isDocEnabled,
isCodeScanEnabled,
isCodeTestEnabled,
highlightCommand
})

// eslint-disable-next-line prefer-const
Expand Down
26 changes: 24 additions & 2 deletions plugins/amazonq/mynah-ui/src/mynah-ui/ui/tabs/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { ChatItemType, MynahUIDataModel, QuickActionCommandGroup } from '@aws/mynah-ui-chat'
import { ChatItemType, MynahUIDataModel, QuickActionCommandGroup, QuickActionCommand } from '@aws/mynah-ui-chat'
import { TabType } from '../storages/tabsStorage'
import { FollowUpGenerator } from '../followUps/generator'
import { QuickActionGenerator } from '../quickActions/generator'
Expand All @@ -15,11 +15,13 @@ export interface TabDataGeneratorProps {
isDocEnabled: boolean
isCodeScanEnabled: boolean
isCodeTestEnabled: boolean
highlightCommand?: QuickActionCommand
}

export class TabDataGenerator {
private followUpsGenerator: FollowUpGenerator
public quickActionsGenerator: QuickActionGenerator
private highlightCommand?: QuickActionCommand

private tabTitle: Map<TabType, string> = new Map([
['unknown', 'Chat'],
Expand Down Expand Up @@ -88,6 +90,7 @@ What would you like to work on?`,
isCodeScanEnabled: props.isCodeScanEnabled,
isCodeTestEnabled: props.isCodeTestEnabled,
})
this.highlightCommand = props.highlightCommand
}

public getTabData(tabType: TabType, needWelcomeMessages: boolean, taskName?: string): MynahUIDataModel {
Expand All @@ -97,7 +100,7 @@ What would you like to work on?`,
'Amazon Q Developer uses generative AI. You may need to verify responses. See the [AWS Responsible AI Policy](https://aws.amazon.com/machine-learning/responsible-ai/policy/).',
quickActionCommands: this.quickActionsGenerator.generateForTab(tabType),
promptInputPlaceholder: this.tabInputPlaceholder.get(tabType),
contextCommands: this.tabContextCommand.get(tabType),
contextCommands: this.getContextCommands(tabType),
chatItems: needWelcomeMessages
? [
{
Expand All @@ -112,4 +115,23 @@ What would you like to work on?`,
: [],
}
}

private getContextCommands(tabType: TabType): QuickActionCommandGroup[] | undefined {
const contextCommands = this.tabContextCommand.get(tabType)

if (this.highlightCommand) {
const commandHighlight: QuickActionCommandGroup = {
groupName: 'Additional Commands',
commands: [this.highlightCommand],
}

if (contextCommands !== undefined) {
return [...contextCommands, commandHighlight]
}

return [commandHighlight]
}

return contextCommands
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ class CodeWhispererFeatureConfigService {

fun getCustomizationFeature(): FeatureContext? = getFeature(CUSTOMIZATION_ARN_OVERRIDE_NAME)

fun getHighlightCommandFeature(): FeatureContext? = getFeature(HIGHLIGHT_COMMAND_NAME)

fun getNewAutoTriggerUX(): Boolean = getFeatureValueForKey(NEW_AUTO_TRIGGER_UX).stringValue() == "TREATMENT"

fun getInlineCompletion(): Boolean = getFeatureValueForKey(INLINE_COMPLETION).stringValue() == "TREATMENT"
Expand All @@ -131,7 +133,8 @@ class CodeWhispererFeatureConfigService {
fun getInstance(): CodeWhispererFeatureConfigService = service()
private const val TEST_FEATURE_NAME = "testFeature"
private const val INLINE_COMPLETION = "ProjectContextV2"
const val CUSTOMIZATION_ARN_OVERRIDE_NAME = "customizationArnOverride"
private const val CUSTOMIZATION_ARN_OVERRIDE_NAME = "customizationArnOverride"
private const val HIGHLIGHT_COMMAND_NAME = "highlightCommand"
private const val NEW_AUTO_TRIGGER_UX = "newAutoTriggerUX"
private val LOG = getLogger<CodeWhispererFeatureConfigService>()

Expand Down
Loading