From b6e3db28002d4cde22d3941f27fe075266c3be6f Mon Sep 17 00:00:00 2001 From: "vpatil16@ext.uber.com" Date: Thu, 21 Aug 2025 13:32:32 -0700 Subject: [PATCH 1/2] Documentation for Workflow Qureies with Markdown --- .../13-workflow-queries-formatted-data.md | 418 ++++++++++++++++++ 1 file changed, 418 insertions(+) create mode 100644 docs/03-concepts/13-workflow-queries-formatted-data.md diff --git a/docs/03-concepts/13-workflow-queries-formatted-data.md b/docs/03-concepts/13-workflow-queries-formatted-data.md new file mode 100644 index 000000000..f94d0198c --- /dev/null +++ b/docs/03-concepts/13-workflow-queries-formatted-data.md @@ -0,0 +1,418 @@ +--- +layout: default +title: Workflow Queries with Formatted Data +permalink: /docs/concepts/workflow-queries-formatted-data +--- + +# Workflow Queries with Formatted Data + +
+

Introduction

+ +This guide explains how to implement workflow queries that return preformatted data for enhanced rendering in Cadence Web UI. This feature allows workflow authors to return structured data in various formats (Markdown, CSV, images, etc.) that can be rendered directly in the Cadence Web interface, providing richer visualization and better user experience. + +The formatted data feature enables workflows to respond to queries with data that includes rendering instructions, allowing the UI to display content beyond simple text responses. + +
+ +
+

Overview

+ +### The Goal + +Support rendering preformatted data on cadence-web in places such as the Query API. Examples of data that can be preformatted include: + +- **Markdown** - Rich text with formatting, links, and structure +- **CSV** - Tabular data for easy viewing +- **JPEG/PNG images** - Visual content and charts +- **SVG** - Scalable vector graphics + +The reason for prerendering is that workflow authors have access to workflow data that they may wish to render on the Cadence UI, and such rendering entirely client-side is difficult given the current Cadence workflow API. + +### How It Works + +When a workflow query responds with data in a specific shape, Cadence Web can render it with appropriate formatting. The response must include: + +1. A response type identifier +2. A MIME type format specifier +3. The actual formatted data + +
+ +
+

Response Format

+ +### Basic Structure + +To enable formatted rendering, your workflow query must respond with data in the following shape: + +```json +{ + "cadenceResponseType": "formattedData", + "format": "", + "data": "" +} +``` + +### Supported MIME Types + +The `format` field should contain well-known MIME type identifiers: + +- `text/markdown` - Markdown content +- `text/csv` - Comma-separated values +- `image/png` - PNG images +- `image/jpeg` - JPEG images +- `image/svg+xml` - SVG graphics +- `text/html` - HTML content (sanitized) + +
+ +
+

Examples

+ +### Markdown Response + +```json +{ + "cadenceResponseType": "formattedData", + "format": "text/markdown", + "data": "### Workflow Status Report\n\n**Current Stage:** Processing\n\n- [x] Data validation completed\n- [x] Initial processing done\n- [ ] Final verification pending\n\n[View detailed logs](https://internal.example.com/logs/workflow-123)\n\n**Progress:** 75% complete" +} +``` + +This would render as: + +### Workflow Status Report + +**Current Stage:** Processing + +- [x] Data validation completed +- [x] Initial processing done +- [ ] Final verification pending + +[View detailed logs](https://internal.example.com/logs/workflow-123) + +**Progress:** 75% complete + +### CSV Response + +```json +{ + "cadenceResponseType": "formattedData", + "format": "text/csv", + "data": [["Task", "Status", "Duration"], ["Data Validation", "Complete", "2m 15s"], ["Processing", "In Progress", "5m 30s"], ["Verification", "Pending", "0s"]] +} +``` + +### Image Response + +```json +{ + "cadenceResponseType": "formattedData", + "format": "image/svg+xml", + "data": "Workflow Progress" +} +``` + +
+ +
+

Go Implementation

+ +### Type Definition + +```go +// PrerenderedQueryResponse defines the structure for formatted query responses +type PrerenderedQueryResponse struct { + CadenceResponseType string `json:"cadenceResponseType"` + Format string `json:"format"` + Data json.RawMessage `json:"data"` +} +``` + +### Example Usage + +```go +package main + +import ( + "context" + "encoding/json" + "go.uber.org/cadence/workflow" +) + +// Example workflow with formatted query response +func SampleWorkflow(ctx workflow.Context) error { + // Workflow implementation... + return nil +} + +// Query handler that returns formatted markdown +func WorkflowStatusQuery(ctx workflow.Context) (PrerenderedQueryResponse, error) { + // Get current workflow state + progress := getWorkflowProgress(ctx) + + // Create markdown content + markdown := fmt.Sprintf(`### Workflow Status Report + +**Current Stage:** %s + +**Progress:** %d%% complete + +**Tasks Completed:** +%s + +**Next Steps:** +%s + +--- +*Last updated: %s* +`, + progress.CurrentStage, + progress.PercentComplete, + formatTaskList(progress.CompletedTasks), + formatTaskList(progress.PendingTasks), + time.Now().Format("2006-01-02 15:04:05"), + ) + + return PrerenderedQueryResponse{ + CadenceResponseType: "formattedData", + Format: "text/markdown", + Data: json.RawMessage(fmt.Sprintf(`"%s"`, markdown)), + }, nil +} + +// Helper function for creating markdown responses +func NewMarkdownQueryResponse(md string) PrerenderedQueryResponse { + data, _ := json.Marshal(md) + return PrerenderedQueryResponse{ + CadenceResponseType: "formattedData", + Format: "text/markdown", + Data: data, + } +} + +// Helper function for CSV responses +func NewCSVQueryResponse(rows [][]string) PrerenderedQueryResponse { + data, _ := json.Marshal(rows) + return PrerenderedQueryResponse{ + CadenceResponseType: "formattedData", + Format: "text/csv", + Data: data, + } +} + +// Register the query handler +func init() { + workflow.RegisterQueryHandler("workflow_status", WorkflowStatusQuery) +} +``` + +### Advanced Example with Error Handling + +```go +func DetailedWorkflowQuery(ctx workflow.Context, queryType string) (interface{}, error) { + switch queryType { + case "status": + return createStatusMarkdown(ctx) + case "metrics": + return createMetricsCSV(ctx) + case "diagram": + return createProgressDiagram(ctx) + default: + return nil, fmt.Errorf("unknown query type: %s", queryType) + } +} + +func createStatusMarkdown(ctx workflow.Context) (PrerenderedQueryResponse, error) { + status := getCurrentStatus(ctx) + + markdown := fmt.Sprintf(`# Workflow Execution Report + +## Summary +- **ID:** %s +- **Status:** %s +- **Started:** %s +- **Duration:** %s + +## Recent Activities +%s + +## Errors +%s +`, + workflow.GetInfo(ctx).WorkflowExecution.ID, + status.State, + status.StartTime.Format("2006-01-02 15:04:05"), + time.Since(status.StartTime).String(), + formatActivities(status.Activities), + formatErrors(status.Errors), + ) + + return NewMarkdownQueryResponse(markdown), nil +} +``` + +
+ +
+

Security Considerations

+ +### XSS Prevention + +Taking input from a workflow and rendering it as HTML without sanitization is a potential XSS (Cross-Site Scripting) vector. An attacker could inject malicious HTML tags including: + +- `