Skip to content

Commit 3d54b6b

Browse files
remove useProject, useProjectPermissions, useProjectActions, useProjectData in favour useProjectPermissionsQuery and useProjectQuery (#2753)
* remove enrichNodes * remove enrichNodes * remove enrichNodes * fix * rm * rm * upd * upd * upd * upd * upd * upd * keys * upd * format * format * rm * upd * upd * rm * rm * Apply suggestions from code review Co-authored-by: Dimitri POSTOLOV <dmytropostolov@gmail.com> * Add changeset for project context removal Co-authored-by: Dimitri POSTOLOV <dimaMachina@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Dimitri POSTOLOV <dmytropostolov@gmail.com> --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Dimitri POSTOLOV <dimaMachina@users.noreply.github.com>
1 parent 4f75550 commit 3d54b6b

35 files changed

+299
-318
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@inkeep/agents-manage-ui": patch
3+
---
4+
5+
Remove `useProject`, `useProjectPermissions`, `useProjectActions`, `useProjectData` in favor of `useProjectPermissionsQuery` and `useProjectQuery` React Query hooks

agents-manage-ui/src/app/[tenantId]/projects/[projectId]/agents/[agentId]/page.client.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use client';
22

3+
import { useQueryClient } from '@tanstack/react-query';
34
import {
45
Background,
56
ConnectionMode,
@@ -40,7 +41,6 @@ import { useAgentShortcuts } from '@/components/agent/use-agent-shortcuts';
4041
import { useAnimateGraph } from '@/components/agent/use-animate-graph';
4142
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable';
4243
import { useCopilotContext } from '@/contexts/copilot';
43-
import { useProjectPermissions } from '@/contexts/project';
4444
import { commandManager } from '@/features/agent/commands/command-manager';
4545
import { AddNodeCommand, AddPreparedEdgeCommand } from '@/features/agent/commands/commands';
4646
import {
@@ -52,13 +52,14 @@ import {
5252
validateSerializedData,
5353
} from '@/features/agent/domain';
5454
import { useAgentActions, useAgentStore } from '@/features/agent/state/use-agent-store';
55-
import { useProjectActions } from '@/features/project/state/use-project-store';
5655
import { useAgentErrors } from '@/hooks/use-agent-errors';
5756
import { useIsMounted } from '@/hooks/use-is-mounted';
5857
import { useSidePane } from '@/hooks/use-side-pane';
5958
import { EdgeArrow, SelectedEdgeArrow } from '@/icons';
6059
import { getFullProjectAction } from '@/lib/actions/project-full';
60+
import { projectQueryKeys } from '@/lib/query/keys/projects';
6161
import { useMcpToolsQuery } from '@/lib/query/mcp-tools';
62+
import { useProjectPermissionsQuery } from '@/lib/query/projects';
6263
import { saveAgent } from '@/lib/services/save-agent';
6364
import { getErrorSummaryMessage, parseAgentValidationErrors } from '@/lib/utils/agent-error-parser';
6465
import { generateId } from '@/lib/utils/id-utils';
@@ -108,7 +109,10 @@ export const Agent: FC<AgentProps> = ({ agent }) => {
108109
isCopilotConfigured,
109110
isStreaming: isCopilotStreaming,
110111
} = useCopilotContext();
111-
const { canEdit } = useProjectPermissions();
112+
const {
113+
data: { canEdit },
114+
} = useProjectPermissionsQuery();
115+
const queryClient = useQueryClient();
112116
const router = useRouter();
113117
const { tenantId, projectId } = useParams<{ tenantId: string; projectId: string }>();
114118
const { refetch: refetchMcpTools } = useMcpToolsQuery({ skipDiscovery: true });
@@ -140,7 +144,6 @@ export const Agent: FC<AgentProps> = ({ agent }) => {
140144
markUnsaved,
141145
reset,
142146
} = useAgentActions();
143-
const { setProject: setProjectStore, reset: resetProjectStore } = useProjectActions();
144147
const { errors, showErrors, setErrors, clearErrors, setShowErrors } = useAgentErrors();
145148

146149
const onAddInitialNode = () => {
@@ -206,8 +209,6 @@ export const Agent: FC<AgentProps> = ({ agent }) => {
206209
return () => {
207210
// we need to reset the agent store when the component unmounts otherwise the agent store will persist the changes from the previous agent
208211
reset();
209-
// Also reset the project store to prevent stale data
210-
resetProjectStore();
211212
};
212213
}, []);
213214

@@ -282,10 +283,9 @@ export const Agent: FC<AgentProps> = ({ agent }) => {
282283
// Update the store with all refreshed data
283284
setInitial(nodesWithSelection, edgesWithSelection, metadata);
284285

285-
// Update project data in store so components using useProjectData get fresh data
286+
// Update project data in React Query cache so components using useProjectQuery get fresh data
286287
const convertedProject = convertFullProjectToProject(fullProject, tenantId);
287-
288-
setProjectStore(convertedProject);
288+
queryClient.setQueryData(projectQueryKeys.detail(tenantId, projectId), convertedProject);
289289
}
290290

291291
try {

agents-manage-ui/src/app/[tenantId]/projects/[projectId]/credentials/new/bearer/page.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import { toast } from 'sonner';
77
import { CredentialForm } from '@/components/credentials/views/credential-form';
88
import { CredentialFormInkeepCloud } from '@/components/credentials/views/credential-form-inkeep-cloud';
99
import type { CredentialFormOutput } from '@/components/credentials/views/credential-form-validation';
10-
import { useProjectPermissions } from '@/contexts/project';
1110
import { useRuntimeConfig } from '@/contexts/runtime-config';
1211
import { useAuthSession } from '@/hooks/use-auth';
1312
import { createCredentialInStore } from '@/lib/api/credentialStores';
1413
import { updateExternalAgent } from '@/lib/api/external-agents';
1514
import { updateMCPTool } from '@/lib/api/tools';
15+
import { useProjectPermissionsQuery } from '@/lib/query/projects';
1616
import { findOrCreateCredential } from '@/lib/utils/credentials-utils';
1717
import { generateId } from '@/lib/utils/id-utils';
1818

@@ -23,7 +23,9 @@ export default function NewCredentialForm({
2323
const { PUBLIC_IS_INKEEP_CLOUD_DEPLOYMENT } = useRuntimeConfig();
2424
const { tenantId, projectId } = use(params);
2525
const { user } = useAuthSession();
26-
const { canEdit } = useProjectPermissions();
26+
const {
27+
data: { canEdit },
28+
} = useProjectPermissionsQuery();
2729

2830
// Redirect if user doesn't have edit permission
2931
useEffect(() => {

agents-manage-ui/src/app/[tenantId]/projects/[projectId]/credentials/new/providers/[providerId]/page.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import {
2929
DropdownMenuTrigger,
3030
} from '@/components/ui/dropdown-menu';
3131
import { useAuthClient } from '@/contexts/auth-client';
32-
import { useProjectPermissions } from '@/contexts/project';
3332
import { useAuthSession } from '@/hooks/use-auth';
3433
import { useNangoConnect } from '@/hooks/use-nango-connect';
3534
import { useNangoProviders } from '@/hooks/use-nango-providers';
@@ -42,6 +41,7 @@ import {
4241
updateNangoIntegrationCredentials,
4342
} from '@/lib/mcp-tools/nango';
4443
import { NangoError } from '@/lib/mcp-tools/nango-types';
44+
import { useProjectPermissionsQuery } from '@/lib/query/projects';
4545
import { findOrCreateCredential } from '@/lib/utils/credentials-utils';
4646
import { generateId } from '@/lib/utils/id-utils';
4747

@@ -51,7 +51,9 @@ function ProviderSetupPage({
5151
params,
5252
}: PageProps<'/[tenantId]/projects/[projectId]/credentials/new/providers/[providerId]'>) {
5353
const router = useRouter();
54-
const { canEdit } = useProjectPermissions();
54+
const {
55+
data: { canEdit },
56+
} = useProjectPermissionsQuery();
5557
const { providers, loading: providersLoading } = useNangoProviders();
5658
const [loading, setLoading] = useState(false);
5759
const [hasAttempted, setHasAttempted] = useState(false);

agents-manage-ui/src/app/[tenantId]/projects/[projectId]/layout.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query';
12
import FullPageError from '@/components/errors/full-page-error';
2-
import { ProjectProvider } from '@/contexts/project';
3-
import { fetchProject, fetchProjectPermissions } from '@/lib/api/projects';
3+
import { fetchProjectPermissions } from '@/lib/api/projects';
4+
import { projectQueryKeys } from '@/lib/query/keys/projects';
45
import { getErrorCode } from '@/lib/utils/error-serialization';
56

67
export const dynamic = 'force-dynamic';
@@ -12,14 +13,13 @@ export default async function ProjectLayout({
1213
const { tenantId, projectId } = await params;
1314

1415
try {
15-
const [project, permissions] = await Promise.all([
16-
fetchProject(tenantId, projectId),
17-
fetchProjectPermissions(tenantId, projectId),
18-
]);
16+
const queryClient = new QueryClient();
17+
const permissions = await fetchProjectPermissions(tenantId, projectId);
18+
queryClient.setQueryData(projectQueryKeys.permissions(tenantId, projectId), permissions);
1919

20-
return (
21-
<ProjectProvider value={{ project: project.data, permissions }}>{children}</ProjectProvider>
22-
);
20+
// Hydrates React Query before any child client component renders.
21+
// That makes useProjectPermissionsQuery() in projects.ts start with real data
22+
return <HydrationBoundary state={dehydrate(queryClient)}>{children}</HydrationBoundary>;
2323
} catch (error) {
2424
return (
2525
<FullPageError

agents-manage-ui/src/components/agent/nodes/__tests__/nodes.browser.test.tsx

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { act, render } from '@testing-library/react';
1+
import { act } from '@testing-library/react';
22
import { type NodeProps, ReactFlowProvider } from '@xyflow/react';
33
import { NodeType } from '@/components/agent/configuration/node-types';
44
import { ExternalAgentNode } from '@/components/agent/nodes/external-agent-node';
@@ -7,13 +7,23 @@ import { MCPNode } from '@/components/agent/nodes/mcp-node';
77
import { PlaceholderNode } from '@/components/agent/nodes/placeholder-node';
88
import { SubAgentNode } from '@/components/agent/nodes/sub-agent-node';
99
import { TeamAgentNode } from '@/components/agent/nodes/team-agent-node';
10-
import { ProjectProvider } from '@/contexts/project';
10+
import { createTestQueryClient, renderWithClient } from '@/lib/query/__tests__/test-utils';
11+
import { mcpToolQueryKeys } from '@/lib/query/keys/mcp-tools';
12+
import { projectQueryKeys } from '@/lib/query/keys/projects';
1113
import '@/lib/utils/test-utils/styles.css';
1214

15+
const TENANT_ID = 'tenant-1';
16+
const PROJECT_ID = 'project-1';
17+
const TOOL_ID = 'tool-1';
18+
const DATA = {
19+
name: 'name '.repeat(10),
20+
description: 'description '.repeat(10),
21+
};
22+
1323
vi.mock('next/navigation', () => {
1424
return {
1525
useParams() {
16-
return {};
26+
return { tenantId: TENANT_ID, projectId: PROJECT_ID };
1727
},
1828
};
1929
});
@@ -24,14 +34,13 @@ vi.mock('@/contexts/runtime-config', () => {
2434
},
2535
};
2636
});
27-
vi.mock('@/lib/query/mcp-tools', () => {
37+
vi.mock('@/lib/query/mcp-tools', async () => {
38+
const actual = await vi.importActual('@/lib/query/mcp-tools');
2839
return {
40+
...actual,
2941
useMcpToolsQuery() {
3042
return { data: [] };
3143
},
32-
useMcpToolStatusQuery() {
33-
return {};
34-
},
3544
};
3645
});
3746
vi.mock('@/lib/query/data-components', () => {
@@ -51,10 +60,6 @@ vi.mock('@/lib/query/artifact-components', () => {
5160

5261
function Nodes() {
5362
const divider = <hr style={{ borderColor: 'green' }} />;
54-
const data = {
55-
name: 'name '.repeat(10),
56-
description: 'description '.repeat(10),
57-
};
5863

5964
const baseProps: NodeProps = {
6065
type: 'foo',
@@ -73,38 +78,34 @@ function Nodes() {
7378

7479
return (
7580
<ReactFlowProvider>
76-
<ExternalAgentNode {...baseProps} data={{ ...data, id: 'foo', baseUrl: 'foo' }} />
81+
<ExternalAgentNode {...baseProps} data={{ ...DATA, id: 'foo', baseUrl: 'foo' }} />
7782
{divider}
78-
<FunctionToolNode {...baseProps} data={{ ...data, functionToolId: 'foo' }} />
83+
<FunctionToolNode {...baseProps} data={{ ...DATA, functionToolId: 'foo' }} />
7984
{divider}
80-
<MCPNode
81-
{...baseProps}
82-
data={{ ...data, imageUrl: 'https://pilot.inkeep.com/icon.svg', toolId: 'foo' }}
83-
/>
85+
<MCPNode {...baseProps} data={{ ...DATA, toolId: TOOL_ID }} />
8486
{divider}
85-
<PlaceholderNode {...baseProps} data={{ ...data, type: NodeType.MCPPlaceholder }} />
87+
<PlaceholderNode {...baseProps} data={{ ...DATA, type: NodeType.MCPPlaceholder }} />
8688
{divider}
87-
<ProjectProvider
88-
value={{
89-
// @ts-expect-error
90-
project: {
91-
models: {
92-
base: { model: `openai/${data.name}` },
93-
},
94-
},
95-
}}
96-
>
97-
<SubAgentNode {...baseProps} data={{ ...data, id: 'foo', isDefault: true, skills: [] }} />
98-
</ProjectProvider>
89+
<SubAgentNode {...baseProps} data={{ ...DATA, id: 'foo', isDefault: true, skills: [] }} />
9990
{divider}
100-
<TeamAgentNode {...baseProps} data={{ ...data, id: 'foo' }} />
91+
<TeamAgentNode {...baseProps} data={{ ...DATA, id: 'foo' }} />
10192
</ReactFlowProvider>
10293
);
10394
}
10495

10596
describe('Nodes', () => {
10697
test('should handle long names with character limit', async () => {
107-
const { container } = render(<Nodes />);
98+
const queryClient = createTestQueryClient();
99+
queryClient.setQueryData(projectQueryKeys.detail(TENANT_ID, PROJECT_ID), {
100+
models: {
101+
base: { model: `openai/${DATA.name}` },
102+
},
103+
});
104+
queryClient.setQueryData(mcpToolQueryKeys.status(TENANT_ID, PROJECT_ID, TOOL_ID), {
105+
name: DATA.name,
106+
});
107+
108+
const { container } = renderWithClient(<Nodes />, queryClient);
108109
await act(async () => {
109110
await expect(container).toMatchScreenshot();
110111
});

agents-manage-ui/src/components/agent/nodes/sub-agent-node.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import { GoogleIcon } from '@/components/icons/google';
66
import { OpenAIIcon } from '@/components/icons/openai';
77
import { Badge } from '@/components/ui/badge';
88
import { STATIC_LABELS } from '@/constants/theme';
9-
import { useProject } from '@/contexts/project';
109
import { NODE_WIDTH } from '@/features/agent/domain/deserialize';
1110
import { useAgentStore } from '@/features/agent/state/use-agent-store';
1211
import { useAgentErrors } from '@/hooks/use-agent-errors';
1312
import { useArtifactComponentsQuery } from '@/lib/query/artifact-components';
1413
import { useDataComponentsQuery } from '@/lib/query/data-components';
14+
import { useProjectQuery } from '@/lib/query/projects';
1515
import { cn, createLookup } from '@/lib/utils';
1616
import type { AgentNodeData } from '../configuration/node-types';
1717
import { agentNodeSourceHandleId, agentNodeTargetHandleId } from '../configuration/node-types';
@@ -50,9 +50,8 @@ export function SubAgentNode({ data, selected, id }: NodeProps & { data: AgentNo
5050
const { data: artifactComponents } = useArtifactComponentsQuery();
5151

5252
const agentModel = useAgentStore((state) => state.metadata.models);
53-
const { project } = useProject();
54-
const projectModel = project.models;
55-
const modelName = (data.models ?? agentModel ?? projectModel).base?.model ?? '';
53+
const { data: project } = useProjectQuery();
54+
const modelName = (data.models ?? agentModel ?? project?.models)?.base?.model ?? '';
5655

5756
const { data: dataComponents } = useDataComponentsQuery();
5857
const dataComponentsById = createLookup(dataComponents);

agents-manage-ui/src/components/agent/sidepane/edges/edge-editor.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import { Checkbox } from '@/components/ui/checkbox';
99
import { Label } from '@/components/ui/label';
1010
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
1111
import { Separator } from '@/components/ui/separator';
12-
import { useProjectPermissions } from '@/contexts/project';
1312
import { useAgentActions } from '@/features/agent/state/use-agent-store';
13+
import { useProjectPermissionsQuery } from '@/lib/query/projects';
1414
import { getCycleErrorMessage, wouldCreateCycle } from '@/lib/utils/cycle-detection';
1515
import type { A2AEdgeData } from '../../configuration/edge-types';
1616

@@ -125,7 +125,9 @@ interface EdgeEditorProps {
125125
function EdgeEditor({ selectedEdge }: EdgeEditorProps) {
126126
const { updateEdgeData, setEdges, deleteElements, getEdges } = useReactFlow();
127127

128-
const { canEdit } = useProjectPermissions();
128+
const {
129+
data: { canEdit },
130+
} = useProjectPermissionsQuery();
129131

130132
const deleteEdge = useCallback(() => {
131133
deleteElements({ edges: [{ id: selectedEdge.id }] });

agents-manage-ui/src/components/agent/sidepane/metadata/metadata-editor.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@ import { Label } from '@/components/ui/label';
1919
import { Separator } from '@/components/ui/separator';
2020
import { Textarea } from '@/components/ui/textarea';
2121
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
22-
import { useProjectPermissions } from '@/contexts/project';
2322
import { useRuntimeConfig } from '@/contexts/runtime-config';
2423
import { agentStore, useAgentActions, useAgentStore } from '@/features/agent/state/use-agent-store';
2524
import { useAutoPrefillIdZustand } from '@/hooks/use-auto-prefill-id-zustand';
26-
import { useProjectData } from '@/hooks/use-project-data';
25+
import { useProjectPermissionsQuery, useProjectQuery } from '@/lib/query/projects';
2726
import {
2827
azureModelProviderOptionsTemplate,
2928
azureModelSummarizerProviderOptionsTemplate,
@@ -69,10 +68,12 @@ export function MetadataEditor() {
6968
metadata;
7069
const { PUBLIC_INKEEP_AGENTS_API_URL } = useRuntimeConfig();
7170
const baseUrl = PUBLIC_INKEEP_AGENTS_API_URL;
72-
const { canUse } = useProjectPermissions();
71+
const {
72+
data: { canUse },
73+
} = useProjectPermissionsQuery();
7374

7475
// Fetch project data for inheritance indicators
75-
const { project } = useProjectData();
76+
const { data: project } = useProjectQuery();
7677

7778
const { markUnsaved, setMetadata } = useAgentActions();
7879

agents-manage-ui/src/components/agent/sidepane/nodes/external-agent-node-editor.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import { StandaloneJsonEditor } from '@/components/editors/standalone-json-edito
66
import { Button } from '@/components/ui/button';
77
import { ExternalLink } from '@/components/ui/external-link';
88
import { Separator } from '@/components/ui/separator';
9-
import { useProjectPermissions } from '@/contexts/project';
109
import { useAgentActions } from '@/features/agent/state/use-agent-store';
1110
import type { ErrorHelpers } from '@/hooks/use-agent-errors';
1211
import { useAutoPrefillIdZustand } from '@/hooks/use-auto-prefill-id-zustand';
1312
import { useNodeEditor } from '@/hooks/use-node-editor';
13+
import { useProjectPermissionsQuery } from '@/lib/query/projects';
1414
import { externalAgentHeadersTemplate } from '@/lib/templates';
1515
import type { ExternalAgentNodeData } from '../../configuration/node-types';
1616
import { InputField } from '../form-components/input';
@@ -28,7 +28,9 @@ export function ExternalAgentNodeEditor({
2828
}: ExternalAgentNodeEditorProps) {
2929
const { updateNodeData } = useReactFlow();
3030
const { markUnsaved } = useAgentActions();
31-
const { canEdit } = useProjectPermissions();
31+
const {
32+
data: { canEdit },
33+
} = useProjectPermissionsQuery();
3234
const { handleInputChange, getFieldError, setFieldRef, updateField, deleteNode } = useNodeEditor({
3335
selectedNodeId: selectedNode.id,
3436
errorHelpers,

0 commit comments

Comments
 (0)