Skip to content

Commit 3c166d7

Browse files
author
Vlad Nikolaenko
committed
feat(stepfunctions): handle launching with invalid JSON file, refactor save metric
1 parent f48080f commit 3c166d7

File tree

9 files changed

+111
-43
lines changed

9 files changed

+111
-43
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: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@
1717
"allowedValues": ["Create", "Update"],
1818
"description": "SSM Publish Document operation type"
1919
},
20+
{
21+
"name": "stepFunctionsSyncType",
22+
"type": "string",
23+
"allowedValues": ["manualSaveWFS", "manualSaveVSCode", "autoSyncWFS"],
24+
"description": "The action type that triggered the save or sync event between SFN Workflow Studio and the local source file."
25+
},
26+
{
27+
"name": "isInvalidJson",
28+
"type": "boolean",
29+
"description": "Indicates whether the message contains an invalid JSON definition."
30+
},
2031
{
2132
"name": "starterTemplate",
2233
"type": "string",
@@ -527,20 +538,19 @@
527538
]
528539
},
529540
{
530-
"name": "stepfunctions_saveFile",
531-
"description": "Called after the user saves local ASL file (inlcuding autosave) from VSCode editor or Workflow Studio",
541+
"name": "stepfunctions_syncFile",
542+
"description": "Triggered when Workflow Studio auto-syncs to the local file or when unsaved local changes are saved from Workflow Studio or VSCode.",
532543
"metadata": [
533544
{
534545
"type": "id",
535546
"required": true
536547
},
537548
{
538-
"type": "saveType",
549+
"type": "stepFunctionsSyncType",
539550
"required": true
540551
},
541552
{
542-
"type": "source",
543-
"required": true
553+
"type": "isInvalidJson"
544554
}
545555
]
546556
},

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: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,32 @@ 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 fileExtension = textDocument.fileName.split('.').pop()?.toLowerCase()
235+
236+
if (fileExtension === 'json') {
237+
const jsonFileContent = textDocument.getText().trim()
238+
// An empty file or whitespace-only text is considered valid JSON for our use case
239+
if (!jsonFileContent) {
240+
return false
241+
}
242+
try {
243+
JSON.parse(jsonFileContent)
244+
return false
245+
} catch {
246+
return true
247+
}
248+
}
249+
return false
250+
}
251+
226252
const descriptor = {
227253
maxItemsComputed: (v: unknown) => Math.trunc(Math.max(0, Number(v))),
228254
['format.enable']: Boolean,

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

Lines changed: 17 additions & 23 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)
@@ -131,17 +132,15 @@ export function closeCustomEditorMessageHandler(context: WebviewContext) {
131132
* @param context The webview context containing the necessary information for saving the file.
132133
*/
133134
async function saveFileMessageHandler(request: SaveFileRequestMessage, context: WebviewContext) {
134-
await telemetry.stepfunctions_saveFile.run(async (span) => {
135+
await telemetry.stepfunctions_syncFile.run(async (span) => {
135136
span.record({
136137
id: context.fileId,
137-
saveType: 'MANUAL_SAVE',
138-
source: 'WORKFLOW_STUDIO',
138+
stepFunctionsSyncType: 'manualSaveWFS',
139+
isInvalidJson: request.isInvalidJson,
139140
})
140141

141142
try {
142-
await saveWorkspace(context, request.fileContents)
143143
await context.textDocument.save()
144-
145144
void vscode.window.showInformationMessage(
146145
localize(
147146
'AWS.stepFunctions.workflowStudio.actions.saveSuccessMessage',
@@ -161,29 +160,24 @@ async function saveFileMessageHandler(request: SaveFileRequestMessage, context:
161160
* @param request The request message containing the file contents.
162161
* @param context The webview context containing the necessary information for saving the file.
163162
*/
164-
async function autoSaveFileMessageHandler(request: SaveFileRequestMessage, context: WebviewContext) {
165-
await telemetry.stepfunctions_saveFile.run(async (span) => {
163+
async function autoSyncFileMessageHandler(request: SyncFileRequestMessage, context: WebviewContext) {
164+
await telemetry.stepfunctions_syncFile.run(async (span) => {
166165
span.record({
167166
id: context.fileId,
168-
saveType: 'AUTO_SAVE',
169-
source: 'WORKFLOW_STUDIO',
167+
stepFunctionsSyncType: 'autoSyncWFS',
168+
isInvalidJson: request.isInvalidJson,
170169
})
171170

172171
try {
173-
await saveWorkspace(context, request.fileContents)
172+
const edit = new vscode.WorkspaceEdit()
173+
edit.replace(
174+
context.textDocument.uri,
175+
new vscode.Range(0, 0, context.textDocument.lineCount, 0),
176+
request.fileContents
177+
)
178+
await vscode.workspace.applyEdit(edit)
174179
} catch (err) {
175180
throw ToolkitError.chain(err, 'Could not autosave asl file.', { code: 'AutoSaveFailed' })
176181
}
177182
})
178183
}
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: 22 additions & 7 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
@@ -139,11 +141,11 @@ export class WorkflowStudioEditor {
139141
// The text document acts as our model, thus we send and event to the webview on file save to trigger update
140142
contextObject.disposables.push(
141143
vscode.workspace.onDidSaveTextDocument(async () => {
142-
await telemetry.stepfunctions_saveFile.run(async (span) => {
144+
await telemetry.stepfunctions_syncFile.run(async (span) => {
143145
span.record({
144146
id: contextObject.fileId,
145-
saveType: 'MANUAL_SAVE',
146-
source: 'VSCODE',
147+
stepFunctionsSyncType: 'manualSaveVSCode',
148+
isInvalidJson: isInvalidJsonFile(contextObject.textDocument),
147149
})
148150
await broadcastFileChange(contextObject, 'MANUAL_SAVE')
149151
})
@@ -152,18 +154,31 @@ export class WorkflowStudioEditor {
152154

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

160174
// When the panel is closed, dispose of any disposables/remove subscriptions
161175
this.disposables.push(
162-
this.webviewPanel.onDidDispose(() => {
176+
this.webviewPanel.onDidDispose(async () => {
163177
if (this.isPanelDisposed) {
164178
return
165179
}
166180

181+
await setContext('aws.stepFunctions.isWorkflowStudioFocused', false)
167182
this.isPanelDisposed = true
168183
resolve()
169184
this.onVisualizationDisposeEmitter.fire()

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ 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

1719
// TODO: switch to production mode: change isLocalDev to false and add CDN link
1820
const isLocalDev = true
@@ -30,7 +32,7 @@ export class WorkflowStudioEditorProvider implements vscode.CustomTextEditorProv
3032
public static readonly viewType = 'workflowStudio.asl'
3133

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

0 commit comments

Comments
 (0)