├── .eslintrc.json ├── .github └── workflows │ ├── bump-version.yml │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── extension ├── src │ ├── extension.ts │ ├── get-base-configuration.test.ts │ ├── get-base-configuration.ts │ ├── get-workspace-path.ts │ ├── is-esm.ts │ ├── node-debug-tracker.ts │ ├── panel.ts │ └── server.ts └── tsconfig.json ├── logo.png ├── package-lock.json ├── package.json ├── rollup.config.mjs ├── samples ├── events.completed.json ├── multiple-signals-terminated-after-days.json ├── single-activity.json └── subscription-canceled.json ├── scripts └── reload-webview.mjs └── webview ├── src ├── components │ ├── breakpoint-button.svelte │ ├── icon │ │ ├── icon.svelte │ │ └── svg │ │ │ ├── arrow-small-left.svelte │ │ │ ├── arrow-small-right.svelte │ │ │ ├── circle-filled.svelte │ │ │ ├── error.svelte │ │ │ └── svg.svelte │ └── submit-button.svelte ├── globals.d.ts ├── lib.ts ├── pages │ └── app.ts ├── utilities │ ├── duration.spec.ts │ ├── duration.ts │ ├── get-workflow-tasks.spec.ts │ ├── get-workflow-tasks.ts │ ├── label-text-for-history-event.spec.ts │ └── label-text-for-history-event.ts └── views │ ├── app.svelte │ ├── history.svelte │ ├── main.svelte │ └── settings.svelte └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "project": ["./extension/tsconfig.json", "./webview/tsconfig.json"] 6 | }, 7 | "plugins": ["@typescript-eslint", "eslint-plugin-tsdoc", "deprecation"], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended", 12 | "prettier" 13 | ], 14 | "rules": { 15 | "@typescript-eslint/naming-convention": "error", 16 | "curly": "error", 17 | "eqeqeq": "error", 18 | "no-throw-literal": "error", 19 | "semi": "off", 20 | "object-shorthand": ["error", "always"], 21 | "deprecation/deprecation": "error", 22 | "@typescript-eslint/no-explicit-any": 0, 23 | "@typescript-eslint/no-floating-promises": ["error"], 24 | "@typescript-eslint/explicit-module-boundary-types": "error", 25 | "@typescript-eslint/no-unused-vars": [ 26 | "error", 27 | { 28 | "argsIgnorePattern": "^_", 29 | "varsIgnorePattern": "^_" 30 | } 31 | ] 32 | }, 33 | "ignorePatterns": ["out", "**/*.d.ts"] 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/bump-version.yml: -------------------------------------------------------------------------------- 1 | name: Bump Version 2 | 3 | on: 4 | workflow_dispatch: 5 | branches: 6 | - main 7 | inputs: 8 | bump: 9 | required: true 10 | description: "Release Type" 11 | type: choice 12 | default: "patch" 13 | options: 14 | - patch 15 | - minor 16 | - major 17 | 18 | jobs: 19 | bump-version: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v3 24 | - name: Node setup 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: 18 28 | registry-url: "https://registry.npmjs.org" 29 | - name: Configure Git 30 | run: | 31 | git config --local user.name 'Temporal Data (cicd)' 32 | git config --local user.email 'commander-data@temporal.io' 33 | - name: Bump version 34 | run: npm version --no-commit-hooks ${{ inputs.bump }} 35 | - name: Create Pull Request 36 | run: | 37 | version=`git describe --abbrev=0 --tags` 38 | branch="releases/$version" 39 | git checkout -b $branch 40 | git remote set-url origin "https://x-access-token:${{ secrets.COMMANDER_DATA_TOKEN }}@github.com/${{ github.repository }}" 41 | git push origin $branch 42 | gh pr create -B main -H $branch --title "bump version in package.json to $version" --body "release version $version" 43 | env: 44 | GH_TOKEN: ${{ secrets.COMMANDER_DATA_TOKEN }} 45 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | # Trigger the workflow on push or pull request, 5 | # but only for the main branch 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | build-lint-test: 15 | name: Build, Lint, Test 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Check out Git repository 20 | uses: actions/checkout@v3 21 | 22 | - name: Set up Node.js 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: 16 26 | 27 | - name: Install Node.js dependencies 28 | run: npm ci 29 | 30 | - name: Run linters 31 | run: npm run lint 32 | 33 | - name: Build 34 | run: npm run build 35 | 36 | - name: Test 37 | run: npm test 38 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release to VS Code Extension Marketplace 2 | 3 | on: 4 | pull_request: 5 | types: [closed] 6 | 7 | jobs: 8 | publish: 9 | if: startsWith(github.head_ref, 'releases/') && github.event.pull_request.merged == true 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: Clean install 14 | run: npm ci 15 | - name: Build package 16 | run: npm run build 17 | - name: Test 18 | run: npm test 19 | - name: Publish package to marketplace 20 | run: npm run publish 21 | env: 22 | VSCE_PAT: ${{secrets.VSCODE_MARKETPLACE_PERSONAL_ACCESS_TOKEN}} 23 | create_release: 24 | needs: publish 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v2 29 | - name: Create Release 30 | run: | 31 | version=`cat ./package.json | jq -r .version` 32 | echo "latest version: $version" 33 | tag="v$version" 34 | echo "creating release: $tag" 35 | gh release create $tag --generate-notes 36 | env: 37 | GH_TOKEN: ${{ secrets.COMMANDER_DATA_TOKEN }} 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vscode 3 | dist 4 | coverage 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | out 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "semi": false, 5 | "trailingComma": "all", 6 | "bracketSameLine": true, 7 | "bracketSpacing": true, 8 | "printWidth": 120 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": ["dbaeumer.vscode-eslint"] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 13 | "env": { 14 | "TEMPORAL_DEBUGGER_EXTENSION_DEV_MODE": "true" 15 | }, 16 | "outFiles": ["${workspaceFolder}/extension/dist/**/*.js"] 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off", 11 | "editor.formatOnSave": true 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .github 2 | .gitignore 3 | .prettierignore 4 | .vscode 5 | extension/src 6 | extension/tsconfig.json 7 | rollup.config.mjs 8 | samples 9 | scripts 10 | webview/src 11 | webview/tsconfig.json 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | The development flow is pretty standard for a Node.js / VS Code extension. Some useful commands listed below: 4 | 5 | - Install dependencies: `npm ci` 6 | - Build the extension and WebView: `npm run build` 7 | - Watch filesystem and rebuild on change: `npm run build.watch` (recommended) 8 | 9 | The repository uses `eslint` and `prettier` to maintain consistent a style, run validation with `npm run lint` and 10 | auto-format code with `npm run format`. 11 | 12 | ## Debug 13 | 14 | - Use the provided [launch configuration](./.vscode/launch.json) to run the extension in a new VS Code window (see [Run and Debug view](https://code.visualstudio.com/docs/editor/debugging#_run-and-debug-view)). 15 | - In that window, open a Temporal project `Ctrl-K-O` (or `Cmd-K-O` on Mac) and create a file with `startDebugReplayer` (e.g. `./src/debug-replayer.ts`) as described above. 16 | - Use `Ctrl-Shift-P` (or `Cmd-Shift-P` on Mac) to open the panel using the command ("Temporal: Open Panel") mentioned above. 17 | 18 | One useful thing to note is that for local development, we've set up a local HTTP server and a rollup watch hook that 19 | will reload the WebView after bundling is complete. The HTTP handler is defined in [the panel 20 | code](./extension/src/panel.ts) - look for `TEMPORAL_DEBUGGER_EXTENSION_DEV_MODE` in that file. When working on a 21 | specific view, it may be useful to send events to the webview from the handler to quickly get the app state to that 22 | view. For example to switch to the history view you may use something like: 23 | 24 | ```ts 25 | await this.panel.webview.postMessage({ type: "historyProcessed", history }) 26 | ``` 27 | 28 | ## Release 29 | 30 | - Under GitHub `Actions` select `Bump Version` 31 | - Then `Run workflow` from `main` and select `Release Type` 32 | - Review and merge the PR that is generated 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 temporal.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Temporal for VS Code 3 |
4 |
5 | Temporal for VS Code 6 |
7 |

8 | 9 |

Debug TypeScript workflows by their ID or history file.

10 |

Set breakpoints in code or on history events.

