From bccbf551e37853ea962f47dfb9416cb51e705a5b Mon Sep 17 00:00:00 2001 From: Nasyarobby Putra Date: Sat, 15 Aug 2026 19:14:19 +0700 Subject: [PATCH] feat(scripts): add previewConfigKey to various scripts and enhance UI components - Added `previewConfigKey` metadata to multiple scripts including fetch-binary, fetch-html, fetch-http, fetch-rss-feed, fingerprint, get-secret, jsonata, ntfy, render-template, and send-email, improving configuration visibility. - Updated the `FieldHelp` and `ScriptCard` components to utilize the new preview configuration, enhancing the user interface for displaying script metadata. - Refactored `TriggerCard` and `WorkflowTestPanel` components to support expanded views, improving user interaction and accessibility of configuration details. - Enhanced the `WorkflowVisualEditor` to manage test panel visibility more effectively. This update improves the overall user experience by providing clearer context and configuration options for scripts and workflows. --- packages/server/scripts/fetch-binary.js | 1 + packages/server/scripts/fetch-html.js | 1 + packages/server/scripts/fetch-http.js | 1 + packages/server/scripts/fetch-rss-feed.js | 1 + packages/server/scripts/fingerprint.js | 1 + packages/server/scripts/get-secret.js | 1 + packages/server/scripts/jsonata.js | 1 + packages/server/scripts/ntfy.js | 1 + packages/server/scripts/render-template.js | 1 + packages/server/scripts/send-email.js | 1 + packages/server/scripts/trigger-workflow.js | 1 + packages/server/workflows/default/track.yaml | 1 + .../web/src/components/workflow/FieldHelp.jsx | 139 +++++++++- .../src/components/workflow/ScriptCard.jsx | 247 ++++++++++-------- .../src/components/workflow/TriggerCard.jsx | 93 +++++-- .../components/workflow/WorkflowTestPanel.jsx | 94 +++++-- .../workflow/WorkflowVisualEditor.jsx | 6 +- packages/web/src/lib/script.js | 1 + packages/web/src/pages/EventDetailPage.jsx | 16 +- packages/web/src/pages/WorkflowEditPage.jsx | 14 +- 20 files changed, 443 insertions(+), 179 deletions(-) diff --git a/packages/server/scripts/fetch-binary.js b/packages/server/scripts/fetch-binary.js index 5f6209a..8712dda 100644 --- a/packages/server/scripts/fetch-binary.js +++ b/packages/server/scripts/fetch-binary.js @@ -71,6 +71,7 @@ async function fetchBinary(ctx) { fetchBinary.meta = { description: "Download a binary URL into ctx.data as a Buffer", + previewConfigKey: "url", config: { url: { type: "string", required: false, description: "Direct download URL" }, urlVar: { type: "string", required: false, description: "Key in ctx.data that holds the URL" }, diff --git a/packages/server/scripts/fetch-html.js b/packages/server/scripts/fetch-html.js index f86a0ed..6947e8f 100644 --- a/packages/server/scripts/fetch-html.js +++ b/packages/server/scripts/fetch-html.js @@ -86,6 +86,7 @@ async function fetchHtml(ctx) { fetchHtml.meta = { description: "Fetch HTML and optionally select elements or transform with JSONata", + previewConfigKey: "url", config: { url: { type: "string", required: true, description: "Page URL" }, selector: { type: "string", required: false, description: "CSS selector" }, diff --git a/packages/server/scripts/fetch-http.js b/packages/server/scripts/fetch-http.js index 76ea114..63d5af9 100644 --- a/packages/server/scripts/fetch-http.js +++ b/packages/server/scripts/fetch-http.js @@ -115,6 +115,7 @@ async function fetchHttp(ctx) { fetchHttp.meta = { description: "Fetch a URL and store the response body.", + previewConfigKey: "url", config: { url: { type: "string", required: true, description: "Request URL" }, method: { diff --git a/packages/server/scripts/fetch-rss-feed.js b/packages/server/scripts/fetch-rss-feed.js index 64af8ef..ecce454 100644 --- a/packages/server/scripts/fetch-rss-feed.js +++ b/packages/server/scripts/fetch-rss-feed.js @@ -68,6 +68,7 @@ async function fetchRssFeed(ctx) { fetchRssFeed.meta = { description: "Fetch an RSS/Atom feed and optionally transform it with JSONata", + previewConfigKey: "url", config: { url: { type: "string", required: false, description: "Feed URL (or pass data.url)" }, outputVar: { diff --git a/packages/server/scripts/fingerprint.js b/packages/server/scripts/fingerprint.js index b02424c..1baf860 100644 --- a/packages/server/scripts/fingerprint.js +++ b/packages/server/scripts/fingerprint.js @@ -56,6 +56,7 @@ async function fingerprint(ctx) { fingerprint.meta = { description: "Hash a value, compare it to the last stored fingerprint, and skip remaining steps when unchanged", + previewConfigKey: "key", config: { key: { type: "string", diff --git a/packages/server/scripts/get-secret.js b/packages/server/scripts/get-secret.js index 6091c2f..2212955 100644 --- a/packages/server/scripts/get-secret.js +++ b/packages/server/scripts/get-secret.js @@ -22,6 +22,7 @@ async function getSecret(ctx) { getSecret.meta = { description: "Load a named secret for this workflow owner into ctx.data. The value is wrapped and redacted in logs.", + previewConfigKey: "name", config: { name: { type: "string", diff --git a/packages/server/scripts/jsonata.js b/packages/server/scripts/jsonata.js index ba4f363..5948104 100644 --- a/packages/server/scripts/jsonata.js +++ b/packages/server/scripts/jsonata.js @@ -11,6 +11,7 @@ async function jsonataFn(ctx) { jsonataFn.meta = { description: "Evaluate a JSONata expression against ctx.data and return the result as the next context", + previewConfigKey: "expression", config: { expression: { type: "string", required: true, description: "JSONata expression" }, }, diff --git a/packages/server/scripts/ntfy.js b/packages/server/scripts/ntfy.js index 7555ce8..892db01 100644 --- a/packages/server/scripts/ntfy.js +++ b/packages/server/scripts/ntfy.js @@ -131,6 +131,7 @@ async function ntfy(ctx) { ntfy.meta = { description: "Send a message or file to an ntfy topic", + previewConfigKey: "url", config: { url: { type: "string", diff --git a/packages/server/scripts/render-template.js b/packages/server/scripts/render-template.js index 7870055..2c066a5 100644 --- a/packages/server/scripts/render-template.js +++ b/packages/server/scripts/render-template.js @@ -79,6 +79,7 @@ async function renderTemplate(ctx) { renderTemplate.meta = { description: "Render an HTML template from Responses (kind: template) with Mustache and return html + plain-text fallback", + previewConfigKey: "template", config: { template: { type: "string", diff --git a/packages/server/scripts/send-email.js b/packages/server/scripts/send-email.js index e56960e..840bf94 100644 --- a/packages/server/scripts/send-email.js +++ b/packages/server/scripts/send-email.js @@ -230,6 +230,7 @@ async function sendEmail(ctx) { sendEmail.meta = { description: "Send an email via SMTP (nodemailer); plain text, HTML, or both", + previewConfigKey: "from", config: { service: { type: "string", diff --git a/packages/server/scripts/trigger-workflow.js b/packages/server/scripts/trigger-workflow.js index 5e78d6b..ff8bdab 100644 --- a/packages/server/scripts/trigger-workflow.js +++ b/packages/server/scripts/trigger-workflow.js @@ -28,6 +28,7 @@ async function triggerWorkflow(ctx) { triggerWorkflow.meta = { description: "Fire-and-forget another workflow by YAML name (same owner). Destination must declare triggers: [{ type: workflow }]. Optionally reshape ctx.data with JSONata before sending.", + previewConfigKey: "name", config: { name: { type: "string", diff --git a/packages/server/workflows/default/track.yaml b/packages/server/workflows/default/track.yaml index c4a70f2..e2f21a7 100644 --- a/packages/server/workflows/default/track.yaml +++ b/packages/server/workflows/default/track.yaml @@ -30,3 +30,4 @@ triggers: - type: HTTP method: GET path: /new +enabled: false diff --git a/packages/web/src/components/workflow/FieldHelp.jsx b/packages/web/src/components/workflow/FieldHelp.jsx index cd1edb2..b965868 100644 --- a/packages/web/src/components/workflow/FieldHelp.jsx +++ b/packages/web/src/components/workflow/FieldHelp.jsx @@ -4,7 +4,7 @@ export function FieldHelp({ description }) { if (!description) return null; const long = String(description).length > 80; return ( -
+
{long ? (

{description}

@@ -27,3 +27,140 @@ export function FieldLabel({ name, required, description, children }) {
); } + +function fieldNotes(field) { + return [ + field.required ? "required" : null, + field.default !== undefined ? `default ${JSON.stringify(field.default)}` : null, + Array.isArray(field.enum) + ? field.enum.map((x) => (x && typeof x === "object" ? x.value : x)).join(" | ") + : Array.isArray(field.options) + ? field.options.map((x) => (x && typeof x === "object" ? x.value : x)).join(" | ") + : null, + field.description, + ] + .filter(Boolean) + .join(" · "); +} + +const TITLE_PREVIEW_MAX = 40; +const TOOLTIP_VALUE_MAX = 120; + +function stringifyConfigValue(val) { + if (typeof val === "string") return val.replace(/\s+/g, " ").trim(); + if (typeof val === "number" || typeof val === "boolean") return String(val); + try { + return JSON.stringify(val); + } catch { + return String(val); + } +} + +function truncateConfigValue(val, maxLen) { + const text = stringifyConfigValue(val); + if (!text) return ""; + if (text.length <= maxLen) return text; + return `${text.slice(0, Math.max(0, maxLen - 1))}…`; +} + +/** Collapsed card title suffix from `meta.previewConfigKey`. */ +export function previewConfigValue(config, key, maxLen = TITLE_PREVIEW_MAX) { + const text = configValueText(config, key); + if (!text) return ""; + if (text.length <= maxLen) return text; + return `${text.slice(0, Math.max(0, maxLen - 1))}…`; +} + +export function configValueText(config, key) { + if (typeof key !== "string" || !key.trim()) return ""; + if (config == null || typeof config !== "object") return ""; + const val = config[key]; + if (val == null || val === "") return ""; + return stringifyConfigValue(val); +} + +/** Formatted DaisyUI tooltip for a step's current config. */ +export function ConfigTooltip({ config, label = "config" }) { + const entries = + config && typeof config === "object" && !Array.isArray(config) ? Object.entries(config) : []; + + return ( +
+
+
+ {entries.length === 0 ? ( +

No config

+ ) : ( + + + + + + + + + {entries.map(([name, val]) => ( + + + + + ))} + +
KeyValue
{name} + {val == null || val === "" ? ( + — + ) : ( + truncateConfigValue(val, TOOLTIP_VALUE_MAX) + )} +
+ )} +
+
+ +
+ ); +} + +/** Formatted DaisyUI tooltip for script.meta.input (or similar field maps). */ +export function SchemaTooltip({ label = "Input", fields }) { + const entries = Object.entries(fields ?? {}); + if (entries.length === 0) return null; + + return ( +
+
+
+ + + + + + + + + + {entries.map(([name, spec]) => { + const field = spec && typeof spec === "object" ? spec : { description: String(spec) }; + return ( + + + + + + ); + })} + +
FieldTypeNotes
+ {name} + {field.required ? * : null} + {field.type ?? ""}{fieldNotes(field)}
+
+
+ +
+ ); +} diff --git a/packages/web/src/components/workflow/ScriptCard.jsx b/packages/web/src/components/workflow/ScriptCard.jsx index 06b8131..c4f1630 100644 --- a/packages/web/src/components/workflow/ScriptCard.jsx +++ b/packages/web/src/components/workflow/ScriptCard.jsx @@ -1,17 +1,15 @@ import { useEffect, useMemo, useState } from "react"; -import { Link } from "react-router-dom"; import { useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; -import { LuGripVertical, LuMaximize2, LuMinimize2, LuPencil, LuPlay, LuTrash2 } from "react-icons/lu"; +import { LuChevronDown, LuGripVertical, LuMaximize2, LuMinimize2, LuPlay, LuTrash2 } from "react-icons/lu"; import { useDryRunScript, useScript } from "../../api/hooks.js"; import { errorMessage } from "../../api/client.js"; import { CodeEditor } from "../CodeEditor.jsx"; import { LogViewer } from "../LogViewer.jsx"; -import { ScriptMetaPanel } from "../ScriptMetaPanel.jsx"; import { StatusBadge } from "../../lib/format.jsx"; import { contextFromMeta, prettyJson } from "../../lib/script.js"; import { ConfigFields } from "./ConfigFields.jsx"; -import { FieldLabel } from "./FieldHelp.jsx"; +import { ConfigTooltip, configValueText, FieldLabel, previewConfigValue, SchemaTooltip } from "./FieldHelp.jsx"; export function ScriptCard({ step, @@ -35,24 +33,32 @@ export function ScriptCard({ opacity: isDragging ? 0.6 : 1, }; const [tryOpen, setTryOpen] = useState(false); + const [expanded, setExpanded] = useState(false); const listed = scriptsByName?.get(step.script); const meta = listed?.meta ?? null; - const metaError = listed?.metaError ?? null; const duplicateId = step.id && otherSteps.some((s) => s.id === step.id && s.uiId !== step.uiId); + const preview = previewConfigValue(step.config, meta?.previewConfigKey); + const previewFull = configValueText(step.config, meta?.previewConfigKey); + const baseName = step.kind === "set" ? `set:${step.as || "…"}` : step.script || "untitled"; + const titleFull = previewFull ? `${baseName} (${previewFull})` : baseName; + const setConfig = + step.kind === "set" ? { as: step.as ?? "", expression: step.expression ?? "" } : null; return (
-
-
+
+
-
-

- {step.kind === "set" ? `set:${step.as || "…"}` : step.script || "untitled"} -

- {step.id ? {step.id} : null} - {step.kind === "script" && meta?.description ? ( +
+ + {step.kind === "script" ? ( + + ) : null} + {!expanded ? ( + + ) : null}
{step.kind === "script" && step.script ? ( - <> - - - - - + ) : null} +
- {step.kind === "script" && (meta?.input || meta?.output) ? ( -
- - Input / output - -
- -
-
- ) : null} - - {step.kind === "set" ? ( -
- - onChange({ ...step, as: e.target.value })} - /> - -