Skip to content

Commit e00b3b0

Browse files
authored
Merge branch 'feature/auto-debug' into feature/auto-debug
2 parents 0b93c2c + dbb1cc5 commit e00b3b0

File tree

21 files changed

+1132
-277
lines changed

21 files changed

+1132
-277
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "/transform: Show transformation history in Transformation Hub and allow users to resume jobs"
4+
}

packages/amazonq/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,7 @@
746746
},
747747
{
748748
"command": "aws.amazonq.showHistoryInHub",
749-
"title": "%AWS.command.q.transform.viewJobStatus%"
749+
"title": "%AWS.command.q.transform.viewJobHistory%"
750750
},
751751
{
752752
"command": "aws.amazonq.selectCustomization",

packages/amazonq/src/app/inline/completion.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,14 +290,16 @@ export class AmazonQInlineCompletionItemProvider implements InlineCompletionItem
290290
const prevSessionId = prevSession?.sessionId
291291
const prevItemId = this.sessionManager.getActiveRecommendation()?.[0]?.itemId
292292
const prevStartPosition = prevSession?.startPosition
293-
if (prevSession?.triggerOnAcceptance) {
293+
const editsTriggerOnAcceptance = prevSession?.triggerOnAcceptance
294+
if (editsTriggerOnAcceptance) {
294295
getAllRecommendationsOptions = {
295296
...getAllRecommendationsOptions,
296297
editsStreakToken: prevSession?.editsStreakPartialResultToken,
297298
}
298299
}
299300
const editor = window.activeTextEditor
300-
if (prevSession && prevSessionId && prevItemId && prevStartPosition) {
301+
// Skip prefix matching for Edits suggestions that trigger on acceptance.
302+
if (prevSession && prevSessionId && prevItemId && prevStartPosition && !editsTriggerOnAcceptance) {
301303
const prefix = document.getText(new Range(prevStartPosition, position))
302304
const prevItemMatchingPrefix = []
303305
for (const item of this.sessionManager.getActiveRecommendation()) {

packages/amazonq/src/lsp/client.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import vscode, { version } from 'vscode'
6+
import vscode, { env, version } from 'vscode'
77
import * as nls from 'vscode-nls'
88
import { LanguageClient, LanguageClientOptions, RequestType, State } from 'vscode-languageclient'
99
import { InlineCompletionManager } from '../app/inline/completion'
@@ -38,7 +38,6 @@ import {
3838
getOptOutPreference,
3939
isAmazonLinux2,
4040
getClientId,
41-
getClientName,
4241
extensionVersion,
4342
isSageMaker,
4443
DevSettings,
@@ -164,7 +163,7 @@ export async function startLanguageServer(
164163
initializationOptions: {
165164
aws: {
166165
clientInfo: {
167-
name: getClientName(),
166+
name: env.appName,
168167
version: version,
169168
extension: {
170169
name: 'AmazonQ-For-VSCode',

packages/core/package.nls.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@
281281
"AWS.command.q.transform.rejectChanges": "Reject",
282282
"AWS.command.q.transform.stopJobInHub": "Stop job",
283283
"AWS.command.q.transform.viewJobProgress": "View job progress",
284-
"AWS.command.q.transform.viewJobStatus": "View job status",
284+
"AWS.command.q.transform.viewJobHistory": "View job history",
285285
"AWS.command.q.transform.showTransformationPlan": "View plan",
286286
"AWS.command.q.transform.showChangeSummary": "View summary",
287287
"AWS.command.threatComposer.createNew": "Create New Threat Composer File",

packages/core/src/amazonqGumby/activation.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { setContext } from '../shared/vscode/setContext'
2121
export async function activate(context: ExtContext) {
2222
void setContext('gumby.wasQCodeTransformationUsed', false)
2323

24-
const transformationHubViewProvider = new TransformationHubViewProvider()
24+
const transformationHubViewProvider = TransformationHubViewProvider.instance
2525
new ProposedTransformationExplorer(context.extensionContext)
2626
// Register an activation event listener to determine when the IDE opens, closes or users
2727
// select to open a new workspace
@@ -72,6 +72,13 @@ export async function activate(context: ExtContext) {
7272
)
7373
}),
7474

75+
Commands.register(
76+
'aws.amazonq.transformationHub.updateContent',
77+
async (button, startTime, historyFileUpdated) => {
78+
await transformationHubViewProvider.updateContent(button, startTime, historyFileUpdated)
79+
}
80+
),
81+
7582
workspaceChangeEvent
7683
)
7784
}

packages/core/src/amazonqGumby/chat/controller/controller.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ import {
5757
} from '../../../codewhisperer/service/transformByQ/transformFileHandler'
5858
import { getAuthType } from '../../../auth/utils'
5959
import fs from '../../../shared/fs/fs'
60+
import { setContext } from '../../../shared/vscode/setContext'
61+
import { readHistoryFile } from '../../../codewhisperer/service/transformByQ/transformationHubViewProvider'
6062

6163
// These events can be interactions within the chat,
6264
// or elsewhere in the IDE
@@ -188,6 +190,15 @@ export class GumbyController {
188190
}
189191

190192
private async transformInitiated(message: any) {
193+
// check if any jobs potentially still in progress on backend
194+
const history = await readHistoryFile()
195+
const numInProgress = history.filter((job) => job.status === 'FAILED').length
196+
this.messenger.sendViewHistoryMessage(message.tabID, numInProgress)
197+
if (transformByQState.isRefreshInProgress()) {
198+
this.messenger.sendMessage(CodeWhispererConstants.refreshInProgressChatMessage, message.tabID, 'ai-prompt')
199+
return
200+
}
201+
191202
// silently check for projects eligible for SQL conversion
192203
let embeddedSQLProjects: TransformationCandidateProject[] = []
193204
try {
@@ -383,6 +394,11 @@ export class GumbyController {
383394
case ButtonActions.VIEW_TRANSFORMATION_HUB:
384395
await vscode.commands.executeCommand(GumbyCommands.FOCUS_TRANSFORMATION_HUB, CancelActionPositions.Chat)
385396
break
397+
case ButtonActions.VIEW_JOB_HISTORY:
398+
await setContext('gumby.wasQCodeTransformationUsed', true)
399+
await vscode.commands.executeCommand(GumbyCommands.FOCUS_TRANSFORMATION_HUB)
400+
await vscode.commands.executeCommand(GumbyCommands.FOCUS_JOB_HISTORY, CancelActionPositions.Chat)
401+
break
386402
case ButtonActions.VIEW_SUMMARY:
387403
await vscode.commands.executeCommand('aws.amazonq.transformationHub.summary.reveal')
388404
break
@@ -452,6 +468,10 @@ export class GumbyController {
452468
}
453469

454470
private async handleUserLanguageUpgradeProjectChoice(message: any) {
471+
if (transformByQState.isRefreshInProgress()) {
472+
this.messenger.sendMessage(CodeWhispererConstants.refreshInProgressChatMessage, message.tabID, 'ai-prompt')
473+
return
474+
}
455475
await telemetry.codeTransform_submitSelection.run(async () => {
456476
const pathToProject: string = message.formSelectedValues['GumbyTransformLanguageUpgradeProjectForm']
457477
const toJDKVersion: JDKVersion = message.formSelectedValues['GumbyTransformJdkToForm']
@@ -484,6 +504,10 @@ export class GumbyController {
484504
}
485505

486506
private async handleUserSQLConversionProjectSelection(message: any) {
507+
if (transformByQState.isRefreshInProgress()) {
508+
this.messenger.sendMessage(CodeWhispererConstants.refreshInProgressChatMessage, message.tabID, 'ai-prompt')
509+
return
510+
}
487511
await telemetry.codeTransform_submitSelection.run(async () => {
488512
const pathToProject: string = message.formSelectedValues['GumbyTransformSQLConversionProjectForm']
489513
const schema: string = message.formSelectedValues['GumbyTransformSQLSchemaForm']

packages/core/src/amazonqGumby/chat/controller/messenger/messenger.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,38 @@ export class Messenger {
377377
this.dispatcher.sendChatMessage(jobSubmittedMessage)
378378
}
379379

380+
public sendViewHistoryMessage(tabID: string, numInProgress: number) {
381+
const buttons: ChatItemButton[] = []
382+
383+
buttons.push({
384+
keepCardAfterClick: true,
385+
text: CodeWhispererConstants.jobHistoryButtonText,
386+
id: ButtonActions.VIEW_JOB_HISTORY,
387+
disabled: false,
388+
})
389+
390+
const messageText = CodeWhispererConstants.viewHistoryMessage(numInProgress)
391+
392+
const message = new ChatMessage(
393+
{
394+
message: messageText,
395+
messageType: 'ai-prompt',
396+
buttons,
397+
},
398+
tabID
399+
)
400+
this.dispatcher.sendChatMessage(message)
401+
}
402+
403+
public sendJobRefreshInProgressMessage(tabID: string, jobId: string) {
404+
this.dispatcher.sendAsyncEventProgress(
405+
new AsyncEventProgressMessage(tabID, {
406+
inProgress: true,
407+
message: CodeWhispererConstants.refreshingJobChatMessage(jobId),
408+
})
409+
)
410+
}
411+
380412
public sendMessage(prompt: string, tabID: string, type: 'prompt' | 'ai-prompt') {
381413
this.dispatcher.sendChatMessage(
382414
new ChatMessage(

packages/core/src/amazonqGumby/chat/controller/messenger/messengerUtils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import DependencyVersions from '../../../models/dependencies'
1313
export enum ButtonActions {
1414
STOP_TRANSFORMATION_JOB = 'gumbyStopTransformationJob',
1515
VIEW_TRANSFORMATION_HUB = 'gumbyViewTransformationHub',
16+
VIEW_JOB_HISTORY = 'gumbyViewJobHistory',
1617
VIEW_SUMMARY = 'gumbyViewSummary',
1718
CONFIRM_LANGUAGE_UPGRADE_TRANSFORMATION_FORM = 'gumbyLanguageUpgradeTransformFormConfirm',
1819
CONFIRM_SQL_CONVERSION_TRANSFORMATION_FORM = 'gumbySQLConversionTransformFormConfirm',
@@ -33,6 +34,7 @@ export enum GumbyCommands {
3334
CLEAR_CHAT = 'aws.awsq.clearchat',
3435
START_TRANSFORMATION_FLOW = 'aws.awsq.transform',
3536
FOCUS_TRANSFORMATION_HUB = 'aws.amazonq.showTransformationHub',
37+
FOCUS_JOB_HISTORY = 'aws.amazonq.showHistoryInHub',
3638
}
3739

3840
export default class MessengerUtils {

packages/core/src/codewhisperer/commands/startTransformByQ.ts

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
TransformationType,
2121
TransformationCandidateProject,
2222
RegionProfile,
23+
sessionJobHistory,
2324
} from '../models/model'
2425
import {
2526
createZipManifest,
@@ -474,6 +475,30 @@ export async function startTransformationJob(
474475
codeTransformRunTimeLatency: calculateTotalLatency(transformStartTime),
475476
})
476477
})
478+
479+
// create local history folder(s) and store metadata
480+
const jobHistoryPath = path.join(os.homedir(), '.aws', 'transform', transformByQState.getProjectName(), jobId)
481+
if (!fs.existsSync(jobHistoryPath)) {
482+
fs.mkdirSync(jobHistoryPath, { recursive: true })
483+
}
484+
transformByQState.setJobHistoryPath(jobHistoryPath)
485+
// save a copy of the upload zip
486+
fs.copyFileSync(transformByQState.getPayloadFilePath(), path.join(jobHistoryPath, 'zipped-code.zip'))
487+
488+
const fields = [
489+
jobId,
490+
transformByQState.getTransformationType(),
491+
transformByQState.getSourceJDKVersion(),
492+
transformByQState.getTargetJDKVersion(),
493+
transformByQState.getCustomDependencyVersionFilePath(),
494+
transformByQState.getCustomBuildCommand(),
495+
transformByQState.getTargetJavaHome(),
496+
transformByQState.getProjectPath(),
497+
transformByQState.getStartTime(),
498+
]
499+
500+
const jobDetails = fields.join('\t')
501+
fs.writeFileSync(path.join(jobHistoryPath, 'metadata.txt'), jobDetails)
477502
} catch (error) {
478503
getLogger().error(`CodeTransformation: ${CodeWhispererConstants.failedToStartJobNotification}`, error)
479504
const errorMessage = (error as Error).message.toLowerCase()
@@ -724,9 +749,18 @@ export async function postTransformationJob() {
724749
})
725750
}
726751

727-
if (transformByQState.getPayloadFilePath()) {
728-
// delete original upload ZIP at very end of transformation
729-
fs.rmSync(transformByQState.getPayloadFilePath(), { force: true })
752+
// delete original upload ZIP at very end of transformation
753+
fs.rmSync(transformByQState.getPayloadFilePath(), { force: true })
754+
755+
if (
756+
transformByQState.isSucceeded() ||
757+
transformByQState.isPartiallySucceeded() ||
758+
transformByQState.isCancelled()
759+
) {
760+
// delete the copy of the upload ZIP
761+
fs.rmSync(path.join(transformByQState.getJobHistoryPath(), 'zipped-code.zip'), { force: true })
762+
// delete transformation job metadata file (no longer needed)
763+
fs.rmSync(path.join(transformByQState.getJobHistoryPath(), 'metadata.txt'), { force: true })
730764
}
731765
// delete temporary build logs file
732766
const logFilePath = path.join(os.tmpdir(), 'build-logs.txt')
@@ -739,31 +773,52 @@ export async function postTransformationJob() {
739773
if (transformByQState.isSucceeded() || transformByQState.isPartiallySucceeded()) {
740774
await vscode.commands.executeCommand('aws.amazonq.transformationHub.reviewChanges.startReview')
741775
}
776+
777+
// store job details and diff path locally (history)
778+
// TODO: ideally when job is cancelled, should be stored as CANCELLED instead of FAILED (remove this if statement after bug is fixed)
779+
if (!transformByQState.isCancelled()) {
780+
const historyLogFilePath = path.join(os.homedir(), '.aws', 'transform', 'transformation_history.tsv')
781+
// create transform folder if necessary
782+
if (!fs.existsSync(historyLogFilePath)) {
783+
fs.mkdirSync(path.dirname(historyLogFilePath), { recursive: true })
784+
// create headers of new transformation history file
785+
fs.writeFileSync(historyLogFilePath, 'date\tproject_name\tstatus\tduration\tdiff_patch\tsummary\tjob_id\n')
786+
}
787+
const latest = sessionJobHistory[transformByQState.getJobId()]
788+
const fields = [
789+
latest.startTime,
790+
latest.projectName,
791+
latest.status,
792+
latest.duration,
793+
transformByQState.isSucceeded() || transformByQState.isPartiallySucceeded()
794+
? path.join(transformByQState.getJobHistoryPath(), 'diff.patch')
795+
: '',
796+
transformByQState.isSucceeded() || transformByQState.isPartiallySucceeded()
797+
? path.join(transformByQState.getJobHistoryPath(), 'summary', 'summary.md')
798+
: '',
799+
transformByQState.getJobId(),
800+
]
801+
802+
const jobDetails = fields.join('\t') + '\n'
803+
fs.writeFileSync(historyLogFilePath, jobDetails, { flag: 'a' }) // 'a' flag used to append to file
804+
await vscode.commands.executeCommand(
805+
'aws.amazonq.transformationHub.updateContent',
806+
'job history',
807+
undefined,
808+
true
809+
)
810+
}
742811
}
743812

744813
export async function transformationJobErrorHandler(error: any) {
745814
if (!transformByQState.isCancelled()) {
746815
// means some other error occurred; cancellation already handled by now with stopTransformByQ
747-
await stopJob(transformByQState.getJobId())
748816
transformByQState.setToFailed()
749817
transformByQState.setPolledJobStatus('FAILED')
750818
// jobFailureErrorNotification should always be defined here
751-
const displayedErrorMessage =
752-
transformByQState.getJobFailureErrorNotification() ?? CodeWhispererConstants.failedToCompleteJobNotification
753819
transformByQState.setJobFailureErrorChatMessage(
754820
transformByQState.getJobFailureErrorChatMessage() ?? CodeWhispererConstants.failedToCompleteJobChatMessage
755821
)
756-
void vscode.window
757-
.showErrorMessage(displayedErrorMessage, CodeWhispererConstants.amazonQFeedbackText)
758-
.then((choice) => {
759-
if (choice === CodeWhispererConstants.amazonQFeedbackText) {
760-
void submitFeedback(
761-
placeholder,
762-
CodeWhispererConstants.amazonQFeedbackKey,
763-
getFeedbackCommentData()
764-
)
765-
}
766-
})
767822
} else {
768823
transformByQState.setToCancelled()
769824
transformByQState.setPolledJobStatus('CANCELLED')

0 commit comments

Comments
 (0)