swarm-wire, swarm-ui: the flow schema lists the values a field takes, and the editor picks them instead of asking for typed names
The schema's event fields gain values, a values_from marker (agents, repos) and a closed flag; a node field gains the marker and the flag. kind is now listed as issue and PR, what the router sends. The flow editor shows a dropdown where the list is complete, suggestions where it is only examples, and a plain box where there is none.
This commit is contained in:
parent
3d142feb6a
commit
b6da2bc9d0
8 changed files with 503 additions and 26 deletions
148
frontend/packages/swarm-ui/test/flow-choices.test.mjs
Normal file
148
frontend/packages/swarm-ui/test/flow-choices.test.mjs
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
// What the flow editor offers for a value: dropdown, suggestions or plain
|
||||
// text, from the server's schema. Run by `npm test`.
|
||||
import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import {
|
||||
eventSpec,
|
||||
kindSpec,
|
||||
markersIn,
|
||||
offerFor,
|
||||
} from "../src/pages/notifications/FlowChoices.ts";
|
||||
|
||||
const d = (name, description = "") => ({ name, description });
|
||||
|
||||
const schema = {
|
||||
source_events: [d("issues")],
|
||||
fields: [
|
||||
{
|
||||
...d("kind"),
|
||||
values: [d("issue"), d("PR", "A pull request")],
|
||||
closed: true,
|
||||
},
|
||||
{ ...d("action"), values: [d("opened"), d("closed")], closed: false },
|
||||
{ ...d("repo"), values: [], values_from: "repos", closed: false },
|
||||
{ ...d("title"), values: [] },
|
||||
],
|
||||
node_kinds: [
|
||||
{
|
||||
kind: "todo",
|
||||
description: "",
|
||||
fields: [
|
||||
{
|
||||
...d("to"),
|
||||
required: true,
|
||||
values: [],
|
||||
values_from: "agents",
|
||||
closed: true,
|
||||
},
|
||||
{
|
||||
...d("op"),
|
||||
required: true,
|
||||
values: [d("upsert"), d("clear")],
|
||||
closed: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
kind: "request_reviewer",
|
||||
description: "",
|
||||
fields: [
|
||||
{
|
||||
...d("reviewer"),
|
||||
required: true,
|
||||
values: [],
|
||||
values_from: "agents",
|
||||
closed: false,
|
||||
},
|
||||
{
|
||||
...d("later"),
|
||||
required: false,
|
||||
values: [],
|
||||
values_from: "somewhere-new",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
limits: { max_nodes: 1, max_depth: 1, max_flows: 1 },
|
||||
};
|
||||
|
||||
test("a field with a complete list is a dropdown, its labels carrying the description", () => {
|
||||
const offer = offerFor(eventSpec(schema, "kind"), {}, "PR");
|
||||
assert.equal(offer.closed, true);
|
||||
assert.deepEqual(
|
||||
offer.options.map((o) => o.value),
|
||||
["issue", "PR"],
|
||||
);
|
||||
assert.equal(offer.options[1].label, "PR — A pull request");
|
||||
});
|
||||
|
||||
test("a list that is only examples stays a text box with suggestions", () => {
|
||||
const offer = offerFor(eventSpec(schema, "action"), {}, "");
|
||||
assert.equal(offer.closed, false);
|
||||
assert.deepEqual(
|
||||
offer.options.map((o) => o.value),
|
||||
["opened", "closed"],
|
||||
);
|
||||
});
|
||||
|
||||
test("a field with no known values is plain text", () => {
|
||||
assert.equal(offerFor(eventSpec(schema, "title"), {}, ""), null);
|
||||
assert.equal(offerFor(undefined, {}, ""), null, "no schema entry at all");
|
||||
});
|
||||
|
||||
test("a list fetched from the swarm fills the options, and a closed one becomes a dropdown", () => {
|
||||
const spec = kindSpec(schema, "todo", "to");
|
||||
const offer = offerFor(spec, { agents: ["atlas", "iris"] }, "iris");
|
||||
assert.equal(offer.closed, true);
|
||||
assert.deepEqual(
|
||||
offer.options.map((o) => o.value),
|
||||
["atlas", "iris"],
|
||||
);
|
||||
});
|
||||
|
||||
test("until its list arrives, or when it cannot be fetched, there is nothing to offer", () => {
|
||||
const spec = kindSpec(schema, "todo", "to");
|
||||
assert.equal(offerFor(spec, {}, ""), null);
|
||||
// Open lists with names fetched are suggestions: any login still goes.
|
||||
const reviewer = kindSpec(schema, "request_reviewer", "reviewer");
|
||||
const offer = offerFor(reviewer, { agents: ["argus"] }, "review-classifier");
|
||||
assert.equal(offer.closed, false);
|
||||
assert.deepEqual(
|
||||
offer.options.map((o) => o.value),
|
||||
["argus"],
|
||||
);
|
||||
});
|
||||
|
||||
test("a dropdown whose source has not arrived falls back to suggestions", () => {
|
||||
const spec = {
|
||||
values: [d("a")],
|
||||
values_from: "agents",
|
||||
closed: true,
|
||||
};
|
||||
assert.equal(offerFor(spec, {}, "").closed, false);
|
||||
assert.equal(offerFor(spec, { agents: ["b"] }, "").closed, true);
|
||||
});
|
||||
|
||||
test("a value the list lacks stays selectable, marked as not listed", () => {
|
||||
const spec = kindSpec(schema, "todo", "to");
|
||||
const offer = offerFor(spec, { agents: ["atlas"] }, "gone");
|
||||
assert.equal(offer.options[0].value, "gone");
|
||||
assert.equal(offer.options[0].label, "gone (not listed)");
|
||||
assert.equal(offer.options.length, 2);
|
||||
// Nothing set yet adds nothing.
|
||||
assert.equal(offerFor(spec, { agents: ["atlas"] }, "").options.length, 1);
|
||||
});
|
||||
|
||||
test("a name in both the fixed values and the fetched list is offered once", () => {
|
||||
const spec = { values: [d("x")], values_from: "agents" };
|
||||
const offer = offerFor(spec, { agents: ["x", "y"] }, "");
|
||||
assert.deepEqual(
|
||||
offer.options.map((o) => o.value),
|
||||
["x", "y"],
|
||||
);
|
||||
});
|
||||
|
||||
test("the markers to fetch are the ones the schema names and the editor knows, once each", () => {
|
||||
assert.deepEqual(markersIn(schema), ["agents", "repos"]);
|
||||
assert.deepEqual(markersIn({ ...schema, fields: [], node_kinds: [] }), []);
|
||||
});
|
||||
Loading…
Reference in a new issue