Skip to content

Commit f1b75fb

Browse files
committed
Add skill tags to accept submission dialog
1 parent 0240beb commit f1b75fb

4 files changed

Lines changed: 65 additions & 14 deletions

File tree

web/src/api/client.test.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,11 @@ describe("API functions", () => {
375375

376376
it("accept() calls POST with stamp data", async () => {
377377
setActiveUpstream("hop/wl-commons");
378-
await accept("abc", { quality: 5, reliability: 4 });
378+
await accept("abc", {
379+
quality: 5,
380+
reliability: 4,
381+
skill_tags: ["ops", "review"],
382+
});
379383
const call = vi.mocked(globalThis.fetch).mock.calls[0];
380384
expect(call[0]).toBe("/api/wanted/abc/accept");
381385
expect(call[1]?.headers).toEqual(
@@ -387,6 +391,7 @@ describe("API functions", () => {
387391
expect(JSON.parse(call[1]?.body as string)).toEqual({
388392
quality: 5,
389393
reliability: 4,
394+
skill_tags: ["ops", "review"],
390395
});
391396
});
392397

@@ -398,7 +403,12 @@ describe("API functions", () => {
398403
rig_handle: "charlie",
399404
pr_url: "https://www.dolthub.com/repositories/org/db/pulls/1",
400405
},
401-
{ quality: 5, reliability: 4, severity: "branch" },
406+
{
407+
quality: 5,
408+
reliability: 4,
409+
severity: "branch",
410+
skill_tags: ["go", "sql"],
411+
},
402412
);
403413
const call = vi.mocked(globalThis.fetch).mock.calls[0];
404414
expect(call[0]).toBe("/api/wanted/abc/accept-upstream");
@@ -415,6 +425,7 @@ describe("API functions", () => {
415425
quality: 5,
416426
reliability: 4,
417427
severity: "branch",
428+
skill_tags: ["go", "sql"],
418429
});
419430
});
420431

web/src/components/AcceptDialog.module.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
}
4848

4949
.select,
50+
.input,
5051
.textarea {
5152
width: 100%;
5253
box-sizing: border-box;
@@ -128,6 +129,7 @@
128129
.cancelBtn,
129130
.confirmBtn,
130131
.select,
132+
.input,
131133
.textarea {
132134
min-height: 44px;
133135
}

web/src/components/AcceptDialog.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export interface AcceptStampInput {
66
quality: number;
77
reliability?: number;
88
severity?: string;
9+
skill_tags?: string[];
910
message?: string;
1011
}
1112

@@ -22,6 +23,7 @@ export function AcceptDialog({ label, submitting, onCancel, onSubmit }: AcceptDi
2223
const [quality, setQuality] = useState("5");
2324
const [reliability, setReliability] = useState("");
2425
const [severity, setSeverity] = useState("leaf");
26+
const [skillTags, setSkillTags] = useState("");
2527
const [message, setMessage] = useState("");
2628

2729
useEffect(() => {
@@ -33,10 +35,15 @@ export function AcceptDialog({ label, submitting, onCancel, onSubmit }: AcceptDi
3335
}, [onCancel, submitting]);
3436

3537
const submit = async () => {
38+
const parsedTags = skillTags
39+
.split(",")
40+
.map((tag) => tag.trim())
41+
.filter(Boolean);
3642
await onSubmit({
3743
quality: Number(quality),
3844
reliability: reliability ? Number(reliability) : undefined,
3945
severity,
46+
skill_tags: parsedTags.length > 0 ? parsedTags : undefined,
4047
message: message.trim() || undefined,
4148
});
4249
};
@@ -103,6 +110,18 @@ export function AcceptDialog({ label, submitting, onCancel, onSubmit }: AcceptDi
103110
</select>
104111
</label>
105112

113+
<label className={styles.field}>
114+
<span className={styles.label}>Tags</span>
115+
<input
116+
className={styles.input}
117+
type="text"
118+
value={skillTags}
119+
onChange={(e) => setSkillTags(e.target.value)}
120+
placeholder="go, sql, testing"
121+
disabled={submitting}
122+
/>
123+
</label>
124+
106125
<label className={styles.field}>
107126
<span className={styles.label}>Message</span>
108127
<textarea

web/src/components/DetailView.test.tsx

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
1+
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
22
import userEvent from "@testing-library/user-event";
33
import { MemoryRouter, Route, Routes } from "react-router-dom";
44
import { afterEach, describe, expect, it, vi } from "vitest";
@@ -59,6 +59,7 @@ function renderDetail(id = "item-1") {
5959
let cleanupFetch: () => void;
6060

6161
afterEach(() => {
62+
cleanup();
6263
setActiveUpstream(null);
6364
localStorage.removeItem("wl_active");
6465
cleanupFetch?.();
@@ -525,17 +526,28 @@ describe("DetailView", () => {
525526
screen.getByRole("button", { name: "accept" }),
526527
).toBeInTheDocument(),
527528
);
528-
await userEvent.click(screen.getByRole("button", { name: "accept" }));
529+
fireEvent.click(screen.getByRole("button", { name: "accept" }));
529530
await waitFor(() =>
530531
expect(
531532
screen.getByRole("dialog", { name: "Accept Submission" }),
532533
).toBeInTheDocument(),
533534
);
534-
await userEvent.selectOptions(screen.getByLabelText("Quality"), "4");
535-
await userEvent.selectOptions(screen.getByLabelText("Reliability"), "3");
536-
await userEvent.selectOptions(screen.getByLabelText("Severity"), "branch");
537-
await userEvent.type(screen.getByLabelText("Message"), "Solid review");
538-
await userEvent.click(screen.getByRole("button", { name: "Accept" }));
535+
fireEvent.change(screen.getByLabelText("Quality"), {
536+
target: { value: "4" },
537+
});
538+
fireEvent.change(screen.getByLabelText("Reliability"), {
539+
target: { value: "3" },
540+
});
541+
fireEvent.change(screen.getByLabelText("Severity"), {
542+
target: { value: "branch" },
543+
});
544+
fireEvent.change(screen.getByLabelText("Tags"), {
545+
target: { value: "go, sql" },
546+
});
547+
fireEvent.change(screen.getByLabelText("Message"), {
548+
target: { value: "Solid review" },
549+
});
550+
fireEvent.click(screen.getByRole("button", { name: "Accept" }));
539551
await waitFor(() => {
540552
const acceptCalls = fetchFn.mock.calls.filter(([u]) =>
541553
u.endsWith("/accept-upstream"),
@@ -548,11 +560,12 @@ describe("DetailView", () => {
548560
quality: 4,
549561
reliability: 3,
550562
severity: "branch",
563+
skill_tags: ["go", "sql"],
551564
message: "Solid review",
552565
}),
553566
);
554567
});
555-
});
568+
}, 10000);
556569

557570
it("opens an accept dialog for mainline acceptance and lets reliability default to quality", async () => {
558571
setActiveUpstream("hop/wl-commons");
@@ -573,16 +586,21 @@ describe("DetailView", () => {
573586
screen.getByRole("button", { name: "accept" }),
574587
).toBeInTheDocument(),
575588
);
576-
await userEvent.click(screen.getByRole("button", { name: "accept" }));
589+
fireEvent.click(screen.getByRole("button", { name: "accept" }));
577590
expect(
578591
screen.getByRole("dialog", { name: "Accept Submission" }),
579592
).toBeInTheDocument();
580593
expect(
581594
fetchFn.mock.calls.filter(([u]) => u.endsWith("/accept")),
582595
).toHaveLength(0);
583596

584-
await userEvent.selectOptions(screen.getByLabelText("Quality"), "5");
585-
await userEvent.click(screen.getByRole("button", { name: "Accept" }));
597+
fireEvent.change(screen.getByLabelText("Quality"), {
598+
target: { value: "5" },
599+
});
600+
fireEvent.change(screen.getByLabelText("Tags"), {
601+
target: { value: "ops, review" },
602+
});
603+
fireEvent.click(screen.getByRole("button", { name: "Accept" }));
586604

587605
await waitFor(() => {
588606
const acceptCalls = fetchFn.mock.calls.filter(([u]) =>
@@ -593,10 +611,11 @@ describe("DetailView", () => {
593611
JSON.stringify({
594612
quality: 5,
595613
severity: "leaf",
614+
skill_tags: ["ops", "review"],
596615
}),
597616
);
598617
});
599-
});
618+
}, 10000);
600619

601620
it("reloads detail after a conflicting mainline accept", async () => {
602621
setActiveUpstream("hop/wl-commons");

0 commit comments

Comments
 (0)