From 639baf782cfc238aa437add9b8caee7b11ff04d6 Mon Sep 17 00:00:00 2001 From: kirtimanmishrazipstack Date: Wed, 2 Sep 2026 16:08:54 +0530 Subject: [PATCH 1/6] UN-2868 [FIX] Hide edit and delete actions on resources shared with the user Sharing grants read only, but every resource list still offered Edit, Share, Delete and the enable/disable toggle to the people it was shared with. The backend refused them; the UI did not say so. All eight shareable resources render through two shared widgets, so the row actions are gated in one place each -- ResourceTable covers workflows, Prompt Studio, connectors, adapters, agentic projects and lookups; CardActionBox covers pipelines and API deployments. The two card kebabs mix read and write actions, so those are filtered per page: Manage Keys, Notifications and Clear File History go, while View Logs, File History, Sync Now, Code Snippets and Download Postman stay. Running and watching a shared pipeline is still allowed -- that is what sharing is for. The rule itself now lives in one helper, canEditResource, which useWorkflowCanEdit also delegates to. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_016eB6bLmieWVwUnmYH6H5WZ --- .../ApiDeploymentCardConfig.jsx | 70 ++++++++------ .../pipelines/PipelineCardConfig.jsx | 84 +++++++++------- .../card-grid-view/CardFieldComponents.jsx | 96 +++++++++++-------- .../widgets/resource-table/ResourceTable.jsx | 6 ++ frontend/src/helpers/resourceAccess.js | 15 +++ frontend/src/hooks/useWorkflowCanEdit.js | 8 +- 6 files changed, 170 insertions(+), 109 deletions(-) create mode 100644 frontend/src/helpers/resourceAccess.js diff --git a/frontend/src/components/deployments/api-deployment/ApiDeploymentCardConfig.jsx b/frontend/src/components/deployments/api-deployment/ApiDeploymentCardConfig.jsx index fde548844c..50f65a4612 100644 --- a/frontend/src/components/deployments/api-deployment/ApiDeploymentCardConfig.jsx +++ b/frontend/src/components/deployments/api-deployment/ApiDeploymentCardConfig.jsx @@ -12,6 +12,7 @@ import { Flex, Space } from "@/components/ui/shims/antd-layout"; import { Tooltip } from "@/components/ui/shims/antd-overlays"; import { Typography } from "@/components/ui/shims/antd-typography"; +import { canEditResource } from "../../../helpers/resourceAccess"; import { StatusPills } from "../../pipelines-or-deployments/pipelines/PipelineCardConfig"; import { ApiEndpointSection, @@ -51,6 +52,9 @@ function createApiDeploymentCardConfig({ }, expandable: false, listContent: (deployment) => { + // Sharing grants read only: keep the read actions, drop the ones that + // change the deployment. + const canEdit = canEditResource(deployment, sessionDetails); const kebabMenuItems = { items: [ { @@ -59,19 +63,23 @@ function createApiDeploymentCardConfig({ label: "View Logs", onClick: () => onViewLogs?.(deployment), }, - { type: "divider" }, - { - key: "manage-keys", - icon: , - label: "Manage Keys", - onClick: () => onManageKeys?.(deployment), - }, - { - key: "notifications", - icon: , - label: "Notifications", - onClick: () => onSetupNotifications?.(deployment), - }, + ...(canEdit + ? [ + { type: "divider" }, + { + key: "manage-keys", + icon: , + label: "Manage Keys", + onClick: () => onManageKeys?.(deployment), + }, + { + key: "notifications", + icon: , + label: "Notifications", + onClick: () => onSetupNotifications?.(deployment), + }, + ] + : []), { type: "divider" }, { key: "code-snippets", @@ -95,23 +103,25 @@ function createApiDeploymentCardConfig({ description={deployment.description} > - - { - e.stopPropagation(); - updateStatus(deployment); - }} - /> - + {canEdit && ( + + { + e.stopPropagation(); + updateStatus(deployment); + }} + /> + + )} onViewFileHistory?.(pipeline), }, - { - key: "clear-history", - icon: , - label: isClearingFileHistory ? "Clearing..." : "Clear File History", - disabled: isClearingFileHistory, - onClick: () => onClearFileHistory?.(pipeline), - }, + ...(canEdit + ? [ + { + key: "clear-history", + icon: , + label: isClearingFileHistory + ? "Clearing..." + : "Clear File History", + disabled: isClearingFileHistory, + onClick: () => onClearFileHistory?.(pipeline), + }, + ] + : []), { type: "divider" }, { key: "sync-now", @@ -289,19 +299,23 @@ function createPipelineCardConfig({ label: "Sync Now", onClick: () => onSyncNow?.(pipeline), }, - { type: "divider" }, - { - key: "manage-keys", - icon: , - label: "Manage Keys", - onClick: () => onManageKeys?.(pipeline), - }, - { - key: "notifications", - icon: , - label: "Notifications", - onClick: () => onSetupNotifications?.(pipeline), - }, + ...(canEdit + ? [ + { type: "divider" }, + { + key: "manage-keys", + icon: , + label: "Manage Keys", + onClick: () => onManageKeys?.(pipeline), + }, + { + key: "notifications", + icon: , + label: "Notifications", + onClick: () => onSetupNotifications?.(pipeline), + }, + ] + : []), { type: "divider" }, { key: "download-postman", @@ -323,19 +337,23 @@ function createPipelineCardConfig({ - - { - e.stopPropagation(); - handleEnablePipeline(checked, pipeline.id); - }} - /> - + {canEdit && ( + + { + e.stopPropagation(); + handleEnablePipeline(checked, pipeline.id); + }} + /> + + )} testIdPrefix ? `${testIdPrefix}-${suffix}-${item?.id}` : undefined; const handleEditAction = (e) => { @@ -63,48 +69,54 @@ function CardActionBox({ return ( - - + {canEdit && ( + + + + )} {handleShare && ( - + + + )} ); }; From 8d405fcd5ed504c0d9f66ba7f516571bbb8ff15b Mon Sep 17 00:00:00 2001 From: kirtimanmishrazipstack Date: Wed, 2 Sep 2026 16:42:13 +0530 Subject: [PATCH 4/6] UN-2868 [FIX] Make Prompt Studio settings read-only for shared users Settings hold the project's LLM profiles and adapter selections -- the credential-bearing part. A shared user can read them but not change them: the panel gets the read-only notice and its controls are inert. Prompts are deliberately untouched. Editing, running and deleting prompts is what a project is shared for; only the settings panel is restricted. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_016eB6bLmieWVwUnmYH6H5WZ --- .../custom-tools/settings-modal/SettingsModal.jsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/custom-tools/settings-modal/SettingsModal.jsx b/frontend/src/components/custom-tools/settings-modal/SettingsModal.jsx index 99a68d5fb9..453583dcad 100644 --- a/frontend/src/components/custom-tools/settings-modal/SettingsModal.jsx +++ b/frontend/src/components/custom-tools/settings-modal/SettingsModal.jsx @@ -12,6 +12,8 @@ import { Modal } from "@/components/ui/shims/antd-overlays"; import { Menu } from "@/components/ui/shims/antd-structure"; import { Typography } from "@/components/ui/shims/antd-typography"; import { getMenuItem } from "../../../helpers/GetStaticData"; +import { usePromptStudioCanEdit } from "../../../hooks/usePromptStudioCanEdit"; +import { ReadOnlyNotice } from "../../widgets/read-only-notice/ReadOnlyNotice"; import SpaceWrapper from "../../widgets/space-wrapper/SpaceWrapper"; import { CustomDataSettings } from "../custom-data-settings/CustomDataSettings"; import { CustomSynonyms } from "../custom-synonyms/CustomSynonyms"; @@ -41,6 +43,10 @@ try { // Component will remain null if it is not present. } function SettingsModal({ open, setOpen, handleUpdateTool }) { + // Settings hold the project's adapter credentials, so a shared user reads + // them but cannot change them. Prompts stay editable -- that is what the + // project was shared for. + const canEdit = usePromptStudioCanEdit(); const [selectedId, setSelectedId] = useState(1); const [menuItems, setMenuItems] = useState([]); const [components, setComponents] = useState([]); @@ -140,6 +146,9 @@ function SettingsModal({ open, setOpen, handleUpdateTool }) { Settings + {!canEdit && ( + + )}
@@ -154,7 +163,11 @@ function SettingsModal({ open, setOpen, handleUpdateTool }) {
-
+
{components[selectedId]}
From 91c840495c12aeec1e571c15b5e225901a012e8d Mon Sep 17 00:00:00 2001 From: kirtimanmishrazipstack Date: Wed, 2 Sep 2026 16:59:01 +0530 Subject: [PATCH 5/6] UN-2868 [FIX] Restrict the gate to edit and delete only The scope is settings and deletion, nothing else. The pipeline and API deployment cards had also lost their enable/disable toggle, Manage Keys, Notifications and Clear File History for shared users, which goes further than intended. Both card configs are reverted. Only the Edit and Delete controls in the two shared list widgets stay gated; Share, the toggle and every kebab action are available again. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_016eB6bLmieWVwUnmYH6H5WZ --- .../ApiDeploymentCardConfig.jsx | 70 +++++++--------- .../pipelines/PipelineCardConfig.jsx | 84 ++++++++----------- 2 files changed, 63 insertions(+), 91 deletions(-) diff --git a/frontend/src/components/deployments/api-deployment/ApiDeploymentCardConfig.jsx b/frontend/src/components/deployments/api-deployment/ApiDeploymentCardConfig.jsx index 50f65a4612..fde548844c 100644 --- a/frontend/src/components/deployments/api-deployment/ApiDeploymentCardConfig.jsx +++ b/frontend/src/components/deployments/api-deployment/ApiDeploymentCardConfig.jsx @@ -12,7 +12,6 @@ import { Flex, Space } from "@/components/ui/shims/antd-layout"; import { Tooltip } from "@/components/ui/shims/antd-overlays"; import { Typography } from "@/components/ui/shims/antd-typography"; -import { canEditResource } from "../../../helpers/resourceAccess"; import { StatusPills } from "../../pipelines-or-deployments/pipelines/PipelineCardConfig"; import { ApiEndpointSection, @@ -52,9 +51,6 @@ function createApiDeploymentCardConfig({ }, expandable: false, listContent: (deployment) => { - // Sharing grants read only: keep the read actions, drop the ones that - // change the deployment. - const canEdit = canEditResource(deployment, sessionDetails); const kebabMenuItems = { items: [ { @@ -63,23 +59,19 @@ function createApiDeploymentCardConfig({ label: "View Logs", onClick: () => onViewLogs?.(deployment), }, - ...(canEdit - ? [ - { type: "divider" }, - { - key: "manage-keys", - icon: , - label: "Manage Keys", - onClick: () => onManageKeys?.(deployment), - }, - { - key: "notifications", - icon: , - label: "Notifications", - onClick: () => onSetupNotifications?.(deployment), - }, - ] - : []), + { type: "divider" }, + { + key: "manage-keys", + icon: , + label: "Manage Keys", + onClick: () => onManageKeys?.(deployment), + }, + { + key: "notifications", + icon: , + label: "Notifications", + onClick: () => onSetupNotifications?.(deployment), + }, { type: "divider" }, { key: "code-snippets", @@ -103,25 +95,23 @@ function createApiDeploymentCardConfig({ description={deployment.description} > - {canEdit && ( - - { - e.stopPropagation(); - updateStatus(deployment); - }} - /> - - )} + + { + e.stopPropagation(); + updateStatus(deployment); + }} + /> + onViewFileHistory?.(pipeline), }, - ...(canEdit - ? [ - { - key: "clear-history", - icon: , - label: isClearingFileHistory - ? "Clearing..." - : "Clear File History", - disabled: isClearingFileHistory, - onClick: () => onClearFileHistory?.(pipeline), - }, - ] - : []), + { + key: "clear-history", + icon: , + label: isClearingFileHistory ? "Clearing..." : "Clear File History", + disabled: isClearingFileHistory, + onClick: () => onClearFileHistory?.(pipeline), + }, { type: "divider" }, { key: "sync-now", @@ -299,23 +289,19 @@ function createPipelineCardConfig({ label: "Sync Now", onClick: () => onSyncNow?.(pipeline), }, - ...(canEdit - ? [ - { type: "divider" }, - { - key: "manage-keys", - icon: , - label: "Manage Keys", - onClick: () => onManageKeys?.(pipeline), - }, - { - key: "notifications", - icon: , - label: "Notifications", - onClick: () => onSetupNotifications?.(pipeline), - }, - ] - : []), + { type: "divider" }, + { + key: "manage-keys", + icon: , + label: "Manage Keys", + onClick: () => onManageKeys?.(pipeline), + }, + { + key: "notifications", + icon: , + label: "Notifications", + onClick: () => onSetupNotifications?.(pipeline), + }, { type: "divider" }, { key: "download-postman", @@ -337,23 +323,19 @@ function createPipelineCardConfig({ - {canEdit && ( - - { - e.stopPropagation(); - handleEnablePipeline(checked, pipeline.id); - }} - /> - - )} + + { + e.stopPropagation(); + handleEnablePipeline(checked, pipeline.id); + }} + /> + Date: Wed, 2 Sep 2026 17:37:26 +0530 Subject: [PATCH 6/6] UN-2868 [FIX] Show Edit and Delete disabled rather than hiding them Hiding the two controls left a shared user with no idea they existed or why they were missing. They now stay on screen, greyed out, with a tooltip reading "Only the owner can change this". Same treatment in both list widgets so every resource looks the same. The rename pencil beside a project title follows the same rule: ToolNavBar takes an editTitleDisabled prop, and Prompt Studio passes it. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_016eB6bLmieWVwUnmYH6H5WZ --- .../components/custom-tools/header/Header.jsx | 4 + .../navigations/tool-nav-bar/ToolNavBar.jsx | 28 +++++-- .../card-grid-view/CardFieldComponents.jsx | 54 +++++++------- .../widgets/resource-table/ResourceTable.jsx | 74 ++++++++++--------- 4 files changed, 92 insertions(+), 68 deletions(-) diff --git a/frontend/src/components/custom-tools/header/Header.jsx b/frontend/src/components/custom-tools/header/Header.jsx index 34bae4dd8e..bae4bbdc4a 100644 --- a/frontend/src/components/custom-tools/header/Header.jsx +++ b/frontend/src/components/custom-tools/header/Header.jsx @@ -9,6 +9,7 @@ import { ExportToolIcon } from "../../../assets"; import { useAxiosPrivate } from "../../../hooks/useAxiosPrivate"; import { useExceptionHandler } from "../../../hooks/useExceptionHandler"; import usePostHogEvents from "../../../hooks/usePostHogEvents"; +import { usePromptStudioCanEdit } from "../../../hooks/usePromptStudioCanEdit"; import { useAlertStore } from "../../../store/alert-store"; import { useCustomToolStore } from "../../../store/custom-tool-store"; import { useSessionStore } from "../../../store/session-store"; @@ -53,6 +54,8 @@ function Header({ const { details, isPublicSource, markChangesAsExported } = useCustomToolStore(); const { sessionDetails } = useSessionStore(); + // Renaming a shared project is an edit, so it follows the same rule. + const canEdit = usePromptStudioCanEdit(); const { setAlertDetails } = useAlertStore(); const axiosPrivate = useAxiosPrivate(); const handleException = useExceptionHandler(); @@ -444,6 +447,7 @@ function Header({ onEditTitle={ isPublicSource || !details?.tool_id ? undefined : handleOpenEditModal } + editTitleDisabled={!canEdit} customButtons={actionButtons} /> {titleAdornment} {onEditTitle && ( -
{subtitle && ( @@ -134,6 +145,7 @@ ToolNavBar.propTypes = { titleAdornment: PropTypes.node, subtitle: PropTypes.string, onEditTitle: PropTypes.func, + editTitleDisabled: PropTypes.bool, enableSearch: PropTypes.bool, customButtons: PropTypes.node, setSearchList: PropTypes.func, diff --git a/frontend/src/components/widgets/card-grid-view/CardFieldComponents.jsx b/frontend/src/components/widgets/card-grid-view/CardFieldComponents.jsx index 8fc990feae..e678109b9f 100644 --- a/frontend/src/components/widgets/card-grid-view/CardFieldComponents.jsx +++ b/frontend/src/components/widgets/card-grid-view/CardFieldComponents.jsx @@ -53,6 +53,7 @@ function CardActionBox({ // Sharing grants read only: no edit, no delete. Sharing onward stays // available -- see the Share button below. const canEdit = canEditResource(item, sessionDetails); + const lockedTitle = canEdit ? undefined : "Only the owner can change this"; const testId = (suffix) => testIdPrefix ? `${testIdPrefix}-${suffix}-${item?.id}` : undefined; const handleEditAction = (e) => { @@ -69,15 +70,16 @@ function CardActionBox({ return ( - {canEdit && ( + - - )} + + + {handleShare && ( - - )} + + + ); };