FN-8017: add imported-item filter to task import
Add a persisted control that declutters Import Tasks by hiding items already on the board. - Filter imported GitHub issues, pull requests, and GitLab resources while retaining full imported counts. - Clear hidden selections, show an all-imported empty state, and preserve the preference per project. - Document the toggle, add styling, coverage, and a minor changeset. Files changed: .changeset/fn-8017-hide-imported-toggle.md | 7 ++ docs/dashboard-guide.md | 4 +- .../dashboard/app/components/GitHubImportModal.css | 21 ++++ .../dashboard/app/components/GitHubImportModal.tsx | 70 ++++++++++++- .../__tests__/GitHubImportModal.test.tsx | 108 +++++++++++++++++++++ packages/dashboard/app/hooks/modalPersistence.ts | 6 ++ 6 files changed, 209 insertions(+), 7 deletions(-) Fusion-Task-Id: FN-8017 Fusion-Task-Lineage: 5f03fe92-6dce-4de7-a745-0c8a46dc2dbb Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8017-hide-imported-toggle.md
Normal file
7
.changeset/fn-8017-hide-imported-toggle.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Add a Hide imported toggle that filters imported issues, PRs, and GitLab items from Import Tasks.
|
||||
category: feature
|
||||
dev: GitHubImportModal renders a persisted per-project hideImported toggle in the list-pane header (and the GitLab toolbar/header); when on, imported rows (importedUrls predicate) are excluded from the issues/pulls/GitLab render sets while the "{n} imported" count still reflects the full fetched set, with a dedicated all-imported empty state. Toggle persists via GitHubImportPersistedState.hideImported.
|
||||
@@ -302,13 +302,13 @@ Use Import Tasks on desktop/tablet:
|
||||
2. Choose or enter a repository (`owner/repo`). If Git remotes are detected, use the remote selector.
|
||||
Expected outcome: Fusion loads import candidates for the selected repository and shows repository/load state feedback.
|
||||
3. Stay on **Issues** or switch to **Pull Requests**, then optionally enter issue label filters before loading results.
|
||||
Expected outcome: the list pane shows matching open issues or pull requests and marks entries that already exist on the board. After a successful GitHub or GitLab import, the source row is marked **Imported** and made unavailable immediately, without waiting for the board list to refresh.
|
||||
Expected outcome: the list pane shows matching open issues or pull requests and marks entries that already exist on the board. Use **Hide imported** beside the imported count to remove those unavailable rows from the current Issues, Pull Requests, or GitLab list; turning it off restores the greyed **Imported** rows. After a successful GitHub or GitLab import, the source row is marked **Imported** and made unavailable immediately, without waiting for the board list to refresh.
|
||||
4. Select an issue or pull request row.
|
||||
Expected outcome: the full-width candidate list stays visible while its title, source link, body, labels or PR metadata, and import controls open in a draggable and resizable detail window. On mobile, that detail is a full-screen sheet. When selected title/body content is in another language, the detail offers **Translate**, **Show original** / **Show translation**, and **Dismiss**; translation is display-only.
|
||||
5. Select the import action in the detail window.
|
||||
Expected outcome: Fusion creates a task (or review task for a pull request), preserves GitHub provenance/tracking metadata, closes the detail window, and returns to the list.
|
||||
|
||||
Leaving and returning to **Import Tasks** (for example switching to Board and back) restores the prior context for the current project — provider (GitHub/GitLab), active Issues/PRs tab, label filter, selected repository/remote, GitLab project/group inputs, and the previously selected issue/PR — instead of resetting to defaults. When GitLab integration is disabled in Settings, the GitLab provider tab is hidden and any restored GitLab provider preference opens on GitHub instead; saved GitLab URLs and tokens remain configured. The restored selection re-validates against the freshly reloaded list; a selection that no longer exists (e.g. the issue was closed upstream) clears gracefully rather than showing a stuck or empty preview. First-time opens with no prior state keep the existing default-remote auto-detect behavior. State is scoped per project and does not leak across projects.
|
||||
Leaving and returning to **Import Tasks** (for example switching to Board and back) restores the prior context for the current project — provider (GitHub/GitLab), active Issues/PRs tab, label filter, selected repository/remote, GitLab project/group inputs, the **Hide imported** preference, and the previously selected issue/PR — instead of resetting to defaults. When GitLab integration is disabled in Settings, the GitLab provider tab is hidden and any restored GitLab provider preference opens on GitHub instead; saved GitLab URLs and tokens remain configured. The restored selection re-validates against the freshly reloaded list; a selection that no longer exists (e.g. the issue was closed upstream) clears gracefully rather than showing a stuck or empty preview. First-time opens with no prior state keep the existing default-remote auto-detect behavior. State is scoped per project and does not leak across projects.
|
||||
|
||||
Use GitHub import on mobile:
|
||||
|
||||
|
||||
@@ -565,6 +565,26 @@ The Import Tasks sub-header tab bar should match the Artifacts view's button bar
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:GitHubImport 2026-07-15-16:30:
|
||||
Hide imported must remain a compact, reachable control beside loaded-result metadata and within the GitLab toolbar, including
|
||||
when either layout wraps on mobile. Token-based spacing keeps it consistent with the surrounding import controls.
|
||||
*/
|
||||
.github-import-hide-imported {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.github-import-hide-imported input {
|
||||
margin: 0;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
|
||||
.github-import-state {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
@@ -1246,6 +1266,7 @@ Across the thread: a top filter (All/Human/Bot) and prev/next chevrons live in t
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
|
||||
.issue-item {
|
||||
flex-wrap: wrap;
|
||||
min-height: 36px;
|
||||
|
||||
@@ -429,6 +429,13 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId,
|
||||
// Gates the persist-on-change effect until the mount hydration effect has applied its (possibly restored) values to
|
||||
// state, so the FIRST commit's still-default state is never written over a real persisted value.
|
||||
const [readyToPersistImportState, setReadyToPersistImportState] = useState(false);
|
||||
const [hideImported, setHideImported] = useState(false);
|
||||
|
||||
/*
|
||||
FNXC:GitHubImport 2026-07-15-16:30:
|
||||
The Hide imported control is a view-only filter over already-imported candidates. Persist it per project with the other
|
||||
cheap Import Tasks preferences so navigation preserves the operator's decluttering choice without retaining fetched lists.
|
||||
*/
|
||||
|
||||
/*
|
||||
FNXC:GitHubImport 2026-07-15-15:30:
|
||||
@@ -493,6 +500,7 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId,
|
||||
setOwner("");
|
||||
setRepo("");
|
||||
setLabels(persisted?.labels ?? "");
|
||||
setHideImported(persisted?.hideImported ?? false);
|
||||
setProvider(persisted?.provider ?? "github");
|
||||
setGitlabResource(persisted?.gitlabResource ?? "project_issue");
|
||||
setGitlabProject(persisted?.gitlabProject ?? "");
|
||||
@@ -832,6 +840,7 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId,
|
||||
selectedIssueNumber,
|
||||
selectedPullNumber,
|
||||
selectedGitlabKey,
|
||||
hideImported,
|
||||
},
|
||||
projectId,
|
||||
);
|
||||
@@ -849,6 +858,7 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId,
|
||||
selectedIssueNumber,
|
||||
selectedPullNumber,
|
||||
selectedGitlabKey,
|
||||
hideImported,
|
||||
projectId,
|
||||
]);
|
||||
|
||||
@@ -1147,6 +1157,13 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId,
|
||||
autoTranslateEnabled,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!hideImported) return;
|
||||
if (selectedIssue && isUrlImported(selectedIssue.html_url)) setSelectedIssueNumber(null);
|
||||
if (selectedPull && isUrlImported(selectedPull.html_url)) setSelectedPullNumber(null);
|
||||
if (selectedGitlabItem && isUrlImported(selectedGitlabItem.webUrl)) setSelectedGitlabKey(null);
|
||||
}, [hideImported, selectedIssue, selectedPull, selectedGitlabItem, isUrlImported]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
// Determine state flags
|
||||
@@ -1156,6 +1173,17 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId,
|
||||
// Tab-specific counts
|
||||
const importedIssueCount = issues.filter((issue) => isUrlImported(issue.html_url)).length;
|
||||
const importedPullCount = pulls.filter((pull) => isUrlImported(pull.html_url)).length;
|
||||
/*
|
||||
FNXC:GitHubImport 2026-07-15-16:30:
|
||||
Hide imported removes imported rows from every provider's render set, while toggle-off preserves the original arrays and
|
||||
imported counts always use the full fetched sets. Use `isUrlImported` so optimistic imports also disappear immediately.
|
||||
*/
|
||||
const visibleIssues = hideImported ? issues.filter((issue) => !isUrlImported(issue.html_url)) : issues;
|
||||
const visiblePulls = hideImported ? pulls.filter((pull) => !isUrlImported(pull.html_url)) : pulls;
|
||||
const visibleGitlabItems = hideImported ? gitlabItems.filter((item) => !isUrlImported(item.webUrl)) : gitlabItems;
|
||||
const allIssuesHidden = hideImported && issues.length > 0 && visibleIssues.length === 0;
|
||||
const allPullsHidden = hideImported && pulls.length > 0 && visiblePulls.length === 0;
|
||||
const allGitlabItemsHidden = hideImported && gitlabItems.length > 0 && visibleGitlabItems.length === 0;
|
||||
|
||||
// Empty states
|
||||
const isIssuesEmpty = isIssuesEmptyState;
|
||||
@@ -1357,12 +1385,20 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId,
|
||||
<div className="github-import-results-meta" aria-live="polite">
|
||||
<span>{t("git.issueCount", { count: issues.length, defaultValue_one: "{{count}} issue", defaultValue_other: "{{count}} issues" })}</span>
|
||||
<span>{t("git.importedCount", "{{count}} imported", { count: importedIssueCount })}</span>
|
||||
<label className="github-import-hide-imported">
|
||||
<input type="checkbox" checked={hideImported} onChange={(event) => setHideImported(event.target.checked)} data-testid="github-import-hide-imported-toggle" />
|
||||
{t("git.hideImported", "Hide imported")}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
{activeTab === "pulls" && pulls.length > 0 && (
|
||||
<div className="github-import-results-meta" aria-live="polite">
|
||||
<span>{t("git.pullCount", { count: pulls.length, defaultValue_one: "{{count}} pull request", defaultValue_other: "{{count}} pull requests" })}</span>
|
||||
<span>{t("git.importedCount", "{{count}} imported", { count: importedPullCount })}</span>
|
||||
<label className="github-import-hide-imported">
|
||||
<input type="checkbox" checked={hideImported} onChange={(event) => setHideImported(event.target.checked)} data-testid="github-import-hide-imported-toggle" />
|
||||
{t("git.hideImported", "Hide imported")}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -1405,10 +1441,16 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId,
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === "issues" && allIssuesHidden && (
|
||||
<div className="github-import-state github-import-state--empty" data-testid="github-import-all-imported-empty" role="status">
|
||||
<div><strong>{t("git.allImportedHidden", "All loaded items are already imported")}</strong></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Issues list */}
|
||||
{activeTab === "issues" && issues.length > 0 && (
|
||||
{activeTab === "issues" && visibleIssues.length > 0 && (
|
||||
<div className="issues-list" aria-live="polite">
|
||||
{issues.map((issue) => {
|
||||
{visibleIssues.map((issue) => {
|
||||
const isImported = isUrlImported(issue.html_url);
|
||||
return (
|
||||
<div
|
||||
@@ -1456,10 +1498,16 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId,
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === "pulls" && allPullsHidden && (
|
||||
<div className="github-import-state github-import-state--empty" data-testid="github-import-all-imported-empty" role="status">
|
||||
<div><strong>{t("git.allImportedHidden", "All loaded items are already imported")}</strong></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Pulls list */}
|
||||
{activeTab === "pulls" && pulls.length > 0 && (
|
||||
{activeTab === "pulls" && visiblePulls.length > 0 && (
|
||||
<div className="issues-list" aria-live="polite">
|
||||
{pulls.map((pull) => {
|
||||
{visiblePulls.map((pull) => {
|
||||
const isImported = isUrlImported(pull.html_url);
|
||||
return (
|
||||
<div
|
||||
@@ -1746,13 +1794,24 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId,
|
||||
{loading ? <Loader2 size={14} className="spin" /> : <RefreshCw size={14} />}
|
||||
{t("git.load", "Load")}
|
||||
</button>
|
||||
{gitlabItems.length > 0 && (
|
||||
<label className="github-import-hide-imported">
|
||||
<input type="checkbox" checked={hideImported} onChange={(event) => setHideImported(event.target.checked)} data-testid="github-import-hide-imported-toggle" />
|
||||
{t("git.hideImported", "Hide imported")}
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
{!gitlabEnabled && <div className="github-import-state github-import-state--idle" data-testid="gitlab-import-disabled"><strong>{t("git.gitlabDisabledHeading", "GitLab integration disabled")}</strong><span>{t("git.gitlabDisabledHint", "Enable GitLab integration in Settings to fetch or import GitLab resources. Saved GitLab URLs and tokens remain configured.")}</span></div>}
|
||||
{error && <div className="github-import-state github-import-state--error" data-testid="gitlab-import-error"><strong>{t("git.gitlabError", "GitLab import unavailable")}</strong><span>{error}</span></div>}
|
||||
{gitlabItems.length === 0 && !loading && !error ? <div className="github-import-state github-import-state--idle" data-testid="gitlab-import-empty"><strong>{t("git.gitlabNoResources", "No GitLab resources loaded")}</strong><span>{t("git.gitlabLoadHint", "Enter a project or group and load resources from the configured GitLab instance.")}</span></div> : null}
|
||||
<div className="github-import-gitlab__workspace">
|
||||
{allGitlabItemsHidden ? (
|
||||
<div className="github-import-state github-import-state--empty" data-testid="github-import-all-imported-empty" role="status">
|
||||
<div><strong>{t("git.allImportedHidden", "All loaded items are already imported")}</strong></div>
|
||||
</div>
|
||||
) : visibleGitlabItems.length > 0 ? (
|
||||
<div className="issues-list" aria-live="polite">
|
||||
{gitlabItems.map((item) => {
|
||||
{visibleGitlabItems.map((item) => {
|
||||
const key = `${item.resourceKind}:${item.projectId ?? item.projectPath ?? ""}:${item.iid}`;
|
||||
const imported = isUrlImported(item.webUrl);
|
||||
return (
|
||||
@@ -1763,6 +1822,7 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId,
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
{selectedGitlabItem && (
|
||||
<FloatingWindow
|
||||
windowKey="github-import-detail"
|
||||
|
||||
@@ -1912,4 +1912,112 @@ describe("GitHubImportModal", () => {
|
||||
expect(source).toMatch(/@media \(max-width: 768px\)[\s\S]*\.floating-window--github-import-detail[\s\S]*width: 100vw !important/);
|
||||
expect(source).toContain(".floating-window--github-import-detail .floating-window__resize-handle");
|
||||
});
|
||||
|
||||
describe("Hide imported", () => {
|
||||
const issueItems = [
|
||||
{ number: 1, title: "Imported issue", body: "", html_url: "https://github.com/owner/repo/issues/1", labels: [] },
|
||||
{ number: 2, title: "Available issue", body: "", html_url: "https://github.com/owner/repo/issues/2", labels: [] },
|
||||
];
|
||||
const gitlabItems = [
|
||||
{ resourceKind: "project_issue" as const, id: 1, iid: 1, projectId: 3, projectPath: "group/project", title: "Imported GitLab item", description: "", webUrl: "https://gitlab.example.com/group/project/-/issues/1", state: "opened", labels: [] },
|
||||
{ resourceKind: "project_issue" as const, id: 2, iid: 2, projectId: 3, projectPath: "group/project", title: "Available GitLab item", description: "", webUrl: "https://gitlab.example.com/group/project/-/issues/2", state: "opened", labels: [] },
|
||||
];
|
||||
|
||||
it("filters imported issues and pulls without changing their full imported counts", async () => {
|
||||
const importedIssueTask = { ...mockTask, description: "Source: https://github.com/owner/repo/issues/1" };
|
||||
const importedPullTask = { ...mockPRTask, description: "PR: https://github.com/owner/repo/pull/1" };
|
||||
vi.mocked(fetchGitRemotes).mockResolvedValueOnce([{ name: "origin", owner: "owner", repo: "repo", url: "" }]);
|
||||
vi.mocked(apiFetchGitHubIssues).mockResolvedValueOnce(issueItems);
|
||||
vi.mocked(apiFetchGitHubPulls).mockResolvedValueOnce(mockPulls);
|
||||
render(<GitHubImportModal isOpen onClose={onClose} onImport={onImport} tasks={[importedIssueTask, importedPullTask]} projectId="project-1" />);
|
||||
|
||||
await screen.findByText("Imported issue");
|
||||
const toggle = screen.getByRole("checkbox", { name: /hide imported/i });
|
||||
fireEvent.click(toggle);
|
||||
expect(screen.queryByText("Imported issue")).toBeNull();
|
||||
expect(screen.getByText("Available issue")).toBeTruthy();
|
||||
expect(screen.getByText("1 imported")).toBeTruthy();
|
||||
fireEvent.click(toggle);
|
||||
expect(await screen.findByText("Imported issue")).toBeTruthy();
|
||||
expect(screen.getByText("Imported issue").closest(".issue-item")).toHaveClass("imported");
|
||||
|
||||
fireEvent.click(screen.getByRole("tab", { name: /pull requests/i }));
|
||||
await screen.findByText("Test PR");
|
||||
fireEvent.click(screen.getByRole("checkbox", { name: /hide imported/i }));
|
||||
expect(screen.queryByText("Test PR")).toBeNull();
|
||||
expect(screen.getByText("Another PR")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("persists the toggle per project and clears a selection that becomes hidden", async () => {
|
||||
vi.mocked(fetchGitRemotes).mockResolvedValue([{ name: "origin", owner: "owner", repo: "repo", url: "" }]);
|
||||
vi.mocked(apiFetchGitHubIssues).mockResolvedValue(issueItems);
|
||||
const view = render(<GitHubImportModal isOpen onClose={onClose} onImport={onImport} tasks={[]} projectId="project-1" />);
|
||||
await screen.findByText("Imported issue");
|
||||
fireEvent.click(screen.getByRole("radio", { name: /select issue #1/i }));
|
||||
expect(await screen.findByTestId("github-import-preview-card")).toHaveTextContent("Imported issue");
|
||||
view.rerender(<GitHubImportModal isOpen onClose={onClose} onImport={onImport} tasks={[{ ...mockTask, description: "Source: https://github.com/owner/repo/issues/1" }]} projectId="project-1" />);
|
||||
fireEvent.click(screen.getByTestId("github-import-hide-imported-toggle"));
|
||||
await waitFor(() => expect(screen.queryByTestId("github-import-preview-card")).toBeNull());
|
||||
view.unmount();
|
||||
|
||||
render(<GitHubImportModal isOpen onClose={onClose} onImport={onImport} tasks={[]} projectId="project-1" />);
|
||||
await screen.findByText("Imported issue");
|
||||
expect(screen.getByTestId("github-import-hide-imported-toggle")).toBeChecked();
|
||||
});
|
||||
|
||||
it("filters GitLab rows and renders a dedicated all-imported state for every provider", async () => {
|
||||
const importedGitlabTask = { ...mockTask, description: "Source: https://gitlab.example.com/group/project/-/issues/1" };
|
||||
vi.mocked(fetchGitRemotes).mockResolvedValueOnce([]);
|
||||
vi.mocked(apiFetchGitLabProjectIssues).mockResolvedValueOnce(gitlabItems);
|
||||
const filteredView = render(<GitHubImportModal isOpen onClose={onClose} onImport={onImport} tasks={[importedGitlabTask]} projectId="project-2" />);
|
||||
fireEvent.click(await screen.findByRole("button", { name: "GitLab" }));
|
||||
fireEvent.change(screen.getByLabelText("GitLab project path or ID"), { target: { value: "group/project" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Load" }));
|
||||
await screen.findByText(/Imported GitLab item/);
|
||||
fireEvent.click(screen.getByTestId("github-import-hide-imported-toggle"));
|
||||
expect(screen.queryByText(/Imported GitLab item/)).toBeNull();
|
||||
expect(screen.getByText(/Available GitLab item/)).toBeTruthy();
|
||||
|
||||
const allImportedTask = { ...mockTask, description: "Source: https://gitlab.example.com/group/project/-/issues/2" };
|
||||
filteredView.unmount();
|
||||
window.localStorage.removeItem(`kb:project-2:${GITHUB_IMPORT_STATE_KEY}`);
|
||||
vi.mocked(fetchGitRemotes).mockResolvedValueOnce([]);
|
||||
vi.mocked(apiFetchGitLabProjectIssues).mockResolvedValueOnce(gitlabItems);
|
||||
render(<GitHubImportModal isOpen onClose={onClose} onImport={onImport} tasks={[importedGitlabTask, allImportedTask]} projectId="project-a" />);
|
||||
fireEvent.click(await screen.findByRole("button", { name: "GitLab" }));
|
||||
fireEvent.change(screen.getByLabelText("GitLab project path or ID"), { target: { value: "group/project" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Load" }));
|
||||
fireEvent.click(await screen.findByTestId("github-import-hide-imported-toggle"));
|
||||
expect(await screen.findByTestId("github-import-all-imported-empty")).toHaveTextContent("All loaded items are already imported");
|
||||
});
|
||||
|
||||
it("keeps the shared toggle reachable and functional at the mobile breakpoint", async () => {
|
||||
const originalInnerWidth = window.innerWidth;
|
||||
Object.defineProperty(window, "innerWidth", { writable: true, configurable: true, value: 480 });
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
const importedIssueTask = { ...mockTask, description: "Source: https://github.com/owner/repo/issues/1" };
|
||||
vi.mocked(fetchGitRemotes).mockResolvedValueOnce([{ name: "origin", owner: "owner", repo: "repo", url: "" }]);
|
||||
vi.mocked(apiFetchGitHubIssues).mockResolvedValueOnce(issueItems);
|
||||
const view = render(<GitHubImportModal isOpen onClose={onClose} onImport={onImport} tasks={[importedIssueTask]} projectId="project-b" />);
|
||||
await screen.findByText("Imported issue");
|
||||
fireEvent.click(screen.getByTestId("github-import-hide-imported-toggle"));
|
||||
expect(screen.queryByText("Imported issue")).toBeNull();
|
||||
expect(screen.getByText("Available issue")).toBeTruthy();
|
||||
view.unmount();
|
||||
|
||||
window.localStorage.removeItem(`kb:project-b:${GITHUB_IMPORT_STATE_KEY}`);
|
||||
vi.mocked(fetchGitRemotes).mockResolvedValueOnce([]);
|
||||
vi.mocked(apiFetchGitLabProjectIssues).mockResolvedValueOnce(gitlabItems);
|
||||
render(<GitHubImportModal isOpen onClose={onClose} onImport={onImport} tasks={[{ ...mockTask, description: "Source: https://gitlab.example.com/group/project/-/issues/1" }]} projectId="project-b" />);
|
||||
fireEvent.click(await screen.findByRole("button", { name: "GitLab" }));
|
||||
fireEvent.change(screen.getByLabelText("GitLab project path or ID"), { target: { value: "group/project" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Load" }));
|
||||
await screen.findByText(/Imported GitLab item/);
|
||||
fireEvent.click(screen.getByTestId("github-import-hide-imported-toggle"));
|
||||
expect(screen.queryByText(/Imported GitLab item/)).toBeNull();
|
||||
expect(screen.getByText(/Available GitLab item/)).toBeTruthy();
|
||||
Object.defineProperty(window, "innerWidth", { writable: true, configurable: true, value: originalInnerWidth });
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,6 +72,11 @@ export interface GitHubImportPersistedState {
|
||||
selectedIssueNumber: number | null;
|
||||
selectedPullNumber: number | null;
|
||||
selectedGitlabKey: string | null;
|
||||
/*
|
||||
FNXC:GitHubImport 2026-07-15-16:30:
|
||||
Hide imported is a per-project view preference: it filters already-imported candidates without persisting fetched data or changing import state.
|
||||
*/
|
||||
hideImported?: boolean;
|
||||
}
|
||||
|
||||
export function saveGitHubImportState(state: GitHubImportPersistedState, projectId?: string): void {
|
||||
@@ -109,6 +114,7 @@ export function getGitHubImportState(projectId?: string): GitHubImportPersistedS
|
||||
selectedIssueNumber: typeof p.selectedIssueNumber === "number" ? p.selectedIssueNumber : null,
|
||||
selectedPullNumber: typeof p.selectedPullNumber === "number" ? p.selectedPullNumber : null,
|
||||
selectedGitlabKey: typeof p.selectedGitlabKey === "string" ? p.selectedGitlabKey : null,
|
||||
hideImported: typeof p.hideImported === "boolean" ? p.hideImported : undefined,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user