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
37import { Tabs, TabsContent, TabsList, TabsTrigger } from "../components/tabs";37import { Tabs, TabsContent, TabsList, TabsTrigger } from "../components/tabs";
38import { authMethodsFromDescriptors, type AuthMethod } from "../lib/auth-placements";38import { authMethodsFromDescriptors, type AuthMethod } from "../lib/auth-placements";
39import { usePolicyActions } from "../hooks/use-policy-actions";39import { usePolicyActions } from "../hooks/use-policy-actions";
40import { useCanCreateWorkspaceConnections } from "../multiplayer/use-admin-nav";
40import { useIntegrationPlugins, type IntegrationAccountHandoff } from "@executor-js/sdk/client";41import { useIntegrationPlugins, type IntegrationAccountHandoff } from "@executor-js/sdk/client";
41import { Button } from "../components/button";42import { Button } from "../components/button";
42import { Skeleton } from "../components/skeleton";43import { Skeleton } from "../components/skeleton";
@@ -84,9 +85,13 @@ export function IntegrationDetailPage(props: { · 41 unmodified lines
84 const connectionsResult = useAtomValue(connectionsAllAtom);85 const connectionsResult = useAtomValue(connectionsAllAtom);
85 const refreshIntegrations = useAtomRefresh(integrationsOptimisticAtom);86 const refreshIntegrations = useAtomRefresh(integrationsOptimisticAtom);
86 const refreshTools = useAtomRefresh(integrationToolsAllAtom(slug));87 const refreshTools = useAtomRefresh(integrationToolsAllAtom(slug));
87 const doRemove = useAtomSet(removeIntegrationOptimistic, { mode: "promiseExit" });88 const doRemove = useAtomSet(removeIntegrationOptimistic, {
89 mode: "promiseExit",
90 });
88 const doRefresh = useAtomSet(refreshConnection, { mode: "promiseExit" });91 const doRefresh = useAtomSet(refreshConnection, { mode: "promiseExit" });
89 const doCheckHealth = useAtomSet(checkConnectionHealth, { mode: "promiseExit" });92 const doCheckHealth = useAtomSet(checkConnectionHealth, {
93 mode: "promiseExit",
94 });
90 // Policies are owner-partitioned on write; the integration policy menu writes95 // Policies are owner-partitioned on write; the integration policy menu writes
91 // Workspace (org) rules, preserving the prior default behavior.96 // Workspace (org) rules, preserving the prior default behavior.
92 const policyActions = usePolicyActions("org");97 const policyActions = usePolicyActions("org");
@@ -133,8 +138,12 @@ export function IntegrationDetailPage(props: { · 40 unmodified lines
133 useExecutorDocumentTitle(integrationData?.name || namespace);138 useExecutorDocumentTitle(integrationData?.name || namespace);
134 const isBuiltInIntegration = namespace === "executor" || integrationData?.kind === "built-in";139 const isBuiltInIntegration = namespace === "executor" || integrationData?.kind === "built-in";
135 const currentTab = isBuiltInIntegration ? "tools" : activeTab;140 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;
136 const canRefresh = integrationData?.canRefresh ?? false;145 const canRefresh = integrationData?.canRefresh ?? false;
137 const canRemove = integrationData?.canRemove ?? false;146 const canRemove = canMutateIntegration && (integrationData?.canRemove ?? false);
138 const urlAccountHandoff = useMemo<IntegrationAccountHandoff | null>(() => {147 const urlAccountHandoff = useMemo<IntegrationAccountHandoff | null>(() => {
139 const search = new URLSearchParams(locationSearch);148 const search = new URLSearchParams(locationSearch);
140 // The route-validated flag and the raw `addAccount=1` are the same request;149 // The route-validated flag and the raw `addAccount=1` are the same request;
@@ -389,7 +398,11 @@ export function IntegrationDetailPage(props: { · 248 unmodified lines
389 if (connection.integration !== slug) continue;398 if (connection.integration !== slug) continue;
390 connectionCount++;399 connectionCount++;
391 const refreshExit = await doRefresh({400 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 },
393 reactivityKeys: connectionWriteKeys,406 reactivityKeys: connectionWriteKeys,
394 });407 });
395 refreshExits.push(Exit.isSuccess(refreshExit));408 refreshExits.push(Exit.isSuccess(refreshExit));
@@ -485,7 +498,7 @@ export function IntegrationDetailPage(props: { · 89 unmodified lines
485 </div>498 </div>
486 499
487 <div className="flex shrink-0 items-center gap-2">500 <div className="flex shrink-0 items-center gap-2">
488 {!confirmDelete && !isBuiltInIntegration && integrationData && (501 {!confirmDelete && canEdit && (
489 <Button variant="outline" size="sm" onClick={() => setEditSheetOpen(true)}>502 <Button variant="outline" size="sm" onClick={() => setEditSheetOpen(true)}>
490 Edit503 Edit
491 </Button>504 </Button>