mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 05:11:49 +01:00
perf(ui): serve every project list from one includeArchived read (#20395)
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - The board UI reads the company project list in many places: the
sidebar, starred projects, the command palette, the new-task dialog, and
archive-aware pages such as the Mine inbox
> - Active-only consumers read `GET /projects`, and archive-aware ones
read `GET /projects?includeArchived=true`, under two React Query keys
> - A Mine load therefore fetched the same large payload twice (580–590
KB each on our dev instance)
> - The two responses differ only by archived rows; the server's active
list is exactly the rows where `archivedAt` is null
> - This pull request makes every consumer share the
`includeArchived=true` query, and active-only consumers filter it with
`select`
> - The benefit is one project read per page instead of two
## Linked Issues or Issue Description
**What existing behavior does this improve?**
Duplicate project list reads on page load.
**Subsystem affected**
Board UI project queries (`ui/src/lib/company-projects-query.ts` and its
consumers).
**Current behavior**
A cold Mine load sends both `/companies/:id/projects` (sidebar) and
`/companies/:id/projects?includeArchived=true` (inbox).
**Proposed behavior**
One `includeArchived=true` read serves both. Active-only consumers apply
`selectActiveProjects`.
**Reason and benefit**
The project list is one of the largest shell payloads. Reading it once
saves a large response on every page that has both kinds of consumer.
## What Changed
- New `companyProjectsQuery(companyId, { includeArchived? })` in
`ui/src/lib/company-projects-query.ts`. It always uses the
`includeArchived: true` key and fetch. For active-only use it adds a
stable, module-level `select` that keeps rows without `archivedAt`.
- 22 files that read the active list now spread
`companyProjectsQuery(...)`, and so do the plugin SDK project picker and
16 archive-aware pages that wrote the shared key and fetcher inline.
Their `enabled` and other options are unchanged. `OnboardingWizard`'s
one imperative read is unchanged. `pages/Inbox.tsx` already reads the
same key and fetcher, so it shares the entry without an edit.
- Invalidation: project mutations and live events invalidate
`queryKeys.projects.all(companyId)`, which matches the shared key. The
onboarding wizard invalidated the old active-only key; it now
invalidates `projects.all`.
- Tests: new `company-projects-query.test.tsx` (one request for both
consumers, correct filtering, and one refetch after `projects.all`
invalidation). Three existing tests (`CommandPalette`, `CompanyExport`,
`GitHubBotManagement`) now seed or expect the shared key.
- New browser check
`tests/perf/collections/shared-project-list.spec.ts`: after the Mine
inbox read completes, the page has sent no active-only project request.
## Verification
- `pnpm verify` passes.
- `ui`: the consumer suites (`CoordinatorSidebar`, `SidebarProjects`,
`SidebarStarredProjects`, `CommandPalette`, `NewIssueDialog`,
`AgentWorkPanels`, `CoordinatorNeedsYou`, `IssueDetail`,
`ProjectDetail`, `Projects`, `CompanyExport`, `ChatExecutionDefaults`,
`GatewaysList`, `Search`, `IssueProperties`, `persisted-query-cache`)
pass.
- Browser check:
`PAPERCLIP_COLLECTION_PERF_BASE_URL=http://127.0.0.1:<port> pnpm exec
playwright test --config tests/perf/collections/playwright.config.ts
shared-project-list.spec.ts`. It passes on this branch and fails on
`master`.
## Risks
- A page that needs only active projects now downloads archived rows
too. For most companies that is a small difference; the duplicate read
it removes is a full copy.
- The server does not order either list, and consumers sort for display,
so filtering on the client keeps behavior.
## Model Used
- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, file edits, browser). 1M-token context window. Extended
thinking was off.
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c7eb35809e
commit
b184e40108
46 files changed
+213
-122
No files matched your search
@@ -0,0 +1,28 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { collectionFixture, type CollectionFixture } from "./fixture";
|
||||
|
||||
let fixture: CollectionFixture;
|
||||
test.beforeAll(async ({ request }) => {
|
||||
fixture = await collectionFixture(request);
|
||||
});
|
||||
|
||||
// The sidebar wants active projects and the inbox wants archived ones too.
|
||||
// One includeArchived read serves both.
|
||||
test("Mine reads the company project list once", async ({ page }) => {
|
||||
const path = `/api/companies/${fixture.company.id}/projects`;
|
||||
const reads: URL[] = [];
|
||||
page.on("request", item => {
|
||||
const url = new URL(item.url());
|
||||
if (url.pathname === path) reads.push(url);
|
||||
});
|
||||
const inbox = page.waitForResponse(item => new URL(item.url()).pathname === `/api/companies/${fixture.company.id}/inbox/mine` && item.ok());
|
||||
await page.goto(`/${fixture.company.issuePrefix}/issues?view=mine`);
|
||||
// Mine's own read has completed, so the inbox and the sidebar have both
|
||||
// mounted their project consumers.
|
||||
await inbox;
|
||||
await expect.poll(() => reads.some(url => url.searchParams.get("includeArchived") === "true")).toBe(true);
|
||||
// The live-updates socket may refresh the shared list once after it opens;
|
||||
// that is the same read. An active-only read means the payload was fetched twice.
|
||||
expect(reads.filter(url => url.searchParams.get("includeArchived") !== "true")).toEqual([]);
|
||||
expect(reads.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
@@ -1,18 +1,18 @@
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { isUuidLike } from "@paperclipai/shared";
|
||||
import { agentsApi } from "../api/agents";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { Link, useSearchParams } from "../lib/router";
|
||||
import { AllowedProjectsEditor } from "./AllowedProjectsEditor";
|
||||
import { Button } from "./ui/button";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
|
||||
export function AgentProjectAccessSettings({ agent }: { agent: { id: string; companyId: string; name: string; urlKey?: string } }) {
|
||||
const client = useQueryClient();
|
||||
const [params] = useSearchParams();
|
||||
const fromTask = params.get("fromTask");
|
||||
const access = useQuery({ queryKey: queryKeys.agents.projectAccess(agent.id), queryFn: () => agentsApi.projectAccess(agent.id) });
|
||||
const projects = useQuery({ queryKey: queryKeys.projects.list(agent.companyId), queryFn: () => projectsApi.list(agent.companyId) });
|
||||
const projects = useQuery({ ...companyProjectsQuery(agent.companyId) });
|
||||
if (access.error || projects.error) return <div role="alert" className="space-y-2 text-sm">
|
||||
<p>Couldn’t load project access.</p><Button variant="outline" size="sm" onClick={() => { void access.refetch(); void projects.refetch(); }}>Try again</Button>
|
||||
</div>;
|
||||
|
||||
@@ -346,7 +346,7 @@ describe("CommandPalette", () => {
|
||||
const { root } = renderWithQueryClient(<CommandPalette />, container, (queryClient) => {
|
||||
// Seed the caches so the already-loaded data is available synchronously —
|
||||
// this harness's flush model doesn't reliably propagate fresh async fetches.
|
||||
queryClient.setQueryData(queryKeys.projects.list("company-1"), projects);
|
||||
queryClient.setQueryData(queryKeys.projects.list("company-1", { includeArchived: true }), projects);
|
||||
queryClient.setQueryData(queryKeys.issues.search("company-1", "mob", undefined, 10), [
|
||||
{ id: "i1", identifier: "ENG-9", title: "Fix login" },
|
||||
]);
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useSidebar } from "../context/SidebarContext";
|
||||
import { issuesApi } from "../api/issues";
|
||||
import { authApi } from "../api/auth";
|
||||
import { agentsApi } from "../api/agents";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { instanceSettingsApi } from "../api/instanceSettings";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import {
|
||||
@@ -43,6 +42,7 @@ import {
|
||||
parseSearchQuery,
|
||||
type SearchQueryParserContext,
|
||||
} from "../lib/search-query-parser";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
|
||||
const SEARCH_ALL_VALUE = "__paperclip-search-all__";
|
||||
|
||||
@@ -129,8 +129,7 @@ export function CommandPalette() {
|
||||
});
|
||||
|
||||
const { data: allProjects = [] } = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId!),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!),
|
||||
...companyProjectsQuery(selectedCompanyId!),
|
||||
enabled: !!selectedCompanyId && open,
|
||||
});
|
||||
const projects = useMemo(
|
||||
|
||||
@@ -5,7 +5,6 @@ import type { Agent, Project } from "@paperclipai/shared";
|
||||
import { useCoordinatorAttentionCount } from "@/hooks/useCoordinatorAttentionCount";
|
||||
import { issuesApi } from "@/api/issues";
|
||||
import { instanceSettingsApi } from "@/api/instanceSettings";
|
||||
import { projectsApi } from "@/api/projects";
|
||||
import { useAgentChatNavigation } from "@/hooks/useAgentChatNavigation";
|
||||
import { useRecentTasks } from "@/hooks/useRecentTasks";
|
||||
import { usePinnedNavigation } from "@/hooks/usePinnedNavigation";
|
||||
@@ -27,6 +26,7 @@ import { taskStatusIconProps } from "@/lib/task-status-presentation";
|
||||
import { AgentAvatar } from "./AgentAvatar";
|
||||
import { agentChatPinKey } from "./AgentChatPin";
|
||||
import { PinnedNavigationList, type PinnedNavigationItem } from "./PinnedNavigationList";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
|
||||
const moreDestinations = [
|
||||
{ label: "Search", to: "/search", icon: Search },
|
||||
@@ -122,7 +122,7 @@ export function CoordinatorSidebar({ children }: { children?: ReactNode }) {
|
||||
const { openNewIssue } = useDialogActions();
|
||||
const location = useLocation();
|
||||
const attentionCount = useCoordinatorAttentionCount(companyId);
|
||||
const { data: projects } = useQuery({ queryKey: queryKeys.projects.list(companyId!), queryFn: () => projectsApi.list(companyId!), enabled: !!companyId });
|
||||
const { data: projects } = useQuery({ ...companyProjectsQuery(companyId!), enabled: !!companyId });
|
||||
// Only project IDs and visit times are kept in memory. Labels and task lists
|
||||
// always come from the authorized API, scoped to the current person/company.
|
||||
const scope = `${companyId}:${userId}`;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { usePrimaryAgentPresentation } from "./primary-agent/PrimaryAgentPresentation";
|
||||
import { useWorkspaceIsolationControls } from "@/hooks/useWorkspaceIsolationControls";
|
||||
import { useState, useEffect, useRef, useCallback, useMemo, type CSSProperties } from "react";
|
||||
@@ -10,7 +11,6 @@ import { executionWorkspacesApi } from "../api/execution-workspaces";
|
||||
import { issuesApi } from "../api/issues";
|
||||
import { MissingUserSecretsBanner } from "../pages/secrets/MissingUserSecretsBanner";
|
||||
import { instanceSettingsApi } from "../api/instanceSettings";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { agentsApi } from "../api/agents";
|
||||
import { accessApi } from "../api/access";
|
||||
import { authApi } from "../api/auth";
|
||||
@@ -517,8 +517,7 @@ function AccountNewIssueDialog({ ignoreDefaults }: { ignoreDefaults: boolean })
|
||||
});
|
||||
|
||||
const { data: projects } = useQuery({
|
||||
queryKey: queryKeys.projects.list(effectiveCompanyId!),
|
||||
queryFn: () => projectsApi.list(effectiveCompanyId!),
|
||||
...companyProjectsQuery(effectiveCompanyId!),
|
||||
enabled: !!effectiveCompanyId && newIssueOpen,
|
||||
});
|
||||
const { data: mentionIssues } = useQuery({
|
||||
|
||||
@@ -1920,7 +1920,7 @@ function OnboardingWizardInner({
|
||||
);
|
||||
projectId = project.id;
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.projects.list(createdCompanyId)
|
||||
queryKey: queryKeys.projects.all(createdCompanyId)
|
||||
});
|
||||
}
|
||||
if (stillTheSameCompany(createdCompanyId)) setCreatedProjectId(projectId);
|
||||
|
||||
@@ -16,7 +16,6 @@ import { useCompany } from "../context/CompanyContext";
|
||||
import { useDialogActions } from "../context/DialogContext";
|
||||
import { useSidebar } from "../context/SidebarContext";
|
||||
import { authApi } from "../api/auth";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { SIDEBAR_SCROLL_RESET_STATE } from "../lib/navigation-scroll";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { cn, projectRouteRef, SIDEBAR_RAIL_HIDDEN_LABEL } from "../lib/utils";
|
||||
@@ -45,6 +44,7 @@ import {
|
||||
writeProjectSortMode,
|
||||
} from "../lib/project-order";
|
||||
import type { Project } from "@paperclipai/shared";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
|
||||
type ProjectSidebarSlot = ReturnType<typeof usePluginSlots>["slots"][number];
|
||||
|
||||
@@ -263,8 +263,7 @@ export function SidebarProjects() {
|
||||
const location = useLocation();
|
||||
|
||||
const { data: projects } = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId!),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!),
|
||||
...companyProjectsQuery(selectedCompanyId!),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const membershipsQuery = useResourceMemberships(selectedCompanyId);
|
||||
|
||||
@@ -4,9 +4,7 @@ import { useQuery } from "@tanstack/react-query";
|
||||
import { Lock, Loader2, LogOut, MoreHorizontal, Star } from "lucide-react";
|
||||
import { useCompany } from "../context/CompanyContext";
|
||||
import { useSidebar } from "../context/SidebarContext";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { SIDEBAR_SCROLL_RESET_STATE } from "../lib/navigation-scroll";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { cn, projectRouteRef, SIDEBAR_RAIL_HIDDEN_LABEL } from "../lib/utils";
|
||||
import {
|
||||
isStarred,
|
||||
@@ -27,6 +25,7 @@ import {
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import type { Project } from "@paperclipai/shared";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
|
||||
// Sidebar star reveals with the row's own group, not the shared unnamed group.
|
||||
const STAR_ROW_REVEAL =
|
||||
@@ -46,8 +45,7 @@ export function SidebarStarredProjects() {
|
||||
const location = useLocation();
|
||||
|
||||
const { data: projects } = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId!),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!),
|
||||
...companyProjectsQuery(selectedCompanyId!),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const membershipsQuery = useResourceMemberships(selectedCompanyId);
|
||||
|
||||
@@ -4,9 +4,7 @@ import { useQuery } from "@tanstack/react-query";
|
||||
import { Lock, Loader2, LogOut, MoreHorizontal, Star } from "lucide-react";
|
||||
import { useCompany } from "../context/CompanyContext";
|
||||
import { useSidebar } from "../context/SidebarContext";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { SIDEBAR_SCROLL_RESET_STATE } from "../lib/navigation-scroll";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { cn, projectRouteRef, SIDEBAR_RAIL_HIDDEN_LABEL } from "../lib/utils";
|
||||
import {
|
||||
isStarred,
|
||||
@@ -27,6 +25,7 @@ import {
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import type { Project } from "@paperclipai/shared";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
|
||||
// Sidebar star reveals with the row's own group, not the shared unnamed group.
|
||||
const STAR_ROW_REVEAL =
|
||||
@@ -46,8 +45,7 @@ export function SidebarStarredProjects() {
|
||||
const location = useLocation();
|
||||
|
||||
const { data: projects } = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId!),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!),
|
||||
...companyProjectsQuery(selectedCompanyId!),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const membershipsQuery = useResourceMemberships(selectedCompanyId);
|
||||
|
||||
@@ -13,7 +13,6 @@ import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn, formatDateTime } from "@/lib/utils";
|
||||
import { fileResourcesApi } from "@/api/file-resources";
|
||||
import { projectsApi } from "@/api/projects";
|
||||
import { ApiError } from "@/api/client";
|
||||
import { describeError } from "@/api/errors";
|
||||
import { queryViewKind } from "@/components/QueryView";
|
||||
@@ -27,6 +26,7 @@ import type {
|
||||
WorkspaceFileListMode,
|
||||
WorkspaceFileSelector,
|
||||
} from "@paperclipai/shared";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
|
||||
type BrowserSource = "current" | "other";
|
||||
|
||||
@@ -539,8 +539,7 @@ export function WorkspaceFileBrowser({
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const projectsQuery = useQuery({
|
||||
queryKey: companyId ? queryKeys.projects.list(companyId) : ["projects", "__none__"],
|
||||
queryFn: () => projectsApi.list(companyId!),
|
||||
...companyProjectsQuery(companyId ?? ""),
|
||||
enabled: source === "other" && !!companyId,
|
||||
staleTime: 30_000,
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@ import { ArrowUpDown, File, FileText, Image, Search, Video } from "lucide-react"
|
||||
import type { CompanyArtifact, Issue } from "@paperclipai/shared";
|
||||
import { artifactsApi } from "@/api/artifacts";
|
||||
import { issuesApi } from "@/api/issues";
|
||||
import { projectsApi } from "@/api/projects";
|
||||
import { IssueFiltersPopover } from "@/components/IssueFiltersPopover";
|
||||
import { StatusIcon } from "@/components/StatusIcon";
|
||||
import { QueryErrorState, useQueryView } from "@/components/QueryView";
|
||||
@@ -22,6 +21,7 @@ import { queryKeys } from "@/lib/queryKeys";
|
||||
import { Link } from "@/lib/router";
|
||||
import { cn, formatDate, relativeTime } from "@/lib/utils";
|
||||
import { taskStatusIconProps } from "@/lib/task-status-presentation";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
|
||||
/**
|
||||
* The agent-scoped side panels shown beside an agent chat: every task the
|
||||
@@ -131,8 +131,7 @@ export function AgentTasksPanel({
|
||||
});
|
||||
const tasksView = useQueryView(tasksQuery);
|
||||
const projectsQuery = useQuery({
|
||||
queryKey: queryKeys.projects.list(companyId),
|
||||
queryFn: () => projectsApi.list(companyId),
|
||||
...companyProjectsQuery(companyId),
|
||||
});
|
||||
|
||||
const visible = useMemo(() => {
|
||||
|
||||
@@ -7,7 +7,6 @@ import { LayoutGroup, motion, useReducedMotion } from "motion/react";
|
||||
import type { Agent, AttentionItem, DecisionHistoryItem } from "@paperclipai/shared";
|
||||
import { attentionApi } from "@/api/attention";
|
||||
import { agentsApi } from "@/api/agents";
|
||||
import { projectsApi } from "@/api/projects";
|
||||
import { useCompany } from "@/context/CompanyContext";
|
||||
import { useBreadcrumbs } from "@/context/BreadcrumbContext";
|
||||
import { usePanel } from "@/context/PanelContext";
|
||||
@@ -30,6 +29,7 @@ import { parseCssTimeMs } from "@/components/task-chat/motion-tokens";
|
||||
import { readIssueReviewPolicyMetadata } from "@/lib/review-policy";
|
||||
import { CoordinatorDecisionReceipt as Receipt } from "./CoordinatorDecisionReceipt";
|
||||
import { CoordinatorTaskMotion } from "./CoordinatorTaskCommitment";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
|
||||
type Group = "none" | "task" | "agent" | "project";
|
||||
|
||||
@@ -41,7 +41,7 @@ function HistoryPanel({ companyId, agents, onClose, mobile = false }: { companyI
|
||||
const [projectId, setProjectId] = useState("");
|
||||
const [groupBy, setGroupBy] = useState<Group>("none");
|
||||
const [sort, setSort] = useState<"newest" | "oldest">("newest");
|
||||
const projects = useQuery({ queryKey: queryKeys.projects.list(companyId), queryFn: () => projectsApi.list(companyId) });
|
||||
const projects = useQuery({ ...companyProjectsQuery(companyId) });
|
||||
const history = useInfiniteQuery({
|
||||
queryKey: [...queryKeys.attention(companyId), "history", { search, outcome, agentId, projectId, sort }],
|
||||
queryFn: ({ pageParam }) => attentionApi.history(companyId, { search, outcome, agentId, projectId, sort, cursor: pageParam, limit: 40 }),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { isLockedIssueStub } from "@/components/LockedIssueChip";
|
||||
import { IssuePullRequestLinks } from "../IssuePullRequestLinks";
|
||||
import { useIssueWorkProducts } from "../../hooks/useIssueWorkProducts";
|
||||
@@ -32,7 +33,6 @@ import { useIssuePlanDocument } from "@/hooks/useIssuePlanDocument";
|
||||
import { useIssueDocuments } from "@/hooks/useIssueDocuments";
|
||||
import { useStreamlinedUiEnabled } from "@/hooks/useStreamlinedUiEnabled";
|
||||
import { selectArtifactAttachments } from "@/lib/issue-artifacts";
|
||||
import { projectsApi } from "../../api/projects";
|
||||
import { useCompany } from "../../context/CompanyContext";
|
||||
import { useSidebar } from "../../context/SidebarContext";
|
||||
import { queryKeys } from "../../lib/queryKeys";
|
||||
@@ -452,8 +452,7 @@ export function IssueProperties({
|
||||
enabled: !!companyId,
|
||||
});
|
||||
const { data: projects } = useQuery({
|
||||
queryKey: queryKeys.projects.list(companyId!, { includeArchived: true }),
|
||||
queryFn: () => projectsApi.list(companyId!, { includeArchived: true }),
|
||||
...companyProjectsQuery(companyId!, { includeArchived: true }),
|
||||
enabled: !!companyId,
|
||||
});
|
||||
const activeProjects = useMemo(
|
||||
|
||||
@@ -2,10 +2,10 @@ import { useMemo } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { agentsApi } from "../api/agents";
|
||||
import { accessApi } from "../api/access";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { useCompany } from "../context/CompanyContext";
|
||||
import { buildMarkdownMentionOptions } from "../lib/company-members";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
|
||||
type MarkdownMentionInputs = Parameters<typeof buildMarkdownMentionOptions>[0];
|
||||
|
||||
@@ -25,8 +25,7 @@ export function useStandardMarkdownMentionOptions(args: StandardMarkdownMentionO
|
||||
enabled: enabled && args.agents === undefined,
|
||||
});
|
||||
const projectsQuery = useQuery({
|
||||
queryKey: companyId ? queryKeys.projects.list(companyId) : ["projects", "standard-mentions", "none"],
|
||||
queryFn: () => projectsApi.list(companyId!),
|
||||
...companyProjectsQuery(companyId ?? ""),
|
||||
enabled: enabled && args.projects === undefined,
|
||||
});
|
||||
const usersQuery = useQuery({
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { act } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { QueryClient, QueryClientProvider, useQuery } from "@tanstack/react-query";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({ list: vi.fn() }));
|
||||
vi.mock("@/api/projects", () => ({ projectsApi: { list: mocks.list } }));
|
||||
|
||||
import { companyProjectsQuery } from "./company-projects-query";
|
||||
import { queryKeys } from "./queryKeys";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
const active = { id: "p-active", name: "Active", archivedAt: null };
|
||||
const archived = { id: "p-archived", name: "Archived", archivedAt: "2026-11-01T00:00:00.000Z" };
|
||||
|
||||
describe("companyProjectsQuery", () => {
|
||||
let container: HTMLDivElement;
|
||||
let root: ReturnType<typeof createRoot>;
|
||||
let queryClient: QueryClient;
|
||||
|
||||
beforeEach(() => {
|
||||
mocks.list.mockReset().mockResolvedValue([active, archived]);
|
||||
queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
act(() => root.unmount());
|
||||
container.remove();
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
it("serves active-only and archive-inclusive consumers from one request", async () => {
|
||||
const seen: Record<string, string[] | undefined> = {};
|
||||
function Sidebar() {
|
||||
const { data } = useQuery(companyProjectsQuery("company-1"));
|
||||
seen.sidebar = data?.map((project) => project.id);
|
||||
return null;
|
||||
}
|
||||
function Inbox() {
|
||||
const { data } = useQuery(companyProjectsQuery("company-1", { includeArchived: true }));
|
||||
seen.inbox = data?.map((project) => project.id);
|
||||
return null;
|
||||
}
|
||||
|
||||
act(() => {
|
||||
root.render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Sidebar />
|
||||
<Inbox />
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
});
|
||||
await vi.waitFor(() => {
|
||||
expect(seen.sidebar).toEqual(["p-active"]);
|
||||
expect(seen.inbox).toEqual(["p-active", "p-archived"]);
|
||||
});
|
||||
expect(mocks.list).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.list).toHaveBeenCalledWith("company-1", { includeArchived: true });
|
||||
|
||||
// Project mutations invalidate the company prefix; both views refresh from one read.
|
||||
mocks.list.mockResolvedValue([active, { ...archived, archivedAt: null }]);
|
||||
await act(async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.projects.all("company-1") });
|
||||
});
|
||||
await vi.waitFor(() => expect(seen.sidebar).toEqual(["p-active", "p-archived"]));
|
||||
expect(mocks.list).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
import type { Project } from "@paperclipai/shared";
|
||||
import { projectsApi } from "@/api/projects";
|
||||
import { queryKeys } from "@/lib/queryKeys";
|
||||
|
||||
/** Active projects are exactly those the server would return without `includeArchived`. */
|
||||
export function selectActiveProjects(projects: Project[]): Project[] {
|
||||
return projects.filter((project) => !project.archivedAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* The company project list. Every consumer shares the single
|
||||
* `includeArchived=true` read; active-only consumers filter it with `select`.
|
||||
* Two lists under separate keys used to fetch the same large payload twice on
|
||||
* one page (for example the sidebar and the Mine inbox).
|
||||
* `queryKeys.projects.all(companyId)` invalidation still reaches it.
|
||||
*/
|
||||
export function companyProjectsQuery(companyId: string, opts: { includeArchived?: boolean } = {}) {
|
||||
const base = {
|
||||
queryKey: queryKeys.projects.list(companyId, { includeArchived: true }),
|
||||
queryFn: () => projectsApi.list(companyId, { includeArchived: true }),
|
||||
};
|
||||
return opts.includeArchived
|
||||
? queryOptions(base)
|
||||
: queryOptions({ ...base, select: selectActiveProjects });
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { describeError } from "../api/errors";
|
||||
import { useSelectedHeartbeatRun } from "../hooks/useSelectedHeartbeatRun";
|
||||
import { AgentCharacter } from "../components/AgentCharacter";
|
||||
@@ -24,7 +25,6 @@ import { SHOW_TASK_PRIORITY_UI } from "../lib/ui-flags";
|
||||
import { activityApi } from "../api/activity";
|
||||
import { accessApi } from "../api/access";
|
||||
import { issuesApi } from "../api/issues";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { usePanel } from "../context/PanelContext";
|
||||
import { useSidebar } from "../context/SidebarContext";
|
||||
import { useCompany } from "../context/CompanyContext";
|
||||
@@ -1646,8 +1646,7 @@ export function ConfigurationTab({
|
||||
const lowTrustSelected = getTrustPreset(agent.permissions) === "low_trust_review";
|
||||
|
||||
const { data: boundaryProjects, isLoading: boundaryProjectsLoading } = useQuery({
|
||||
queryKey: companyId ? queryKeys.projects.list(companyId) : ["projects", "__low-trust-disabled"],
|
||||
queryFn: () => projectsApi.list(companyId!),
|
||||
...companyProjectsQuery(companyId ?? ""),
|
||||
enabled: Boolean(companyId && lowTrustSelected) && content === "configuration",
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { describeError } from "../api/errors";
|
||||
import { useSelectedHeartbeatRun } from "../hooks/useSelectedHeartbeatRun";
|
||||
import { grokBotApi } from "@/api/grokBot";
|
||||
@@ -30,7 +31,6 @@ import { ApiError } from "../api/client";
|
||||
import { activityApi } from "../api/activity";
|
||||
import { accessApi } from "../api/access";
|
||||
import { issuesApi } from "../api/issues";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { usePanel } from "../context/PanelContext";
|
||||
import { useSidebar } from "../context/SidebarContext";
|
||||
import { useCompany } from "../context/CompanyContext";
|
||||
@@ -1659,8 +1659,7 @@ export function ConfigurationTab({
|
||||
const lowTrustSelected = getTrustPreset(agent.permissions) === "low_trust_review";
|
||||
|
||||
const { data: boundaryProjects, isLoading: boundaryProjectsLoading } = useQuery({
|
||||
queryKey: companyId ? queryKeys.projects.list(companyId) : ["projects", "__low-trust-disabled"],
|
||||
queryFn: () => projectsApi.list(companyId!),
|
||||
...companyProjectsQuery(companyId ?? ""),
|
||||
enabled: Boolean(companyId && lowTrustSelected) && content === "permissions",
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { useQueryView } from "@/components/QueryView";
|
||||
import { useEffect, useLayoutEffect, useRef } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { FolderOpen, Search } from "lucide-react";
|
||||
import { casesApi, CASE_STATUSES, type ListCasesParams } from "@/api/cases";
|
||||
import { projectsApi } from "@/api/projects";
|
||||
import { CaseListRow } from "@/components/cases/CaseListRow";
|
||||
import { CaseListControls } from "@/components/cases/CaseListControls";
|
||||
import { CollectionToolbar } from "@/components/CollectionToolbar";
|
||||
@@ -86,8 +86,7 @@ export function Cases() {
|
||||
});
|
||||
const typesQueryView = useQueryView(typesQuery);
|
||||
const projectsQuery = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId ?? "", { includeArchived: true }),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!, { includeArchived: true }),
|
||||
...companyProjectsQuery(selectedCompanyId ?? "", { includeArchived: true }),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const projectsQueryView = useQueryView(projectsQuery);
|
||||
|
||||
@@ -244,7 +244,7 @@ describe("CompanyExport", () => {
|
||||
expect(container.textContent).not.toContain("Prepare export preview");
|
||||
expect(mockAuthApi.getSession).toHaveBeenCalledTimes(1);
|
||||
expect(mockAgentsApi.list).toHaveBeenCalledWith("company-1");
|
||||
expect(mockProjectsApi.list).toHaveBeenCalledWith("company-1");
|
||||
expect(mockProjectsApi.list).toHaveBeenCalledWith("company-1", { includeArchived: true });
|
||||
expect(mockCompaniesApi.exportPreview).toHaveBeenCalledTimes(1);
|
||||
expect(mockCompaniesApi.exportPreview.mock.calls[0]?.[1]).toMatchObject({
|
||||
include: { company: true, agents: true, projects: true, issues: false, skills: true },
|
||||
|
||||
@@ -15,7 +15,6 @@ import { useToastActions } from "../context/ToastContext";
|
||||
import { agentsApi } from "../api/agents";
|
||||
import { authApi } from "../api/auth";
|
||||
import { companiesApi } from "../api/companies";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { EmptyState } from "../components/EmptyState";
|
||||
import { PageSkeleton } from "../components/PageSkeleton";
|
||||
@@ -59,6 +58,7 @@ import {
|
||||
FileTree,
|
||||
} from "../components/FileTree";
|
||||
import { describeError } from "@/api/errors";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
|
||||
/**
|
||||
* Extract the set of agent/project/task slugs that are "checked" based on
|
||||
@@ -643,8 +643,7 @@ export function CompanyExport() {
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const { data: projects = [] } = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId!),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!),
|
||||
...companyProjectsQuery(selectedCompanyId!),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const { data: fidelityReport } = useQuery({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { readTaskListSortParams, taskListOrderToIssueSort, type TaskListSort } from "@/lib/task-list";
|
||||
import { useDeferredValue, useEffect, useRef, useState, type ReactNode } from "react";
|
||||
import { useWarmTaskDetailCode } from "@/hooks/useWarmTaskDetailCode";
|
||||
@@ -8,7 +9,6 @@ import { agentsApi } from "@/api/agents";
|
||||
import { attentionApi } from "@/api/attention";
|
||||
import { heartbeatsApi } from "@/api/heartbeats";
|
||||
import { issuesApi } from "@/api/issues";
|
||||
import { projectsApi } from "@/api/projects";
|
||||
import { EmptyState } from "@/components/EmptyState";
|
||||
import { KanbanBoard } from "@/components/KanbanBoard";
|
||||
import { TaskList, type TaskListItem } from "@/components/TaskList";
|
||||
@@ -150,7 +150,7 @@ export function CoordinatorTasks({ viewControl }: { viewControl?: ReactNode } =
|
||||
enabled: !!companyId,
|
||||
})) });
|
||||
const agents = useQuery({ queryKey: queryKeys.agents.list(companyId!), queryFn: () => agentsApi.list(companyId!), enabled: !!companyId });
|
||||
const projects = useQuery({ queryKey: queryKeys.projects.list(companyId!, { includeArchived: true }), queryFn: () => projectsApi.list(companyId!, { includeArchived: true }), enabled: !!companyId });
|
||||
const projects = useQuery({ ...companyProjectsQuery(companyId!, { includeArchived: true }), enabled: !!companyId });
|
||||
const directory = useQuery({ queryKey: queryKeys.access.companyUserDirectory(companyId!), queryFn: () => accessApi.listUserDirectory(companyId!), enabled: !!companyId });
|
||||
const labels = useQuery({ queryKey: queryKeys.issues.labels(companyId!), queryFn: () => issuesApi.listLabels(companyId!), enabled: !!companyId });
|
||||
const sharedLiveRuns = useSharedPollingQuery({ companyId, resourceKey: "live-runs", queryKey: queryKeys.liveRuns(companyId!), enabled: !!companyId, refetchInterval: false, leaderOnly: true });
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { AgentIdentity } from "../components/AgentIdentity";
|
||||
import { TaskSummaryList } from "../components/TaskSummaryList";
|
||||
import { useDesignSandbox } from "../context/DesignSandboxContext";
|
||||
@@ -13,7 +14,6 @@ import { activityApi } from "../api/activity";
|
||||
import { accessApi } from "../api/access";
|
||||
import { issuesApi } from "../api/issues";
|
||||
import { agentsApi } from "../api/agents";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { buildCompanyUserProfileMap } from "../lib/company-members";
|
||||
import { useCompany } from "../context/CompanyContext";
|
||||
import { useDialogActions } from "../context/DialogContext";
|
||||
@@ -202,8 +202,7 @@ export function Dashboard() {
|
||||
});
|
||||
|
||||
const { data: projects } = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId!, { includeArchived: true }),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!, { includeArchived: true }),
|
||||
...companyProjectsQuery(selectedCompanyId!, { includeArchived: true }),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { useEffect } from "react";
|
||||
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ListChecks, SearchX } from "lucide-react";
|
||||
@@ -5,7 +6,6 @@ import { accessApi } from "@/api/access";
|
||||
import { agentsApi } from "@/api/agents";
|
||||
import { useAccountIdentity } from "@/api/companies-query";
|
||||
import { issuesApi } from "@/api/issues";
|
||||
import { projectsApi } from "@/api/projects";
|
||||
import { EmptyState } from "@/components/EmptyState";
|
||||
import { KanbanBoard } from "@/components/KanbanBoard";
|
||||
import { TaskList, type TaskListItem } from "@/components/TaskList";
|
||||
@@ -45,7 +45,7 @@ export function DesignSandboxTasks() {
|
||||
enabled: !!companyId,
|
||||
});
|
||||
const agents = useQuery({ queryKey: queryKeys.agents.list(companyId!), queryFn: () => agentsApi.list(companyId!), enabled: !!companyId });
|
||||
const projects = useQuery({ queryKey: queryKeys.projects.list(companyId!, { includeArchived: true }), queryFn: () => projectsApi.list(companyId!, { includeArchived: true }), enabled: !!companyId });
|
||||
const projects = useQuery({ ...companyProjectsQuery(companyId!, { includeArchived: true }), enabled: !!companyId });
|
||||
const directory = useQuery({ queryKey: queryKeys.access.companyUserDirectory(companyId!), queryFn: () => accessApi.listUserDirectory(companyId!), enabled: !!companyId });
|
||||
const labels = useQuery({ queryKey: queryKeys.issues.labels(companyId!), queryFn: () => issuesApi.listLabels(companyId!), enabled: !!companyId });
|
||||
// The ordinary organization list does not include viewer read context.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { useEffect } from "react";
|
||||
import { useParams } from "@/lib/router";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { goalsApi } from "../api/goals";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { assetsApi } from "../api/assets";
|
||||
import { usePanel } from "../context/PanelContext";
|
||||
import { useCompany } from "../context/CompanyContext";
|
||||
@@ -71,8 +71,7 @@ export function GoalDetail() {
|
||||
});
|
||||
|
||||
const { data: allProjects } = useQuery({
|
||||
queryKey: queryKeys.projects.list(resolvedCompanyId!, { includeArchived: true }),
|
||||
queryFn: () => projectsApi.list(resolvedCompanyId!, { includeArchived: true }),
|
||||
...companyProjectsQuery(resolvedCompanyId!, { includeArchived: true }),
|
||||
enabled: !!resolvedCompanyId
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { NativeControllerContainmentRecovery } from "../components/IssueRecoveryActionCard";
|
||||
import { useIssueIdBatches } from "@/hooks/useIssueIdBatches";
|
||||
import { TaskChatLoadingShell } from "@/components/task-chat/TaskChatLoadingShell";
|
||||
@@ -103,7 +104,6 @@ import {
|
||||
} from "../lib/recovery-reconcile";
|
||||
import { agentsApi } from "../api/agents";
|
||||
import { authApi } from "../api/auth";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { executionWorkspacesApi } from "../api/execution-workspaces";
|
||||
import { useCompany } from "../context/CompanyContext";
|
||||
import { useDialogActions } from "../context/DialogContext";
|
||||
@@ -3716,8 +3716,7 @@ export function TaskDetailSurface({ conversation, tasksTab }: { tasksTab?: TaskS
|
||||
});
|
||||
|
||||
const { data: projects = EMPTY_PROJECTS } = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId!),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!),
|
||||
...companyProjectsQuery(selectedCompanyId!),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const currentUserId = session?.user?.id ?? session?.session?.userId ?? null;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { QueryErrorState, useQueryView } from "@/components/QueryView";
|
||||
import { useShowAgentCreatedTasks } from "../hooks/useShowAgentCreatedTasks";
|
||||
import { useCoordinatorWorkspaceEnabled } from "../hooks/useCoordinatorWorkspaceEnabled";
|
||||
@@ -10,7 +11,6 @@ import { useLocation, useNavigate, useSearchParams } from "@/lib/router";
|
||||
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
|
||||
import { issuesApi } from "../api/issues";
|
||||
import { agentsApi } from "../api/agents";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { heartbeatsApi } from "../api/heartbeats";
|
||||
import { useCompany } from "../context/CompanyContext";
|
||||
import { useBreadcrumbs } from "../context/BreadcrumbContext";
|
||||
@@ -236,8 +236,7 @@ function OrganizationIssues({
|
||||
});
|
||||
|
||||
const { data: projects } = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId!, { includeArchived: true }),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!, { includeArchived: true }),
|
||||
...companyProjectsQuery(selectedCompanyId!, { includeArchived: true }),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { describeError } from "../api/errors";
|
||||
import { useWarmTaskDetailCode } from "@/hooks/useWarmTaskDetailCode";
|
||||
import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
@@ -16,7 +17,6 @@ import { issuesApi } from "../api/issues";
|
||||
import { agentsApi } from "../api/agents";
|
||||
import { heartbeatsApi } from "../api/heartbeats";
|
||||
import { instanceSettingsApi } from "../api/instanceSettings";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import {
|
||||
BLOCKED_GROUP_OPTIONS,
|
||||
BLOCKED_SORT_OPTIONS,
|
||||
@@ -754,8 +754,7 @@ export function Inbox() {
|
||||
});
|
||||
|
||||
const { data: projects } = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId!, { includeArchived: true }),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!, { includeArchived: true }),
|
||||
...companyProjectsQuery(selectedCompanyId!, { includeArchived: true }),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const { data: labels } = useQuery({
|
||||
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
useResourceMemberships,
|
||||
} from "../hooks/useResourceMemberships";
|
||||
import { useUpdateIssueMutation } from "../hooks/useUpdateIssueMutation";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
|
||||
/* ── Top-level tab types ── */
|
||||
|
||||
@@ -216,8 +217,7 @@ function ProjectIssuesList({ projectId, companyId }: { projectId: string; compan
|
||||
});
|
||||
usePublishSharedQueryData(sharedLiveRuns, liveRuns, liveRunsUpdatedAt);
|
||||
const { data: projects } = useQuery({
|
||||
queryKey: queryKeys.projects.list(companyId),
|
||||
queryFn: () => projectsApi.list(companyId),
|
||||
...companyProjectsQuery(companyId),
|
||||
enabled: !!companyId,
|
||||
});
|
||||
|
||||
@@ -270,8 +270,7 @@ function ProjectPluginOperationsList({
|
||||
enabled: !!companyId,
|
||||
});
|
||||
const { data: projects } = useQuery({
|
||||
queryKey: queryKeys.projects.list(companyId),
|
||||
queryFn: () => projectsApi.list(companyId),
|
||||
...companyProjectsQuery(companyId),
|
||||
enabled: !!companyId,
|
||||
});
|
||||
const liveRunsQueryKey = queryKeys.liveRuns(companyId);
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type { Project } from "@paperclipai/shared";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { useCompany } from "../context/CompanyContext";
|
||||
import { useDialogActions } from "../context/DialogContext";
|
||||
import { useBreadcrumbs } from "../context/BreadcrumbContext";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { EntityRow } from "../components/EntityRow";
|
||||
import { ProjectTile } from "../components/ProjectTile";
|
||||
import { StatusBadge } from "../components/StatusBadge";
|
||||
@@ -26,6 +24,7 @@ import { ProjectsToolbar, type ProjectSortField, type ProjectSortDir } from "@/c
|
||||
import { Hexagon } from "lucide-react";
|
||||
import { Lock } from "lucide-react";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
|
||||
function compareProjectNames(left: Project, right: Project) {
|
||||
const nameDiff = left.name.localeCompare(right.name, undefined, { sensitivity: "base" });
|
||||
@@ -81,8 +80,7 @@ export function Projects() {
|
||||
}, [setBreadcrumbs]);
|
||||
|
||||
const projectsQuery = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId!),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!),
|
||||
...companyProjectsQuery(selectedCompanyId!),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const { data: allProjects, isLoading } = projectsQuery;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Navigate, useNavigate, useParams } from "@/lib/router";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
@@ -13,7 +14,6 @@ import { secretsApi } from "../api/secrets";
|
||||
import { type RoutineHistoryDirtyFieldDescriptor } from "../components/RoutineHistoryTab";
|
||||
import { heartbeatsApi } from "../api/heartbeats";
|
||||
import { agentsApi } from "../api/agents";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { accessApi } from "../api/access";
|
||||
import { useCompany } from "../context/CompanyContext";
|
||||
import { useBreadcrumbs } from "../context/BreadcrumbContext";
|
||||
@@ -241,8 +241,7 @@ export function RoutineDetail() {
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const { data: projects } = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId!, { includeArchived: true }),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!, { includeArchived: true }),
|
||||
...companyProjectsQuery(selectedCompanyId!, { includeArchived: true }),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const { data: companyMembers } = useQuery({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Navigate, useNavigate, useParams, useSearchParams } from "@/lib/router";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
@@ -13,7 +14,6 @@ import { secretsApi } from "../api/secrets";
|
||||
import { type RoutineHistoryDirtyFieldDescriptor } from "../components/RoutineHistoryTab";
|
||||
import { heartbeatsApi } from "../api/heartbeats";
|
||||
import { agentsApi } from "../api/agents";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { accessApi } from "../api/access";
|
||||
import { useCompany } from "../context/CompanyContext";
|
||||
import { useBreadcrumbs } from "../context/BreadcrumbContext";
|
||||
@@ -223,8 +223,7 @@ export function RoutineDetail() {
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const { data: projects } = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId!, { includeArchived: true }),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!, { includeArchived: true }),
|
||||
...companyProjectsQuery(selectedCompanyId!, { includeArchived: true }),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const { data: companyMembers } = useQuery({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { AgentAvatar } from "@/components/AgentAvatar";
|
||||
import { startTransition, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
@@ -6,7 +7,6 @@ import { ArrowUpDown, Check, ChevronDown, ChevronRight, Layers, Plus, Repeat } f
|
||||
import { routinesApi } from "../api/routines";
|
||||
import { foldersApi } from "../api/folders";
|
||||
import { agentsApi } from "../api/agents";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { issuesApi } from "../api/issues";
|
||||
import { heartbeatsApi } from "../api/heartbeats";
|
||||
import { accessApi } from "../api/access";
|
||||
@@ -395,8 +395,7 @@ export function Routines() {
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const { data: projects } = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId!, { includeArchived: true }),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!, { includeArchived: true }),
|
||||
...companyProjectsQuery(selectedCompanyId!, { includeArchived: true }),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const { data: companyMembers } = useQuery({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { AgentAvatar } from "@/components/AgentAvatar";
|
||||
import { startTransition, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
@@ -6,7 +7,6 @@ import { ChevronDown, ChevronRight, Plus, Repeat } from "lucide-react";
|
||||
import { routinesApi } from "../api/routines";
|
||||
import { foldersApi } from "../api/folders";
|
||||
import { agentsApi } from "../api/agents";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { issuesApi } from "../api/issues";
|
||||
import { heartbeatsApi } from "../api/heartbeats";
|
||||
import { accessApi } from "../api/access";
|
||||
@@ -391,8 +391,7 @@ export function Routines() {
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const { data: projects } = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId!, { includeArchived: true }),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!, { includeArchived: true }),
|
||||
...companyProjectsQuery(selectedCompanyId!, { includeArchived: true }),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const { data: companyMembers } = useQuery({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Search as SearchIcon, AlertTriangle, FileQuestion, Plus, X } from "lucide-react";
|
||||
@@ -25,7 +26,6 @@ import { useQueryView } from "../components/QueryView";
|
||||
import { agentsApi } from "../api/agents";
|
||||
import { authApi } from "../api/auth";
|
||||
import { issuesApi } from "../api/issues";
|
||||
import { projectsApi } from "../api/projects";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { loadRecentSearches, pushRecentSearch } from "../lib/recent-searches";
|
||||
import { PageTabBar, type PageTabItem } from "../components/PageTabBar";
|
||||
@@ -187,8 +187,7 @@ export function Search() {
|
||||
});
|
||||
|
||||
const { data: projects = [] } = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId!, { includeArchived: true }),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!, { includeArchived: true }),
|
||||
...companyProjectsQuery(selectedCompanyId!, { includeArchived: true }),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useEffect, useId, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type { ChatExecutionDefaults as Defaults, TaskWorkspaceSelection } from "@paperclipai/shared";
|
||||
import { projectsApi } from "@/api/projects";
|
||||
import { executionWorkspacesApi } from "@/api/execution-workspaces";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { NativeSelect } from "@/components/ui/select";
|
||||
import { queryKeys } from "@/lib/queryKeys";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
|
||||
function selectionKey(value: TaskWorkspaceSelection | null | undefined): string {
|
||||
if (value === undefined) return "inherit";
|
||||
@@ -26,7 +25,7 @@ export function ChatExecutionDefaults({ companyId, value, resource = false, onSa
|
||||
const [pending, setPending] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [savedFrom, setSavedFrom] = useState<string | null>(null);
|
||||
const projects = useQuery({ queryKey: queryKeys.projects.list(companyId), queryFn: () => projectsApi.list(companyId) });
|
||||
const projects = useQuery({ ...companyProjectsQuery(companyId) });
|
||||
const workspaces = useQuery({
|
||||
queryKey: ["channel-workspace-choices", companyId],
|
||||
queryFn: () => executionWorkspacesApi.listSummaries(companyId, { selectableForTask: true }),
|
||||
|
||||
@@ -20,7 +20,6 @@ import { useCompany } from "@/context/CompanyContext";
|
||||
import { useNavigate, useSearchParams, Link } from "@/lib/router";
|
||||
import { agentsApi } from "@/api/agents";
|
||||
import { issuesApi } from "@/api/issues";
|
||||
import { projectsApi } from "@/api/projects";
|
||||
import { toolsApi } from "@/api/tools";
|
||||
import { emailApi } from "@/api/email";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -51,6 +50,7 @@ import type {
|
||||
AgentPermissions,
|
||||
EmailEndpointSummary,
|
||||
} from "@paperclipai/shared";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
const selectClass =
|
||||
"w-full rounded-md border border-input bg-background px-3 py-2 text-sm";
|
||||
|
||||
@@ -134,8 +134,7 @@ function EmailEndpointSetupForm({ companyId }: { companyId: string }) {
|
||||
const agents = useQuery({ queryKey: queryKeys.agents.list(companyId),
|
||||
queryFn: () => agentsApi.list(companyId), enabled: !!companyId });
|
||||
const agentsView = useQueryView(agents);
|
||||
const projects = useQuery({ queryKey: queryKeys.projects.list(companyId),
|
||||
queryFn: () => projectsApi.list(companyId), enabled: !!companyId && trustOpen });
|
||||
const projects = useQuery({ ...companyProjectsQuery(companyId), enabled: !!companyId && trustOpen });
|
||||
const projectsView = useQueryView(projects);
|
||||
const boundaryIssues = useQuery({ queryKey: ["email-boundary-issues", companyId],
|
||||
queryFn: () => issuesApi.list(companyId), enabled: !!companyId && trustOpen });
|
||||
|
||||
@@ -324,7 +324,7 @@ describe("GitHub bot management", () => {
|
||||
const editor = container.querySelector('[role="region"] form')!;
|
||||
const [project, workspace] = [...editor.querySelectorAll("select")];
|
||||
await vi.waitFor(() => expect(workspace.disabled).toBe(false));
|
||||
expect(mocks.projects).toHaveBeenCalledWith("company");
|
||||
expect(mocks.projects).toHaveBeenCalledWith("company", { includeArchived: true });
|
||||
expect(mocks.workspaces).toHaveBeenCalledWith("company", { selectableForTask: true });
|
||||
expect(project.value).toBe("inherit");
|
||||
expect(workspace.value).toBe("inherit");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { Pencil, Send } from "lucide-react";
|
||||
@@ -9,7 +10,6 @@ import { useToast } from "@/context/ToastContext";
|
||||
import { queryKeys } from "@/lib/queryKeys";
|
||||
import { toolsApi } from "@/api/tools";
|
||||
import { agentsApi } from "@/api/agents";
|
||||
import { projectsApi } from "@/api/projects";
|
||||
import { QueryErrorState, useQueryView } from "@/components/QueryView";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
@@ -66,8 +66,7 @@ export function GatewayDetail() {
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const projectsQuery = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId ?? "__none__", { includeArchived: true }),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!, { includeArchived: true }),
|
||||
...companyProjectsQuery(selectedCompanyId ?? "__none__", { includeArchived: true }),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const gatewaysView = useQueryView(gatewaysQuery);
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useToast } from "@/context/ToastContext";
|
||||
import { queryKeys } from "@/lib/queryKeys";
|
||||
import { toolsApi } from "@/api/tools";
|
||||
import { agentsApi } from "@/api/agents";
|
||||
import { projectsApi } from "@/api/projects";
|
||||
import { QueryErrorState, useQueryView } from "@/components/QueryView";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -29,6 +28,7 @@ import {
|
||||
latestTokenActivity,
|
||||
} from "./gateway-helpers";
|
||||
import { describeError } from "@/api/errors";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
|
||||
export function GatewaysList() {
|
||||
const navigate = useNavigate();
|
||||
@@ -73,8 +73,7 @@ export function GatewaysList() {
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const projectsQuery = useQuery({
|
||||
queryKey: queryKeys.projects.list(selectedCompanyId ?? "__none__"),
|
||||
queryFn: () => projectsApi.list(selectedCompanyId!),
|
||||
...companyProjectsQuery(selectedCompanyId ?? "__none__"),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const gatewaysView = useQueryView(gatewaysQuery);
|
||||
|
||||
@@ -31,7 +31,6 @@ import { normalizeAgentUrlKey } from "@paperclipai/shared";
|
||||
import { Link } from "@/lib/router";
|
||||
import { ApiError } from "../../api/client";
|
||||
import { companySkillsApi } from "../../api/companySkills";
|
||||
import { projectsApi } from "../../api/projects";
|
||||
import { useToastActions } from "../../context/ToastContext";
|
||||
import { queryKeys } from "../../lib/queryKeys";
|
||||
import { skillStudioRoute } from "../../lib/company-skill-routes";
|
||||
@@ -49,6 +48,7 @@ import { QueryErrorState, useQueryView } from "@/components/QueryView";
|
||||
import { EmptyState } from "../../components/EmptyState";
|
||||
import { cn } from "../../lib/utils";
|
||||
import { describeError } from "@/api/errors";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
|
||||
type Step = "pick" | "scanning" | "select" | "result";
|
||||
export type SkillSelection = { workspaceId: string; path: string; slug?: string };
|
||||
@@ -295,8 +295,7 @@ export function ImportSkillsFromProjectDialog({
|
||||
const scanTokenRef = useRef(0);
|
||||
|
||||
const projectsQuery = useQuery({
|
||||
queryKey: queryKeys.projects.list(companyId),
|
||||
queryFn: () => projectsApi.list(companyId),
|
||||
...companyProjectsQuery(companyId),
|
||||
enabled: open,
|
||||
});
|
||||
const projectsView = useQueryView(projectsQuery);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { type FormEvent, useMemo, useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type {
|
||||
@@ -9,7 +10,6 @@ import type {
|
||||
} from "@paperclipai/shared";
|
||||
import { Check, ChevronDown, Copy, KeyRound, Link as LinkIcon, Plus, RotateCcw, X } from "lucide-react";
|
||||
import { agentsApi } from "@/api/agents";
|
||||
import { projectsApi } from "@/api/projects";
|
||||
import { toolsApi } from "@/api/tools";
|
||||
import { QueryErrorState, useQueryView } from "@/components/QueryView";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -141,8 +141,7 @@ export function GatewaysTab({ companyId }: { companyId: string }) {
|
||||
queryFn: () => agentsApi.list(companyId),
|
||||
});
|
||||
const projectsQuery = useQuery({
|
||||
queryKey: queryKeys.projects.list(companyId, { includeArchived: true }),
|
||||
queryFn: () => projectsApi.list(companyId, { includeArchived: true }),
|
||||
...companyProjectsQuery(companyId, { includeArchived: true }),
|
||||
});
|
||||
const gatewaysView = useQueryView(gatewaysQuery);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { AlertTriangle, Layers, Plus, Pencil, Trash2, Link2, ShieldCheck } from "lucide-react";
|
||||
@@ -14,7 +15,6 @@ import type {
|
||||
ToolRiskLevel,
|
||||
} from "@paperclipai/shared";
|
||||
import { agentsApi } from "@/api/agents";
|
||||
import { projectsApi } from "@/api/projects";
|
||||
import { routinesApi } from "@/api/routines";
|
||||
import {
|
||||
toolsApi,
|
||||
@@ -313,8 +313,7 @@ function useLookupData(companyId: string) {
|
||||
queryFn: () => agentsApi.list(companyId),
|
||||
});
|
||||
const projects = useQuery({
|
||||
queryKey: queryKeys.projects.list(companyId, { includeArchived: true }),
|
||||
queryFn: () => projectsApi.list(companyId, { includeArchived: true }),
|
||||
...companyProjectsQuery(companyId, { includeArchived: true }),
|
||||
});
|
||||
const routines = useQuery({
|
||||
queryKey: queryKeys.routines.list(companyId),
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useMemo } from "react";
|
||||
import { useQueries, useQuery } from "@tanstack/react-query";
|
||||
import { agentsApi } from "@/api/agents";
|
||||
import { projectsApi } from "@/api/projects";
|
||||
import { routinesApi } from "@/api/routines";
|
||||
import { toolsApi } from "@/api/tools";
|
||||
import { queryKeys } from "@/lib/queryKeys";
|
||||
import { groupCatalogByApp } from "./profile-model";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
|
||||
/**
|
||||
* Shared data layer for the access-profiles index and wizard. Assembles the
|
||||
@@ -30,8 +30,7 @@ export function useProfilesData(companyId: string) {
|
||||
queryFn: () => agentsApi.list(companyId),
|
||||
});
|
||||
const projects = useQuery({
|
||||
queryKey: queryKeys.projects.list(companyId),
|
||||
queryFn: () => projectsApi.list(companyId),
|
||||
...companyProjectsQuery(companyId),
|
||||
});
|
||||
const routines = useQuery({
|
||||
queryKey: queryKeys.routines.list(companyId),
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
usePluginStream,
|
||||
usePluginToast,
|
||||
} from "./bridge.js";
|
||||
import { companyProjectsQuery } from "@/lib/company-projects-query";
|
||||
import { Component, createElement, useEffect, useMemo, useState, type ComponentType, type ReactNode } from "react";
|
||||
import * as ReactJsxRuntimeModule from "react/jsx-runtime";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
@@ -39,7 +40,6 @@ import { agentsApi } from "@/api/agents";
|
||||
import { authApi } from "@/api/auth";
|
||||
import { heartbeatsApi } from "@/api/heartbeats";
|
||||
import { issuesApi } from "@/api/issues";
|
||||
import { projectsApi } from "@/api/projects";
|
||||
import {
|
||||
buildCompanyUserInlineOptions,
|
||||
} from "@/lib/company-members";
|
||||
@@ -277,8 +277,7 @@ function PluginSdkIssuesList({
|
||||
enabled: !!companyId,
|
||||
});
|
||||
const { data: projects } = useQuery({
|
||||
queryKey: queryKeys.projects.list(companyId ?? "__no-company__", { includeArchived: true }),
|
||||
queryFn: () => projectsApi.list(companyId!, { includeArchived: true }),
|
||||
...companyProjectsQuery(companyId ?? "__no-company__", { includeArchived: true }),
|
||||
enabled: !!companyId,
|
||||
});
|
||||
const liveRunsQueryKey = queryKeys.liveRuns(companyId ?? "__no-company__");
|
||||
@@ -473,8 +472,7 @@ function PluginSdkProjectPicker({
|
||||
});
|
||||
const currentUserId = session?.user?.id ?? session?.session?.userId ?? null;
|
||||
const { data: projects } = useQuery({
|
||||
queryKey: queryKeys.projects.list(resolvedCompanyId ?? "__no-company__", { includeArchived }),
|
||||
queryFn: () => projectsApi.list(resolvedCompanyId!, { includeArchived }),
|
||||
...companyProjectsQuery(resolvedCompanyId ?? "__no-company__", { includeArchived }),
|
||||
enabled: !!resolvedCompanyId,
|
||||
});
|
||||
const visibleProjects = useMemo(
|
||||
|
||||
Reference in new issue
Block a user