├── .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 |
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 |
8 |
--------------------------------------------------------------------------------
/webview/src/components/icon/svg/arrow-small-right.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/webview/src/components/icon/svg/circle-filled.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
9 |
--------------------------------------------------------------------------------
/webview/src/components/icon/svg/error.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
11 |
--------------------------------------------------------------------------------
/webview/src/components/icon/svg/svg.svelte:
--------------------------------------------------------------------------------
1 |
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 |
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 |
78 |
79 | Debug from history file
80 |
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 |
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 |
--------------------------------------------------------------------------------