History

Hide integration edit/delete controls for non-admin members (#2013)

devin-ai-integration[bot] committed 1 month ago

Commit 59be51a1 parent 5b5db981 file · +18 −5Browse files

Unified · Split

packages/react/src/pages/integration-detail.tsx+18−5
@@ -37,6 +37,7 @@ import { IntegrationEditSheet } from "../components/metadata-edit-sheet"; · 36 unmodified lines
3737 import { Tabs, TabsContent, TabsList, TabsTrigger } from "../components/tabs";
3838 import { authMethodsFromDescriptors, type AuthMethod } from "../lib/auth-placements";
3939 import { usePolicyActions } from "../hooks/use-policy-actions";
40+import { useCanCreateWorkspaceConnections } from "../multiplayer/use-admin-nav";
4041 import { useIntegrationPlugins, type IntegrationAccountHandoff } from "@executor-js/sdk/client";
4142 import { Button } from "../components/button";
4243 import { Skeleton } from "../components/skeleton";
@@ -84,9 +85,13 @@ export function IntegrationDetailPage(props: { · 41 unmodified lines
8485 const connectionsResult = useAtomValue(connectionsAllAtom);
8586 const refreshIntegrations = useAtomRefresh(integrationsOptimisticAtom);
8687 const refreshTools = useAtomRefresh(integrationToolsAllAtom(slug));
87− const doRemove = useAtomSet(removeIntegrationOptimistic, { mode: "promiseExit" });
88+ const doRemove = useAtomSet(removeIntegrationOptimistic, {
89+ mode: "promiseExit",
90+ });
8891 const doRefresh = useAtomSet(refreshConnection, { mode: "promiseExit" });
89− const doCheckHealth = useAtomSet(checkConnectionHealth, { mode: "promiseExit" });
92+ const doCheckHealth = useAtomSet(checkConnectionHealth, {
93+ mode: "promiseExit",
94+ });
9095 // Policies are owner-partitioned on write; the integration policy menu writes
9196 // Workspace (org) rules, preserving the prior default behavior.
9297 const policyActions = usePolicyActions("org");
@@ -133,8 +138,12 @@ export function IntegrationDetailPage(props: { · 40 unmodified lines
133138 useExecutorDocumentTitle(integrationData?.name || namespace);
134139 const isBuiltInIntegration = namespace === "executor" || integrationData?.kind === "built-in";
135140 const currentTab = isBuiltInIntegration ? "tools" : activeTab;
141+ // Integrations are workspace-owned; the server refuses catalog mutations
142+ // (update/remove) from non-admin members, so hide the controls for them.
143+ const canMutateIntegration = useCanCreateWorkspaceConnections();
144+ const canEdit = canMutateIntegration && !isBuiltInIntegration && integrationData !== null;
136145 const canRefresh = integrationData?.canRefresh ?? false;
137− const canRemove = integrationData?.canRemove ?? false;
146+ const canRemove = canMutateIntegration && (integrationData?.canRemove ?? false);
138147 const urlAccountHandoff = useMemo<IntegrationAccountHandoff | null>(() => {
139148 const search = new URLSearchParams(locationSearch);
140149 // The route-validated flag and the raw `addAccount=1` are the same request;
@@ -389,7 +398,11 @@ export function IntegrationDetailPage(props: { · 248 unmodified lines
389398 if (connection.integration !== slug) continue;
390399 connectionCount++;
391400 const refreshExit = await doRefresh({
392− params: { owner: connection.owner, integration: slug, name: connection.name },
401+ params: {
402+ owner: connection.owner,
403+ integration: slug,
404+ name: connection.name,
405+ },
393406 reactivityKeys: connectionWriteKeys,
394407 });
395408 refreshExits.push(Exit.isSuccess(refreshExit));
@@ -485,7 +498,7 @@ export function IntegrationDetailPage(props: { · 89 unmodified lines
485498 </div>
486499
487500 <div className="flex shrink-0 items-center gap-2">
488− {!confirmDelete && !isBuiltInIntegration && integrationData && (
501+ {!confirmDelete && canEdit && (
489502 <Button variant="outline" size="sm" onClick={() => setEditSheetOpen(true)}>
490503 Edit
491504 </Button>