fix: allow workflow comments for layout users (#39014)

This commit is contained in:
Wu Tianwei 2026-07-15 17:53:45 +08:00 committed by GitHub
parent e5b7281eb9
commit 1121a17c11
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 7 additions and 98 deletions

View File

@ -114,10 +114,9 @@ vi.mock('@/app/components/workflow', () => ({
hooksStore,
}: PropsWithChildren<{
onWorkflowDataUpdate?: (payload: unknown) => void
hooksStore?: { accessControl?: { canComment?: boolean; canEdit?: boolean } }
hooksStore?: { accessControl?: { canEdit?: boolean } }
}>) => (
<div data-testid="workflow-inner-context">
<div data-testid="can-comment">{String(hooksStore?.accessControl?.canComment)}</div>
<div data-testid="can-edit">{String(hooksStore?.accessControl?.canEdit)}</div>
{children}
<button
@ -235,15 +234,6 @@ describe('RagPipelineMain', () => {
})
describe('hooks integration', () => {
it('should pass readonly ACL permission as comment-only workflow access', () => {
mockPermissionState.permissionKeys = [DatasetACLPermission.Readonly]
render(<RagPipelineMain {...defaultProps} />)
expect(screen.getByTestId('can-comment')).toHaveTextContent('true')
expect(screen.getByTestId('can-edit')).toHaveTextContent('false')
})
it('should use useNodesSyncDraft hook', () => {
render(<RagPipelineMain {...defaultProps} />)

View File

@ -134,7 +134,6 @@ const RagPipelineMain = ({ nodes, edges, viewport }: RagPipelineMainProps) => {
invalidateConversationVarValues,
accessControl: {
canEdit: datasetACLCapabilities.canEdit,
canComment: datasetACLCapabilities.canReadonly || datasetACLCapabilities.canEdit,
canRun: datasetACLCapabilities.canPipelineTest,
canImportExportDSL: datasetACLCapabilities.canImportExportDSL,
canReleaseAndVersion: datasetACLCapabilities.canPipelineRelease,

View File

@ -339,7 +339,6 @@ const SnippetMain = ({
invalidateConversationVarValues,
accessControl: {
canEdit: canEditSnippet,
canComment: true,
canRun: true,
canImportExportDSL: canEditSnippet,
canReleaseAndVersion: canEditSnippet,

View File

@ -4,7 +4,6 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { useStore as useAppStore } from '@/app/components/app/store'
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
import { BlockEnum } from '@/app/components/workflow/types'
import { AppACLPermission } from '@/utils/permission'
import WorkflowMain from '../workflow-main'
const mockSetFeatures = vi.fn()
@ -445,24 +444,6 @@ describe('WorkflowMain', () => {
})
})
it('should pass view-layout ACL permission as comment-only workflow access', () => {
useAppStore.setState({
appDetail: {
permission_keys: [AppACLPermission.ViewLayout],
} as never,
})
render(<WorkflowMain nodes={[]} edges={[]} viewport={{ x: 0, y: 0, zoom: 1 }} />)
expect(capturedContextProps?.hooksStore).toMatchObject({
accessControl: {
canEdit: false,
canComment: true,
canRun: false,
},
})
})
it('passes collaboration props and tracks cursors when collaboration is enabled', () => {
collaborationRuntime.isEnabled = true
collaborationRuntime.isConnected = true

View File

@ -297,7 +297,6 @@ const WorkflowMain = ({ nodes, edges, viewport }: WorkflowMainProps) => {
invalidateConversationVarValues,
accessControl: {
canEdit: appACLCapabilities.canEdit,
canComment: appACLCapabilities.canComment,
canRun: appACLCapabilities.canTestAndRun,
canImportExportDSL: appACLCapabilities.canImportExportDSL,
canReleaseAndVersion: appACLCapabilities.canReleaseAndVersion,

View File

@ -189,7 +189,7 @@ describe('PanelContextmenu', () => {
})
})
it('should hide add note when editing is denied but comments are allowed', async () => {
it('should hide add note but keep comments available when editing is denied', async () => {
mockUseWorkflowMoveMode.mockReturnValue({
isCommentModeAvailable: true,
})
@ -202,7 +202,6 @@ describe('PanelContextmenu', () => {
accessControl: {
...fullWorkflowAccessControl,
canEdit: false,
canComment: true,
},
},
})

View File

@ -28,7 +28,6 @@ export type SyncDraftCallback = {
export type WorkflowAccessControl = {
canEdit: boolean
canComment: boolean
canRun: boolean
canImportExportDSL: boolean
canReleaseAndVersion: boolean
@ -36,7 +35,6 @@ export type WorkflowAccessControl = {
export const fullWorkflowAccessControl: WorkflowAccessControl = {
canEdit: true,
canComment: true,
canRun: true,
canImportExportDSL: true,
canReleaseAndVersion: true,

View File

@ -142,28 +142,4 @@ describe('useWorkflowMoveMode', () => {
expect(store.getState().controlMode).toBe(ControlMode.Pointer)
expect(mockHandleSelectionCancel).not.toHaveBeenCalled()
})
it('does not switch to comment mode when commenting is denied', () => {
const { result, store } = renderWorkflowHook(() => useWorkflowMoveMode(), {
initialStoreState: {
controlMode: ControlMode.Pointer,
},
hooksStoreProps: {
accessControl: {
canEdit: true,
canComment: false,
canRun: true,
canImportExportDSL: true,
canReleaseAndVersion: true,
},
},
})
act(() => {
result.current.handleModeComment()
})
expect(store.getState().controlMode).toBe(ControlMode.Pointer)
expect(mockHandleSelectionCancel).not.toHaveBeenCalled()
})
})

View File

@ -225,7 +225,6 @@ describe('useNodesReadOnly', () => {
hooksStoreProps: {
accessControl: {
canEdit: false,
canComment: true,
canRun: true,
canImportExportDSL: true,
canReleaseAndVersion: true,

View File

@ -1,7 +1,6 @@
import { useSuspenseQuery } from '@tanstack/react-query'
import { useCallback } from 'react'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useHooksStore } from '../hooks-store'
import { useStore, useWorkflowStore } from '../store'
import { ControlMode, WorkflowRunningStatus } from '../types'
import { useEdgesInteractionsWithoutSync } from './use-edges-interactions-without-sync'
@ -33,7 +32,6 @@ export const useWorkflowMoveMode = () => {
const workflowRunningData = useStore((s) => s.workflowRunningData)
const historyWorkflowData = useStore((s) => s.historyWorkflowData)
const isRestoring = useStore((s) => s.isRestoring)
const canComment = useHooksStore((s) => s.accessControl.canComment)
const { getNodesReadOnly } = useNodesReadOnly()
const { handleSelectionCancel } = useSelectionInteractions()
const { data: isCommentModeAvailable } = useSuspenseQuery({
@ -46,11 +44,7 @@ export const useWorkflowMoveMode = () => {
historyWorkflowData ||
isRestoring
)
const canUseCommentMode = !!(
canComment &&
!isCommentModeOperationBlocked &&
isCommentModeAvailable
)
const canUseCommentMode = !!(!isCommentModeOperationBlocked && isCommentModeAvailable)
const handleModePointer = useCallback(() => {
if (getNodesReadOnly()) return

View File

@ -147,7 +147,6 @@ describe('NodeControl', () => {
it('should hide the run control when workflow run permission is missing', () => {
renderNodeControl(<NodeControlHarness id="node-5" data={makeData()} />, {
canEdit: false,
canComment: true,
canRun: false,
canImportExportDSL: false,
canReleaseAndVersion: false,

View File

@ -22,8 +22,6 @@ const {
}))
let mockNodesReadOnly = false
let mockCanComment = true
let mockCanEdit = true
let mockCanUseCommentMode = true
let mockIsCommentModeAvailable = true
let mockStoreState: WorkflowStoreState
@ -55,18 +53,6 @@ vi.mock('../../store', () => ({
useStore: (selector: (state: WorkflowStoreState) => unknown) => selector(mockStoreState),
}))
vi.mock('../../hooks-store', () => ({
useHooksStore: <T,>(
selector: (state: { accessControl: { canComment: boolean; canEdit: boolean } }) => T,
): T =>
selector({
accessControl: {
canComment: mockCanComment,
canEdit: mockCanEdit,
},
}),
}))
vi.mock('../add-block', () => ({
default: () => <div data-testid="add-block" />,
}))
@ -85,8 +71,6 @@ describe('Control', () => {
beforeEach(() => {
vi.clearAllMocks()
mockNodesReadOnly = false
mockCanComment = true
mockCanEdit = true
mockCanUseCommentMode = true
mockIsCommentModeAvailable = true
mockStoreState = {
@ -151,8 +135,6 @@ describe('Control', () => {
})
it('should block note creation when editing is not allowed', () => {
mockCanEdit = false
mockCanComment = true
mockNodesReadOnly = true
render(<Control />)
@ -164,7 +146,7 @@ describe('Control', () => {
expect(mockHandleAddNote).not.toHaveBeenCalled()
})
it('should keep comment mode enabled for readonly layout users who can comment', () => {
it('should keep comment mode enabled when nodes are read-only', () => {
mockNodesReadOnly = true
mockCanUseCommentMode = true

View File

@ -50,7 +50,6 @@ export function PanelContextmenu({ onClose }: { onClose: () => void }) {
isRestoring
)
const canEditWorkflow = accessControl.canEdit && !workflowOperationReadOnly
const canCommentWorkflow = accessControl.canComment && !workflowOperationReadOnly
const shouldHideImportApp = flowType === FlowType.snippet || isSnippetCanvas()
const renderAddBlockTrigger = useCallback(() => {
@ -105,7 +104,7 @@ export function PanelContextmenu({ onClose }: { onClose: () => void }) {
{t(($) => $['nodes.note.addNote'], { ns: 'workflow' })}
</ContextMenuItem>
)}
{canCommentWorkflow && isCommentModeAvailable && (
{!workflowOperationReadOnly && isCommentModeAvailable && (
<ContextMenuItem
disabled={!!pendingComment}
className={cn(

View File

@ -311,7 +311,6 @@ describe('InputsPanel', () => {
handleRun,
accessControl: {
canEdit: true,
canComment: true,
canRun: false,
canImportExportDSL: true,
canReleaseAndVersion: true,

View File

@ -25,21 +25,19 @@ describe('permission', () => {
})
describe('getAppACLCapabilities', () => {
it('allows test-and-run users to access layout without edit or comment', () => {
it('allows test-and-run users to access layout without edit', () => {
const capabilities = getAppACLCapabilities([AppACLPermission.TestAndRun])
expect(capabilities.canTestAndRun).toBe(true)
expect(capabilities.canAccessLayout).toBe(true)
expect(capabilities.canComment).toBe(false)
expect(capabilities.canEdit).toBe(false)
})
it('allows view-layout users to preview the app and comment but not run/debug', () => {
it('allows view-layout users to preview the app but not run/debug', () => {
const capabilities = getAppACLCapabilities([AppACLPermission.ViewLayout])
expect(capabilities.canPreviewApp).toBe(true)
expect(capabilities.canAccessLayout).toBe(true)
expect(capabilities.canComment).toBe(true)
expect(capabilities.canTestAndRun).toBe(false)
})

View File

@ -47,7 +47,6 @@ type AppACLCapabilities = {
canTestAndRun: boolean
canEdit: boolean
canAccessLayout: boolean
canComment: boolean
canPreviewApp: boolean
canImportExportDSL: boolean
canDelete: boolean
@ -144,7 +143,6 @@ export const getAppACLCapabilities = (
canTestAndRun,
canEdit,
canAccessLayout: canViewLayout || canTestAndRun || canEdit,
canComment: canViewLayout || canEdit,
canPreviewApp: canViewLayout || canTestAndRun,
canImportExportDSL: hasResourcePermission(
permissionKeys,