Skip to content

Commit 7dc982c

Browse files
authored
fix(lambda): Updates for Lambda quick edit (#7656)
This PR contains updates for miscellaneous features related to the new Lambda quick edit. ## Handling of empty directories ### Problem Previously, having an empty directory in the Lambda quick edit flow led to errors when trying to view the function code in the explorer or in a workspace. An empty folder could be the result of an error during download. The unintended bug in the code was related to how we were checking for existence of a function saved locally. ### Solution Check if there are any files in the directory before determining if it exists locally. If the directory is empty, we must redownload the code (a Lambda function cannot be empty, so we know it is unintended) ## README updates ### Problem - A couple typographic errors - README was opening as separate tab, not as split pane - README was referencing "cloud deploy icon" rather than showing the actual icon, same with the invoke icon ### Solution - Fix errors - Adjust README open to use `markdown.showPreviewToSide` - Save the icons locally so we can display them in the actual README. ## Quick Edit Flow updates ### Problem - Some non-passive metrics were being emitted on activation - Downloading code on activation lead to race conditions with authentication - Downloading code on activation lead to extra slow load times ### Solution All of this is fixed by moving things around so that there is never any code downloaded during activation. This is beneficial because it keeps us from doing a lot of our main functionality on activation. Previously, opening a function in a workspace, which could be done through a URI handler or from the explorer, would cause the extension to restart. Once the extension restarted, at that point we would download the code and start the watchers. These changes download the code first, and only start the watchers on activation. Because the user doesn't need to be authenticated to start the watchers (only to deploy), this is not an issue for being authenticated. If it's a workspace folder, we're assuming the function exists locally, so that's why we know we've downloaded the code already. The only edge case is if the local code is out of sync with what's in the cloud; in this case, we prompt the user to overwrite their code, but they need to go to the explorer to manually download it to avoid the activation race conditions. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 4028800 commit 7dc982c

File tree

12 files changed

+344
-137
lines changed

12 files changed

+344
-137
lines changed

packages/amazonq/package.json

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,26 +1325,40 @@
13251325
"fontCharacter": "\\f1de"
13261326
}
13271327
},
1328-
"aws-schemas-registry": {
1328+
"aws-sagemaker-code-editor": {
13291329
"description": "AWS Contributed Icon",
13301330
"default": {
13311331
"fontPath": "./resources/fonts/aws-toolkit-icons.woff",
13321332
"fontCharacter": "\\f1df"
13331333
}
13341334
},
1335-
"aws-schemas-schema": {
1335+
"aws-sagemaker-jupyter-lab": {
13361336
"description": "AWS Contributed Icon",
13371337
"default": {
13381338
"fontPath": "./resources/fonts/aws-toolkit-icons.woff",
13391339
"fontCharacter": "\\f1e0"
13401340
}
13411341
},
1342-
"aws-stepfunctions-preview": {
1342+
"aws-schemas-registry": {
13431343
"description": "AWS Contributed Icon",
13441344
"default": {
13451345
"fontPath": "./resources/fonts/aws-toolkit-icons.woff",
13461346
"fontCharacter": "\\f1e1"
13471347
}
1348+
},
1349+
"aws-schemas-schema": {
1350+
"description": "AWS Contributed Icon",
1351+
"default": {
1352+
"fontPath": "./resources/fonts/aws-toolkit-icons.woff",
1353+
"fontCharacter": "\\f1e2"
1354+
}
1355+
},
1356+
"aws-stepfunctions-preview": {
1357+
"description": "AWS Contributed Icon",
1358+
"default": {
1359+
"fontPath": "./resources/fonts/aws-toolkit-icons.woff",
1360+
"fontCharacter": "\\f1e3"
1361+
}
13481362
}
13491363
},
13501364
"walkthroughs": [
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Welcome to Lambda Local Development
1+
# Welcome to Lambda local development
22

3-
Learn how to view your Lambda Function locally, iterate, and deploy changes to the AWS Cloud.
3+
Learn how to view your Lambda function locally, iterate, and deploy changes to the AWS Cloud.
44

55
## Edit your Lambda function
66

@@ -9,11 +9,11 @@ Learn how to view your Lambda Function locally, iterate, and deploy changes to t
99

1010
## Manage your Lambda functions
1111

12-
- Select the AWS icon in the left sidebar and select **EXPLORER**
12+
- Select the AWS Toolkit icon in the left sidebar and select **EXPLORER**
1313
- In your desired region, select the Lambda dropdown menu:
14-
- To save and deploy a previously edited Lambda function, select the cloud deploy icon next to your Lambda function.
15-
- To remotely invoke a function, select the play icon next to your Lambda function.
14+
- To save and deploy a previously edited Lambda function, select the ![deploy](./deploy.svg) icon next to your Lambda function.
15+
- To remotely invoke a function, select the ![invoke](./invoke.svg) icon next to your Lambda function.
1616

17-
## Advanced Features
17+
## Advanced features
1818

1919
- To convert to a Lambda function to an AWS SAM application, select the ![createStack](./create-stack.svg) icon next to your Lambda function. For details on what AWS SAM is and how it can help you, see the [AWS Serverless Application Model Developer Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html).

packages/core/src/lambda/activation.ts

Lines changed: 110 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { ExtContext } from '../shared/extensions'
1717
import { invokeRemoteLambda } from './vue/remoteInvoke/invokeLambda'
1818
import { registerSamDebugInvokeVueCommand, registerSamInvokeVueCommand } from './vue/configEditor/samInvokeBackend'
1919
import { Commands } from '../shared/vscode/commands2'
20-
import { DefaultLambdaClient, getFunctionWithCredentials } from '../shared/clients/lambdaClient'
20+
import { DefaultLambdaClient } from '../shared/clients/lambdaClient'
2121
import { copyLambdaUrl } from './commands/copyLambdaUrl'
2222
import { ResourceNode } from '../awsService/appBuilder/explorer/nodes/resourceNode'
2323
import { isTreeNode, TreeNode } from '../shared/treeview/resourceTreeDataProvider'
@@ -29,47 +29,125 @@ import { ToolkitError, isError } from '../shared/errors'
2929
import { LogStreamFilterResponse } from '../awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu'
3030
import { tempDirPath } from '../shared/filesystemUtilities'
3131
import fs from '../shared/fs/fs'
32-
import { deployFromTemp, editLambda, getReadme, openLambdaFolderForEdit } from './commands/editLambda'
33-
import { getTempLocation } from './utils'
32+
import {
33+
confirmOutdatedChanges,
34+
deleteFilesInFolder,
35+
deployFromTemp,
36+
getReadme,
37+
openLambdaFolderForEdit,
38+
watchForUpdates,
39+
} from './commands/editLambda'
40+
import { compareCodeSha, getFunctionInfo, getTempLocation, setFunctionInfo } from './utils'
3441
import { registerLambdaUriHandler } from './uriHandlers'
42+
import globals from '../shared/extensionGlobals'
3543

3644
const localize = nls.loadMessageBundle()
3745

38-
/**
39-
* Activates Lambda components.
40-
*/
41-
export async function activate(context: ExtContext): Promise<void> {
42-
try {
43-
if (vscode.workspace.workspaceFolders) {
44-
for (const workspaceFolder of vscode.workspace.workspaceFolders) {
45-
// Making the comparison case insensitive because Windows can have `C\` or `c\`
46-
const workspacePath = workspaceFolder.uri.fsPath.toLowerCase()
47-
const tempPath = path.join(tempDirPath, 'lambda').toLowerCase()
48-
if (workspacePath.startsWith(tempPath)) {
49-
const name = path.basename(workspaceFolder.uri.fsPath)
50-
const region = path.basename(path.dirname(workspaceFolder.uri.fsPath))
51-
const getFunctionOutput = await getFunctionWithCredentials(region, name)
52-
const configuration = getFunctionOutput.Configuration
53-
await editLambda(
54-
{
55-
name,
56-
region,
57-
// Configuration as any due to the difference in types between sdkV2 and sdkV3
58-
configuration: configuration as any,
59-
},
60-
true
46+
async function openReadme() {
47+
const readmeUri = vscode.Uri.file(await getReadme())
48+
// We only want to do it if there's not a readme already
49+
const isPreviewOpen = vscode.window.tabGroups.all.some((group) =>
50+
group.tabs.some((tab) => tab.label.includes('README'))
51+
)
52+
if (!isPreviewOpen) {
53+
await vscode.commands.executeCommand('markdown.showPreviewToSide', readmeUri)
54+
}
55+
}
56+
57+
async function quickEditActivation() {
58+
if (vscode.workspace.workspaceFolders) {
59+
for (const workspaceFolder of vscode.workspace.workspaceFolders) {
60+
// Making the comparison case insensitive because Windows can have `C\` or `c\`
61+
const workspacePath = workspaceFolder.uri.fsPath.toLowerCase()
62+
const tempPath = path.join(tempDirPath, 'lambda').toLowerCase()
63+
if (workspacePath.includes(tempPath)) {
64+
const name = path.basename(workspaceFolder.uri.fsPath)
65+
const region = path.basename(path.dirname(workspaceFolder.uri.fsPath))
66+
67+
const lambda = { name, region, configuration: undefined }
68+
69+
watchForUpdates(lambda, vscode.Uri.file(workspacePath))
70+
71+
await openReadme()
72+
73+
// Open handler function
74+
try {
75+
const handler = await getFunctionInfo(lambda, 'handlerFile')
76+
const lambdaLocation = path.join(workspacePath, handler)
77+
await openLambdaFile(lambdaLocation, vscode.ViewColumn.One)
78+
} catch (e) {
79+
void vscode.window.showWarningMessage(
80+
localize('AWS.lambda.openFile.failure', `Failed to determine handler location: ${e}`)
6181
)
82+
}
6283

63-
const readmeUri = vscode.Uri.file(await getReadme())
64-
await vscode.commands.executeCommand('markdown.showPreview', readmeUri, vscode.ViewColumn.Two)
84+
// Check if there are changes that need overwritten
85+
try {
86+
// Checking if there are changes that need to be overwritten
87+
const prompt = localize(
88+
'AWS.lambda.download.confirmOutdatedSync',
89+
'There are changes to your function in the cloud since you last edited locally, do you want to overwrite your local changes?'
90+
)
91+
92+
// Adding delay to give the authentication time to catch up
93+
await new Promise((resolve) => globals.clock.setTimeout(resolve, 1000))
94+
95+
const overwriteChanges = !(await compareCodeSha(lambda))
96+
? await confirmOutdatedChanges(prompt)
97+
: false
98+
if (overwriteChanges) {
99+
// Close all open tabs from this workspace
100+
const workspaceUri = vscode.Uri.file(workspacePath)
101+
for (const tabGroup of vscode.window.tabGroups.all) {
102+
const tabsToClose = tabGroup.tabs.filter(
103+
(tab) =>
104+
tab.input instanceof vscode.TabInputText &&
105+
tab.input.uri.fsPath.startsWith(workspaceUri.fsPath)
106+
)
107+
if (tabsToClose.length > 0) {
108+
await vscode.window.tabGroups.close(tabsToClose)
109+
}
110+
}
111+
112+
// Delete all files in the directory
113+
await deleteFilesInFolder(workspacePath)
114+
115+
// Show message to user about next steps
116+
void vscode.window.showInformationMessage(
117+
localize(
118+
'AWS.lambda.refresh.complete',
119+
'Local workspace cleared. Navigate to the Toolkit explorer to get fresh code from the cloud.'
120+
)
121+
)
122+
123+
await setFunctionInfo(lambda, { undeployed: false })
124+
125+
// Remove workspace folder
126+
const workspaceIndex = vscode.workspace.workspaceFolders?.findIndex(
127+
(folder) => folder.uri.fsPath.toLowerCase() === workspacePath
128+
)
129+
if (workspaceIndex !== undefined && workspaceIndex >= 0) {
130+
vscode.workspace.updateWorkspaceFolders(workspaceIndex, 1)
131+
}
132+
}
133+
} catch (e) {
134+
void vscode.window.showWarningMessage(
135+
localize(
136+
'AWS.lambda.pull.failure',
137+
`Failed to pull latest changes from the cloud, you can still edit locally: ${e}`
138+
)
139+
)
65140
}
66141
}
67142
}
68-
} catch (e) {
69-
void vscode.window.showWarningMessage(
70-
localize('AWS.lambda.open.failure', `Unable to edit Lambda Function locally: ${e}`)
71-
)
72143
}
144+
}
145+
146+
/**
147+
* Activates Lambda components.
148+
*/
149+
export async function activate(context: ExtContext): Promise<void> {
150+
void quickEditActivation()
73151

74152
context.extensionContext.subscriptions.push(
75153
Commands.register('aws.deleteLambda', async (node: LambdaFunctionNode | TreeNode) => {

packages/core/src/lambda/commands/downloadLambda.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ async function downloadAndUnzipLambda(
194194
}
195195
}
196196

197-
export async function openLambdaFile(lambdaLocation: string): Promise<void> {
197+
export async function openLambdaFile(lambdaLocation: string, viewColumn?: vscode.ViewColumn): Promise<void> {
198198
if (!(await fs.exists(lambdaLocation))) {
199199
const warning = localize(
200200
'AWS.lambda.download.fileNotFound',
@@ -206,7 +206,7 @@ export async function openLambdaFile(lambdaLocation: string): Promise<void> {
206206
throw new Error()
207207
}
208208
const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(lambdaLocation))
209-
await vscode.window.showTextDocument(doc)
209+
await vscode.window.showTextDocument(doc, viewColumn)
210210
}
211211

212212
async function addLaunchConfigEntry(

0 commit comments

Comments
 (0)