feat: experimental: Interactivity for queries#1025
feat: experimental: Interactivity for queries#1025davidporter-id-au merged 17 commits intomasterfrom
Conversation
- Fix prettier formatting in blocks.styles.ts, blocks.tsx, and blocks.types.tsx - Remove console statements from blocks.tsx - Fix unused variable warning in error handling - Fix import order in workflow-queries-result.types.ts 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Assem Hafez <assem.hafez@uber.com>
| const textToCopy = useMemo(() => { | ||
| return losslessJsonStringify(content, null, '\t'); | ||
| }, [content]); | ||
| if (queryResultContent.contentType === 'blocks') { |
There was a problem hiding this comment.
Do we need this condition ?
src/components/blocks/blocks.tsx
Outdated
| // Only handle signal actions | ||
| if (button.action.type !== 'signal') { | ||
| // eslint-disable-next-line no-console | ||
| console.warn( |
There was a problem hiding this comment.
Better disable or show error banner/snackbar on UI
There was a problem hiding this comment.
ah thank you, i didn't know that was a thing
src/components/blocks/blocks.tsx
Outdated
|
|
||
| if (!response.ok) { | ||
| const errorData = await response.json(); | ||
| throw new Error(errorData.message || 'Failed to signal workflow'); |
There was a problem hiding this comment.
Show message as snackbar for the user
There was a problem hiding this comment.
Pull Request Overview
This PR introduces experimental support for interactive queries in Cadence workflows by implementing a block-based UI system similar to Slack's Block Kit. The feature allows workflows to render query responses with interactive elements like buttons that can send signals back to workflows.
- Adds a new
blockscontent type for query responses alongside existingjsonandmarkdowntypes - Implements a block system with support for sections, dividers, and interactive button actions
- Enables button clicks to trigger workflow signals with customizable payloads
Reviewed Changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/views/workflow-queries/workflow-queries.tsx |
Passes workflow parameters (domain, cluster, workflowId, runId) to query result component |
src/views/workflow-queries/workflow-queries-result/workflow-queries-result.types.ts |
Extends types to support blocks content type and adds workflow parameters |
src/views/workflow-queries/workflow-queries-result/workflow-queries-result.tsx |
Adds rendering logic for blocks content type using new Blocks component |
src/views/workflow-queries/workflow-queries-result/helpers/get-query-result-content.ts |
Implements content parsing logic for blocks format with cadenceResponseType validation |
src/views/workflow-queries/workflow-queries-result/helpers/__tests__/get-query-result-content.test.ts |
Adds comprehensive test coverage for blocks content type parsing |
src/views/workflow-queries/workflow-queries-result/__tests__/workflow-queries-result-json.test.tsx |
Updates tests to handle new workflow parameters and blocks rendering |
src/views/workflow-queries/__tests__/workflow-queries.test.tsx |
Updates mock to verify workflow parameters are passed correctly |
src/components/blocks/blocks.types.tsx |
Defines TypeScript interfaces for block components and button actions |
src/components/blocks/blocks.tsx |
Implements the main Blocks component with signal handling and UI rendering |
src/components/blocks/blocks.styles.ts |
Provides styled components for block layout and visual presentation |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| <styled.ActionsContainer> | ||
| {actions.elements.map((element, index) => { | ||
| if (element.type === 'button') { | ||
| const buttonKey = `${element.action.type}-${element.action.signal_name}-${index}`; |
There was a problem hiding this comment.
The buttonKey generation logic is inconsistent between lines 41 and 125. Line 41 uses ${button.action.signal_name}-${index} while line 125 uses ${element.action.type}-${element.action.signal_name}-${index}. This will cause the loading state check to fail since the keys don't match.
| const buttonKey = `${element.action.type}-${element.action.signal_name}-${index}`; | |
| const buttonKey = `${element.action.signal_name}-${index}`; |
| <styled.ActionsContainer> | ||
| {actions.elements.map((element, index) => { | ||
| if (element.type === 'button') { | ||
| const buttonKey = `${element.action.type}-${element.action.signal_name}-${index}`; |
There was a problem hiding this comment.
This buttonKey should match the one used in handleButtonClick (line 41). Change this to ${element.action.signal_name}-${index} to maintain consistency.
| const buttonKey = `${element.action.type}-${element.action.signal_name}-${index}`; | |
| const buttonKey = `${element.action.signal_name}-${index}`; |
This is an experimental feature or adding something very similar to slack's Block kit, to queries to allow for a degree of interactivity and sending of signals. It should allow users to specify an arbitrary markdown and response and also buttons with some signals to the workflows.
This is a draft only. I've not finished responding to all the comments (particularly Assem's)
Rough spec:
This is a small RFC about extending the preformatted query setup available in Cadence, which renders the workflow Query response in the UI as markdown with a small bit more capability: UI interactivity.
The key idea, first pointed out by Taylan Isikdemir , is
to shamelessly stealto be inspired by Slack’s block-kit that a workflow could render a query response with some buttons, giving the user a choice for how to interact.Example:
The buttons would signal the workflow, allowing the backend to then continue doing whatever user-specified behaviour. It would likely need a small amount of UI state to capture that the button had been clicked and that the signal had completed.
From the point of view of the workflow author, they would be looking to create a query with a response like this (to render the above example):
{ "cadenceResponseType": "formattedData", "format": "blocks", "blocks": [ { "type": "section", "format": "text/markdown", "componentOptions": { "text": "### Lunch options\n\n| | |\n|---|----|\n|  | <b>Farmhouse - Red Thai Curry</h5> </b>: The base Thai red curry paste ... |\n|  | <b>Ler Ros: Lemongrass Tofu Bahn Mi</b> In Vietnamese cuisine, bánh mì, bánh mỳ or banh mi ... |\n\n\n\n\n" } } }, { "type": "divider" }, { "type": "actions", "elements": [ { "type": "button", "componentOptions": { "type": "plain_text", "text": "Farmhouse" }, "action": { "type": "signal", "signal_name": "lunch_order", "signal_value": { "location": "farmhouse - red thai curry", "requests": "spicy" } } }, { "type": "button", "componentOptions": { "type": "plain_text", "text": "Kin Khao" }, "action": { "type": "signal", "signal_name": "no_lunch_order_walk_in_person", "workflow_id": "some-other-workflow", "run_id": "49ea9236-0903-420a-8c26-09bbeeb6c50f" } }, { "type": "button", "componentOptions": { "type": "plain_text", "text": "Ler Ros" }, "action": { "type": "signal", "signal_name": "lunch_order", "signal_value": { "location": "Ler Ros", "meal": "tofo Bahn Mi"} } } ] } ] }Implementation and details
Top level
The top level has a section called ‘blocks’ which must always be an array/list of arbitrary length. Ie:
{ "cadenceResponseType": "formattedData", "format": "blocks", "blocks": [...] }Where possible values for the type blocks are:
section,dividerandactions. They are CSS ‘block’ elements so they take up the entire div. Contents within typically are inline.Section
This is identical to the earlier proposal in that it is intended to be a formatted component with the intent of it being markdown, csv, svg etc (whatever existing components are supported).
{ "type": "section", "format": "text/markdown", "componentOptions": { "text": "### Lunch options\n\n| | |\n|---|----|\n|  | <b>Farmhouse - Red Thai Curry</h5> </b>: The base Thai red curry paste ... |\n|  | <b>Ler Ros: Lemongrass Tofu Bahn Mi</b> In Vietnamese cuisine, bánh mì, bánh mỳ or banh mi ... |\n\n\n\n\n" } }Divider
This inserts a
<br />or similar horizontal line for the aesthetic purpose of dividing content{ "type": "divider" }Actions
The actions section is an array of inputs, for now only buttons are proposed:
{ "type": "actions", "elements": [ ... ] }And each button is proposed with the following set of fields
{ "type": "button", "componentOptions": { "type": "<TBA>", // Baseweb button states such as highlighted or greyed out (design input needed here) "text": "Ler Ros" }, "action": { "type": "signal" // an identifyier of the type of action (other types might be added later) "signal_name": "lunch_order", // the signal name "signal_value": { // the signal payload "location": "Ler Ros", "meal": "tofo Bahn Mi" }, "workflow_id": "some-other-workflow", "run_id": "49ea9236-0903-420a-8c26-09bbeeb6c50f" } }For each button, in addition to the basic html fields (text etc) they would use additional data fields
signalandsignal_value, stored as props to the component which correspond to their signal API components. The user may optionally wish to specify the workflow/runID for the signal (TBA).Clicking the button would trigger a HTTP POST to the web backend using the existing signal API
Non-goals:
The buttons themselves should not allow for:
Security and alternatives considered
Allowing arbitrary html in markdown:
This is really quite unsafe due to the (high) risk that a workflow might run some input and unintentionally cause XSS attacks to occur. Ie, a user might not intend to have their markdown rendering trigger a http request to a hostile site, but it might occur if we were to turn off the security in markdown rendering it’d be quite easy for a workflow input getting rendered to send a HTTP request to a hostile site, allowing for things such as cookies or local-storage tokens to get stolen.