Skip to content

Commit 9c40f4c

Browse files
author
Vlad Nikolaenko
committed
feat(stepfunctions): handle launching with invalid JSON file, add invalid JSON metric
1 parent f48080f commit 9c40f4c

File tree

9 files changed

+101
-35
lines changed

9 files changed

+101
-35
lines changed

packages/core/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3653,6 +3653,12 @@
36533653
"key": "ctrl+shift+v",
36543654
"mac": "cmd+shift+v",
36553655
"when": "editorTextFocus && editorLangId == asl || editorTextFocus && editorLangId == asl-yaml"
3656+
},
3657+
{
3658+
"command": "noop",
3659+
"key": "ctrl+z",
3660+
"mac": "cmd+z",
3661+
"when": "aws.stepFunctions.isWorkflowStudioFocused"
36563662
}
36573663
],
36583664
"grammars": [

packages/core/package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"AWS.stepFunctions.asl.maxItemsComputed.desc": "The maximum number of outline symbols and folding regions computed (limited for performance reasons).",
2525
"AWS.stepFunctions.workflowStudio.actions.progressMessage": "Opening asl file in Workflow Studio",
2626
"AWS.stepFunctions.workflowStudio.actions.saveSuccessMessage": "{0} has been saved",
27+
"AWS.stepFunctions.workflowStudio.actions.invalidJson": "The Workflow Studio editor was not opened because the JSON in the file is invalid. To access Workflow Studio, please fix the JSON and manually reopen the integration.",
2728
"AWS.configuration.description.awssam.debug.api": "API Gateway configuration",
2829
"AWS.configuration.description.awssam.debug.api.clientCertId": "The API Gateway client certificate ID",
2930
"AWS.configuration.description.awssam.debug.api.headers": "Additional HTTP headers",

packages/core/src/shared/telemetry/vscodeTelemetry.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
"allowedValues": ["Create", "Update"],
1818
"description": "SSM Publish Document operation type"
1919
},
20+
{
21+
"name": "isInvalidJson",
22+
"type": "boolean",
23+
"description": "Indicates whether the message contains an invalid JSON definition."
24+
},
2025
{
2126
"name": "starterTemplate",
2227
"type": "string",
@@ -528,7 +533,7 @@
528533
},
529534
{
530535
"name": "stepfunctions_saveFile",
531-
"description": "Called after the user saves local ASL file (inlcuding autosave) from VSCode editor or Workflow Studio",
536+
"description": "Triggered when Workflow Studio auto-syncs to the local file or when unsaved local changes are saved from Workflow Studio or VSCode.",
532537
"metadata": [
533538
{
534539
"type": "id",
@@ -541,6 +546,9 @@
541546
{
542547
"type": "source",
543548
"required": true
549+
},
550+
{
551+
"type": "isInvalidJson"
544552
}
545553
]
546554
},

packages/core/src/shared/vscode/setContext.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type contextKey =
2323
| 'aws.explorer.showAuthView'
2424
| 'aws.toolkit.amazonq.dismissed'
2525
| 'aws.toolkit.amazonqInstall.dismissed'
26+
| 'aws.stepFunctions.isWorkflowStudioFocused'
2627
// Deprecated/legacy names. New keys should start with "aws.".
2728
| 'codewhisperer.activeLine'
2829
| 'gumby.isPlanAvailable'

packages/core/src/stepFunctions/utils.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,30 @@ export async function isDocumentValid(text: string, textDocument?: vscode.TextDo
223223
return isValid
224224
}
225225

226+
/**
227+
* Checks if the JSON content in a text document is invalid.
228+
* Returns `true` for invalid JSON; `false` for valid JSON, empty content, or non-JSON files.
229+
*
230+
* @param textDocument - The text document to check.
231+
* @returns `true` if invalid; `false` otherwise.
232+
*/
233+
export const isInvalidJsonFile = (textDocument: vscode.TextDocument): boolean => {
234+
const documentContent = textDocument.getText().trim()
235+
// An empty file or whitespace-only text is considered valid JSON for our use case
236+
return textDocument.fileName.toLowerCase().endsWith('.json') && documentContent
237+
? isInvalidJson(documentContent)
238+
: false
239+
}
240+
241+
const isInvalidJson = (content: string): boolean => {
242+
try {
243+
JSON.parse(content)
244+
return false
245+
} catch {
246+
return true
247+
}
248+
}
249+
226250
const descriptor = {
227251
maxItemsComputed: (v: unknown) => Math.trunc(Math.max(0, Number(v))),
228252
['format.enable']: Boolean,

packages/core/src/stepFunctions/workflowStudio/handleMessage.ts

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
InitResponseMessage,
1313
FileChangedMessage,
1414
FileChangeEventTrigger,
15+
SyncFileRequestMessage,
1516
} from './types'
1617
import { submitFeedback } from '../../feedback/vue/submitFeedback'
1718
import { placeholder } from '../../shared/vscode/commands2'
@@ -38,8 +39,8 @@ export async function handleMessage(message: Message, context: WebviewContext) {
3839
case Command.SAVE_FILE:
3940
void saveFileMessageHandler(message as SaveFileRequestMessage, context)
4041
break
41-
case Command.AUTO_SAVE_FILE:
42-
void autoSaveFileMessageHandler(message as SaveFileRequestMessage, context)
42+
case Command.AUTO_SYNC_FILE:
43+
void autoSyncFileMessageHandler(message as SyncFileRequestMessage, context)
4344
break
4445
case Command.CLOSE_WFS:
4546
void closeCustomEditorMessageHandler(context)
@@ -136,12 +137,11 @@ async function saveFileMessageHandler(request: SaveFileRequestMessage, context:
136137
id: context.fileId,
137138
saveType: 'MANUAL_SAVE',
138139
source: 'WORKFLOW_STUDIO',
140+
isInvalidJson: request.isInvalidJson,
139141
})
140142

141143
try {
142-
await saveWorkspace(context, request.fileContents)
143144
await context.textDocument.save()
144-
145145
void vscode.window.showInformationMessage(
146146
localize(
147147
'AWS.stepFunctions.workflowStudio.actions.saveSuccessMessage',
@@ -156,34 +156,30 @@ async function saveFileMessageHandler(request: SaveFileRequestMessage, context:
156156
}
157157

158158
/**
159-
* Handler for auto saving a file from the webview which updates the workspace but does not save the file.
160-
* Triggered on every code change from WFS.
159+
* Handler for auto syncing a file from the webview which updates the workspace but does not save the file.
160+
* Triggered on every code change from WFS, including invalid JSON.
161161
* @param request The request message containing the file contents.
162162
* @param context The webview context containing the necessary information for saving the file.
163163
*/
164-
async function autoSaveFileMessageHandler(request: SaveFileRequestMessage, context: WebviewContext) {
164+
async function autoSyncFileMessageHandler(request: SyncFileRequestMessage, context: WebviewContext) {
165165
await telemetry.stepfunctions_saveFile.run(async (span) => {
166166
span.record({
167167
id: context.fileId,
168168
saveType: 'AUTO_SAVE',
169169
source: 'WORKFLOW_STUDIO',
170+
isInvalidJson: request.isInvalidJson,
170171
})
171172

172173
try {
173-
await saveWorkspace(context, request.fileContents)
174+
const edit = new vscode.WorkspaceEdit()
175+
edit.replace(
176+
context.textDocument.uri,
177+
new vscode.Range(0, 0, context.textDocument.lineCount, 0),
178+
request.fileContents
179+
)
180+
await vscode.workspace.applyEdit(edit)
174181
} catch (err) {
175182
throw ToolkitError.chain(err, 'Could not autosave asl file.', { code: 'AutoSaveFailed' })
176183
}
177184
})
178185
}
179-
180-
/**
181-
* Saves to the workspace with the provided file contents.
182-
* @param context The webview context containing the necessary information for saving the file.
183-
* @param fileContents The file contents to save.
184-
*/
185-
async function saveWorkspace(context: WebviewContext, fileContents: string) {
186-
const edit = new vscode.WorkspaceEdit()
187-
edit.replace(context.textDocument.uri, new vscode.Range(0, 0, context.textDocument.lineCount, 0), fileContents)
188-
await vscode.workspace.applyEdit(edit)
189-
}

packages/core/src/stepFunctions/workflowStudio/types.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export enum MessageType {
3434
export enum Command {
3535
INIT = 'INIT',
3636
SAVE_FILE = 'SAVE_FILE',
37-
AUTO_SAVE_FILE = 'AUTO_SAVE_FILE',
37+
AUTO_SYNC_FILE = 'AUTO_SYNC_FILE',
3838
FILE_CHANGED = 'FILE_CHANGED',
3939
LOAD_STAGE = 'LOAD_STAGE',
4040
OPEN_FEEDBACK = 'OPEN_FEEDBACK',
@@ -45,12 +45,6 @@ export type FileWatchInfo = {
4545
fileContents: string
4646
}
4747

48-
export enum SaveCompleteSubType {
49-
SAVED = 'SAVED',
50-
SAVE_SKIPPED_SAME_CONTENT = 'SAVE_SKIPPED_SAME_CONTENT',
51-
SAVE_FAILED = 'SAVE_FAILED',
52-
}
53-
5448
export interface Message {
5549
command: Command
5650
messageType: MessageType
@@ -71,5 +65,9 @@ export interface InitResponseMessage extends Omit<FileChangedMessage, 'trigger'>
7165
}
7266

7367
export interface SaveFileRequestMessage extends Message {
68+
isInvalidJson: boolean
69+
}
70+
71+
export interface SyncFileRequestMessage extends SaveFileRequestMessage {
7472
fileContents: string
7573
}

packages/core/src/stepFunctions/workflowStudio/workflowStudioEditor.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { broadcastFileChange } from './handleMessage'
1111
import { FileWatchInfo, WebviewContext } from './types'
1212
import { CancellationError } from '../../shared/utilities/timeoutUtils'
1313
import { handleMessage } from './handleMessage'
14+
import { isInvalidJsonFile } from '../utils'
15+
import { setContext } from '../../shared/vscode/setContext'
1416

1517
/**
1618
* The main class for Workflow Studio Editor. This class handles the creation and management
@@ -144,6 +146,7 @@ export class WorkflowStudioEditor {
144146
id: contextObject.fileId,
145147
saveType: 'MANUAL_SAVE',
146148
source: 'VSCODE',
149+
isInvalidJson: isInvalidJsonFile(contextObject.textDocument),
147150
})
148151
await broadcastFileChange(contextObject, 'MANUAL_SAVE')
149152
})
@@ -152,18 +155,31 @@ export class WorkflowStudioEditor {
152155

153156
// Handle messages from the webview
154157
this.disposables.push(
155-
this.webviewPanel.webview.onDidReceiveMessage((message) =>
156-
handleMessage(message, contextObject)
157-
)
158+
this.webviewPanel.webview.onDidReceiveMessage(async (message) => {
159+
await handleMessage(message, contextObject)
160+
})
161+
)
162+
163+
// Track webview focus to suppress VSCode's default undo, as WFS has its own
164+
await setContext('aws.stepFunctions.isWorkflowStudioFocused', true)
165+
this.disposables.push(
166+
this.webviewPanel.onDidChangeViewState(async (event) => {
167+
if (event.webviewPanel.active) {
168+
await setContext('aws.stepFunctions.isWorkflowStudioFocused', true)
169+
} else {
170+
await setContext('aws.stepFunctions.isWorkflowStudioFocused', false)
171+
}
172+
})
158173
)
159174

160175
// When the panel is closed, dispose of any disposables/remove subscriptions
161176
this.disposables.push(
162-
this.webviewPanel.onDidDispose(() => {
177+
this.webviewPanel.onDidDispose(async () => {
163178
if (this.isPanelDisposed) {
164179
return
165180
}
166181

182+
await setContext('aws.stepFunctions.isWorkflowStudioFocused', false)
167183
this.isPanelDisposed = true
168184
resolve()
169185
this.onVisualizationDisposeEmitter.fire()

packages/core/src/stepFunctions/workflowStudio/workflowStudioEditorProvider.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ import globals from '../../shared/extensionGlobals'
1313
import { getRandomString, getStringHash } from '../../shared/utilities/textUtilities'
1414
import { ToolkitError } from '../../shared/errors'
1515
import { WorkflowStudioEditor } from './workflowStudioEditor'
16+
import { i18n } from '../../shared/i18n-helper'
17+
import { isInvalidJsonFile } from '../utils'
1618

17-
// TODO: switch to production mode: change isLocalDev to false and add CDN link
18-
const isLocalDev = true
19+
const isLocalDev = false
1920
const localhost = 'http://127.0.0.1:3002'
20-
const cdn = 'TBD'
21+
const cdn = 'https://d5t62uwepi9lu.cloudfront.net'
2122
let clientId = ''
2223

2324
/**
@@ -30,7 +31,7 @@ export class WorkflowStudioEditorProvider implements vscode.CustomTextEditorProv
3031
public static readonly viewType = 'workflowStudio.asl'
3132

3233
/**
33-
* Registers a new custom editor provider for `.tc.json` files.
34+
* Registers a new custom editor provider for asl files.
3435
* @remarks This should only be called once per extension.
3536
* @param context The extension context
3637
*/
@@ -115,6 +116,21 @@ export class WorkflowStudioEditorProvider implements vscode.CustomTextEditorProv
115116
_token: vscode.CancellationToken
116117
): Promise<void> {
117118
await telemetry.stepfunctions_openWorkflowStudio.run(async () => {
119+
// For invalid JSON, open default editor and show warning message
120+
if (isInvalidJsonFile(document)) {
121+
await vscode.commands.executeCommand('vscode.openWith', document.uri, 'default')
122+
webviewPanel.dispose()
123+
void vscode.window.showWarningMessage(i18n('AWS.stepFunctions.workflowStudio.actions.invalidJson'))
124+
125+
throw ToolkitError.chain(
126+
'Invalid JSON file',
127+
'The Workflow Studio editor was not opened because the JSON in the file is invalid',
128+
{
129+
code: 'InvalidJSONContent',
130+
}
131+
)
132+
}
133+
118134
if (!this.webviewHtml) {
119135
await this.fetchWebviewHtml()
120136
}

0 commit comments

Comments
 (0)