huskies: merge 975
This commit is contained in:
@@ -0,0 +1,37 @@
|
|||||||
|
/** Tests for GatewayPanel — verifies story id and name rendering in the gateway aggregate view. */
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import type { PipelineItem } from "../api/gateway";
|
||||||
|
import { StoryRow } from "./GatewayPanel";
|
||||||
|
|
||||||
|
describe("StoryRow", () => {
|
||||||
|
it("renders #id prefix before the story name", () => {
|
||||||
|
const item: PipelineItem = {
|
||||||
|
story_id: "42_story_add_feature",
|
||||||
|
name: "Add Feature",
|
||||||
|
stage: "current",
|
||||||
|
};
|
||||||
|
const { container } = render(<StoryRow item={item} />);
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders #id prefix for a backlogged story", () => {
|
||||||
|
const item: PipelineItem = {
|
||||||
|
story_id: "7_bug_fix_crash",
|
||||||
|
name: "Fix crash on startup",
|
||||||
|
stage: "qa",
|
||||||
|
};
|
||||||
|
const { container } = render(<StoryRow item={item} />);
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders name without id prefix when story_id has no leading number", () => {
|
||||||
|
const item: PipelineItem = {
|
||||||
|
story_id: "no-number-id",
|
||||||
|
name: "Mystery Story",
|
||||||
|
stage: "merge",
|
||||||
|
};
|
||||||
|
const { container } = render(<StoryRow item={item} />);
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -63,9 +63,11 @@ const STAGE_LABELS: Record<string, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// A single story row inside a project pipeline card.
|
/// A single story row inside a project pipeline card.
|
||||||
function StoryRow({ item }: { item: PipelineItem }) {
|
/** Render one story row in a gateway-aggregate panel: `#<id> <name>` with stage badge. */
|
||||||
|
export function StoryRow({ item }: { item: PipelineItem }) {
|
||||||
const color = STAGE_COLORS[item.stage] ?? "#8b949e";
|
const color = STAGE_COLORS[item.stage] ?? "#8b949e";
|
||||||
const label = STAGE_LABELS[item.stage] ?? item.stage;
|
const label = STAGE_LABELS[item.stage] ?? item.stage;
|
||||||
|
const idNum = item.story_id.match(/^(\d+)/)?.[1];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -91,6 +93,7 @@ function StoryRow({ item }: { item: PipelineItem }) {
|
|||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
<span style={{ color: "#e6edf3", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
<span style={{ color: "#e6edf3", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||||
|
{idNum && <span style={{ color: "#8b949e", fontFamily: "monospace" }}>#{idNum}{" "}</span>}
|
||||||
{item.name}
|
{item.name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,7 +113,9 @@ function ProjectPipelineCard({
|
|||||||
onSwitch: (name: string) => void;
|
onSwitch: (name: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const activeItems = pipeline.active ?? [];
|
const activeItems = pipeline.active ?? [];
|
||||||
|
const backlogItems = pipeline.backlog ?? [];
|
||||||
const backlogCount = pipeline.backlog_count ?? 0;
|
const backlogCount = pipeline.backlog_count ?? 0;
|
||||||
|
const remainingBacklog = backlogCount - backlogItems.length;
|
||||||
const hasError = Boolean(pipeline.error);
|
const hasError = Boolean(pipeline.error);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -131,7 +136,7 @@ function ProjectPipelineCard({
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: "8px",
|
gap: "8px",
|
||||||
marginBottom: activeItems.length > 0 ? "8px" : 0,
|
marginBottom: activeItems.length > 0 || backlogItems.length > 0 ? "8px" : 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ fontWeight: 600, color: "#e6edf3" }}>{name}</span>
|
<span style={{ fontWeight: 600, color: "#e6edf3" }}>{name}</span>
|
||||||
@@ -149,20 +154,47 @@ function ProjectPipelineCard({
|
|||||||
active
|
active
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span style={{ marginLeft: "auto", fontSize: "0.75em", color: "#6e7681" }}>
|
|
||||||
{backlogCount > 0 ? `${backlogCount} in backlog` : ""}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{hasError ? (
|
{hasError ? (
|
||||||
<div style={{ fontSize: "0.8em", color: "#f85149" }}>{pipeline.error}</div>
|
<div style={{ fontSize: "0.8em", color: "#f85149" }}>{pipeline.error}</div>
|
||||||
) : activeItems.length === 0 ? (
|
) : activeItems.length === 0 && backlogItems.length === 0 ? (
|
||||||
<div style={{ fontSize: "0.8em", color: "#6e7681" }}>No active stories</div>
|
<div style={{ fontSize: "0.8em", color: "#6e7681" }}>
|
||||||
|
{backlogCount > 0 ? `${backlogCount} in backlog` : "No active stories"}
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
{activeItems.map((item) => (
|
{activeItems.map((item) => (
|
||||||
<StoryRow key={item.story_id} item={item} />
|
<StoryRow key={item.story_id} item={item} />
|
||||||
))}
|
))}
|
||||||
|
{backlogItems.length > 0 && (
|
||||||
|
<div style={{ marginTop: activeItems.length > 0 ? "6px" : 0, borderTop: activeItems.length > 0 ? "1px solid #21262d" : "none", paddingTop: activeItems.length > 0 ? "6px" : 0 }}>
|
||||||
|
{backlogItems.map((item) => {
|
||||||
|
const idNum = item.story_id.match(/^(\d+)/)?.[1];
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={item.story_id}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: "6px",
|
||||||
|
padding: "2px 0",
|
||||||
|
fontSize: "0.82em",
|
||||||
|
color: "#6e7681",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{idNum && <span style={{ fontFamily: "monospace", flexShrink: 0 }}>#{idNum}</span>}
|
||||||
|
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{item.name}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{remainingBacklog > 0 && (
|
||||||
|
<div style={{ fontSize: "0.75em", color: "#6e7681", paddingTop: "2px" }}>
|
||||||
|
…and {remainingBacklog} more
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`StoryRow > renders #id prefix before the story name 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style="display: flex; align-items: center; gap: 8px; padding: 4px 0px; font-size: 0.82em;"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style="padding: 1px 6px; border-radius: 10px; background: rgba(63, 185, 80, 0.133); color: rgb(63, 185, 80); border: 1px solid rgba(63, 185, 80, 0.267); white-space: nowrap; flex-shrink: 0;"
|
||||||
|
>
|
||||||
|
In Progress
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style="color: rgb(230, 237, 243); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style="color: rgb(139, 148, 158); font-family: monospace;"
|
||||||
|
>
|
||||||
|
#
|
||||||
|
42
|
||||||
|
|
||||||
|
</span>
|
||||||
|
Add Feature
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`StoryRow > renders #id prefix for a backlogged story 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style="display: flex; align-items: center; gap: 8px; padding: 4px 0px; font-size: 0.82em;"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style="padding: 1px 6px; border-radius: 10px; background: rgba(210, 166, 121, 0.133); color: rgb(210, 166, 121); border: 1px solid rgba(210, 166, 121, 0.267); white-space: nowrap; flex-shrink: 0;"
|
||||||
|
>
|
||||||
|
QA
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style="color: rgb(230, 237, 243); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style="color: rgb(139, 148, 158); font-family: monospace;"
|
||||||
|
>
|
||||||
|
#
|
||||||
|
7
|
||||||
|
|
||||||
|
</span>
|
||||||
|
Fix crash on startup
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`StoryRow > renders name without id prefix when story_id has no leading number 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style="display: flex; align-items: center; gap: 8px; padding: 4px 0px; font-size: 0.82em;"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style="padding: 1px 6px; border-radius: 10px; background: rgba(121, 192, 255, 0.133); color: rgb(121, 192, 255); border: 1px solid rgba(121, 192, 255, 0.267); white-space: nowrap; flex-shrink: 0;"
|
||||||
|
>
|
||||||
|
Merging
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style="color: rgb(230, 237, 243); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
|
||||||
|
>
|
||||||
|
Mystery Story
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
Reference in New Issue
Block a user