11 | 12 | ## Usage 13 | 14 | Watch [the demo](https://www.youtube.com/watch?v=3IjQde9HMNY) or follow these instructions: 15 | 16 | - Install [the extension](https://marketplace.visualstudio.com/items?itemName=temporal-technologies.temporalio) 17 | - Add a file at `src/debug-replayer.ts` (or can [configure](#entrypoint) an alternate location): 18 | 19 | ```ts 20 | import { startDebugReplayer } from "@temporalio/worker" 21 | 22 | startDebugReplayer({ 23 | workflowsPath: require.resolve("./workflows"), 24 | }) 25 | ``` 26 | 27 | - Edit the `'./workflows'` path to match the location of your workflows file 28 | - Run `Temporal: Open Panel` (use `Cmd/Ctrl-Shift-P` to open Command Palette) 29 | - Enter a Workflow Id or choose a history JSON file 30 | - Click `Start` 31 | - The Workflow Execution will start replaying and hit a breakpoint set on the first event 32 | - Set breakpoints in Workflow code (the extension uses a [replay Worker](https://typescript.temporal.io/api/classes/worker.Worker#runreplayhistory), so Activity code is not run) or on history events 33 | - Hit play or step forward 34 | - To restart from the beginning, click the green restart icon at the top of the screen, or if the debug session has ended, go back to the `MAIN` tab and `Start` again 35 | 36 | ## Configuration 37 | 38 | ### Server 39 | 40 | When starting a replay by Workflow Id, the extension downloads the history from the Temporal Server. By default, it connects to a Server running on the default `localhost:7233`. 41 | 42 | To connect to a different Server: 43 | 44 | - Open the `SETTINGS` tab 45 | - Edit the `Address` field 46 | - If you're using TLS (e.g. to connect to Temporal Cloud), check the box and select your client cert and key 47 | 48 | ### Entrypoint 49 | 50 | By default, the extension will look for the file that calls [`startDebugReplayer`](https://typescript.temporal.io/api/namespaces/worker#startdebugreplayer) at `src/debug-replayer.ts`. To use a different TypeScript or JavaScript file, set the `temporal.replayerEntrypoint` config: 51 | 52 | - Open or create `.vscode/settings.json` 53 | - Add the config field: 54 | 55 | ```json 56 | { 57 | "temporal.replayerEntrypoint": "test/different-file.ts" 58 | } 59 | ``` 60 | 61 | _Note that the file must be within your project directory so it can find `node_modules/`._ 62 | 63 | ## Contributing 64 | 65 | Thank you to [all who have contributed](https://github.com/temporalio/vscode-debugger-extension/graphs/contributors) 🙏😊. If you'd like to contribute, check out our [issues](https://github.com/temporalio/vscode-debugger-extension/issues) and [CONTRIBUTING.md](https://github.com/temporalio/vscode-debugger-extension/blob/main/CONTRIBUTING.md). 66 | -------------------------------------------------------------------------------- /extension/src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode" 2 | import { registerDebugAdapterTrackerFactory } from "./node-debug-tracker" 3 | import { HistoryDebuggerPanel } from "./panel" 4 | 5 | export async function activate(context: vscode.ExtensionContext): Promise { 6 | const debugAdapterTrackerFactory: vscode.Disposable = registerDebugAdapterTrackerFactory() 7 | const openCommand: vscode.Disposable = vscode.commands.registerCommand( 8 | "temporal.debugger-extension.open-panel", 9 | async () => { 10 | await HistoryDebuggerPanel.install(context.extensionUri, context.secrets) 11 | }, 12 | ) 13 | context.subscriptions.push(openCommand, debugAdapterTrackerFactory) 14 | } 15 | -------------------------------------------------------------------------------- /extension/src/get-base-configuration.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, describe, it, vi } from "vitest" 2 | import { getBaseConfiguration } from "./get-base-configuration" 3 | import * as moduleType from "./is-esm" 4 | 5 | vi.mock("./is-esm", async () => ({ 6 | supportsESM: vi.fn().mockResolvedValue(false), 7 | })) 8 | 9 | describe("getBaseConfiguration", () => { 10 | it("returns the correct configuration for environments", async () => { 11 | const { runtimeArgs } = await getBaseConfiguration() 12 | 13 | expect(runtimeArgs).toEqual(["--nolazy", "-r", "ts-node/register/transpile-only"]) 14 | }) 15 | 16 | it("returns the correct configuration for ESM environments", async () => { 17 | vi.mocked(moduleType).supportsESM.mockResolvedValueOnce(true) 18 | const { runtimeArgs } = await getBaseConfiguration() 19 | 20 | expect(runtimeArgs).toEqual(["--loader=ts-node/esm"]) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /extension/src/get-base-configuration.ts: -------------------------------------------------------------------------------- 1 | import type * as vscode from "vscode" 2 | import { supportsESM } from "./is-esm" 3 | 4 | const configuration = { 5 | name: "Launch Program", 6 | type: "node", 7 | request: "launch", 8 | runtimeExecutable: "node", 9 | skipFiles: [ 10 | "/**", 11 | "**/node_modules/@temporalio/worker/src/**", 12 | "**/node_modules/@temporalio/worker/lib/**", 13 | "**/node_modules/@temporalio/common/src/**", 14 | "**/node_modules/@temporalio/common/lib/**", 15 | "**/node_modules/**/source-map/**", 16 | ], 17 | internalConsoleOptions: "openOnSessionStart", 18 | pauseForSourceMap: true, 19 | } satisfies vscode.DebugConfiguration 20 | 21 | export const getBaseConfiguration = async (): Promise => { 22 | const runtimeArgs = (await supportsESM()) 23 | ? ["--loader=ts-node/esm"] 24 | : ["--nolazy", "-r", "ts-node/register/transpile-only"] 25 | 26 | return { ...configuration, runtimeArgs } 27 | } 28 | -------------------------------------------------------------------------------- /extension/src/get-workspace-path.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode" 2 | 3 | export function getWorkspacePath(): string | undefined { 4 | const [workspaceFolder] = vscode.workspace.workspaceFolders || [] 5 | 6 | if (!workspaceFolder) { 7 | return undefined 8 | } 9 | 10 | return workspaceFolder.uri.fsPath 11 | } 12 | -------------------------------------------------------------------------------- /extension/src/is-esm.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from "fs/promises" 2 | import { getWorkspacePath } from "./get-workspace-path" 3 | import { join } from "path" 4 | 5 | type ModuleType = "commonjs" | "module" 6 | 7 | export async function getModuleType(fallback: ModuleType = "commonjs"): Promise { 8 | const rootPath = getWorkspacePath() 9 | 10 | if (!rootPath) { 11 | return fallback 12 | } 13 | 14 | const packageJsonPath = join(rootPath, "package.json") 15 | const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8")) 16 | 17 | if (packageJson.type === "commonjs") { 18 | return "commonjs" 19 | } 20 | 21 | if (packageJson.type === "module") { 22 | return "module" 23 | } 24 | 25 | return fallback 26 | } 27 | 28 | const supports = async (type: ModuleType): Promise => { 29 | try { 30 | return (await getModuleType()) === type 31 | } catch (e) { 32 | return false 33 | } 34 | } 35 | 36 | export const supportsESM = (): Promise => supports("module") 37 | export const supportsCommonJS = (): Promise => supports("commonjs") 38 | -------------------------------------------------------------------------------- /extension/src/node-debug-tracker.ts: -------------------------------------------------------------------------------- 1 | import vscode from "vscode" 2 | 3 | function ignoreErrors(t: Thenable) { 4 | t.then( 5 | () => { 6 | // ignore 7 | }, 8 | () => { 9 | // ignore 10 | }, 11 | ) 12 | } 13 | 14 | export function registerDebugAdapterTrackerFactory(): vscode.Disposable { 15 | // TODO: register for other types too? 16 | return vscode.debug.registerDebugAdapterTrackerFactory("pwa-node", { 17 | createDebugAdapterTracker(_session) { 18 | let paused = false 19 | 20 | return { 21 | onDidSendMessage(m) { 22 | if (m.type === "event" && m.event === "stopped") { 23 | paused = true 24 | } else if (m.type === "event" && m.event === "continued") { 25 | paused = false 26 | } else if (paused && m.type === "response" && m.command === "stackTrace" && m.success) { 27 | // Empty out the returned stack trace for unwanted sources so they're not displayed in the UI. 28 | // Trigger stepping into / out of irrelevant sources. 29 | const { stackFrames } = m.body 30 | const frame = stackFrames[0] 31 | if (frame?.source?.path.includes("@temporalio/workflow/src/")) { 32 | m.body.stackFrames = [] 33 | if (stackFrames.length === 1) { 34 | ignoreErrors(vscode.commands.executeCommand("workbench.action.debug.stepInto")) 35 | } 36 | } else if (frame?.source?.path.startsWith("/")) { 37 | m.body.stackFrames = [] 38 | if (stackFrames.length === 1) { 39 | ignoreErrors(vscode.commands.executeCommand("workbench.action.debug.stepOut")) 40 | } 41 | } 42 | } 43 | }, 44 | } 45 | }, 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /extension/src/panel.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode" 2 | import os from "node:os" 3 | import path from "node:path" 4 | import http from "node:http" 5 | import { historyFromJSON } from "@temporalio/common/lib/proto-utils" 6 | import { temporal } from "@temporalio/proto" 7 | import { Connection, LOCAL_TARGET } from "@temporalio/client" 8 | import { Server } from "./server" 9 | import { getBaseConfiguration } from "./get-base-configuration" 10 | 11 | interface StartFromId { 12 | namespace?: string 13 | workflowId: string 14 | runId?: string 15 | } 16 | 17 | interface Settings { 18 | address: string 19 | tls: boolean 20 | clientCert?: Uint8Array 21 | clientPrivateKey?: Uint8Array 22 | } 23 | 24 | interface EncodedSettings { 25 | address: string 26 | tls: boolean 27 | base64ClientCert?: string 28 | base64ClientPrivateKey?: string 29 | } 30 | 31 | export class HistoryDebuggerPanel { 32 | protected static _instance?: Promise 33 | 34 | static async install(extensionUri: vscode.Uri, secretStorage: vscode.SecretStorage): Promise { 35 | if (this._instance === undefined) { 36 | this._instance = Server.create().then((server: Server) => { 37 | console.log(`Server listening on ${server.url}`) 38 | return new this(extensionUri, secretStorage, server) 39 | }) 40 | } else { 41 | const instance = await this._instance 42 | instance.show() 43 | } 44 | } 45 | 46 | static get instance(): Promise { 47 | if (this._instance === undefined) { 48 | throw new ReferenceError("HistoryDebuggerPanel not installed") 49 | } 50 | return this._instance 51 | } 52 | 53 | public currentHistoryBuffer?: Buffer 54 | 55 | public static readonly viewType = "temporal-debugger-plugin" 56 | 57 | private readonly panel: vscode.WebviewPanel 58 | private disposables: vscode.Disposable[] = [] 59 | private updateWorkflowTaskHasBreakpoint = (_hasBreakpoint: boolean) => { 60 | // noop, to be set in the updateCurrentWFTStarted handler 61 | } 62 | 63 | show(): void { 64 | this.panel.reveal(vscode.ViewColumn.Beside) 65 | } 66 | 67 | async updateCurrentWFTStarted(eventId: number): Promise { 68 | const p = new Promise((resolve, reject) => { 69 | this.updateWorkflowTaskHasBreakpoint = resolve 70 | setTimeout(() => reject(new Error("Timed out waiting for response from webview")), 5000) 71 | }) 72 | await this.panel.webview.postMessage({ type: "currentWFTUpdated", eventId }) 73 | const hasBreakpoint = await p 74 | if (hasBreakpoint) { 75 | await vscode.commands.executeCommand("workbench.action.debug.pause") 76 | } 77 | } 78 | 79 | private constructor( 80 | protected readonly extensionUri: vscode.Uri, 81 | private readonly secretStorage: vscode.SecretStorage, 82 | protected readonly server: Server, 83 | ) { 84 | this.panel = vscode.window.createWebviewPanel(HistoryDebuggerPanel.viewType, "Temporal", vscode.ViewColumn.Beside, { 85 | // Enable javascript in the webview 86 | enableScripts: true, 87 | // And restrict the webview to only loading content from our extension's compiled directory. 88 | localResourceRoots: [vscode.Uri.joinPath(extensionUri, "webview/dist")], 89 | retainContextWhenHidden: true, 90 | }) 91 | 92 | // Set the webview's initial html content 93 | this.update() 94 | 95 | let reloadServer: http.Server | undefined = undefined 96 | // Start a local HTTP server to automatically reload the webview when rollup completes. 97 | if (process.env.TEMPORAL_DEBUGGER_EXTENSION_DEV_MODE) { 98 | reloadServer = http.createServer((_req, res) => { 99 | void vscode.commands.executeCommand("workbench.action.webview.reloadWebviewAction") 100 | res.writeHead(200, "OK") 101 | res.end() 102 | }) 103 | reloadServer.listen(55666, "127.0.0.1") 104 | } 105 | 106 | // Listen for when the panel is disposed 107 | // This happens when the user closes the panel or when the panel is closed programatically 108 | this.panel.onDidDispose(async () => { 109 | // Close local servers 110 | server.terminate() 111 | if (reloadServer) { 112 | reloadServer.close() 113 | } 114 | 115 | await this.dispose(), null, this.disposables 116 | }) 117 | } 118 | 119 | public async dispose(): Promise { 120 | // Clean up our resources 121 | this.panel.dispose() 122 | 123 | while (this.disposables.length) { 124 | const x = this.disposables.pop() 125 | if (x) { 126 | await x.dispose() 127 | } 128 | } 129 | 130 | delete HistoryDebuggerPanel._instance 131 | } 132 | 133 | private encodeSettings({ address, tls, clientCert, clientPrivateKey }: Settings): EncodedSettings { 134 | return { 135 | address, 136 | tls, 137 | base64ClientCert: clientCert ? Buffer.from(clientCert).toString("base64") : undefined, 138 | base64ClientPrivateKey: clientPrivateKey ? Buffer.from(clientPrivateKey).toString("base64") : undefined, 139 | } 140 | } 141 | 142 | private decodeSettings({ address, tls, base64ClientCert, base64ClientPrivateKey }: EncodedSettings): Settings { 143 | return { 144 | address, 145 | tls, 146 | clientCert: base64ClientCert ? Buffer.from(base64ClientCert, "base64") : undefined, 147 | clientPrivateKey: base64ClientPrivateKey ? Buffer.from(base64ClientPrivateKey, "base64") : undefined, 148 | } 149 | } 150 | 151 | private async getSettings(): Promise { 152 | const secret = await this.secretStorage.get("settings") 153 | if (secret === undefined) { 154 | return { 155 | address: LOCAL_TARGET, 156 | tls: false, 157 | } 158 | } 159 | return JSON.parse(secret) 160 | } 161 | 162 | private async getConnection() { 163 | const encoded = await this.getSettings() 164 | const { address, tls, clientCert, clientPrivateKey } = this.decodeSettings(encoded) 165 | return await Connection.connect({ 166 | address, 167 | tls: 168 | clientCert && clientPrivateKey 169 | ? { clientCertPair: { crt: Buffer.from(clientCert), key: Buffer.from(clientPrivateKey) } } 170 | : tls 171 | ? true 172 | : false, 173 | }) 174 | } 175 | 176 | private async downloadHistory({ namespace, workflowId, runId }: StartFromId) { 177 | const connection = await this.getConnection() 178 | let nextPageToken: Uint8Array | undefined = undefined 179 | const history: temporal.api.history.v1.IHistory = { events: [] } 180 | do { 181 | try { 182 | const response: temporal.api.workflowservice.v1.GetWorkflowExecutionHistoryResponse = 183 | await connection.workflowService.getWorkflowExecutionHistory({ 184 | namespace: namespace || "default", 185 | execution: { 186 | workflowId, 187 | runId, 188 | }, 189 | nextPageToken, 190 | }) 191 | if (!response.history?.events) { 192 | throw new Error("Empty history") 193 | } 194 | history.events?.push(...response.history.events) 195 | nextPageToken = response.nextPageToken 196 | } catch (err) { 197 | throw new Error(`Unable to find workflow execution history for ${workflowId}`) 198 | } 199 | } while (nextPageToken && nextPageToken.length > 0) 200 | return history 201 | } 202 | 203 | private update(): void { 204 | const { webview } = this.panel 205 | 206 | webview.html = this.getHtmlForWebview(webview) 207 | 208 | webview.onDidReceiveMessage(async (e): Promise => { 209 | try { 210 | switch (e.type) { 211 | case "updateWorkflowTaskHasBreakpoint": { 212 | this.updateWorkflowTaskHasBreakpoint(e.hasBreakpoint) 213 | break 214 | } 215 | case "getSettings": { 216 | const settings = await this.getSettings() 217 | await this.panel.webview.postMessage({ 218 | type: "settingsLoaded", 219 | settings: { 220 | address: settings.address, 221 | tls: settings.tls, 222 | hasClientCert: !!settings.base64ClientCert, 223 | hasClientPrivateKey: !!settings.base64ClientPrivateKey, 224 | }, 225 | }) 226 | break 227 | } 228 | case "updateSettings": { 229 | e.settings.address ??= LOCAL_TARGET 230 | e.settings.tls ??= false 231 | const encodedSettings = this.encodeSettings(e.settings) 232 | await this.secretStorage.store("settings", JSON.stringify(encodedSettings)) 233 | await vscode.window.showInformationMessage("Settings updated") 234 | break 235 | } 236 | case "startFromId": { 237 | const history = await this.downloadHistory(e) 238 | await this.handleStartProject(history) 239 | break 240 | } 241 | case "startFromHistory": { 242 | const history = historyFromJSON(e.history) 243 | await this.handleStartProject(history) 244 | break 245 | } 246 | } 247 | } catch (err) { 248 | await vscode.window.showErrorMessage(`${err}`) 249 | } 250 | }) 251 | } 252 | 253 | private async getReplayerEndpoint() { 254 | const config = vscode.workspace.getConfiguration("temporal") 255 | let replayerEntrypoint = config.get("replayerEntrypoint") as string 256 | const workspace = vscode.workspace.workspaceFolders?.[0] 257 | const workspaceFolder = workspace?.uri 258 | const configuredAbsolutePath = path.isAbsolute(replayerEntrypoint) 259 | 260 | if (!configuredAbsolutePath) { 261 | if (workspaceFolder === undefined) { 262 | throw new Error("temporal.replayerEndpoint not configured, cannot use default without a workspace folder") 263 | } else { 264 | replayerEntrypoint = vscode.Uri.joinPath(workspaceFolder, replayerEntrypoint).fsPath 265 | } 266 | } 267 | 268 | try { 269 | const stat: vscode.FileStat = await vscode.workspace.fs.stat(vscode.Uri.file(replayerEntrypoint)) 270 | const { type } = stat 271 | if (type === vscode.FileType.Directory) { 272 | throw new Error( 273 | `Configured temporal.replayerEndpoint (${replayerEntrypoint}) is a folder, please provide a file instead`, 274 | ) 275 | } 276 | if (type === vscode.FileType.Unknown) { 277 | throw new Error( 278 | `Configured temporal.replayerEndpoint (${replayerEntrypoint}) is of unknown type, please provide a file instead`, 279 | ) 280 | } 281 | } catch (err: any) { 282 | if (err?.code === vscode.FileSystemError.FileNotFound.name) { 283 | if (!configuredAbsolutePath && (vscode.workspace.workspaceFolders?.length ?? 0) > 1) { 284 | throw new Error( 285 | `Configured temporal.replayerEndpoint (${replayerEntrypoint}) not found (multiple workspace folders found, consider using an absolute path to disambiguate)`, 286 | ) 287 | } 288 | throw new Error(`Configured temporal.replayerEndpoint (${replayerEntrypoint}) not found`) 289 | } 290 | throw err 291 | } 292 | 293 | return replayerEntrypoint 294 | } 295 | 296 | /* eslint-disable @typescript-eslint/naming-convention */ 297 | private async handleStartProject(history: temporal.api.history.v1.IHistory): Promise { 298 | const bytes = new Uint8Array(temporal.api.history.v1.History.encode(history).finish()) 299 | const buffer = Buffer.from(bytes) 300 | this.currentHistoryBuffer = buffer 301 | const workspace = vscode.workspace.workspaceFolders?.[0] 302 | const replayerEndpoint = await this.getReplayerEndpoint() 303 | 304 | await this.panel.webview.postMessage({ type: "historyProcessed", history: bytes }) 305 | // Make sure the panel is out of focus before starting a debug session, otherwise it will be replaced with an 306 | // editor 307 | if (vscode.window.tabGroups.all.length > 1) { 308 | await vscode.commands.executeCommand("workbench.action.focusFirstEditorGroup") 309 | } else { 310 | await vscode.commands.executeCommand("workbench.action.splitEditorLeft") 311 | } 312 | 313 | const baseConfig = await getBaseConfiguration() 314 | // So this can be used with the TypeScript SDK 315 | if (process.env.TEMPORAL_DEBUGGER_EXTENSION_DEV_MODE) { 316 | baseConfig.skipFiles.push("${workspaceFolder}/packages/worker/src/**") 317 | } 318 | // NOTE: Adding NODE_PATH below in case ts-node is not an installed dependency in the workspace. 319 | // From https://nodejs.org/api/modules.html#loading-from-the-global-folders: 320 | // > Node.js will search those paths for modules **if they are not found elsewhere**. 321 | // Our NODE_PATH will only be used as a fallback which is what we want. 322 | const delim = os.platform() === "win32" ? ";" : ":" 323 | const pathPrefix = process.env.NODE_PATH ? `${process.env.NODE_PATH ?? ""}${delim}` : "" 324 | await vscode.debug.startDebugging(workspace, { 325 | ...baseConfig, 326 | args: [replayerEndpoint], 327 | env: { 328 | TEMPORAL_DEBUGGER_PLUGIN_URL: this.server.url, 329 | NODE_PATH: `${pathPrefix}${path.join(__dirname, "../../node_modules")}`, 330 | }, 331 | }) 332 | await vscode.window.showInformationMessage("Starting debug session") 333 | } 334 | 335 | private getHtmlForWebview(webview: vscode.Webview): string { 336 | // And the uri we use to load this script in the webview 337 | const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, "webview", "dist", "app.js")) 338 | const styleUri = webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, "webview", "dist", "app.css")) 339 | 340 | return ` 341 | 342 | 343 | 344 | 348 | 349 | 350 | 351 | 352 | 353 | 357 | 358 | 359 | ` 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /extension/src/server.ts: -------------------------------------------------------------------------------- 1 | import http from "node:http" 2 | import { AddressInfo } from "node:net" 3 | import express from "express" 4 | import { HistoryDebuggerPanel } from "./panel" 5 | 6 | async function listen(server: http.Server, port: number, hostname?: string): Promise { 7 | await new Promise((resolve, reject) => { 8 | if (hostname) { 9 | server.listen(port, hostname, resolve) 10 | } else { 11 | server.listen(port, resolve) 12 | } 13 | server.once("error", reject) 14 | }) 15 | return server 16 | } 17 | 18 | function mustBeAddrInfo(info: string | AddressInfo | null): asserts info is AddressInfo { 19 | if (info === null) { 20 | throw new TypeError("Expected AddressInfo got null") 21 | } 22 | if (typeof info === "string") { 23 | throw new TypeError("Expected AddressInfo got a string") 24 | } 25 | } 26 | 27 | export class Server { 28 | static async create(address = "127.0.0.1", port = 0): Promise { 29 | const app = express() 30 | app.use(express.json()) 31 | app.get("/history", async (_req, res) => { 32 | try { 33 | const { currentHistoryBuffer } = await HistoryDebuggerPanel.instance 34 | if (!currentHistoryBuffer) { 35 | res.status(404).send({ error: "No current history available" }) 36 | return 37 | } 38 | res.end(currentHistoryBuffer) 39 | } catch (error) { 40 | res.status(500).send({ error: `${error}` }) 41 | } 42 | }) 43 | app.post("/current-wft-started", async (req, res) => { 44 | if (!(typeof req.body === "object" && typeof req.body.eventId === "number")) { 45 | res.status(400).send({ error: "Bad request" }) 46 | return 47 | } 48 | const { eventId } = req.body 49 | try { 50 | const instance = await HistoryDebuggerPanel.instance 51 | await instance.updateCurrentWFTStarted(eventId) 52 | } catch (error) { 53 | res.status(500).send({ error: `${error}` }) 54 | return 55 | } 56 | res.end() 57 | }) 58 | const server = new http.Server(app) 59 | await listen(server, port, address) 60 | return new this(server) 61 | } 62 | 63 | constructor(protected readonly server: http.Server) {} 64 | 65 | get url(): string { 66 | const addr = this.server.address() 67 | mustBeAddrInfo(addr) 68 | return `http://${addr.address}:${addr.port}` 69 | } 70 | 71 | terminate(): void { 72 | console.log(`Closing server on ${this.url}`) 73 | this.server.close() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node16/tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/temporalio/vscode-debugger-extension/af03bf9f8a17967268f14de3643af509ab79c311/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "temporalio", 3 | "displayName": "Temporal.io", 4 | "version": "0.1.2", 5 | "description": "Debug workflows by their ID or history file. Set breakpoints in code or on history events.", 6 | "categories": [ 7 | "Debuggers", 8 | "Visualization" 9 | ], 10 | "keywords": [ 11 | "temporal", 12 | "workflow", 13 | "debugger", 14 | "vscode" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/temporalio/vscode-debugger-extension" 19 | }, 20 | "license": "MIT", 21 | "publisher": "temporal-technologies", 22 | "main": "./extension/dist/extension.js", 23 | "scripts": { 24 | "build": "concurrently \"npm run build.extension\" \"npm run build.webview\"", 25 | "build.extension": "tsc --build extension", 26 | "build.extension.watch": "tsc --build --watch extension", 27 | "build.watch": "concurrently \"npm run build.extension.watch\" \"npm run build.webview.watch\"", 28 | "build.webview": "rollup -c", 29 | "build.webview.watch": "rollup -c -w --watch.onEnd \"node scripts/reload-webview.mjs\"", 30 | "format": "prettier --write .", 31 | "lint": "eslint src --ext .ts --no-error-on-unmatched-pattern && prettier --end-of-line auto --check .", 32 | "publish": "vsce publish", 33 | "test": "vitest" 34 | }, 35 | "contributes": { 36 | "commands": [ 37 | { 38 | "command": "temporal.debugger-extension.open-panel", 39 | "title": "Temporal: Open Panel" 40 | } 41 | ], 42 | "configuration": { 43 | "title": "Temporal debugger", 44 | "properties": { 45 | "temporal.replayerEntrypoint": { 46 | "type": "string", 47 | "default": "src/debug-replayer.ts", 48 | "description": "Specifies which file will be used to start a replayer process." 49 | } 50 | } 51 | } 52 | }, 53 | "dependencies": { 54 | "@temporalio/client": "^1.10.1", 55 | "@temporalio/common": "^1.10.1", 56 | "@temporalio/proto": "^1.10.1", 57 | "@vscode/webview-ui-toolkit": "^1.4.0", 58 | "express": "^4.19.2", 59 | "ts-node": "^10.9.2" 60 | }, 61 | "devDependencies": { 62 | "@rollup/plugin-commonjs": "^25.0.8", 63 | "@rollup/plugin-json": "^6.1.0", 64 | "@rollup/plugin-node-resolve": "^15.2.3", 65 | "@rollup/plugin-typescript": "^11.1.6", 66 | "@tsconfig/node16": "^16.1.3", 67 | "@tsconfig/svelte": "^5.0.4", 68 | "@types/chai": "^4.3.16", 69 | "@types/express": "^4.17.21", 70 | "@types/glob": "^8.1.0", 71 | "@types/humanize-duration": "^3.27.4", 72 | "@types/mocha": "^10.0.6", 73 | "@types/node": "^20.12.12", 74 | "@types/vscode": "^1.89.0", 75 | "@typescript-eslint/eslint-plugin": "^7.11.0", 76 | "@typescript-eslint/parser": "^7.11.0", 77 | "@vitest/coverage-v8": "^1.6.0", 78 | "@vscode/test-electron": "^2.3.10", 79 | "@vscode/vsce": "^2.26.1", 80 | "concurrently": "^8.2.2", 81 | "eslint": "^8.56.0", 82 | "eslint-config-prettier": "^9.1.0", 83 | "eslint-plugin-deprecation": "^2.0.0", 84 | "eslint-plugin-tsdoc": "^0.2.17", 85 | "humanize-duration": "^3.32.1", 86 | "nodemon": "^3.1.1", 87 | "prettier": "3.2.5", 88 | "release": "^6.0.1", 89 | "rollup": "^4.18.0", 90 | "rollup-plugin-css-only": "^4.5.2", 91 | "rollup-plugin-svelte": "^7.2.0", 92 | "svelte": "^4.2.17", 93 | "svelte-check": "^3.7.1", 94 | "svelte-preprocess": "^5.1.4", 95 | "ts-loader": "^9.5.1", 96 | "typescript": "^5.4.5", 97 | "vite": "^5.2.12", 98 | "vitest": "^1.6.0" 99 | }, 100 | "engines": { 101 | "vscode": "^1.77.3" 102 | }, 103 | "icon": "logo.png" 104 | } 105 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import svelte from "rollup-plugin-svelte" 2 | import resolve from "@rollup/plugin-node-resolve" 3 | import commonjs from "@rollup/plugin-commonjs" 4 | import css from "rollup-plugin-css-only" 5 | import sveltePreprocess from "svelte-preprocess" 6 | import typescript from "@rollup/plugin-typescript" 7 | import json from "@rollup/plugin-json" 8 | import { URL, fileURLToPath } from "url" 9 | 10 | export default { 11 | input: fileURLToPath(new URL("webview/src/pages/app.ts", import.meta.url)), 12 | output: { 13 | sourcemap: true, 14 | format: "iife", 15 | name: "app", 16 | file: "webview/dist/app.js", 17 | }, 18 | plugins: [ 19 | svelte({ 20 | preprocess: sveltePreprocess(), 21 | }), 22 | resolve({ 23 | browser: true, 24 | dedupe: ["svelte"], 25 | }), 26 | css({ output: "app.css" }), 27 | commonjs(), 28 | typescript({ 29 | tsconfig: "webview/tsconfig.json", 30 | }), 31 | json(), 32 | ], 33 | watch: { 34 | clearScreen: false, 35 | }, 36 | } 37 | -------------------------------------------------------------------------------- /samples/events.completed.json: -------------------------------------------------------------------------------- 1 | { 2 | "events": [ 3 | { 4 | "eventId": "1", 5 | "eventTime": "2022-07-01T20:28:48.796369169Z", 6 | "eventType": "WorkflowExecutionStarted", 7 | "version": "0", 8 | "taskId": "29887292", 9 | "workflowExecutionStartedEventAttributes": { 10 | "workflowType": { 11 | "name": "workflow.completion" 12 | }, 13 | "parentWorkflowNamespace": "canary", 14 | "parentWorkflowExecution": { 15 | "workflowId": "temporal.fixture.completed.workflow.id", 16 | "runId": "971e2165-c4f8-4f78-87ca-b652a06eb234" 17 | }, 18 | "parentInitiatedEventId": "10", 19 | "taskQueue": { 20 | "name": "canary-task-queue", 21 | "kind": "Normal" 22 | }, 23 | "input": { 24 | "payloads": ["1656707328774263000", "canary"] 25 | }, 26 | "workflowExecutionTimeout": "0s", 27 | "workflowRunTimeout": "1200s", 28 | "workflowTaskTimeout": "20s", 29 | "continuedExecutionRunId": "", 30 | "initiator": "Unspecified", 31 | "continuedFailure": null, 32 | "lastCompletionResult": null, 33 | "originalExecutionRunId": "202dcff6-7f35-4c65-995c-bcadce524fb1", 34 | "identity": "", 35 | "firstExecutionRunId": "202dcff6-7f35-4c65-995c-bcadce524fb1", 36 | "retryPolicy": null, 37 | "attempt": 1, 38 | "workflowExecutionExpirationTime": null, 39 | "cronSchedule": "", 40 | "firstWorkflowTaskBackoff": "0s", 41 | "memo": null, 42 | "searchAttributes": { 43 | "indexedFields": { 44 | "CustomKeywordField": "childWorkflowValue" 45 | } 46 | }, 47 | "prevAutoResetPoints": null, 48 | "header": { 49 | "fields": {} 50 | }, 51 | "parentInitiatedEventVersion": "0" 52 | }, 53 | "attributes": { 54 | "type": "workflowExecutionStartedEventAttributes", 55 | "workflowType": "workflow.completion", 56 | "parentWorkflowNamespace": "canary", 57 | "parentWorkflowExecution": { 58 | "workflowId": "temporal.fixture.completed.workflow.id", 59 | "runId": "971e2165-c4f8-4f78-87ca-b652a06eb234" 60 | }, 61 | "parentInitiatedEventId": "10", 62 | "taskQueue": { 63 | "name": "canary-task-queue", 64 | "kind": "Normal" 65 | }, 66 | "input": { 67 | "payloads": ["1656707328774263000", "canary"] 68 | }, 69 | "workflowExecutionTimeout": "", 70 | "workflowRunTimeout": "20 minutes", 71 | "workflowTaskTimeout": "20 seconds", 72 | "continuedExecutionRunId": "", 73 | "initiator": "Unspecified", 74 | "continuedFailure": null, 75 | "lastCompletionResult": null, 76 | "originalExecutionRunId": "202dcff6-7f35-4c65-995c-bcadce524fb1", 77 | "identity": "", 78 | "firstExecutionRunId": "202dcff6-7f35-4c65-995c-bcadce524fb1", 79 | "retryPolicy": null, 80 | "attempt": 1, 81 | "workflowExecutionExpirationTime": "", 82 | "cronSchedule": "", 83 | "firstWorkflowTaskBackoff": "", 84 | "memo": null, 85 | "searchAttributes": { 86 | "indexedFields": { 87 | "CustomKeywordField": "childWorkflowValue" 88 | } 89 | }, 90 | "prevAutoResetPoints": null, 91 | "header": { 92 | "fields": {} 93 | }, 94 | "parentInitiatedEventVersion": "0" 95 | }, 96 | "classification": "Started", 97 | "category": "workflow", 98 | "id": "1", 99 | "name": "WorkflowExecutionStarted", 100 | "timestamp": "2022-07-01 UTC 20:28:48.79" 101 | }, 102 | { 103 | "eventId": "2", 104 | "eventTime": "2022-07-01T20:28:48.805620294Z", 105 | "eventType": "WorkflowTaskScheduled", 106 | "version": "0", 107 | "taskId": "29887296", 108 | "workflowTaskScheduledEventAttributes": { 109 | "taskQueue": { 110 | "name": "canary-task-queue", 111 | "kind": "Normal" 112 | }, 113 | "startToCloseTimeout": "20s", 114 | "attempt": 1 115 | }, 116 | "attributes": { 117 | "type": "workflowTaskScheduledEventAttributes", 118 | "taskQueue": { 119 | "name": "canary-task-queue", 120 | "kind": "Normal" 121 | }, 122 | "startToCloseTimeout": "20 seconds", 123 | "attempt": 1 124 | }, 125 | "classification": "Scheduled", 126 | "category": "workflow", 127 | "id": "2", 128 | "name": "WorkflowTaskScheduled", 129 | "timestamp": "2022-07-01 UTC 20:28:48.80" 130 | }, 131 | { 132 | "eventId": "3", 133 | "eventTime": "2022-07-01T20:28:48.810509586Z", 134 | "eventType": "WorkflowTaskStarted", 135 | "version": "0", 136 | "taskId": "29887299", 137 | "workflowTaskStartedEventAttributes": { 138 | "scheduledEventId": "2", 139 | "identity": "83579@MacBook-Pro-2.local@", 140 | "requestId": "5a507f4f-fc92-45af-95b5-2a3c8f123817" 141 | }, 142 | "attributes": { 143 | "type": "workflowTaskStartedEventAttributes", 144 | "scheduledEventId": "2", 145 | "identity": "83579@MacBook-Pro-2.local@", 146 | "requestId": "5a507f4f-fc92-45af-95b5-2a3c8f123817" 147 | }, 148 | "classification": "Started", 149 | "category": "workflow", 150 | "id": "3", 151 | "name": "WorkflowTaskStarted", 152 | "timestamp": "2022-07-01 UTC 20:28:48.81" 153 | }, 154 | { 155 | "eventId": "4", 156 | "eventTime": "2022-07-01T20:28:48.820933044Z", 157 | "eventType": "WorkflowTaskCompleted", 158 | "version": "0", 159 | "taskId": "29887306", 160 | "workflowTaskCompletedEventAttributes": { 161 | "scheduledEventId": "2", 162 | "startedEventId": "3", 163 | "identity": "83579@MacBook-Pro-2.local@", 164 | "binaryChecksum": "e56c0141e58df0bd405138565d0526f9" 165 | }, 166 | "attributes": { 167 | "type": "workflowTaskCompletedEventAttributes", 168 | "scheduledEventId": "2", 169 | "startedEventId": "3", 170 | "identity": "83579@MacBook-Pro-2.local@", 171 | "binaryChecksum": "e56c0141e58df0bd405138565d0526f9" 172 | }, 173 | "classification": "Completed", 174 | "category": "workflow", 175 | "id": "4", 176 | "name": "WorkflowTaskCompleted", 177 | "timestamp": "2022-07-01 UTC 20:28:48.82" 178 | }, 179 | { 180 | "eventId": "5", 181 | "eventTime": "2022-07-01T20:28:48.820951669Z", 182 | "eventType": "MarkerRecorded", 183 | "version": "0", 184 | "taskId": "29887307", 185 | "markerRecordedEventAttributes": { 186 | "markerName": "Version", 187 | "details": { 188 | "change-id": { 189 | "payloads": [ 190 | { 191 | "metadata": { 192 | "encoding": "anNvbi9wbGFpbg==" 193 | }, 194 | "data": "ImluaXRpYWwgdmVyc2lvbiI=" 195 | } 196 | ] 197 | }, 198 | "version": { 199 | "payloads": [ 200 | { 201 | "metadata": { 202 | "encoding": "anNvbi9wbGFpbg==" 203 | }, 204 | "data": "Mw==" 205 | } 206 | ] 207 | } 208 | }, 209 | "workflowTaskCompletedEventId": "4", 210 | "header": null, 211 | "failure": null 212 | }, 213 | "attributes": { 214 | "type": "markerRecordedEventAttributes", 215 | "markerName": "Version", 216 | "details": { 217 | "change-id": { 218 | "payloads": [ 219 | { 220 | "metadata": { 221 | "encoding": "anNvbi9wbGFpbg==" 222 | }, 223 | "data": "ImluaXRpYWwgdmVyc2lvbiI=" 224 | } 225 | ] 226 | }, 227 | "version": { 228 | "payloads": [ 229 | { 230 | "metadata": { 231 | "encoding": "anNvbi9wbGFpbg==" 232 | }, 233 | "data": "Mw==" 234 | } 235 | ] 236 | } 237 | }, 238 | "workflowTaskCompletedEventId": "4", 239 | "header": null, 240 | "failure": null 241 | }, 242 | "category": "marker", 243 | "id": "5", 244 | "name": "MarkerRecorded", 245 | "timestamp": "2022-07-01 UTC 20:28:48.82" 246 | }, 247 | { 248 | "eventId": "6", 249 | "eventTime": "2022-07-01T20:28:48.821619794Z", 250 | "eventType": "UpsertWorkflowSearchAttributes", 251 | "version": "0", 252 | "taskId": "29887308", 253 | "upsertWorkflowSearchAttributesEventAttributes": { 254 | "workflowTaskCompletedEventId": "4", 255 | "searchAttributes": { 256 | "indexedFields": { 257 | "TemporalChangeVersion": ["initial version-3"] 258 | } 259 | } 260 | }, 261 | "attributes": { 262 | "type": "upsertWorkflowSearchAttributesEventAttributes", 263 | "workflowTaskCompletedEventId": "4", 264 | "searchAttributes": { 265 | "indexedFields": { 266 | "TemporalChangeVersion": ["initial version-3"] 267 | } 268 | } 269 | }, 270 | "category": "command", 271 | "id": "6", 272 | "name": "UpsertWorkflowSearchAttributes", 273 | "timestamp": "2022-07-01 UTC 20:28:48.82" 274 | }, 275 | { 276 | "eventId": "7", 277 | "eventTime": "2022-07-01T20:28:48.821622711Z", 278 | "eventType": "TimerStarted", 279 | "version": "0", 280 | "taskId": "29887309", 281 | "timerStartedEventAttributes": { 282 | "timerId": "7", 283 | "startToFireTimeout": "4s", 284 | "workflowTaskCompletedEventId": "4" 285 | }, 286 | "attributes": { 287 | "type": "timerStartedEventAttributes", 288 | "timerId": "7", 289 | "startToFireTimeout": "4 seconds", 290 | "workflowTaskCompletedEventId": "4" 291 | }, 292 | "classification": "Started", 293 | "category": "timer", 294 | "id": "7", 295 | "name": "TimerStarted", 296 | "timestamp": "2022-07-01 UTC 20:28:48.82" 297 | }, 298 | { 299 | "eventId": "8", 300 | "eventTime": "2022-07-01T20:28:52.837749879Z", 301 | "eventType": "TimerFired", 302 | "version": "0", 303 | "taskId": "29887630", 304 | "timerFiredEventAttributes": { 305 | "timerId": "7", 306 | "startedEventId": "7" 307 | }, 308 | "attributes": { 309 | "type": "timerFiredEventAttributes", 310 | "timerId": "7", 311 | "startedEventId": "7" 312 | }, 313 | "classification": "Fired", 314 | "category": "timer", 315 | "id": "8", 316 | "name": "TimerFired", 317 | "timestamp": "2022-07-01 UTC 20:28:52.83" 318 | }, 319 | { 320 | "eventId": "9", 321 | "eventTime": "2022-07-01T20:28:52.837778088Z", 322 | "eventType": "WorkflowTaskScheduled", 323 | "version": "0", 324 | "taskId": "29887631", 325 | "workflowTaskScheduledEventAttributes": { 326 | "taskQueue": { 327 | "name": "MacBook-Pro-2.local:0539ec5f-2f46-4d51-8dcf-a3b9b1de6611", 328 | "kind": "Sticky" 329 | }, 330 | "startToCloseTimeout": "20s", 331 | "attempt": 1 332 | }, 333 | "attributes": { 334 | "type": "workflowTaskScheduledEventAttributes", 335 | "taskQueue": { 336 | "name": "MacBook-Pro-2.local:0539ec5f-2f46-4d51-8dcf-a3b9b1de6611", 337 | "kind": "Sticky" 338 | }, 339 | "startToCloseTimeout": "20 seconds", 340 | "attempt": 1 341 | }, 342 | "classification": "Scheduled", 343 | "category": "workflow", 344 | "id": "9", 345 | "name": "WorkflowTaskScheduled", 346 | "timestamp": "2022-07-01 UTC 20:28:52.83" 347 | }, 348 | { 349 | "eventId": "10", 350 | "eventTime": "2022-07-01T20:28:52.859456213Z", 351 | "eventType": "WorkflowTaskStarted", 352 | "version": "0", 353 | "taskId": "29887640", 354 | "workflowTaskStartedEventAttributes": { 355 | "scheduledEventId": "9", 356 | "identity": "83579@MacBook-Pro-2.local@", 357 | "requestId": "39327011-18c2-441e-aae3-5fd2bc596632" 358 | }, 359 | "attributes": { 360 | "type": "workflowTaskStartedEventAttributes", 361 | "scheduledEventId": "9", 362 | "identity": "83579@MacBook-Pro-2.local@", 363 | "requestId": "39327011-18c2-441e-aae3-5fd2bc596632" 364 | }, 365 | "classification": "Started", 366 | "category": "workflow", 367 | "id": "10", 368 | "name": "WorkflowTaskStarted", 369 | "timestamp": "2022-07-01 UTC 20:28:52.85" 370 | }, 371 | { 372 | "eventId": "11", 373 | "eventTime": "2022-07-01T20:28:52.874198546Z", 374 | "eventType": "WorkflowTaskCompleted", 375 | "version": "0", 376 | "taskId": "29887646", 377 | "workflowTaskCompletedEventAttributes": { 378 | "scheduledEventId": "9", 379 | "startedEventId": "10", 380 | "identity": "83579@MacBook-Pro-2.local@", 381 | "binaryChecksum": "e56c0141e58df0bd405138565d0526f9" 382 | }, 383 | "attributes": { 384 | "type": "workflowTaskCompletedEventAttributes", 385 | "scheduledEventId": "9", 386 | "startedEventId": "10", 387 | "identity": "83579@MacBook-Pro-2.local@", 388 | "binaryChecksum": "e56c0141e58df0bd405138565d0526f9" 389 | }, 390 | "classification": "Completed", 391 | "category": "workflow", 392 | "id": "11", 393 | "name": "WorkflowTaskCompleted", 394 | "timestamp": "2022-07-01 UTC 20:28:52.87" 395 | }, 396 | { 397 | "eventId": "12", 398 | "eventTime": "2022-07-01T20:28:52.874228004Z", 399 | "eventType": "ActivityTaskScheduled", 400 | "version": "0", 401 | "taskId": "29887647", 402 | "activityTaskScheduledEventAttributes": { 403 | "activityId": "12", 404 | "activityType": { 405 | "name": "activity.standard-visibility" 406 | }, 407 | "taskQueue": { 408 | "name": "canary-task-queue", 409 | "kind": "Normal" 410 | }, 411 | "header": { 412 | "fields": {} 413 | }, 414 | "input": { 415 | "payloads": [ 416 | "1656707332859456300", 417 | { 418 | "ID": "temporal.fixture.completed.workflow.id", 419 | "RunID": "202dcff6-7f35-4c65-995c-bcadce524fb1" 420 | } 421 | ] 422 | }, 423 | "scheduleToCloseTimeout": "260s", 424 | "scheduleToStartTimeout": "240s", 425 | "startToCloseTimeout": "20s", 426 | "heartbeatTimeout": "0s", 427 | "workflowTaskCompletedEventId": "11", 428 | "retryPolicy": { 429 | "initialInterval": "1s", 430 | "backoffCoefficient": 2, 431 | "maximumInterval": "100s", 432 | "maximumAttempts": 0, 433 | "nonRetryableErrorTypes": [] 434 | } 435 | }, 436 | "attributes": { 437 | "type": "activityTaskScheduledEventAttributes", 438 | "activityId": "12", 439 | "activityType": "activity.standard-visibility", 440 | "taskQueue": { 441 | "name": "canary-task-queue", 442 | "kind": "Normal" 443 | }, 444 | "header": { 445 | "fields": {} 446 | }, 447 | "input": { 448 | "payloads": [ 449 | "1656707332859456300", 450 | { 451 | "ID": "temporal.fixture.completed.workflow.id", 452 | "RunID": "202dcff6-7f35-4c65-995c-bcadce524fb1" 453 | } 454 | ] 455 | }, 456 | "scheduleToCloseTimeout": "4 minutes, 20 seconds", 457 | "scheduleToStartTimeout": "4 minutes", 458 | "startToCloseTimeout": "20 seconds", 459 | "heartbeatTimeout": "", 460 | "workflowTaskCompletedEventId": "11", 461 | "retryPolicy": { 462 | "initialInterval": "1s", 463 | "backoffCoefficient": 2, 464 | "maximumInterval": "100s", 465 | "maximumAttempts": 0, 466 | "nonRetryableErrorTypes": [] 467 | } 468 | }, 469 | "classification": "Scheduled", 470 | "category": "activity", 471 | "id": "12", 472 | "name": "ActivityTaskScheduled", 473 | "timestamp": "2022-07-01 UTC 20:28:52.87" 474 | }, 475 | { 476 | "eventId": "13", 477 | "eventTime": "2022-07-01T20:28:52.887850754Z", 478 | "eventType": "ActivityTaskStarted", 479 | "version": "0", 480 | "taskId": "29887658", 481 | "activityTaskStartedEventAttributes": { 482 | "scheduledEventId": "12", 483 | "identity": "83579@MacBook-Pro-2.local@", 484 | "requestId": "bf8e4570-e5ec-4eb5-a704-6181cc60c562", 485 | "attempt": 1, 486 | "lastFailure": null 487 | }, 488 | "attributes": { 489 | "type": "activityTaskStartedEventAttributes", 490 | "scheduledEventId": "12", 491 | "identity": "83579@MacBook-Pro-2.local@", 492 | "requestId": "bf8e4570-e5ec-4eb5-a704-6181cc60c562", 493 | "attempt": 1, 494 | "lastFailure": null 495 | }, 496 | "classification": "Started", 497 | "category": "activity", 498 | "id": "13", 499 | "name": "ActivityTaskStarted", 500 | "timestamp": "2022-07-01 UTC 20:28:52.88" 501 | }, 502 | { 503 | "eventId": "14", 504 | "eventTime": "2022-07-01T20:28:52.898061546Z", 505 | "eventType": "ActivityTaskCompleted", 506 | "version": "0", 507 | "taskId": "29887659", 508 | "activityTaskCompletedEventAttributes": { 509 | "result": null, 510 | "scheduledEventId": "12", 511 | "startedEventId": "13", 512 | "identity": "83579@MacBook-Pro-2.local@" 513 | }, 514 | "attributes": { 515 | "type": "activityTaskCompletedEventAttributes", 516 | "result": null, 517 | "scheduledEventId": "12", 518 | "startedEventId": "13", 519 | "identity": "83579@MacBook-Pro-2.local@" 520 | }, 521 | "classification": "Completed", 522 | "category": "activity", 523 | "id": "14", 524 | "name": "ActivityTaskCompleted", 525 | "timestamp": "2022-07-01 UTC 20:28:52.89" 526 | }, 527 | { 528 | "eventId": "15", 529 | "eventTime": "2022-07-01T20:28:52.898065338Z", 530 | "eventType": "WorkflowTaskScheduled", 531 | "version": "0", 532 | "taskId": "29887660", 533 | "workflowTaskScheduledEventAttributes": { 534 | "taskQueue": { 535 | "name": "MacBook-Pro-2.local:0539ec5f-2f46-4d51-8dcf-a3b9b1de6611", 536 | "kind": "Sticky" 537 | }, 538 | "startToCloseTimeout": "20s", 539 | "attempt": 1 540 | }, 541 | "attributes": { 542 | "type": "workflowTaskScheduledEventAttributes", 543 | "taskQueue": { 544 | "name": "MacBook-Pro-2.local:0539ec5f-2f46-4d51-8dcf-a3b9b1de6611", 545 | "kind": "Sticky" 546 | }, 547 | "startToCloseTimeout": "20 seconds", 548 | "attempt": 1 549 | }, 550 | "classification": "Scheduled", 551 | "category": "workflow", 552 | "id": "15", 553 | "name": "WorkflowTaskScheduled", 554 | "timestamp": "2022-07-01 UTC 20:28:52.89" 555 | }, 556 | { 557 | "eventId": "16", 558 | "eventTime": "2022-07-01T20:28:52.908801338Z", 559 | "eventType": "WorkflowTaskStarted", 560 | "version": "0", 561 | "taskId": "29887666", 562 | "workflowTaskStartedEventAttributes": { 563 | "scheduledEventId": "15", 564 | "identity": "83579@MacBook-Pro-2.local@", 565 | "requestId": "eed1cf96-0b77-4c4f-8f84-67a141584cf4" 566 | }, 567 | "attributes": { 568 | "type": "workflowTaskStartedEventAttributes", 569 | "scheduledEventId": "15", 570 | "identity": "83579@MacBook-Pro-2.local@", 571 | "requestId": "eed1cf96-0b77-4c4f-8f84-67a141584cf4" 572 | }, 573 | "classification": "Started", 574 | "category": "workflow", 575 | "id": "16", 576 | "name": "WorkflowTaskStarted", 577 | "timestamp": "2022-07-01 UTC 20:28:52.90" 578 | }, 579 | { 580 | "eventId": "17", 581 | "eventTime": "2022-07-01T20:28:52.916365546Z", 582 | "eventType": "WorkflowTaskCompleted", 583 | "version": "0", 584 | "taskId": "29887669", 585 | "workflowTaskCompletedEventAttributes": { 586 | "scheduledEventId": "15", 587 | "startedEventId": "16", 588 | "identity": "83579@MacBook-Pro-2.local@", 589 | "binaryChecksum": "e56c0141e58df0bd405138565d0526f9" 590 | }, 591 | "attributes": { 592 | "type": "workflowTaskCompletedEventAttributes", 593 | "scheduledEventId": "15", 594 | "startedEventId": "16", 595 | "identity": "83579@MacBook-Pro-2.local@", 596 | "binaryChecksum": "e56c0141e58df0bd405138565d0526f9" 597 | }, 598 | "classification": "Completed", 599 | "category": "workflow", 600 | "id": "17", 601 | "name": "WorkflowTaskCompleted", 602 | "timestamp": "2022-07-01 UTC 20:28:52.91" 603 | }, 604 | { 605 | "eventId": "18", 606 | "eventTime": "2022-07-01T20:28:52.916373379Z", 607 | "eventType": "WorkflowExecutionCompleted", 608 | "version": "0", 609 | "taskId": "29887670", 610 | "workflowExecutionCompletedEventAttributes": { 611 | "result": null, 612 | "workflowTaskCompletedEventId": "17", 613 | "newExecutionRunId": "" 614 | }, 615 | "attributes": { 616 | "type": "workflowExecutionCompletedEventAttributes", 617 | "result": null, 618 | "workflowTaskCompletedEventId": "17", 619 | "newExecutionRunId": "" 620 | }, 621 | "classification": "Completed", 622 | "category": "workflow", 623 | "id": "18", 624 | "name": "WorkflowExecutionCompleted", 625 | "timestamp": "2022-07-01 UTC 20:28:52.91" 626 | } 627 | ] 628 | } 629 | -------------------------------------------------------------------------------- /samples/multiple-signals-terminated-after-days.json: -------------------------------------------------------------------------------- 1 | { 2 | "events": [ 3 | { 4 | "eventId": "1", 5 | "eventTime": "2022-07-12T18:23:33.000Z", 6 | "eventType": "WorkflowExecutionStarted", 7 | "version": "0", 8 | "taskId": "6291495", 9 | "workflowExecutionStartedEventAttributes": { 10 | "workflowType": { "name": "calculator" }, 11 | "parentWorkflowNamespace": "", 12 | "parentInitiatedEventId": "0", 13 | "taskQueue": { "name": "replay-history", "kind": "Normal" }, 14 | "input": { "payloads": [] }, 15 | "workflowTaskTimeout": "10s", 16 | "continuedExecutionRunId": "", 17 | "initiator": "Unspecified", 18 | "originalExecutionRunId": "dd5cd8b5-3afb-438a-b908-65aae9edfa60", 19 | "identity": "25992@AnithaLaptop", 20 | "firstExecutionRunId": "dd5cd8b5-3afb-438a-b908-65aae9edfa60", 21 | "attempt": 1, 22 | "cronSchedule": "", 23 | "firstWorkflowTaskBackoff": "0s", 24 | "header": { "fields": {} } 25 | } 26 | }, 27 | { 28 | "eventId": "2", 29 | "eventTime": "2022-07-12T18:23:33.000Z", 30 | "eventType": "WorkflowTaskScheduled", 31 | "version": "0", 32 | "taskId": "6291496", 33 | "workflowTaskScheduledEventAttributes": { 34 | "taskQueue": { "name": "replay-history", "kind": "Normal" }, 35 | "startToCloseTimeout": "10s", 36 | "attempt": 1 37 | } 38 | }, 39 | { 40 | "eventId": "3", 41 | "eventTime": "2022-07-12T18:23:33.000Z", 42 | "eventType": "WorkflowTaskStarted", 43 | "version": "0", 44 | "taskId": "6291503", 45 | "workflowTaskStartedEventAttributes": { 46 | "scheduledEventId": "2", 47 | "identity": "43520@AnithaLaptop", 48 | "requestId": "8d76214b-4f54-45fe-a097-ea82934fd28a" 49 | } 50 | }, 51 | { 52 | "eventId": "4", 53 | "eventTime": "2022-07-12T18:23:33.000Z", 54 | "eventType": "WorkflowTaskCompleted", 55 | "version": "0", 56 | "taskId": "6291507", 57 | "workflowTaskCompletedEventAttributes": { 58 | "scheduledEventId": "2", 59 | "startedEventId": "3", 60 | "identity": "43520@AnithaLaptop", 61 | "binaryChecksum": "@temporalio/worker@1.0.0-rc.0" 62 | } 63 | }, 64 | { 65 | "eventId": "5", 66 | "eventTime": "2022-07-12T18:23:33.000Z", 67 | "eventType": "WorkflowExecutionSignaled", 68 | "version": "0", 69 | "taskId": "6291508", 70 | "workflowExecutionSignaledEventAttributes": { 71 | "signalName": "add", 72 | "input": { "payloads": [{ "metadata": { "encoding": "anNvbi9wbGFpbg==" }, "data": "Mg==" }] }, 73 | "identity": "25992@AnithaLaptop" 74 | } 75 | }, 76 | { 77 | "eventId": "6", 78 | "eventTime": "2022-07-12T18:23:33.000Z", 79 | "eventType": "WorkflowExecutionSignaled", 80 | "version": "0", 81 | "taskId": "6291509", 82 | "workflowExecutionSignaledEventAttributes": { 83 | "signalName": "mul", 84 | "input": { "payloads": [{ "metadata": { "encoding": "anNvbi9wbGFpbg==" }, "data": "Mw==" }] }, 85 | "identity": "25992@AnithaLaptop" 86 | } 87 | }, 88 | { 89 | "eventId": "7", 90 | "eventTime": "2022-07-12T18:23:33.000Z", 91 | "eventType": "WorkflowExecutionSignaled", 92 | "version": "0", 93 | "taskId": "6291510", 94 | "workflowExecutionSignaledEventAttributes": { 95 | "signalName": "sub", 96 | "input": { "payloads": [{ "metadata": { "encoding": "anNvbi9wbGFpbg==" }, "data": "Ng==" }] }, 97 | "identity": "25992@AnithaLaptop" 98 | } 99 | }, 100 | { 101 | "eventId": "8", 102 | "eventTime": "2022-07-12T18:23:33.000Z", 103 | "eventType": "WorkflowExecutionSignaled", 104 | "version": "0", 105 | "taskId": "6291511", 106 | "workflowExecutionSignaledEventAttributes": { 107 | "signalName": "inverseFraction", 108 | "input": { "payloads": [] }, 109 | "identity": "25992@AnithaLaptop" 110 | } 111 | }, 112 | { 113 | "eventId": "9", 114 | "eventTime": "2022-07-12T18:23:33.000Z", 115 | "eventType": "WorkflowTaskScheduled", 116 | "version": "0", 117 | "taskId": "6291512", 118 | "workflowTaskScheduledEventAttributes": { 119 | "taskQueue": { "name": "43520@AnithaLaptop-replay-history-4e901e0dde894a59b69982b9b6f10059", "kind": "Sticky" }, 120 | "startToCloseTimeout": "10s", 121 | "attempt": 1 122 | } 123 | }, 124 | { 125 | "eventId": "10", 126 | "eventTime": "2022-07-12T18:23:33.000Z", 127 | "eventType": "WorkflowTaskStarted", 128 | "version": "0", 129 | "taskId": "6291513", 130 | "workflowTaskStartedEventAttributes": { 131 | "scheduledEventId": "9", 132 | "identity": "43520@AnithaLaptop", 133 | "requestId": "request-from-RespondWorkflowTaskCompleted" 134 | } 135 | }, 136 | { 137 | "eventId": "11", 138 | "eventTime": "2022-07-12T18:23:33.000Z", 139 | "eventType": "WorkflowTaskCompleted", 140 | "version": "0", 141 | "taskId": "6291517", 142 | "workflowTaskCompletedEventAttributes": { 143 | "scheduledEventId": "9", 144 | "startedEventId": "10", 145 | "identity": "43520@AnithaLaptop", 146 | "binaryChecksum": "@temporalio/worker@1.0.0-rc.0" 147 | } 148 | }, 149 | { 150 | "eventId": "12", 151 | "eventTime": "2022-08-03T19:16:00.000Z", 152 | "eventType": "WorkflowExecutionTerminated", 153 | "version": "0", 154 | "taskId": "8388614", 155 | "workflowExecutionTerminatedEventAttributes": { "reason": "", "identity": "" } 156 | } 157 | ] 158 | } 159 | -------------------------------------------------------------------------------- /samples/single-activity.json: -------------------------------------------------------------------------------- 1 | { 2 | "events": [ 3 | { 4 | "eventId": "1", 5 | "eventTime": "2022-07-28T23:47:39.929656Z", 6 | "eventType": "WorkflowExecutionStarted", 7 | "version": "0", 8 | "taskId": "1049561", 9 | "workflowExecutionStartedEventAttributes": { 10 | "workflowType": { 11 | "name": "http" 12 | }, 13 | "parentWorkflowNamespace": "", 14 | "parentWorkflowExecution": null, 15 | "parentInitiatedEventId": "0", 16 | "taskQueue": { 17 | "name": "test", 18 | "kind": "Normal" 19 | }, 20 | "input": { 21 | "payloads": [] 22 | }, 23 | "workflowExecutionTimeout": null, 24 | "workflowRunTimeout": null, 25 | "workflowTaskTimeout": "10s", 26 | "continuedExecutionRunId": "", 27 | "initiator": "Unspecified", 28 | "continuedFailure": null, 29 | "lastCompletionResult": null, 30 | "originalExecutionRunId": "0d693592-0d25-4be9-b8c6-de6a210da5cc", 31 | "identity": "15012@Roey-Bermans-MacBook-Pro.local", 32 | "firstExecutionRunId": "0d693592-0d25-4be9-b8c6-de6a210da5cc", 33 | "retryPolicy": null, 34 | "attempt": 1, 35 | "workflowExecutionExpirationTime": null, 36 | "cronSchedule": "", 37 | "firstWorkflowTaskBackoff": "0s", 38 | "memo": null, 39 | "searchAttributes": null, 40 | "prevAutoResetPoints": null, 41 | "header": { 42 | "fields": {} 43 | } 44 | } 45 | }, 46 | { 47 | "eventId": "2", 48 | "eventTime": "2022-07-28T23:47:39.929670Z", 49 | "eventType": "WorkflowTaskScheduled", 50 | "version": "0", 51 | "taskId": "1049562", 52 | "workflowTaskScheduledEventAttributes": { 53 | "taskQueue": { 54 | "name": "test", 55 | "kind": "Normal" 56 | }, 57 | "startToCloseTimeout": "10s", 58 | "attempt": 1 59 | } 60 | }, 61 | { 62 | "eventId": "3", 63 | "eventTime": "2022-07-28T23:47:39.930899Z", 64 | "eventType": "WorkflowTaskStarted", 65 | "version": "0", 66 | "taskId": "1049566", 67 | "workflowTaskStartedEventAttributes": { 68 | "scheduledEventId": "2", 69 | "identity": "12835@Roey-Bermans-MacBook-Pro.local", 70 | "requestId": "51066cdb-3e27-4fa5-83b9-815c900c7321" 71 | } 72 | }, 73 | { 74 | "eventId": "4", 75 | "eventTime": "2022-07-28T23:47:39.969462Z", 76 | "eventType": "WorkflowTaskCompleted", 77 | "version": "0", 78 | "taskId": "1049569", 79 | "workflowTaskCompletedEventAttributes": { 80 | "scheduledEventId": "2", 81 | "startedEventId": "3", 82 | "identity": "12835@Roey-Bermans-MacBook-Pro.local", 83 | "binaryChecksum": "@temporalio/worker@1.0.0" 84 | } 85 | }, 86 | { 87 | "eventId": "5", 88 | "eventTime": "2022-07-28T23:47:39.969490Z", 89 | "eventType": "ActivityTaskScheduled", 90 | "version": "0", 91 | "taskId": "1049570", 92 | "activityTaskScheduledEventAttributes": { 93 | "activityId": "1", 94 | "activityType": { 95 | "name": "httpGet" 96 | }, 97 | "namespace": "", 98 | "taskQueue": { 99 | "name": "test", 100 | "kind": "Normal" 101 | }, 102 | "header": { 103 | "fields": {} 104 | }, 105 | "input": { 106 | "payloads": [ 107 | { 108 | "metadata": { 109 | "encoding": "anNvbi9wbGFpbg==" 110 | }, 111 | "data": "Imh0dHBzOi8vdGVtcG9yYWwuaW8i" 112 | } 113 | ] 114 | }, 115 | "scheduleToCloseTimeout": "0s", 116 | "scheduleToStartTimeout": "0s", 117 | "startToCloseTimeout": "60s", 118 | "heartbeatTimeout": "0s", 119 | "workflowTaskCompletedEventId": "4", 120 | "retryPolicy": { 121 | "initialInterval": "1s", 122 | "backoffCoefficient": 2, 123 | "maximumInterval": "100s", 124 | "maximumAttempts": 0, 125 | "nonRetryableErrorTypes": [] 126 | } 127 | } 128 | }, 129 | { 130 | "eventId": "6", 131 | "eventTime": "2022-07-28T23:47:39.970362Z", 132 | "eventType": "ActivityTaskStarted", 133 | "version": "0", 134 | "taskId": "1049574", 135 | "activityTaskStartedEventAttributes": { 136 | "scheduledEventId": "5", 137 | "identity": "12835@Roey-Bermans-MacBook-Pro.local", 138 | "requestId": "60542976-7c4c-4ced-9012-0ac1e4ce6625", 139 | "attempt": 1, 140 | "lastFailure": null 141 | } 142 | }, 143 | { 144 | "eventId": "7", 145 | "eventTime": "2022-07-28T23:47:39.977970Z", 146 | "eventType": "ActivityTaskCompleted", 147 | "version": "0", 148 | "taskId": "1049575", 149 | "activityTaskCompletedEventAttributes": { 150 | "result": { 151 | "payloads": [ 152 | { 153 | "metadata": { 154 | "encoding": "anNvbi9wbGFpbg==" 155 | }, 156 | "data": "IjxodG1sPjxib2R5PmhlbGxvIGZyb20gaHR0cHM6Ly90ZW1wb3JhbC5pbzwvYm9keT48L2h0bWw+Ig==" 157 | } 158 | ] 159 | }, 160 | "scheduledEventId": "5", 161 | "startedEventId": "6", 162 | "identity": "12835@Roey-Bermans-MacBook-Pro.local" 163 | } 164 | }, 165 | { 166 | "eventId": "8", 167 | "eventTime": "2022-07-28T23:47:39.977975Z", 168 | "eventType": "WorkflowTaskScheduled", 169 | "version": "0", 170 | "taskId": "1049576", 171 | "workflowTaskScheduledEventAttributes": { 172 | "taskQueue": { 173 | "name": "12835@Roey-Bermans-MacBook-Pro.local-test-45211b680a384c8ea96b09ff31494be6", 174 | "kind": "Sticky" 175 | }, 176 | "startToCloseTimeout": "10s", 177 | "attempt": 1 178 | } 179 | }, 180 | { 181 | "eventId": "9", 182 | "eventTime": "2022-07-28T23:47:39.978813Z", 183 | "eventType": "WorkflowTaskStarted", 184 | "version": "0", 185 | "taskId": "1049580", 186 | "workflowTaskStartedEventAttributes": { 187 | "scheduledEventId": "8", 188 | "identity": "12835@Roey-Bermans-MacBook-Pro.local", 189 | "requestId": "bba5746e-3484-4929-a362-3e0d17ccaa96" 190 | } 191 | }, 192 | { 193 | "eventId": "10", 194 | "eventTime": "2022-07-28T23:47:39.985870Z", 195 | "eventType": "WorkflowTaskCompleted", 196 | "version": "0", 197 | "taskId": "1049583", 198 | "workflowTaskCompletedEventAttributes": { 199 | "scheduledEventId": "8", 200 | "startedEventId": "9", 201 | "identity": "12835@Roey-Bermans-MacBook-Pro.local", 202 | "binaryChecksum": "@temporalio/worker@1.0.0" 203 | } 204 | }, 205 | { 206 | "eventId": "11", 207 | "eventTime": "2022-07-28T23:47:39.985884Z", 208 | "eventType": "WorkflowExecutionCompleted", 209 | "version": "0", 210 | "taskId": "1049584", 211 | "workflowExecutionCompletedEventAttributes": { 212 | "result": { 213 | "payloads": [ 214 | { 215 | "metadata": { 216 | "encoding": "anNvbi9wbGFpbg==" 217 | }, 218 | "data": "IjxodG1sPjxib2R5PmhlbGxvIGZyb20gaHR0cHM6Ly90ZW1wb3JhbC5pbzwvYm9keT48L2h0bWw+Ig==" 219 | } 220 | ] 221 | }, 222 | "workflowTaskCompletedEventId": "10", 223 | "newExecutionRunId": "" 224 | } 225 | } 226 | ] 227 | } 228 | -------------------------------------------------------------------------------- /samples/subscription-canceled.json: -------------------------------------------------------------------------------- 1 | { 2 | "events": [ 3 | { 4 | "eventId": "1", 5 | "eventTime": "2022-08-05T21:18:01.000Z", 6 | "eventType": "WorkflowExecutionStarted", 7 | "version": "0", 8 | "taskId": "13631493", 9 | "workflowExecutionStartedEventAttributes": { 10 | "workflowType": { "name": "example" }, 11 | "parentWorkflowNamespace": "", 12 | "parentInitiatedEventId": "0", 13 | "taskQueue": { "name": "hello-world", "kind": "Normal" }, 14 | "input": { "payloads": [{ "metadata": { "encoding": "anNvbi9wbGFpbg==" }, "data": "IlRlbXBvcmFsIg==" }] }, 15 | "workflowTaskTimeout": "10s", 16 | "continuedExecutionRunId": "", 17 | "initiator": "Unspecified", 18 | "originalExecutionRunId": "3edd6d4a-b4b2-4bcf-ab64-985461e1c9c6", 19 | "identity": "31476@AnithaLaptop", 20 | "firstExecutionRunId": "3edd6d4a-b4b2-4bcf-ab64-985461e1c9c6", 21 | "attempt": 1, 22 | "cronSchedule": "", 23 | "firstWorkflowTaskBackoff": "0s", 24 | "header": { "fields": {} } 25 | } 26 | }, 27 | { 28 | "eventId": "2", 29 | "eventTime": "2022-08-05T21:18:01.000Z", 30 | "eventType": "WorkflowTaskScheduled", 31 | "version": "0", 32 | "taskId": "13631494", 33 | "workflowTaskScheduledEventAttributes": { 34 | "taskQueue": { "name": "hello-world", "kind": "Normal" }, 35 | "startToCloseTimeout": "10s", 36 | "attempt": 1 37 | } 38 | }, 39 | { 40 | "eventId": "3", 41 | "eventTime": "2022-08-05T21:18:01.000Z", 42 | "eventType": "WorkflowTaskStarted", 43 | "version": "0", 44 | "taskId": "13631499", 45 | "workflowTaskStartedEventAttributes": { 46 | "scheduledEventId": "2", 47 | "identity": "18500@AnithaLaptop", 48 | "requestId": "82ab40ac-d102-4fb8-87ab-e48e6757501e" 49 | } 50 | }, 51 | { 52 | "eventId": "4", 53 | "eventTime": "2022-08-05T21:18:01.000Z", 54 | "eventType": "WorkflowTaskCompleted", 55 | "version": "0", 56 | "taskId": "13631503", 57 | "workflowTaskCompletedEventAttributes": { 58 | "scheduledEventId": "2", 59 | "startedEventId": "3", 60 | "identity": "18500@AnithaLaptop", 61 | "binaryChecksum": "@temporalio/worker@1.0.0-rc.0" 62 | } 63 | }, 64 | { 65 | "eventId": "5", 66 | "eventTime": "2022-08-05T21:18:01.000Z", 67 | "eventType": "ActivityTaskScheduled", 68 | "version": "0", 69 | "taskId": "13631504", 70 | "activityTaskScheduledEventAttributes": { 71 | "activityId": "1", 72 | "activityType": { "name": "greet" }, 73 | "namespace": "", 74 | "taskQueue": { "name": "hello-world", "kind": "Normal" }, 75 | "header": { "fields": {} }, 76 | "input": { "payloads": [{ "metadata": { "encoding": "anNvbi9wbGFpbg==" }, "data": "IlRlbXBvcmFsIg==" }] }, 77 | "scheduleToCloseTimeout": "0s", 78 | "scheduleToStartTimeout": "0s", 79 | "startToCloseTimeout": "60s", 80 | "heartbeatTimeout": "0s", 81 | "workflowTaskCompletedEventId": "4", 82 | "retryPolicy": { 83 | "nonRetryableErrorTypes": [], 84 | "initialInterval": "1s", 85 | "backoffCoefficient": 2, 86 | "maximumInterval": "100s", 87 | "maximumAttempts": 0 88 | } 89 | } 90 | }, 91 | { 92 | "eventId": "6", 93 | "eventTime": "2022-08-05T21:18:01.000Z", 94 | "eventType": "ActivityTaskStarted", 95 | "version": "0", 96 | "taskId": "13631510", 97 | "activityTaskStartedEventAttributes": { 98 | "scheduledEventId": "5", 99 | "identity": "18500@AnithaLaptop", 100 | "requestId": "c3bb62ac-b191-4bb4-9043-a34bc1ca22ea", 101 | "attempt": 1 102 | } 103 | }, 104 | { 105 | "eventId": "7", 106 | "eventTime": "2022-08-05T21:18:01.000Z", 107 | "eventType": "ActivityTaskCompleted", 108 | "version": "0", 109 | "taskId": "13631511", 110 | "activityTaskCompletedEventAttributes": { 111 | "result": { 112 | "payloads": [{ "metadata": { "encoding": "anNvbi9wbGFpbg==" }, "data": "IkhlbGxvLCBUZW1wb3JhbCEi" }] 113 | }, 114 | "scheduledEventId": "5", 115 | "startedEventId": "6", 116 | "identity": "18500@AnithaLaptop" 117 | } 118 | }, 119 | { 120 | "eventId": "8", 121 | "eventTime": "2022-08-05T21:18:01.000Z", 122 | "eventType": "WorkflowTaskScheduled", 123 | "version": "0", 124 | "taskId": "13631512", 125 | "workflowTaskScheduledEventAttributes": { 126 | "taskQueue": { "name": "18500@AnithaLaptop-hello-world-94e9e9e823094f0baa9ad3f2f34a089b", "kind": "Sticky" }, 127 | "startToCloseTimeout": "10s", 128 | "attempt": 1 129 | } 130 | }, 131 | { 132 | "eventId": "9", 133 | "eventTime": "2022-08-05T21:18:01.000Z", 134 | "eventType": "WorkflowTaskStarted", 135 | "version": "0", 136 | "taskId": "13631516", 137 | "workflowTaskStartedEventAttributes": { 138 | "scheduledEventId": "8", 139 | "identity": "18500@AnithaLaptop", 140 | "requestId": "650c0c06-0447-46a4-9bf5-c2a80d7e89c3" 141 | } 142 | }, 143 | { 144 | "eventId": "10", 145 | "eventTime": "2022-08-05T21:18:01.000Z", 146 | "eventType": "WorkflowTaskCompleted", 147 | "version": "0", 148 | "taskId": "13631520", 149 | "workflowTaskCompletedEventAttributes": { 150 | "scheduledEventId": "8", 151 | "startedEventId": "9", 152 | "identity": "18500@AnithaLaptop", 153 | "binaryChecksum": "@temporalio/worker@1.0.0-rc.0" 154 | } 155 | }, 156 | { 157 | "eventId": "11", 158 | "eventTime": "2022-08-05T21:18:01.000Z", 159 | "eventType": "TimerStarted", 160 | "version": "0", 161 | "taskId": "13631521", 162 | "timerStartedEventAttributes": { 163 | "timerId": "1", 164 | "startToFireTimeout": "10s", 165 | "workflowTaskCompletedEventId": "10" 166 | } 167 | }, 168 | { 169 | "eventId": "12", 170 | "eventTime": "2022-08-05T21:18:11.000Z", 171 | "eventType": "TimerFired", 172 | "version": "0", 173 | "taskId": "13631524", 174 | "timerFiredEventAttributes": { "timerId": "1", "startedEventId": "11" } 175 | }, 176 | { 177 | "eventId": "13", 178 | "eventTime": "2022-08-05T21:18:11.000Z", 179 | "eventType": "WorkflowTaskScheduled", 180 | "version": "0", 181 | "taskId": "13631525", 182 | "workflowTaskScheduledEventAttributes": { 183 | "taskQueue": { "name": "18500@AnithaLaptop-hello-world-94e9e9e823094f0baa9ad3f2f34a089b", "kind": "Sticky" }, 184 | "startToCloseTimeout": "10s", 185 | "attempt": 1 186 | } 187 | }, 188 | { 189 | "eventId": "14", 190 | "eventTime": "2022-08-05T21:18:11.000Z", 191 | "eventType": "WorkflowTaskStarted", 192 | "version": "0", 193 | "taskId": "13631529", 194 | "workflowTaskStartedEventAttributes": { 195 | "scheduledEventId": "13", 196 | "identity": "18500@AnithaLaptop", 197 | "requestId": "2a48a740-5d88-4705-aac3-dd192a7c5aba" 198 | } 199 | }, 200 | { 201 | "eventId": "15", 202 | "eventTime": "2022-08-05T21:18:11.000Z", 203 | "eventType": "WorkflowTaskCompleted", 204 | "version": "0", 205 | "taskId": "13631533", 206 | "workflowTaskCompletedEventAttributes": { 207 | "scheduledEventId": "13", 208 | "startedEventId": "14", 209 | "identity": "18500@AnithaLaptop", 210 | "binaryChecksum": "@temporalio/worker@1.0.0-rc.0" 211 | } 212 | }, 213 | { 214 | "eventId": "16", 215 | "eventTime": "2022-08-05T21:18:11.000Z", 216 | "eventType": "ActivityTaskScheduled", 217 | "version": "0", 218 | "taskId": "13631534", 219 | "activityTaskScheduledEventAttributes": { 220 | "activityId": "2", 221 | "activityType": { "name": "greet" }, 222 | "namespace": "", 223 | "taskQueue": { "name": "hello-world", "kind": "Normal" }, 224 | "header": { "fields": {} }, 225 | "input": { "payloads": [{ "metadata": { "encoding": "anNvbi9wbGFpbg==" }, "data": "IlRlbXBvcmFsIg==" }] }, 226 | "scheduleToCloseTimeout": "0s", 227 | "scheduleToStartTimeout": "0s", 228 | "startToCloseTimeout": "60s", 229 | "heartbeatTimeout": "0s", 230 | "workflowTaskCompletedEventId": "15", 231 | "retryPolicy": { 232 | "nonRetryableErrorTypes": [], 233 | "initialInterval": "1s", 234 | "backoffCoefficient": 2, 235 | "maximumInterval": "100s", 236 | "maximumAttempts": 0 237 | } 238 | } 239 | }, 240 | { 241 | "eventId": "17", 242 | "eventTime": "2022-08-05T21:18:11.000Z", 243 | "eventType": "ActivityTaskStarted", 244 | "version": "0", 245 | "taskId": "13631539", 246 | "activityTaskStartedEventAttributes": { 247 | "scheduledEventId": "16", 248 | "identity": "18500@AnithaLaptop", 249 | "requestId": "10ff0348-2de3-4fb4-9ec7-71130d6caa83", 250 | "attempt": 1 251 | } 252 | }, 253 | { 254 | "eventId": "18", 255 | "eventTime": "2022-08-05T21:18:11.000Z", 256 | "eventType": "ActivityTaskCompleted", 257 | "version": "0", 258 | "taskId": "13631540", 259 | "activityTaskCompletedEventAttributes": { 260 | "result": { 261 | "payloads": [{ "metadata": { "encoding": "anNvbi9wbGFpbg==" }, "data": "IkhlbGxvLCBUZW1wb3JhbCEi" }] 262 | }, 263 | "scheduledEventId": "16", 264 | "startedEventId": "17", 265 | "identity": "18500@AnithaLaptop" 266 | } 267 | }, 268 | { 269 | "eventId": "19", 270 | "eventTime": "2022-08-05T21:18:11.000Z", 271 | "eventType": "WorkflowTaskScheduled", 272 | "version": "0", 273 | "taskId": "13631541", 274 | "workflowTaskScheduledEventAttributes": { 275 | "taskQueue": { "name": "18500@AnithaLaptop-hello-world-94e9e9e823094f0baa9ad3f2f34a089b", "kind": "Sticky" }, 276 | "startToCloseTimeout": "10s", 277 | "attempt": 1 278 | } 279 | }, 280 | { 281 | "eventId": "20", 282 | "eventTime": "2022-08-05T21:18:11.000Z", 283 | "eventType": "WorkflowTaskStarted", 284 | "version": "0", 285 | "taskId": "13631545", 286 | "workflowTaskStartedEventAttributes": { 287 | "scheduledEventId": "19", 288 | "identity": "18500@AnithaLaptop", 289 | "requestId": "c421fd28-907e-442f-a898-8a663588555c" 290 | } 291 | }, 292 | { 293 | "eventId": "21", 294 | "eventTime": "2022-08-05T21:18:11.000Z", 295 | "eventType": "WorkflowTaskCompleted", 296 | "version": "0", 297 | "taskId": "13631549", 298 | "workflowTaskCompletedEventAttributes": { 299 | "scheduledEventId": "19", 300 | "startedEventId": "20", 301 | "identity": "18500@AnithaLaptop", 302 | "binaryChecksum": "@temporalio/worker@1.0.0-rc.0" 303 | } 304 | }, 305 | { 306 | "eventId": "22", 307 | "eventTime": "2022-08-05T21:18:11.000Z", 308 | "eventType": "TimerStarted", 309 | "version": "0", 310 | "taskId": "13631550", 311 | "timerStartedEventAttributes": { 312 | "timerId": "2", 313 | "startToFireTimeout": "10s", 314 | "workflowTaskCompletedEventId": "21" 315 | } 316 | }, 317 | { 318 | "eventId": "23", 319 | "eventTime": "2022-08-05T21:18:21.000Z", 320 | "eventType": "TimerFired", 321 | "version": "0", 322 | "taskId": "13631553", 323 | "timerFiredEventAttributes": { "timerId": "2", "startedEventId": "22" } 324 | }, 325 | { 326 | "eventId": "24", 327 | "eventTime": "2022-08-05T21:18:21.000Z", 328 | "eventType": "WorkflowTaskScheduled", 329 | "version": "0", 330 | "taskId": "13631554", 331 | "workflowTaskScheduledEventAttributes": { 332 | "taskQueue": { "name": "18500@AnithaLaptop-hello-world-94e9e9e823094f0baa9ad3f2f34a089b", "kind": "Sticky" }, 333 | "startToCloseTimeout": "10s", 334 | "attempt": 1 335 | } 336 | }, 337 | { 338 | "eventId": "25", 339 | "eventTime": "2022-08-05T21:18:23.000Z", 340 | "eventType": "WorkflowExecutionCancelRequested", 341 | "version": "0", 342 | "taskId": "13631558", 343 | "workflowExecutionCancelRequestedEventAttributes": { 344 | "cause": "", 345 | "externalInitiatedEventId": "0", 346 | "identity": "2060@AnithaLaptop" 347 | } 348 | }, 349 | { 350 | "eventId": "26", 351 | "eventTime": "2022-08-05T21:18:31.000Z", 352 | "eventType": "WorkflowTaskTimedOut", 353 | "version": "0", 354 | "taskId": "13631560", 355 | "workflowTaskTimedOutEventAttributes": { 356 | "scheduledEventId": "24", 357 | "startedEventId": "0", 358 | "timeoutType": "ScheduleToStart" 359 | } 360 | }, 361 | { 362 | "eventId": "27", 363 | "eventTime": "2022-08-05T21:18:31.000Z", 364 | "eventType": "WorkflowTaskScheduled", 365 | "version": "0", 366 | "taskId": "13631561", 367 | "workflowTaskScheduledEventAttributes": { 368 | "taskQueue": { "name": "hello-world", "kind": "Normal" }, 369 | "startToCloseTimeout": "10s", 370 | "attempt": 1 371 | } 372 | }, 373 | { 374 | "eventId": "28", 375 | "eventTime": "2022-08-05T21:18:31.000Z", 376 | "eventType": "WorkflowTaskStarted", 377 | "version": "0", 378 | "taskId": "13631564", 379 | "workflowTaskStartedEventAttributes": { 380 | "scheduledEventId": "27", 381 | "identity": "15820@AnithaLaptop", 382 | "requestId": "019b1cb5-585b-4154-8f92-1c6a52da1bcb" 383 | } 384 | }, 385 | { 386 | "eventId": "29", 387 | "eventTime": "2022-08-05T21:18:31.000Z", 388 | "eventType": "WorkflowTaskCompleted", 389 | "version": "0", 390 | "taskId": "13631568", 391 | "workflowTaskCompletedEventAttributes": { 392 | "scheduledEventId": "27", 393 | "startedEventId": "28", 394 | "identity": "15820@AnithaLaptop", 395 | "binaryChecksum": "@temporalio/worker@1.0.0-rc.0" 396 | } 397 | }, 398 | { 399 | "eventId": "30", 400 | "eventTime": "2022-08-05T21:18:31.000Z", 401 | "eventType": "WorkflowExecutionCanceled", 402 | "version": "0", 403 | "taskId": "13631569", 404 | "workflowExecutionCanceledEventAttributes": { "workflowTaskCompletedEventId": "29" } 405 | } 406 | ] 407 | } 408 | -------------------------------------------------------------------------------- /scripts/reload-webview.mjs: -------------------------------------------------------------------------------- 1 | import * as http from "node:http" 2 | import * as util from "node:util" 3 | 4 | await util 5 | .promisify(http.get)("http://127.0.0.1:55666") 6 | .catch(() => { 7 | // ignore 8 | }) 9 | -------------------------------------------------------------------------------- /webview/src/components/breakpoint-button.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 | 28 |
29 | 30 | 35 | -------------------------------------------------------------------------------- /webview/src/components/icon/icon.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 | {#if icon} 23 | 24 | {/if} 25 | -------------------------------------------------------------------------------- /webview/src/components/icon/svg/arrow-small-left.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /webview/src/components/icon/svg/arrow-small-right.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /webview/src/components/icon/svg/circle-filled.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /webview/src/components/icon/svg/error.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /webview/src/components/icon/svg/svg.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /webview/src/components/submit-button.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 33 | -------------------------------------------------------------------------------- /webview/src/globals.d.ts: -------------------------------------------------------------------------------- 1 | export declare global { 2 | // Placeholder for the webview VSCode API, types not verified 3 | // NOTE: `vscode` is set on the global object as part of the page setup in the main HTML file 4 | const vscode: { 5 | postMessage(message: unknown): void 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /webview/src/lib.ts: -------------------------------------------------------------------------------- 1 | import { temporal, google } from "@temporalio/proto" 2 | 3 | export type History = temporal.api.history.v1.IHistory 4 | export type HistoryEvent = temporal.api.history.v1.IHistoryEvent 5 | export type EventType = temporal.api.enums.v1.EventType 6 | // eslint-disable-next-line @typescript-eslint/naming-convention 7 | export const EventType = temporal.api.enums.v1.EventType 8 | export type Timestamp = google.protobuf.ITimestamp 9 | 10 | export interface CategorizedEvent extends HistoryEvent { 11 | category: "EVENT" | "COMMAND" 12 | } 13 | 14 | export interface WorkflowTask { 15 | scheduled?: Date 16 | started?: Date 17 | startedEventId?: number 18 | events: CategorizedEvent[] 19 | status: "COMPLETED" | "FAILED" | "TIMED_OUT" 20 | hasBreakpoint: boolean 21 | } 22 | 23 | export interface ViewSettings { 24 | address: string 25 | tls: boolean 26 | hasClientCert: boolean 27 | hasClientPrivateKey: boolean 28 | } 29 | -------------------------------------------------------------------------------- /webview/src/pages/app.ts: -------------------------------------------------------------------------------- 1 | import App from "../views/app.svelte" 2 | import { 3 | provideVSCodeDesignSystem, 4 | vsCodeButton, 5 | vsCodePanels, 6 | vsCodePanelTab, 7 | vsCodePanelView, 8 | vsCodeProgressRing, 9 | vsCodeTextField, 10 | vsCodeCheckbox, 11 | vsCodeDivider, 12 | } from "@vscode/webview-ui-toolkit" 13 | 14 | const app = new App({ 15 | target: document.body, 16 | }) 17 | 18 | provideVSCodeDesignSystem().register( 19 | vsCodeButton(), 20 | vsCodePanels(), 21 | vsCodePanelTab(), 22 | vsCodePanelView(), 23 | vsCodeProgressRing(), 24 | vsCodeTextField(), 25 | vsCodeCheckbox(), 26 | vsCodeDivider(), 27 | ) 28 | 29 | export default app 30 | -------------------------------------------------------------------------------- /webview/src/utilities/duration.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "vitest" 2 | import { duration } from "./duration" 3 | import { historyFromJSON } from "@temporalio/common/lib/proto-utils" 4 | import completedEvents from "../../../samples/events.completed.json" 5 | 6 | describe("duration", () => { 7 | it("should format the duration between the workflow started event and the final event", () => { 8 | const result = duration(historyFromJSON(completedEvents)) 9 | expect(result).toBe("4.12 seconds") 10 | }) 11 | 12 | it("should error if there is no workflow started event", () => { 13 | const eventsWithoutWorkflowStarted = { events: completedEvents.events.slice(1) } 14 | expect(duration.bind(duration, historyFromJSON(eventsWithoutWorkflowStarted))).toThrow( 15 | "Got history with no WorkflowExecutionStarted event", 16 | ) 17 | }) 18 | 19 | it("should error if there are no events", () => { 20 | expect(duration.bind(duration, { events: [] })).toThrow("Got history with no WorkflowExecutionStarted event") 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /webview/src/utilities/duration.ts: -------------------------------------------------------------------------------- 1 | import humanizeDuration from "humanize-duration" 2 | import { tsToDate } from "@temporalio/common" 3 | import type { History } from "../lib" 4 | 5 | export function duration(history: History): string { 6 | const workflowStartedEvent = history.events?.find((e) => e.workflowExecutionStartedEventAttributes) 7 | if (workflowStartedEvent == null) { 8 | throw new TypeError("Got history with no WorkflowExecutionStarted event") 9 | } 10 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 11 | // @ts-ignore 12 | const startTime = tsToDate(workflowStartedEvent.eventTime!).getTime() 13 | const lastEvent = history.events?.[history.events.length - 1] 14 | if (lastEvent == null) { 15 | throw new TypeError("Got history with no events") 16 | } 17 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 18 | // @ts-ignore 19 | const endTime = tsToDate(lastEvent.eventTime!).getTime() 20 | return humanizeDuration(endTime - startTime) 21 | } 22 | -------------------------------------------------------------------------------- /webview/src/utilities/get-workflow-tasks.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, describe, it } from "vitest" 2 | import { getWorkflowTasks } from "./get-workflow-tasks" 3 | import { historyFromJSON } from "@temporalio/common/lib/proto-utils" 4 | import completedEvents from "../../../samples/events.completed.json" 5 | 6 | describe("getWorkflowTasks", () => { 7 | it("should return an empty array if there are no events", () => { 8 | const result = getWorkflowTasks(historyFromJSON({ events: [] })) 9 | expect(result).toEqual([]) 10 | }) 11 | 12 | it("should return status COMPLETED and hasBreakpoint set to true if the eventType is WorkflowTaskCompleted", () => { 13 | const completedEvent = completedEvents.events[16] 14 | const result = getWorkflowTasks(historyFromJSON({ events: [completedEvent] })) 15 | expect(result).toEqual([{ events: [], status: "COMPLETED", hasBreakpoint: true }]) 16 | }) 17 | 18 | it("should return hasBreakpoint set to false if there has already been a WorkflowTaskCompleted event", () => { 19 | const events = [completedEvents.events[16], completedEvents.events[16]] 20 | const result = getWorkflowTasks(historyFromJSON({ events }))[1].hasBreakpoint 21 | expect(result).toBe(false) 22 | }) 23 | 24 | it("should return a startedEventId if there is a WorkflowTaskStarted event", () => { 25 | const workflowTaskStartedEvent = completedEvents.events.find((e) => e.eventType === "WorkflowTaskStarted") 26 | expect(getWorkflowTasks(historyFromJSON(completedEvents))[0].startedEventId?.toString()).toBe( 27 | workflowTaskStartedEvent?.eventId, 28 | ) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /webview/src/utilities/get-workflow-tasks.ts: -------------------------------------------------------------------------------- 1 | import type { temporal } from "@temporalio/proto" 2 | import { tsToDate } from "@temporalio/common" 3 | import { EventType, type WorkflowTask } from "../lib" 4 | 5 | type Category = 6 | | "WFT_COMPLETED" 7 | | "WFT_FAILED" 8 | | "WFT_TIMED_OUT" 9 | | "WFT_SCHEDULED" 10 | | "WFT_STARTED" 11 | | "IGNORE" 12 | | "COMMAND" 13 | | "EVENT" 14 | 15 | /** 16 | * Map an event type to a category. 17 | */ 18 | function categorizeEvent(eventType: EventType): Category { 19 | switch (eventType) { 20 | // Ignore these for display purposes, we'll show this a status in the title 21 | case EventType.EVENT_TYPE_UNSPECIFIED: 22 | case EventType.EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT: 23 | case EventType.EVENT_TYPE_WORKFLOW_EXECUTION_TERMINATED: 24 | return "IGNORE" 25 | case EventType.EVENT_TYPE_WORKFLOW_TASK_COMPLETED: 26 | return "WFT_COMPLETED" 27 | case EventType.EVENT_TYPE_WORKFLOW_TASK_FAILED: 28 | return "WFT_FAILED" 29 | case EventType.EVENT_TYPE_WORKFLOW_TASK_TIMED_OUT: 30 | return "WFT_TIMED_OUT" 31 | case EventType.EVENT_TYPE_WORKFLOW_TASK_SCHEDULED: 32 | return "WFT_SCHEDULED" 33 | case EventType.EVENT_TYPE_WORKFLOW_TASK_STARTED: 34 | return "WFT_STARTED" 35 | // All of these are commands generated by the workflow, they're associated with the previous WFT 36 | case EventType.EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED: 37 | case EventType.EVENT_TYPE_WORKFLOW_EXECUTION_CONTINUED_AS_NEW: 38 | case EventType.EVENT_TYPE_WORKFLOW_EXECUTION_FAILED: 39 | case EventType.EVENT_TYPE_WORKFLOW_EXECUTION_CANCEL_REQUESTED: 40 | case EventType.EVENT_TYPE_ACTIVITY_TASK_CANCEL_REQUESTED: 41 | case EventType.EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_INITIATED: 42 | case EventType.EVENT_TYPE_TIMER_STARTED: 43 | case EventType.EVENT_TYPE_ACTIVITY_TASK_SCHEDULED: 44 | case EventType.EVENT_TYPE_EXTERNAL_WORKFLOW_EXECUTION_CANCEL_REQUESTED: 45 | case EventType.EVENT_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED: 46 | case EventType.EVENT_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_INITIATED: 47 | case EventType.EVENT_TYPE_MARKER_RECORDED: 48 | case EventType.EVENT_TYPE_TIMER_CANCELED: 49 | case EventType.EVENT_TYPE_UPSERT_WORKFLOW_SEARCH_ATTRIBUTES: 50 | case EventType.EVENT_TYPE_WORKFLOW_EXECUTION_UPDATE_ACCEPTED: 51 | case EventType.EVENT_TYPE_WORKFLOW_EXECUTION_UPDATE_REJECTED: 52 | case EventType.EVENT_TYPE_WORKFLOW_EXECUTION_UPDATE_COMPLETED: 53 | case EventType.EVENT_TYPE_WORKFLOW_PROPERTIES_MODIFIED: 54 | case EventType.EVENT_TYPE_NEXUS_OPERATION_SCHEDULED: 55 | case EventType.EVENT_TYPE_NEXUS_OPERATION_CANCEL_REQUESTED: 56 | return "COMMAND" 57 | // Completions and other non-command events go here 58 | case EventType.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED: 59 | case EventType.EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED: 60 | case EventType.EVENT_TYPE_WORKFLOW_EXECUTION_CANCELED: 61 | case EventType.EVENT_TYPE_ACTIVITY_TASK_STARTED: 62 | case EventType.EVENT_TYPE_ACTIVITY_TASK_COMPLETED: 63 | case EventType.EVENT_TYPE_ACTIVITY_TASK_CANCELED: 64 | case EventType.EVENT_TYPE_ACTIVITY_TASK_FAILED: 65 | case EventType.EVENT_TYPE_ACTIVITY_TASK_TIMED_OUT: 66 | case EventType.EVENT_TYPE_TIMER_FIRED: 67 | case EventType.EVENT_TYPE_START_CHILD_WORKFLOW_EXECUTION_FAILED: 68 | case EventType.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_CANCELED: 69 | case EventType.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_COMPLETED: 70 | case EventType.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_FAILED: 71 | case EventType.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_TERMINATED: 72 | case EventType.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_TIMED_OUT: 73 | case EventType.EVENT_TYPE_CHILD_WORKFLOW_EXECUTION_STARTED: 74 | case EventType.EVENT_TYPE_EXTERNAL_WORKFLOW_EXECUTION_SIGNALED: 75 | case EventType.EVENT_TYPE_SIGNAL_EXTERNAL_WORKFLOW_EXECUTION_FAILED: 76 | case EventType.EVENT_TYPE_REQUEST_CANCEL_EXTERNAL_WORKFLOW_EXECUTION_FAILED: 77 | case EventType.EVENT_TYPE_WORKFLOW_PROPERTIES_MODIFIED_EXTERNALLY: 78 | case EventType.EVENT_TYPE_ACTIVITY_PROPERTIES_MODIFIED_EXTERNALLY: 79 | case EventType.EVENT_TYPE_WORKFLOW_EXECUTION_UPDATE_ADMITTED: 80 | case EventType.EVENT_TYPE_NEXUS_OPERATION_STARTED: 81 | case EventType.EVENT_TYPE_NEXUS_OPERATION_COMPLETED: 82 | case EventType.EVENT_TYPE_NEXUS_OPERATION_FAILED: 83 | case EventType.EVENT_TYPE_NEXUS_OPERATION_CANCELED: 84 | case EventType.EVENT_TYPE_NEXUS_OPERATION_TIMED_OUT: 85 | return "EVENT" 86 | default: 87 | return unhandledEventType(eventType) 88 | } 89 | } 90 | 91 | /** 92 | * Catch-all for uncategorized event types. 93 | * Note that this function takes `never` which improves the error message when new enum variants are added. 94 | */ 95 | function unhandledEventType(eventType: never): Category { 96 | console.error("Unhandled event type", eventType) 97 | return "EVENT" 98 | } 99 | 100 | // Collecting workflow 101 | export function getWorkflowTasks(history: temporal.api.history.v1.IHistory): WorkflowTask[] { 102 | if (history === undefined) { 103 | return [] 104 | } 105 | 106 | let currWFT: WorkflowTask = { events: [], status: "COMPLETED", hasBreakpoint: true } 107 | const wfts = Array() 108 | 109 | for (const ev of history.events ?? []) { 110 | if (ev.eventType == null) { 111 | throw new TypeError("Got event with no type") 112 | } 113 | const category = categorizeEvent(ev.eventType) 114 | switch (category) { 115 | case "IGNORE": 116 | break 117 | case "WFT_COMPLETED": 118 | wfts.push(currWFT) 119 | currWFT = { events: [], status: "COMPLETED", hasBreakpoint: false } 120 | break 121 | case "WFT_FAILED": 122 | currWFT.status = "FAILED" 123 | break 124 | case "WFT_TIMED_OUT": 125 | currWFT.status = "TIMED_OUT" 126 | break 127 | case "WFT_SCHEDULED": 128 | currWFT.scheduled = tsToDate(ev.eventTime!) 129 | break 130 | case "WFT_STARTED": 131 | currWFT.started = tsToDate(ev.eventTime!) 132 | currWFT.startedEventId = +ev.eventId! 133 | break 134 | case "COMMAND": 135 | wfts[wfts.length - 1].events.push({ ...ev, category }) 136 | break 137 | case "EVENT": 138 | currWFT.events.push({ ...ev, category }) 139 | break 140 | } 141 | } 142 | return wfts 143 | } 144 | -------------------------------------------------------------------------------- /webview/src/utilities/label-text-for-history-event.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "vitest" 2 | import { getWorkflowTasks } from "./get-workflow-tasks" 3 | import { labelTextForHistoryEvent } from "./label-text-for-history-event" 4 | import { historyFromJSON } from "@temporalio/common/lib/proto-utils" 5 | import completedEvents from "../../../samples/events.completed.json" 6 | 7 | describe("labelTextForHistoryEvent", () => { 8 | const workflowTasks = getWorkflowTasks(historyFromJSON(completedEvents)) 9 | 10 | it("should error if there is no event type on the event", () => { 11 | const event = { ...workflowTasks[0].events[0] } 12 | delete event.eventType 13 | 14 | expect(labelTextForHistoryEvent.bind(labelTextForHistoryEvent, event)).to.throw( 15 | "Expected history event `eventType` to be defined", 16 | ) 17 | }) 18 | 19 | it("should format the event type name with the workflow type name for a WorkflowExecutionStarted event", () => { 20 | const workflowExecutionStartedEvent = workflowTasks[0].events[0] 21 | expect(labelTextForHistoryEvent(workflowExecutionStartedEvent)).toBe( 22 | "[1] WorkflowExecutionStarted (workflow.completion)", 23 | ) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /webview/src/utilities/label-text-for-history-event.ts: -------------------------------------------------------------------------------- 1 | import humanizeDuration from "humanize-duration" 2 | import { temporal } from "@temporalio/proto" 3 | import { optionalTsToMs } from "@temporalio/common/lib/time" 4 | import type { CategorizedEvent } from "../lib" 5 | 6 | function labelDetailsForHistoryEvent(event: CategorizedEvent) { 7 | switch (event.eventType) { 8 | case temporal.api.enums.v1.EventType.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED: 9 | return `(${event.workflowExecutionStartedEventAttributes?.workflowType?.name})` 10 | case temporal.api.enums.v1.EventType.EVENT_TYPE_ACTIVITY_TASK_SCHEDULED: 11 | return `(${event.activityTaskScheduledEventAttributes?.activityType?.name})` 12 | case temporal.api.enums.v1.EventType.EVENT_TYPE_WORKFLOW_EXECUTION_SIGNALED: 13 | return `(${event.workflowExecutionSignaledEventAttributes?.signalName})` 14 | case temporal.api.enums.v1.EventType.EVENT_TYPE_TIMER_STARTED: 15 | return `⏱ (${humanizeDuration(optionalTsToMs(event.timerStartedEventAttributes?.startToFireTimeout) ?? 0)})` 16 | case temporal.api.enums.v1.EventType.EVENT_TYPE_TIMER_FIRED: 17 | return `⏱🔥` 18 | case temporal.api.enums.v1.EventType.EVENT_TYPE_TIMER_CANCELED: 19 | return `⏱🚫` 20 | } 21 | } 22 | 23 | export function labelTextForHistoryEvent(event: CategorizedEvent): string { 24 | const { eventId, eventType } = event 25 | 26 | if (eventType === undefined || eventType === null) { 27 | throw new TypeError("Expected history event `eventType` to be defined") 28 | } 29 | 30 | const eventTypeName: string = temporal.api.enums.v1.EventType[eventType] 31 | .replace(/^EVENT_TYPE_/, "") 32 | .split("_") 33 | .map((p) => `${p[0]}${p.substring(1).toLowerCase()}`) 34 | .join("") 35 | const details = labelDetailsForHistoryEvent(event) 36 | 37 | return `[${eventId}] ${eventTypeName} ${details ?? ""}` 38 | } 39 | -------------------------------------------------------------------------------- /webview/src/views/app.svelte: -------------------------------------------------------------------------------- 1 | 37 | 38 |
39 | {#key currentHistory} 40 | 41 | SETTINGS 42 | MAIN 43 | {#if currentHistory} 44 | HISTORY 45 | {/if} 46 | 47 | 48 | 49 | 50 | 51 | 52 | {#if currentHistory} 53 | 54 | 55 | 56 | {/if} 57 | 58 | {/key} 59 |
60 | 61 | 81 | -------------------------------------------------------------------------------- /webview/src/views/history.svelte: -------------------------------------------------------------------------------- 1 | 60 | 61 |
62 |

{title(history)}

63 |

Duration: {duration(history)}

64 | {#each workflowTasks as workflowTask, i} 65 |
    66 |
    67 | 68 |

    Workflow Task ({workflowTask.status})

    69 |
    70 | {#each workflowTask.events as event} 71 |
  • 72 | {#if event?.category === "COMMAND"} 73 | 74 | {:else} 75 | 76 | {/if} 77 | {labelTextForHistoryEvent(event)} 78 |
  • 79 | {/each} 80 |
81 | {#if i !== workflowTasks.length - 1} 82 | 83 | {/if} 84 | {/each} 85 |
86 | 87 | 115 | -------------------------------------------------------------------------------- /webview/src/views/main.svelte: -------------------------------------------------------------------------------- 1 | 69 | 70 |
71 |

Debug by ID

72 |
73 | 74 | 75 | 76 | Start 77 | 78 | 79 |

Debug from history file

80 |
81 |
82 | {#if loading} 83 | 84 | {/if} 85 | 86 | 87 |
88 | {#if error} 89 |
90 | 91 |

{error}

92 |
93 | {/if} 94 |
95 | Start 96 |
97 |
98 |
99 | 100 | 132 | -------------------------------------------------------------------------------- /webview/src/views/settings.svelte: -------------------------------------------------------------------------------- 1 | 51 | 52 |
53 | {#await settingsLoadedPromise} 54 | 55 |

Loading...

56 | {:then settings} 57 |

Configure client connection (for downloading histories)

58 |
59 | Address 60 |
61 | TLS? 62 |
63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 |
71 | Submit 72 |
73 |
74 | {/await} 75 |
76 | 77 | 89 | -------------------------------------------------------------------------------- /webview/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "include": ["./**/*"], 4 | "exclude": ["../node_modules/*"], 5 | "compilerOptions": { 6 | "types": ["svelte"], 7 | "strict": true, 8 | "resolveJsonModule": true 9 | } 10 | } 11 | --------------------------------------------------------------------------------