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
19 changes: 8 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,31 +368,28 @@ The `aws.dev.forceDevMode` setting enables or disables Toolkit "dev mode". Witho
```
tail -F ~/awstoolkit.log
```
- Use the `AWS (Developer): Watch Logs` command to watch and filter Toolkit logs (including
telemetry) in VSCode.
- Only available if you enabled "dev mode" (`aws.dev.forceDevMode` setting, see above).
- Enter text in the Debug Console filter box to show only log messages with that text. <br/>
<img src="./docs/images/debug-console-filter.png" alt="VSCode Debug Console" width="320"/>
- Use the Output panel to watch and filter Toolkit logs (including telemetry) in VSCode.
- Enter text in the Output panel filter box to show only log messages with that text.
#### Enabling Debug Logs
How to enable more detailed debug logs in the extensions.
If you need to report an issue attach these to give the most detailed information.
1. Open the Command Palette (`cmd/ctrl` + `shift` + `p`), then search for "View Logs". Choose the correct option for the extension you want, eg: `AWS: View Logs` or `Amazon Q: View Logs`
![](./docs/images/logsView.png)
1. Open the Command Palette (`cmd/ctrl` + `shift` + `p`), then search for "View Logs". Choose either `AWS: View Logs` or `Amazon Q: View Logs`.
- ![](./docs/images/logsView.png)
2. Click the gear icon on the bottom right and select `Debug`
![](./docs/images/logsSetDebug.png)
- ![](./docs/images/logsSetDebug.png)
3. Click the gear icon again and select `Set As Default`. This will ensure we stay in `Debug` until explicitly changed
![](./docs/images/logsSetDefault.png)
- ![](./docs/images/logsSetDefault.png)
4. Open the Command Palette again and select `Reload Window`.
5. Now you should see additional `[debug]` prefixed logs in the output.
![](./docs/images/logsDebugLog.png)
- ![](./docs/images/logsDebugLog.png)
### Telemetry
- See [docs/telemetry.md](./docs/telemetry.md) for guidelines on developing telemetry in this project.
- To watch Toolkit telemetry events, use the `AWS (Developer): Watch Logs` command (see [Logging](#logging) above) and enter "telemetry" in the Debug Console filter box.
- To watch Toolkit telemetry events, use the `Amazon Q: View Logs` command (see [Logging](#logging) above) and enter "telemetry" in the filter box.
### Service Endpoints
Expand Down
Binary file removed docs/images/debug-console-filter.png
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "Bug Fix",
"description": "`Send to prompt` and other context menu options not sent if chat was closed"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "Feature",
"description": "Amazon Q /dev: support `.hbs`, `.gjs`, `.gts`, `.astro`, `.mdx`, `.svelte`, `.erb`, `.rake` files"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "Feature",
"description": "/transform: automatically download results when ready"
}
179 changes: 179 additions & 0 deletions packages/amazonq/test/e2e/inline/inline.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import * as vscode from 'vscode'
import assert from 'assert'
import {
assertTelemetry,
closeAllEditors,
getTestWindow,
registerAuthHook,
resetCodeWhispererGlobalVariables,
TestFolder,
toTextEditor,
using,
} from 'aws-core-vscode/test'
import { RecommendationHandler, RecommendationService } from 'aws-core-vscode/codewhisperer'
import { Commands, globals, sleep, waitUntil } from 'aws-core-vscode/shared'
import { loginToIdC } from '../amazonq/utils/setup'

describe('Amazon Q Inline', async function () {
let tempFolder: string
const waitOptions = {
interval: 500,
timeout: 10000,
retryOnFail: false,
}

before(async function () {
await using(registerAuthHook('amazonq-test-account'), async () => {
await loginToIdC()
})
})

beforeEach(async function () {
registerAuthHook('amazonq-test-account')
const folder = await TestFolder.create()
tempFolder = folder.path
await closeAllEditors()
await resetCodeWhispererGlobalVariables(false)
})

afterEach(async function () {
await closeAllEditors()
})

async function setupEditor({ name, contents }: { name?: string; contents?: string } = {}) {
const fileName = name ?? 'test.ts'
const textContents =
contents ??
`function fib() {
}`
await toTextEditor(textContents, fileName, tempFolder, {
selection: new vscode.Range(new vscode.Position(1, 4), new vscode.Position(1, 4)),
})
}

async function waitForRecommendations() {
const ok = await waitUntil(async () => RecommendationHandler.instance.isSuggestionVisible(), waitOptions)
if (!ok) {
assert.fail('Suggestions failed to become visible')
}
}

async function waitForTelemetry() {
const ok = await waitUntil(
async () =>
globals.telemetry.logger.query({
metricName: 'codewhisperer_userTriggerDecision',
}).length > 0,
waitOptions
)
if (!ok) {
assert.fail('Telemetry failed to be emitted')
}
}

for (const [name, invokeCompletion] of [
['automatic', async () => await vscode.commands.executeCommand('type', { text: '\n' })],
['manual', async () => Commands.tryExecute('aws.amazonq.invokeInlineCompletion')],
] as const) {
describe(`${name} invoke`, async function () {
let originalEditorContents: string | undefined

describe('supported filetypes', () => {
beforeEach(async () => {
await setupEditor()

/**
* Allow some time between when the editor is opened and when we start typing.
* If we don't do this then the time between the initial editor selection
* and invoking the "type" command is too low, causing completion to never
* activate. AFAICT there isn't anything we can use waitUntil on here.
*
* note: this number is entirely arbitrary
**/
await sleep(1000)

await invokeCompletion()
originalEditorContents = vscode.window.activeTextEditor?.document.getText()

// wait until the ghost text appears
await waitForRecommendations()
})

it(`${name} invoke accept`, async function () {
/**
* keep accepting the suggestion until the text contents change
* this is required because we have no access to the inlineSuggest panel
**/
const suggestionAccepted = await waitUntil(async () => {
// Accept the suggestion
await vscode.commands.executeCommand('editor.action.inlineSuggest.commit')
return vscode.window.activeTextEditor?.document.getText() !== originalEditorContents
}, waitOptions)

assert.ok(suggestionAccepted, 'Editor contents should have changed')

await waitForTelemetry()
assertTelemetry('codewhisperer_userTriggerDecision', {
codewhispererSuggestionState: 'Accept',
})
})

it(`${name} invoke reject`, async function () {
// Reject the suggestion
await vscode.commands.executeCommand('aws.amazonq.rejectCodeSuggestion')

// Contents haven't changed
assert.deepStrictEqual(vscode.window.activeTextEditor?.document.getText(), originalEditorContents)

await waitForTelemetry()
assertTelemetry('codewhisperer_userTriggerDecision', {
codewhispererSuggestionState: 'Reject',
})
})

it(`${name} invoke discard`, async function () {
// Discard the suggestion by moving it back to the original position
const position = new vscode.Position(1, 4)
const editor = vscode.window.activeTextEditor
if (!editor) {
assert.fail('Could not find text editor')
}
editor.selection = new vscode.Selection(position, position)

// Contents are the same
assert.deepStrictEqual(vscode.window.activeTextEditor?.document.getText(), originalEditorContents)
})
})

it(`${name} invoke on unsupported filetype`, async function () {
await setupEditor({
name: 'test.zig',
contents: `fn doSomething() void {
}`,
})

/**
* Add delay between editor loading and invoking completion
* @see beforeEach in supported filetypes for more information
*/
await sleep(1000)
await invokeCompletion()

if (name === 'automatic') {
// It should never get triggered since its not a supported file type
assert.deepStrictEqual(RecommendationService.instance.isRunning, false)
} else {
await getTestWindow().waitForMessage('currently not supported by Amazon Q inline suggestions')
}
})
})
}
})
8 changes: 4 additions & 4 deletions packages/core/src/amazonq/apps/initContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@
*/

import { EventEmitter } from 'vscode'
import { MessagePublisher } from '../messages/messagePublisher'
import { MessagePublisher, UiMessagePublisher } from '../messages/messagePublisher'
import { MessageListener } from '../messages/messageListener'
import { TabType } from '../webview/ui/storages/tabsStorage'

export interface AmazonQAppInitContext {
registerWebViewToAppMessagePublisher(eventEmitter: MessagePublisher<any>, tabType: TabType): void
getAppsToWebViewMessagePublisher(): MessagePublisher<any>
getAppsToWebViewMessagePublisher(): UiMessagePublisher<any>
onDidChangeAmazonQVisibility: EventEmitter<boolean>
}

export class DefaultAmazonQAppInitContext implements AmazonQAppInitContext {
private readonly appsToWebViewEventEmitter = new EventEmitter<any>()
private readonly appsToWebViewMessageListener = new MessageListener<any>(this.appsToWebViewEventEmitter)
private readonly appsToWebViewMessagePublisher = new MessagePublisher<any>(this.appsToWebViewEventEmitter)
private readonly appsToWebViewMessagePublisher = new UiMessagePublisher<any>(this.appsToWebViewEventEmitter)
private readonly webViewToAppsMessagePublishers: Map<TabType, MessagePublisher<any>> = new Map()
public readonly onDidChangeAmazonQVisibility = new EventEmitter<boolean>()

Expand All @@ -41,7 +41,7 @@ export class DefaultAmazonQAppInitContext implements AmazonQAppInitContext {
return this.appsToWebViewMessageListener
}

getAppsToWebViewMessagePublisher(): MessagePublisher<any> {
getAppsToWebViewMessagePublisher(): UiMessagePublisher<any> {
return this.appsToWebViewMessagePublisher
}
}
45 changes: 45 additions & 0 deletions packages/core/src/amazonq/messages/messagePublisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,48 @@ export class MessagePublisher<T> {
this.eventEmitter.fire(event)
}
}

/**
* Same as {@link MessagePublisher}, but will wait until the UI indicates it
* is ready to recieve messages, before the message is published.
*
* This solves a problem when running a right click menu option like
* "Send To Prompt" BUT chat is not opened yet, it would result in the prompt failing to
* be recieved by chat.
*/
export class UiMessagePublisher<T> extends MessagePublisher<T> {
private isUiReady: boolean = false
private buffer: T[] = []

constructor(eventEmitter: EventEmitter<T>) {
super(eventEmitter)
}

public override publish(event: T): void {
// immediately send if Chat UI is ready
if (this.isUiReady) {
super.publish(event)
return
}

this.buffer.push(event)
}

/**
* Indicate the Q Chat UI is ready to recieve messages.
*/
public setUiReady() {
this.isUiReady = true
this.flush()
}

/**
* Publishes all blocked messages
*/
private flush() {
for (const msg of this.buffer) {
super.publish(msg)
}
this.buffer = []
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { telemetry } from '../../../shared/telemetry'
import { AmazonQChatMessageDuration } from '../../messages/chatMessageDuration'
import { globals, openUrl } from '../../../shared'
import { isClickTelemetry, isOpenAgentTelemetry } from '../ui/telemetry/actions'
import { DefaultAmazonQAppInitContext } from '../../apps/initContext'

export function dispatchWebViewMessagesToApps(
webview: Webview,
Expand All @@ -21,12 +22,12 @@ export function dispatchWebViewMessagesToApps(
webview.onDidReceiveMessage((msg) => {
switch (msg.command) {
case 'ui-is-ready': {
DefaultAmazonQAppInitContext.instance.getAppsToWebViewMessagePublisher().setUiReady()
/**
* ui-is-ready isn't associated to any tab so just record the telemetry event and continue.
* This would be equivalent of the duration between "user clicked open q" and "ui has become available"
* NOTE: Amazon Q UI is only loaded ONCE. The state is saved between each hide/show of the webview.
*/

telemetry.webview_load.emit({
webviewName: 'amazonq',
duration: performance.measure(amazonqMark.uiReady, amazonqMark.open).duration,
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/codewhisperer/commands/startTransformByQ.ts
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,12 @@ export async function postTransformationJob() {
if (transformByQState.getPayloadFilePath() !== '') {
fs.rmSync(transformByQState.getPayloadFilePath(), { recursive: true, force: true }) // delete ZIP if it exists
}

// attempt download for user
// TODO: refactor as explained here https://github.com/aws/aws-toolkit-vscode/pull/6519/files#r1946873107
if (transformByQState.isSucceeded() || transformByQState.isPartiallySucceeded()) {
await vscode.commands.executeCommand('aws.amazonq.transformationHub.reviewChanges.startReview')
}
}

export async function transformationJobErrorHandler(error: any) {
Expand Down
14 changes: 0 additions & 14 deletions packages/core/src/dev/activation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,20 +199,6 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
.filter((e) => (opts.menuOptions ?? Object.keys(options)).includes(e[0]))
.map((e) => e[1])
)
}),
// "AWS (Developer): Watch Logs"
Commands.register('aws.dev.viewLogs', async () => {
// HACK: Use startDebugging() so we can use the DEBUG CONSOLE (which supports
// user-defined filtering, unlike the OUTPUT panel).
await vscode.debug.startDebugging(undefined, {
name: 'aws-dev-log',
request: 'launch',
type: 'node', // Nonsense, to force the debugger to start.
})
getLogger().enableDebugConsole()
if (!getLogger().logLevelEnabled('debug')) {
getLogger().setLogLevel('debug')
}
})
)

Expand Down
Loading