Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/.storybook/a11y-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -2298,7 +2298,7 @@
"editor/src/portal/views/Integrations.stories.tsx :: No Connections": [
"color-contrast"
],
"editor/src/portal/views/PipelineBuilder.stories.tsx :: Default": [
"editor/src/portal/views/PipelineBuilder.stories.tsx :: New": [
"color-contrast"
],
"editor/src/portal/views/Pipelines.stories.tsx :: Default": [
Expand Down
77 changes: 73 additions & 4 deletions frontend/editor/public/locales/en-GB/translation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7238,16 +7238,34 @@ newPipeline = "New pipeline"
[portal.pipelines.builder]
addStep = "Add tool"
back = "Back to pipelines"
chooseAccount = "Choose an account"
chooseOperation = "Choose what this step does"
discard = "Discard changes"
enabled = "Enabled"
keepEditing = "Keep editing"
mustBeLast = "Must be the last step"
needsUpload = "Needs an uploaded file"
noToolMatches = "No tools match your search."
pipelineSettings = "Pipeline settings"
searchTools = "Search tools"
selectToolBody = "Add a tool to build your pipeline."
selectToolTitle = "No tools yet"
toolSettings = "Tool settings"
sendToSystem = "Send to another system"
settingsCrashed = "This tool's settings failed to load. Try again, or reload the page."
stepsMustBeLast = "These steps lock the file, so they must run last: {{tools}}."
stepsNeedSetup = "These steps still need an operation and an account chosen before saving: {{tools}}."
testChoose = "Choose a file"
testCta = "Run test"
testFailed = "The test run failed."
testFailedCode = "Error code"
testFailedDetails = "View details"
testFailedFile = "Test file"
testFailedOpenStep = "Open step settings"
testFailedRun = "Run id"
testFailedShort = "Test failed on {{tool}}. Click the step for details."
testFailedTitle = "Test failed on {{tool}}"
testHelper = "Send one file through the steps and see the result here. Nothing is delivered to the pipeline's output."
testResults = "Result files"
testRun = "Test run"
testRunning = "Running - step {{n}} of {{total}}..."
testStillRunning = "Still running in the background; check the pipeline's activity for the outcome."
unknownStep = "Unrecognised operation, kept as-is."
unsavedBody = "You have unsaved changes. Save them before leaving, or discard them?"
unsavedTitle = "Unsaved changes"
Expand All @@ -7258,10 +7276,16 @@ usesDefaults = "Runs with default settings"
addTool = "Add tool"
cancel = "Cancel"
chainEmpty = "Add a tool to start building your pipeline."
closePanel = "Close"
create = "Create pipeline"
designOnly = "From the design - not wired up yet"
designPlanned = "Planned"
directory = "Output folder"
directoryHelp = "Absolute path on the server. Must be within the configured allowed folders."
editingUnsupported = "Displaying these tool params for editing is not supported yet."
editSource = "Edit source"
manualUpload = "Manual upload"
manualUploadSub = "Send files with each run"
moveDown = "Move down"
moveUp = "Move up"
name = "Name"
Expand All @@ -7271,12 +7295,18 @@ noToolSettings = "This tool has no configurable settings."
operations_one = "Operation ({{count}})"
operations_other = "Operations ({{count}})"
output = "Output"
outputCurrentNote = "Saved destinations are in the design; for now the output is configured on this pipeline only."
removeStep = "Remove operation"
save = "Save changes"
scheduleEvery = "Run every"
secondDest = "Add a second destination"
secondDestSub = "One destination per pipeline today"
sharepointDest = "SharePoint"
sharepointDestSub = "Write to a SharePoint library"
sources = "Sources"
sourcesLoading = "Loading sources..."
trigger = "Trigger"
triggerCurrentNote = "In the design each input carries its own trigger; for now one trigger runs the whole pipeline."
triggerManual = "Manual only"

[portal.pipelines.composer.unit]
Expand Down Expand Up @@ -7307,6 +7337,45 @@ total = "Pipelines"
[portal.pipelines.output]
folder = "Write to folder"
inline = "Return files"
s3 = "Write to Amazon S3"

[portal.pipelines.overview]
andDo = "And do"
andFrom = "And from"
autoArrange = "Auto-arrange"
chooseInput = "choose a source"
chooseInputCta = "Choose a source"
chooseOutput = "choose a destination"
chooseOutputCta = "Choose a destination"
chooseToolCta = "Choose a tool"
code = "{ } Code"
copied = "Copied"
copy = "Copy"
defaultSettings = "Default settings"
do = "Do"
edit = "edit"
flow = "Flow"
flowHint = "Drag nodes anywhere - the wires follow."
from = "From"
inlineDetail = "Sent back as the API response"
inspector = "Inspector"
intro = "Documents arrive from your inputs, run through each step in order, and land at your output."
kindEnd = "end"
kindStart = "start"
kindTrigger = "trigger"
lockLayout = "Lock layout"
mcpBody = "No MCP tool creates or reads a pipeline yet - the tool set covers convert, pages, security, upload and download. This tab is the plan, not a rendering."
mcpTitle = "MCP support is not built yet"
requestComment = "exactly what Save posts to /api/v1/policies"
sideHintFlow = "Click any node on the canvas and it configures here."
sideHintSpec = "Click any line of the spec and it configures here."
spec = "Spec"
stepOf = "step {{n}} of {{total}}"
title = "Pipeline"
to = "To"
unlockLayout = "Unlock layout"
viewLabel = "Overview view"
when = "When"

[portal.pipelines.run]
completed_one = "Run completed."
Expand Down
68 changes: 64 additions & 4 deletions frontend/editor/public/locales/en-US/translation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7749,15 +7749,29 @@ chooseOperation = "Choose what this step does"
discard = "Discard changes"
enabled = "Enabled"
keepEditing = "Keep editing"
mustBeLast = "Must be the last step"
needsUpload = "Needs an uploaded file"
noToolMatches = "No tools match your search."
pipelineSettings = "Pipeline settings"
searchTools = "Search tools"
selectToolBody = "Add a tool to build your pipeline."
selectToolTitle = "No tools yet"
sendToSystem = "Send to another system"
settingsCrashed = "This tool's settings failed to load. Try again, or reload the page."
stepsMustBeLast = "These steps lock the file, so they must run last: {{tools}}."
stepsNeedSetup = "These steps still need an operation and an account chosen before saving: {{tools}}."
toolSettings = "Tool settings"
testChoose = "Choose a file"
testCta = "Run test"
testFailed = "The test run failed."
testFailedCode = "Error code"
testFailedDetails = "View details"
testFailedFile = "Test file"
testFailedOpenStep = "Open step settings"
testFailedRun = "Run id"
testFailedShort = "Test failed on {{tool}}. Click the step for details."
testFailedTitle = "Test failed on {{tool}}"
testHelper = "Send one file through the steps and see the result here. Nothing is delivered to the pipeline's output."
testResults = "Result files"
testRun = "Test run"
testRunning = "Running - step {{n}} of {{total}}..."
testStillRunning = "Still running in the background; check the pipeline's activity for the outcome."
unknownStep = "Unrecognized operation, kept as-is."
unsavedBody = "You have unsaved changes. Save them before leaving, or discard them?"
unsavedTitle = "Unsaved changes"
Expand All @@ -7768,8 +7782,14 @@ usesDefaults = "Runs with default settings"
addTool = "Add tool"
cancel = "Cancel"
chainEmpty = "Add a tool to start building your pipeline."
closePanel = "Close"
create = "Create pipeline"
designOnly = "From the design - not wired up yet"
designPlanned = "Planned"
editingUnsupported = "Displaying these tool params for editing is not supported yet."
editSource = "Edit source"
manualUpload = "Manual upload"
manualUploadSub = "Send files with each run"
moveDown = "Move down"
moveUp = "Move up"
name = "Name"
Expand All @@ -7781,9 +7801,12 @@ output = "Destinations"
removeStep = "Remove operation"
save = "Save changes"
scheduleEvery = "Run every"
sharepointDest = "SharePoint"
sharepointDestSub = "Write to a SharePoint library"
sources = "Sources"
sourcesLoading = "Loading sources..."
trigger = "Trigger"
triggerCurrentNote = "In the design each input carries its own trigger; for now one trigger runs the whole pipeline."
triggerManual = "Manual only"

[portal.pipelines.composer.unit]
Expand Down Expand Up @@ -7813,6 +7836,43 @@ active = "Active"
paused = "Paused"
total = "Pipelines"

[portal.pipelines.overview]
andDo = "And do"
andFrom = "And from"
autoArrange = "Auto-arrange"
chooseInput = "choose a source"
chooseInputCta = "Choose a source"
chooseOutput = "choose a destination"
chooseOutputCta = "Choose a destination"
chooseToolCta = "Choose a tool"
code = "{ } Code"
copied = "Copied"
copy = "Copy"
defaultSettings = "Default settings"
do = "Do"
edit = "edit"
flow = "Flow"
flowHint = "Drag nodes anywhere - the wires follow."
from = "From"
inspector = "Inspector"
intro = "Documents arrive from your inputs, run through each step in order, and land at your output."
kindEnd = "end"
kindStart = "start"
kindTrigger = "trigger"
lockLayout = "Lock layout"
mcpBody = "No MCP tool creates or reads a pipeline yet - the tool set covers convert, pages, security, upload and download. This tab is the plan, not a rendering."
mcpTitle = "MCP support is not built yet"
requestComment = "exactly what Save posts to /api/v1/policies"
sideHintFlow = "Click any node on the canvas and it configures here."
sideHintSpec = "Click any line of the spec and it configures here."
spec = "Spec"
stepOf = "step {{n}} of {{total}}"
title = "Pipeline"
to = "To"
unlockLayout = "Unlock layout"
viewLabel = "Overview view"
when = "When"

[portal.pipelines.run]
allProcessed_one = "Nothing to run: the source's {{count}} document has already been processed."
allProcessed_other = "Nothing to run: all {{count}} documents in the sources have already been processed."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,15 @@ describe("useAddPasswordOperation", () => {
});

describe("addPassword mappers", () => {
test("falls back to the default key length when the stored step omits it", () => {
// A pipeline step saved without keyLength must not deserialize to
// undefined: the settings UI calls keyLength.toString() on it.
const restored = addPasswordFromApiParams({
password: "user-pw",
} as never);
expect(restored.keyLength).toBe(128);
});

test("round-trips backend params, including the flattened permissions", () => {
// Baseline differs from the configured values so the round trip fails if
// fromApiParams drops a field instead of reconstructing it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const addPasswordFromApiParams = (
): Partial<AddPasswordFullParameters> => ({
password: apiParams.password ?? defaultParameters.password,
ownerPassword: apiParams.ownerPassword ?? defaultParameters.ownerPassword,
keyLength: apiParams.keyLength,
keyLength: apiParams.keyLength ?? defaultParameters.keyLength,
permissions: {
preventAssembly:
apiParams.preventAssembly ?? permissionsDefaults.preventAssembly,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,21 @@ describe("serialize/deserialize round-trip", () => {
});
});

test("a stored step missing fields falls back to defaults, not undefined", () => {
// Mappers echo absent stored fields as explicit undefined; settings UIs
// then crash on things like keyLength.toString(). Defaults must win.
const back = deserializeToolStep(
{ operation: "/api/v1/misc/compress-pdf", parameters: {} },
registry,
);
expect(back.params.compressionLevel).toBe(
compressDefaults.compressionLevel,
);
expect(
Object.values(back.params).every((value) => value !== undefined),
).toBe(true);
});

test("an unknown endpoint is preserved as an unmapped step", () => {
const step = deserializeToolStep(
{ operation: "/api/v1/unknown/thing", parameters: { keep: true } },
Expand Down
22 changes: 16 additions & 6 deletions frontend/editor/src/core/hooks/tools/shared/toolAutomation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,12 +245,22 @@ export function deserializeToolStep(
if (!match) return unmappedStep(step);
const [toolId, entry] = match;
const config = entry.operationConfig;
const params: ErasedToolParams = config?.fromApiParams
? {
...(config.defaultParameters ?? {}),
...config.fromApiParams(step.parameters as never),
}
: { ...(config?.defaultParameters ?? {}) };
// Mappers echo missing stored fields as explicit `undefined`, which would
// clobber the default underneath; strip those so defaults always win.
const mapped = config?.fromApiParams
? Object.fromEntries(
Object.entries(
config.fromApiParams(step.parameters as never) as Record<
string,
unknown
>,
).filter(([, value]) => value !== undefined),
)
: {};
const params: ErasedToolParams = {
...(config?.defaultParameters ?? {}),
...mapped,
} as ErasedToolParams;
// Validate against the generated endpoint set instead of casting the matched string.
const operation =
resolveEndpoint(config, params) ??
Expand Down
3 changes: 2 additions & 1 deletion frontend/editor/src/core/ui/Button.css
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@
[data-theme="dark"]
.sui-btn.mantine-Button-root[data-disabled]:not([data-loading]) {
background: var(--c-surface-sunken);
color: var(--c-text-subtle);
/* c-text-subtle maps to bright zinc-300 in dark and reads enabled; dim it for real. */
color: color-mix(in srgb, var(--c-text) 38%, var(--c-surface));
border-color: transparent;
opacity: 1;
}
24 changes: 24 additions & 0 deletions frontend/editor/src/portal/api/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,29 @@ async function localJson<T>(
return unwrap<T>(res);
}

/** POST a multipart/form-data body (file-carrying endpoints), via the
* localBackend seam. No Content-Type header: the browser sets the boundary. */
async function localMultipart<T>(
path: string,
form: FormData,
options: HttpRequestOptions = {},
): Promise<T> {
const res = await fetch(`${localBaseUrl()}${path}`, {
method: "POST",
headers: {
Accept: "application/json",
...(await localAuthHeader()),
...options.headers,
},
body: form,
signal: options.signal,
});
if (res.status === 401) {
onLocalUnauthorized();
}
return unwrap<T>(res);
}

/** GET returning a binary Blob (e.g. a CSV/JSON export download), via the
* localBackend seam — same base + auth as localJson (SaaS backend + Supabase JWT
* on SaaS, same-origin + Spring bearer self-hosted). */
Expand Down Expand Up @@ -303,6 +326,7 @@ export const apiClient = {
json: localJson,
form: localForm,
blob: localBlob,
multipart: localMultipart,
},
/** Hosted SaaS Java. Admin's Supabase JWT auto-attached. */
saas: {
Expand Down
Loading
Loading