feat(FN-4727): add github tracking toggle to quick entry box
Adds a GitHub tracking toggle to the QuickEntryBox component with co-located tests, shipped as a patch to `@runfusion/fusion`. Fusion-Task-Id: FN-4727
This commit is contained in:
committed by
gsxdsm
parent
b61db63661
commit
e9403df6ba
5
.changeset/fn-4727-quick-entry-github-toggle.md
Normal file
5
.changeset/fn-4727-quick-entry-github-toggle.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Add GitHub tracking indicator/toggle to the quick task entry dropdown — shows project default and overrides for the next task.
|
||||||
@@ -12,6 +12,7 @@ import { getScopedItem, removeScopedItem, setScopedItem } from "../utils/project
|
|||||||
import { useNodes } from "../hooks/useNodes";
|
import { useNodes } from "../hooks/useNodes";
|
||||||
import type { NodeInfo } from "../api";
|
import type { NodeInfo } from "../api";
|
||||||
import { NodeHealthDot } from "./NodeHealthDot";
|
import { NodeHealthDot } from "./NodeHealthDot";
|
||||||
|
import { ProviderIcon } from "./ProviderIcon";
|
||||||
|
|
||||||
const STORAGE_KEY = "kb-quick-entry-text";
|
const STORAGE_KEY = "kb-quick-entry-text";
|
||||||
const ALLOWED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
const ALLOWED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
||||||
@@ -157,6 +158,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
const [settings, setSettings] = useState<Settings | null>(null);
|
const [settings, setSettings] = useState<Settings | null>(null);
|
||||||
const [selectedPresetId, setSelectedPresetId] = useState<string | undefined>(undefined);
|
const [selectedPresetId, setSelectedPresetId] = useState<string | undefined>(undefined);
|
||||||
const [isFastMode, setIsFastMode] = useState(false);
|
const [isFastMode, setIsFastMode] = useState(false);
|
||||||
|
const [githubTrackingOverride, setGithubTrackingOverride] = useState<boolean | null>(null);
|
||||||
const [priority, setPriority] = useState<TaskPriority>(DEFAULT_TASK_PRIORITY);
|
const [priority, setPriority] = useState<TaskPriority>(DEFAULT_TASK_PRIORITY);
|
||||||
const [nodeId, setNodeId] = useState<string | undefined>(undefined);
|
const [nodeId, setNodeId] = useState<string | undefined>(undefined);
|
||||||
const { nodes } = useNodes();
|
const { nodes } = useNodes();
|
||||||
@@ -435,6 +437,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
setPlanningModelId(undefined);
|
setPlanningModelId(undefined);
|
||||||
setSelectedPresetId(undefined);
|
setSelectedPresetId(undefined);
|
||||||
setIsFastMode(false);
|
setIsFastMode(false);
|
||||||
|
setGithubTrackingOverride(null);
|
||||||
setPriority(DEFAULT_TASK_PRIORITY);
|
setPriority(DEFAULT_TASK_PRIORITY);
|
||||||
setNodeId(undefined);
|
setNodeId(undefined);
|
||||||
setShowDeps(false);
|
setShowDeps(false);
|
||||||
@@ -508,6 +511,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
planningModelProvider: hasPlanningOverride ? planningProvider : undefined,
|
planningModelProvider: hasPlanningOverride ? planningProvider : undefined,
|
||||||
planningModelId: hasPlanningOverride ? planningModelId : undefined,
|
planningModelId: hasPlanningOverride ? planningModelId : undefined,
|
||||||
...(isFastMode ? { executionMode: "fast" } : {}),
|
...(isFastMode ? { executionMode: "fast" } : {}),
|
||||||
|
githubTracking: githubTrackingOverride !== null ? { enabled: githubTrackingOverride } : undefined,
|
||||||
priority,
|
priority,
|
||||||
nodeId,
|
nodeId,
|
||||||
});
|
});
|
||||||
@@ -555,6 +559,7 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
addToast,
|
addToast,
|
||||||
resetForm,
|
resetForm,
|
||||||
isFastMode,
|
isFastMode,
|
||||||
|
githubTrackingOverride,
|
||||||
priority,
|
priority,
|
||||||
nodeId,
|
nodeId,
|
||||||
]);
|
]);
|
||||||
@@ -1325,6 +1330,11 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
const selectedAgent = selectedAgentId ? agents.find((agent) => agent.id === selectedAgentId) : undefined;
|
const selectedAgent = selectedAgentId ? agents.find((agent) => agent.id === selectedAgentId) : undefined;
|
||||||
const selectedAgentLabel = selectedAgent?.name ?? selectedAgentId;
|
const selectedAgentLabel = selectedAgent?.name ?? selectedAgentId;
|
||||||
const selectedNode = nodeId ? nodes.find((node) => node.id === nodeId) : undefined;
|
const selectedNode = nodeId ? nodes.find((node) => node.id === nodeId) : undefined;
|
||||||
|
const projectGithubTrackingDefault = settings?.githubTrackingEnabledByDefault === true;
|
||||||
|
const effectiveGithubTracking = githubTrackingOverride ?? projectGithubTrackingDefault;
|
||||||
|
const githubToggleLabel = effectiveGithubTracking
|
||||||
|
? `GitHub tracking ON for next task (project default: ${projectGithubTrackingDefault ? "on" : "off"})`
|
||||||
|
: "GitHub tracking OFF for next task";
|
||||||
|
|
||||||
// Show expanded controls based on disclosure state (user preference), not textarea focus
|
// Show expanded controls based on disclosure state (user preference), not textarea focus
|
||||||
const showExpandedControls = isDisclosureExpanded;
|
const showExpandedControls = isDisclosureExpanded;
|
||||||
@@ -1835,6 +1845,19 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
|
|||||||
Fast
|
Fast
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-sm ${effectiveGithubTracking ? "btn-primary" : ""}`}
|
||||||
|
onClick={() => setGithubTrackingOverride(!effectiveGithubTracking)}
|
||||||
|
onMouseDown={(e) => e.preventDefault()}
|
||||||
|
aria-pressed={effectiveGithubTracking}
|
||||||
|
data-testid="quick-entry-github-toggle"
|
||||||
|
title={githubToggleLabel}
|
||||||
|
aria-label={githubToggleLabel}
|
||||||
|
>
|
||||||
|
<ProviderIcon provider="github" size="sm" />
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-task-create btn-sm"
|
className="btn btn-task-create btn-sm"
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ vi.mock("lucide-react", () => ({
|
|||||||
Bot: () => null,
|
Bot: () => null,
|
||||||
Server: () => null,
|
Server: () => null,
|
||||||
Flag: () => null,
|
Flag: () => null,
|
||||||
|
Github: () => null,
|
||||||
Maximize2: () => null,
|
Maximize2: () => null,
|
||||||
Minimize2: () => null,
|
Minimize2: () => null,
|
||||||
}));
|
}));
|
||||||
@@ -756,6 +757,57 @@ describe("QuickEntryBox", () => {
|
|||||||
expect(fastToggle.getAttribute("aria-pressed")).toBe("false");
|
expect(fastToggle.getAttribute("aria-pressed")).toBe("false");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders GitHub toggle off by default when project setting is disabled", async () => {
|
||||||
|
vi.mocked(fetchSettings).mockResolvedValueOnce({
|
||||||
|
githubTrackingEnabledByDefault: false,
|
||||||
|
} as any);
|
||||||
|
renderQuickEntryBox({ availableModels: undefined });
|
||||||
|
expandQuickEntry();
|
||||||
|
|
||||||
|
const githubToggle = await screen.findByTestId("quick-entry-github-toggle");
|
||||||
|
expect(githubToggle.getAttribute("aria-pressed")).toBe("false");
|
||||||
|
expect(githubToggle.className.includes("btn-primary")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders GitHub toggle on by default when project setting is enabled", async () => {
|
||||||
|
vi.mocked(fetchSettings).mockResolvedValueOnce({
|
||||||
|
githubTrackingEnabledByDefault: true,
|
||||||
|
} as any);
|
||||||
|
renderQuickEntryBox({ availableModels: undefined });
|
||||||
|
expandQuickEntry();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("quick-entry-github-toggle").getAttribute("aria-pressed")).toBe("true");
|
||||||
|
});
|
||||||
|
expect(screen.getByTestId("quick-entry-github-toggle").className.includes("btn-primary")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("flips GitHub toggle from both project-default directions", async () => {
|
||||||
|
vi.mocked(fetchSettings).mockResolvedValueOnce({
|
||||||
|
githubTrackingEnabledByDefault: false,
|
||||||
|
} as any);
|
||||||
|
const { unmount } = renderQuickEntryBox({ availableModels: undefined });
|
||||||
|
expandQuickEntry();
|
||||||
|
|
||||||
|
const githubToggle = await screen.findByTestId("quick-entry-github-toggle");
|
||||||
|
expect(githubToggle.getAttribute("aria-pressed")).toBe("false");
|
||||||
|
fireEvent.click(githubToggle);
|
||||||
|
expect(githubToggle.getAttribute("aria-pressed")).toBe("true");
|
||||||
|
unmount();
|
||||||
|
|
||||||
|
vi.mocked(fetchSettings).mockResolvedValueOnce({
|
||||||
|
githubTrackingEnabledByDefault: true,
|
||||||
|
} as any);
|
||||||
|
renderQuickEntryBox({ availableModels: undefined });
|
||||||
|
expandQuickEntry();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("quick-entry-github-toggle").getAttribute("aria-pressed")).toBe("true");
|
||||||
|
});
|
||||||
|
fireEvent.click(screen.getByTestId("quick-entry-github-toggle"));
|
||||||
|
expect(screen.getByTestId("quick-entry-github-toggle").getAttribute("aria-pressed")).toBe("false");
|
||||||
|
});
|
||||||
|
|
||||||
it.each(["Enter", "Save"] as const)("submits executionMode=fast when Fast is active via %s", async (submitPath) => {
|
it.each(["Enter", "Save"] as const)("submits executionMode=fast when Fast is active via %s", async (submitPath) => {
|
||||||
const { props } = renderQuickEntryBox({});
|
const { props } = renderQuickEntryBox({});
|
||||||
expandQuickEntry();
|
expandQuickEntry();
|
||||||
@@ -796,6 +848,71 @@ describe("QuickEntryBox", () => {
|
|||||||
expect(firstPayload.executionMode).toBeUndefined();
|
expect(firstPayload.executionMode).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("omits githubTracking when toggle is untouched", async () => {
|
||||||
|
const { props } = renderQuickEntryBox({});
|
||||||
|
expandQuickEntry();
|
||||||
|
const textarea = screen.getByTestId("quick-entry-input");
|
||||||
|
|
||||||
|
fireEvent.change(textarea, { target: { value: "No github override" } });
|
||||||
|
fireEvent.keyDown(textarea, { key: "Enter" });
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(props.onCreate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
const payload = props.onCreate.mock.calls[0]?.[0];
|
||||||
|
expect(payload.githubTracking).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("submits githubTracking override when toggle is clicked", async () => {
|
||||||
|
vi.mocked(fetchSettings).mockResolvedValueOnce({
|
||||||
|
githubTrackingEnabledByDefault: false,
|
||||||
|
} as any);
|
||||||
|
const { props } = renderQuickEntryBox({ availableModels: undefined });
|
||||||
|
expandQuickEntry();
|
||||||
|
const textarea = screen.getByTestId("quick-entry-input");
|
||||||
|
|
||||||
|
const githubToggle = await screen.findByTestId("quick-entry-github-toggle");
|
||||||
|
fireEvent.click(githubToggle);
|
||||||
|
fireEvent.change(textarea, { target: { value: "Override github tracking" } });
|
||||||
|
fireEvent.keyDown(textarea, { key: "Enter" });
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(props.onCreate).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
description: "Override github tracking",
|
||||||
|
githubTracking: { enabled: true },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resets GitHub toggle to project default after successful task creation", async () => {
|
||||||
|
vi.mocked(fetchSettings).mockResolvedValueOnce({
|
||||||
|
githubTrackingEnabledByDefault: true,
|
||||||
|
} as any);
|
||||||
|
const { props } = renderQuickEntryBox({ availableModels: undefined });
|
||||||
|
expandQuickEntry();
|
||||||
|
const textarea = screen.getByTestId("quick-entry-input");
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("quick-entry-github-toggle").getAttribute("aria-pressed")).toBe("true");
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId("quick-entry-github-toggle"));
|
||||||
|
expect(screen.getByTestId("quick-entry-github-toggle").getAttribute("aria-pressed")).toBe("false");
|
||||||
|
|
||||||
|
fireEvent.change(textarea, { target: { value: "Reset github toggle" } });
|
||||||
|
fireEvent.keyDown(textarea, { key: "Enter" });
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(props.onCreate).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
expandQuickEntry();
|
||||||
|
expect(screen.getByTestId("quick-entry-github-toggle").getAttribute("aria-pressed")).toBe("true");
|
||||||
|
});
|
||||||
|
|
||||||
it("resets Fast toggle to standard after successful task creation", async () => {
|
it("resets Fast toggle to standard after successful task creation", async () => {
|
||||||
const { props } = renderQuickEntryBox({});
|
const { props } = renderQuickEntryBox({});
|
||||||
expandQuickEntry();
|
expandQuickEntry();
|
||||||
|
|||||||
Reference in New Issue
Block a user