mirror of
https://github.com/langgenius/dify.git
synced 2026-07-14 17:07:03 +08:00
refactor(web): migrate workflow app context consumers (#38552)
This commit is contained in:
@ -29,6 +29,18 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
workspacePermissionKeys: mockWorkspacePermissionKeys.value,
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/app/components/snippets/hooks/use-create-snippet', async () => {
|
||||
const React = await vi.importActual<typeof import('react')>('react')
|
||||
|
||||
|
||||
@ -6,6 +6,13 @@ import { CommentIcon } from './comment-icon'
|
||||
type Position = { x: number, y: number }
|
||||
|
||||
let mockUserId = 'user-1'
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: {
|
||||
id: 'user-1',
|
||||
name: 'User',
|
||||
avatar_url: 'avatar',
|
||||
},
|
||||
}))
|
||||
|
||||
const mockFlowToScreenPosition = vi.fn((position: Position) => position)
|
||||
const mockScreenToFlowPosition = vi.fn((position: Position) => position)
|
||||
@ -25,13 +32,28 @@ vi.mock('reactflow', () => ({
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
userProfile: {
|
||||
...mockAppContextState.userProfile,
|
||||
id: mockUserId,
|
||||
name: 'User',
|
||||
avatar_url: 'avatar',
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => ({
|
||||
...mockAppContextState,
|
||||
userProfile: {
|
||||
...mockAppContextState.userProfile,
|
||||
id: mockUserId,
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/app/components/base/user-avatar-list', () => ({
|
||||
UserAvatarList: ({ users }: { users: Array<{ id: string }> }) => (
|
||||
<div data-testid="avatar-list">{users.map(user => user.id).join(',')}</div>
|
||||
|
||||
@ -2,10 +2,11 @@
|
||||
|
||||
import type { FC, PointerEvent as ReactPointerEvent } from 'react'
|
||||
import type { WorkflowCommentList } from '@/app/components/workflow/comment/types'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { memo, useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { useReactFlow, useViewport } from 'reactflow'
|
||||
import { UserAvatarList } from '@/app/components/base/user-avatar-list'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { userProfileIdAtom } from '@/context/app-context-state'
|
||||
import CommentPreview from './comment-preview'
|
||||
|
||||
type CommentIconProps = {
|
||||
@ -18,8 +19,8 @@ type CommentIconProps = {
|
||||
export const CommentIcon: FC<CommentIconProps> = memo(({ comment, onClick, isActive = false, onPositionUpdate }) => {
|
||||
const { flowToScreenPosition, screenToFlowPosition } = useReactFlow()
|
||||
const viewport = useViewport()
|
||||
const { userProfile } = useAppContext()
|
||||
const isAuthor = comment.created_by_account?.id === userProfile?.id
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const isAuthor = comment.created_by_account?.id === currentUserId
|
||||
const [showPreview, setShowPreview] = useState(false)
|
||||
const [dragPosition, setDragPosition] = useState<{ x: number, y: number } | null>(null)
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
|
||||
@ -18,6 +18,13 @@ const stableT = (key: string, options?: { ns?: string }) => (
|
||||
)
|
||||
|
||||
let mentionInputProps: MentionInputProps | null = null
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: {
|
||||
id: 'user-1',
|
||||
name: 'Alice',
|
||||
avatar_url: 'avatar',
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
@ -27,14 +34,20 @@ vi.mock('react-i18next', () => ({
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
userProfile: {
|
||||
id: 'user-1',
|
||||
name: 'Alice',
|
||||
avatar_url: 'avatar',
|
||||
},
|
||||
userProfile: mockAppContextState.userProfile,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@langgenius/dify-ui/avatar', () => ({
|
||||
Avatar: ({ name }: { name: string }) => <div data-testid="avatar">{name}</div>,
|
||||
default: ({ name }: { name: string }) => <div data-testid="avatar">{name}</div>,
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import type { FC, PointerEvent as ReactPointerEvent } from 'react'
|
||||
import { Avatar } from '@langgenius/dify-ui/avatar'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { memo, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { userProfileAtom } from '@/context/app-context-state'
|
||||
import { MentionInput } from './mention-input'
|
||||
|
||||
type CommentInputProps = {
|
||||
@ -30,7 +31,7 @@ export const CommentInput: FC<CommentInputProps> = memo(({
|
||||
}) => {
|
||||
const [content, setContent] = useState('')
|
||||
const { t } = useTranslation()
|
||||
const { userProfile } = useAppContext()
|
||||
const userProfile = useAtomValue(userProfileAtom)
|
||||
const dragStateRef = useRef<{
|
||||
pointerId: number | null
|
||||
startPointerX: number
|
||||
|
||||
@ -4,6 +4,13 @@ import { CommentThread } from './thread'
|
||||
|
||||
const mockSetCommentPreviewHovering = vi.hoisted(() => vi.fn())
|
||||
const mockFlowToScreenPosition = vi.hoisted(() => vi.fn(({ x, y }: { x: number, y: number }) => ({ x, y })))
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: {
|
||||
id: 'user-1',
|
||||
name: 'Alice',
|
||||
avatar_url: 'alice.png',
|
||||
},
|
||||
}))
|
||||
|
||||
const storeState = vi.hoisted(() => ({
|
||||
mentionableUsersCache: {
|
||||
@ -33,14 +40,20 @@ vi.mock('@/hooks/use-format-time-from-now', () => ({
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
userProfile: {
|
||||
id: 'user-1',
|
||||
name: 'Alice',
|
||||
avatar_url: 'alice.png',
|
||||
},
|
||||
userProfile: mockAppContextState.userProfile,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('reactflow', () => ({
|
||||
useReactFlow: () => ({
|
||||
flowToScreenPosition: mockFlowToScreenPosition,
|
||||
|
||||
@ -11,13 +11,14 @@ import {
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { RiArrowDownSLine, RiArrowUpSLine, RiCheckboxCircleFill, RiCheckboxCircleLine, RiCloseLine, RiDeleteBinLine, RiMoreFill } from '@remixicon/react'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useReactFlow, useViewport } from 'reactflow'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import InlineDeleteConfirm from '@/app/components/base/inline-delete-confirm'
|
||||
import { getUserColor } from '@/app/components/workflow/collaboration/utils/user-color'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { userProfileAtom, userProfileIdAtom } from '@/context/app-context-state'
|
||||
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
|
||||
import { useParams } from '@/next/navigation'
|
||||
import { useStore } from '../store'
|
||||
@ -52,8 +53,7 @@ const ThreadMessage: FC<{
|
||||
className?: string
|
||||
}> = ({ authorId, authorName, avatarUrl, createdAt, content, mentionableNames, className }) => {
|
||||
const { formatTimeFromNow } = useFormatTimeFromNow()
|
||||
const { userProfile } = useAppContext()
|
||||
const currentUserId = userProfile?.id
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const isCurrentUser = authorId === currentUserId
|
||||
const userColor = isCurrentUser ? undefined : getUserColor(authorId)
|
||||
|
||||
@ -175,7 +175,8 @@ export const CommentThread: FC<CommentThreadProps> = memo(({
|
||||
const appId = params.appId as string
|
||||
const { flowToScreenPosition } = useReactFlow()
|
||||
const viewport = useViewport()
|
||||
const { userProfile } = useAppContext()
|
||||
const userProfile = useAtomValue(userProfileAtom)
|
||||
const currentUserId = userProfile.id
|
||||
const { t } = useTranslation()
|
||||
const [replyContent, setReplyContent] = useState('')
|
||||
const [editingCommentContent, setEditingCommentContent] = useState('')
|
||||
@ -369,7 +370,7 @@ export const CommentThread: FC<CommentThreadProps> = memo(({
|
||||
}, [editingReply, onReplyEdit])
|
||||
|
||||
const replies = comment.replies || []
|
||||
const isOwnComment = comment.created_by_account?.id === userProfile?.id
|
||||
const isOwnComment = comment.created_by_account?.id === currentUserId
|
||||
const messageListRef = useRef<HTMLDivElement>(null)
|
||||
const previousReplyCountRef = useRef<number | undefined>(undefined)
|
||||
const previousCommentIdRef = useRef<string | undefined>(undefined)
|
||||
@ -604,7 +605,7 @@ export const CommentThread: FC<CommentThreadProps> = memo(({
|
||||
<div className="mt-2 space-y-3 pt-3">
|
||||
{replies.map((reply) => {
|
||||
const isReplyEditing = editingReply?.id === reply.id
|
||||
const isOwnReply = reply.created_by_account?.id === userProfile?.id
|
||||
const isOwnReply = reply.created_by_account?.id === currentUserId
|
||||
return (
|
||||
<div
|
||||
key={reply.id}
|
||||
|
||||
@ -13,6 +13,22 @@ const mockHandleLoadBackupDraft = vi.fn()
|
||||
const mockHandleRefreshWorkflowDraft = vi.fn()
|
||||
let mockPlanType = Plan.professional
|
||||
let mockEnableBilling = true
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: {
|
||||
id: '',
|
||||
name: '',
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
|
||||
@ -25,6 +25,22 @@ const mockViewHistory = vi.fn()
|
||||
|
||||
let mockNodesReadOnly = false
|
||||
let mockTheme: 'light' | 'dark' = 'light'
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: {
|
||||
id: '',
|
||||
name: '',
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('reactflow', () => ({
|
||||
useNodes: () => mockUseNodes(),
|
||||
|
||||
@ -2,6 +2,7 @@ import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { RiHistoryLine } from '@remixicon/react'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import {
|
||||
useCallback,
|
||||
useState,
|
||||
@ -9,7 +10,7 @@ import {
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { PlanUpgradeModal } from '@/app/components/billing/plan-upgrade-modal'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import { useSelector as useAppContextSelector } from '@/context/app-context'
|
||||
import { userProfileAtom } from '@/context/app-context-state'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import { useInvalidAllLastRun, useResetWorkflowVersionHistory, useRestoreWorkflow } from '@/service/use-workflow'
|
||||
@ -39,7 +40,7 @@ const HeaderInRestoring = ({
|
||||
const [isRestorePlanUpgradeModalOpen, setIsRestorePlanUpgradeModalOpen] = useState(false)
|
||||
const { plan, enableBilling } = useProviderContext()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const userProfile = useAppContextSelector(s => s.userProfile)
|
||||
const userProfile = useAtomValue(userProfileAtom)
|
||||
const configsMap = useHooksStore(s => s.configsMap)
|
||||
const invalidAllLastRun = useInvalidAllLastRun(configsMap?.flowType, configsMap?.flowId)
|
||||
const {
|
||||
|
||||
@ -9,10 +9,11 @@ import {
|
||||
PopoverTrigger,
|
||||
} from '@langgenius/dify-ui/popover'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useReactFlow } from 'reactflow'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { userProfileIdAtom } from '@/context/app-context-state'
|
||||
import { getAvatar } from '@/service/common'
|
||||
import { useCollaboration } from '../collaboration/hooks/use-collaboration'
|
||||
import { getUserColor } from '../collaboration/utils/user-color'
|
||||
@ -54,12 +55,11 @@ const OnlineUsers = () => {
|
||||
const { t } = useTranslation()
|
||||
const appId = useStore(s => s.appId)
|
||||
const { onlineUsers, cursors, isEnabled: isCollaborationEnabled } = useCollaboration(appId as string)
|
||||
const { userProfile } = useAppContext()
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const reactFlow = useReactFlow()
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false)
|
||||
const avatarUrls = useAvatarUrls(onlineUsers || [])
|
||||
|
||||
const currentUserId = userProfile?.id
|
||||
const fallbackUsername = t('comments.fallback.user', { ns: 'workflow' })
|
||||
const currentUserSuffix = t('members.you', { ns: 'common' })
|
||||
|
||||
|
||||
@ -28,6 +28,14 @@ const commentsUpdateState = vi.hoisted(() => ({
|
||||
const globalFeatureState = vi.hoisted(() => ({
|
||||
enableCollaboration: true,
|
||||
}))
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: {
|
||||
id: 'user-1',
|
||||
name: 'Alice',
|
||||
email: 'alice@example.com',
|
||||
avatar_url: 'alice.png',
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('reactflow', () => ({
|
||||
useReactFlow: () => ({
|
||||
@ -43,15 +51,20 @@ vi.mock('@/next/navigation', () => ({
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
userProfile: {
|
||||
id: 'user-1',
|
||||
name: 'Alice',
|
||||
email: 'alice@example.com',
|
||||
avatar_url: 'alice.png',
|
||||
},
|
||||
userProfile: mockAppContextState.userProfile,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleClient: {
|
||||
systemFeatures: {
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import type { UserProfile, WorkflowCommentDetail, WorkflowCommentList } from '@/app/components/workflow/comment/types'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
import { useReactFlow } from 'reactflow'
|
||||
import { collaborationManager } from '@/app/components/workflow/collaboration/core/collaboration-manager'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { userProfileAtom } from '@/context/app-context-state'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { useParams } from '@/next/navigation'
|
||||
import { consoleClient } from '@/service/client'
|
||||
@ -67,7 +68,7 @@ export const useWorkflowComment = () => {
|
||||
() => new Map(mentionableUsers.map(user => [user.id, user])),
|
||||
[mentionableUsers],
|
||||
)
|
||||
const { userProfile } = useAppContext()
|
||||
const userProfile = useAtomValue(userProfileAtom)
|
||||
const { data: isCollaborationEnabled } = useSuspenseQuery({
|
||||
...systemFeaturesQueryOptions(),
|
||||
select: s => s.enable_collaboration_mode,
|
||||
|
||||
@ -11,6 +11,25 @@ const mockHandleNodeIterationChildSizeChange = vi.fn()
|
||||
const mockHandleNodeLoopChildSizeChange = vi.fn()
|
||||
const mockUseNodeResizeObserver = vi.fn()
|
||||
const mockUseCollaboration = vi.fn()
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: {
|
||||
id: 'user-1',
|
||||
name: 'User',
|
||||
email: 'user@example.com',
|
||||
avatar: '',
|
||||
avatar_url: '',
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/app/components/workflow/hooks', () => ({
|
||||
useNodesReadOnly: () => ({ nodesReadOnly: false }),
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
RiPlayLargeLine,
|
||||
} from '@remixicon/react'
|
||||
import { debounce } from 'es-toolkit/compat'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import {
|
||||
cloneElement,
|
||||
@ -69,7 +70,7 @@ import {
|
||||
hasRetryNode,
|
||||
isSupportCustomRunForm,
|
||||
} from '@/app/components/workflow/utils'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { userProfileAtom } from '@/context/app-context-state'
|
||||
import { useAllBuiltInTools } from '@/service/use-tools'
|
||||
import { useAllTriggerPlugins } from '@/service/use-triggers'
|
||||
import { FlowType } from '@/types/common'
|
||||
@ -114,7 +115,7 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
const { t } = useTranslation()
|
||||
const language = useLanguage()
|
||||
const appId = useStore(s => s.appId)
|
||||
const { userProfile } = useAppContext()
|
||||
const userProfile = useAtomValue(userProfileAtom)
|
||||
const { isConnected, nodePanelPresence } = useCollaboration(appId as string)
|
||||
const { showMessageLogModal } = useAppStore(useShallow(state => ({
|
||||
showMessageLogModal: state.showMessageLogModal,
|
||||
|
||||
@ -4,6 +4,7 @@ import type {
|
||||
} from 'react'
|
||||
import type { NodeProps } from '@/app/components/workflow/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import {
|
||||
cloneElement,
|
||||
memo,
|
||||
@ -28,7 +29,7 @@ import {
|
||||
NodeRunningStatus,
|
||||
} from '@/app/components/workflow/types'
|
||||
import { hasErrorHandleNode, hasRetryNode } from '@/app/components/workflow/utils'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { userProfileAtom } from '@/context/app-context-state'
|
||||
import { selectWorkflowNode } from '../../utils/node-navigation'
|
||||
import AddVariablePopupWithPosition from './components/add-variable-popup-with-position'
|
||||
import EntryNodeContainer, { StartNodeTypeEnum } from './components/entry-node-container'
|
||||
@ -76,7 +77,7 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
const { handleNodeIterationChildSizeChange } = useNodeIterationInteractions()
|
||||
const { handleNodeLoopChildSizeChange } = useNodeLoopInteractions()
|
||||
const toolIcon = useToolIcon(data)
|
||||
const { userProfile } = useAppContext()
|
||||
const userProfile = useAtomValue(userProfileAtom)
|
||||
const appId = useStore(s => s.appId)
|
||||
const { nodePanelPresence } = useCollaboration(appId as string)
|
||||
const controlMode = useStore(s => s.controlMode)
|
||||
|
||||
@ -3,7 +3,11 @@ import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import EmailConfigureModal from '../email-configure-modal'
|
||||
|
||||
const mockToastError = vi.hoisted(() => vi.fn())
|
||||
const mockUseAppContextSelector = vi.hoisted(() => vi.fn())
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: {
|
||||
email: 'owner@example.com',
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
toast: {
|
||||
@ -13,9 +17,19 @@ vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useSelector: (selector: (state: { userProfile: { email: string } }) => string) =>
|
||||
mockUseAppContextSelector(selector),
|
||||
selector({ userProfile: mockAppContextState.userProfile }),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('../mail-body-input', () => ({
|
||||
default: ({ value, onChange }: { value: string, onChange: (value: string) => void }) => (
|
||||
<textarea
|
||||
@ -80,11 +94,6 @@ const createEmailConfig = (overrides: Partial<EmailConfig> = {}): EmailConfig =>
|
||||
describe('human-input/delivery-method/email-configure-modal', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockUseAppContextSelector.mockImplementation(selector => selector({
|
||||
userProfile: {
|
||||
email: 'owner@example.com',
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
it('should save a valid email configuration with updated values', () => {
|
||||
|
||||
@ -18,15 +18,29 @@ type TestEmailSenderProps = {
|
||||
jumpToEmailConfigModal: () => void
|
||||
}
|
||||
|
||||
const mockUseAppContextSelector = vi.hoisted(() => vi.fn())
|
||||
const mockEmailConfigureModal = vi.hoisted(() => vi.fn())
|
||||
const mockTestEmailSender = vi.hoisted(() => vi.fn())
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: {
|
||||
email: 'owner@example.com',
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useSelector: (selector: (state: { userProfile: { email: string } }) => string) =>
|
||||
mockUseAppContextSelector(selector),
|
||||
selector({ userProfile: mockAppContextState.userProfile }),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('../email-configure-modal', () => ({
|
||||
default: (props: EmailConfigureModalProps) => {
|
||||
mockEmailConfigureModal(props)
|
||||
@ -107,11 +121,6 @@ const getMethodRow = (label: string) => {
|
||||
describe('human-input/delivery-method/method-item', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockUseAppContextSelector.mockImplementation(selector => selector({
|
||||
userProfile: {
|
||||
email: 'owner@example.com',
|
||||
},
|
||||
}))
|
||||
})
|
||||
|
||||
it('should toggle and delete a webapp delivery method', () => {
|
||||
|
||||
@ -21,6 +21,28 @@ vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: {
|
||||
id: 'user-1',
|
||||
email: 'owner@example.com',
|
||||
name: 'Owner',
|
||||
},
|
||||
currentWorkspace: {
|
||||
id: 'workspace-1',
|
||||
name: 'Product Team',
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
type RecordedRequest = {
|
||||
url: string
|
||||
method: string
|
||||
@ -48,14 +70,11 @@ const renderWithProviders = (ui: ReactNode) => {
|
||||
value={{
|
||||
userProfile: {
|
||||
...userProfilePlaceholder,
|
||||
id: 'user-1',
|
||||
email: 'owner@example.com',
|
||||
name: 'Owner',
|
||||
...mockAppContextState.userProfile,
|
||||
},
|
||||
currentWorkspace: {
|
||||
...initialWorkspaceInfo,
|
||||
id: 'workspace-1',
|
||||
name: 'Product Team',
|
||||
...mockAppContextState.currentWorkspace,
|
||||
},
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: true,
|
||||
@ -67,14 +86,11 @@ const renderWithProviders = (ui: ReactNode) => {
|
||||
useSelector: selector => selector({
|
||||
userProfile: {
|
||||
...userProfilePlaceholder,
|
||||
id: 'user-1',
|
||||
email: 'owner@example.com',
|
||||
name: 'Owner',
|
||||
...mockAppContextState.userProfile,
|
||||
},
|
||||
currentWorkspace: {
|
||||
...initialWorkspaceInfo,
|
||||
id: 'workspace-1',
|
||||
name: 'Product Team',
|
||||
...mockAppContextState.currentWorkspace,
|
||||
},
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: true,
|
||||
|
||||
@ -8,10 +8,11 @@ import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgeni
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { RiBugLine } from '@remixicon/react'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { memo, useCallback, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { userProfileEmailAtom } from '@/context/app-context-state'
|
||||
import MailBodyInput from './mail-body-input'
|
||||
import Recipient from './recipient'
|
||||
|
||||
@ -35,7 +36,7 @@ const EmailConfigureModal = ({
|
||||
availableNodes = [],
|
||||
}: EmailConfigureModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
const email = useAppContextWithSelector(s => s.userProfile.email)
|
||||
const email = useAtomValue(userProfileEmailAtom)
|
||||
const [recipients, setRecipients] = useState(config?.recipients || { whole_workspace: false, items: [] })
|
||||
const [subject, setSubject] = useState(config?.subject || '')
|
||||
const [body, setBody] = useState(config?.body || '{{#url#}}')
|
||||
|
||||
@ -16,11 +16,12 @@ import {
|
||||
RiRobot2Fill,
|
||||
RiSendPlane2Line,
|
||||
} from '@remixicon/react'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
|
||||
import Badge from '@/app/components/base/badge/index'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { userProfileEmailAtom } from '@/context/app-context-state'
|
||||
import { DeliveryMethodType } from '../../types'
|
||||
import EmailConfigureModal from './email-configure-modal'
|
||||
import TestEmailSender from './test-email-sender'
|
||||
@ -51,7 +52,7 @@ const DeliveryMethodItem: FC<DeliveryMethodItemProps> = ({
|
||||
readonly,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const email = useAppContextWithSelector(s => s.userProfile.email)
|
||||
const email = useAtomValue(userProfileEmailAtom)
|
||||
const [isHovering, setIsHovering] = useState(false)
|
||||
const [showEmailModal, setShowEmailModal] = useState(false)
|
||||
const [showTestEmailModal, setShowTestEmailModal] = useState(false)
|
||||
|
||||
@ -4,6 +4,10 @@ import Recipient from '../index'
|
||||
const mockUseTranslation = vi.hoisted(() => vi.fn())
|
||||
const mockUseAppContext = vi.hoisted(() => vi.fn())
|
||||
const mockUseMembers = vi.hoisted(() => vi.fn())
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: { email: 'owner@example.com' },
|
||||
currentWorkspace: { name: 'Dify\'s Lab' },
|
||||
}))
|
||||
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => mockUseTranslation(),
|
||||
@ -13,6 +17,16 @@ vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => mockUseAppContext(),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/service/use-common', () => ({
|
||||
useMembers: () => mockUseMembers(),
|
||||
}))
|
||||
@ -69,10 +83,7 @@ describe('Recipient', () => {
|
||||
mockUseTranslation.mockReturnValue({
|
||||
t: (key: string, options?: { workspaceName?: string }) => options?.workspaceName ?? key,
|
||||
})
|
||||
mockUseAppContext.mockReturnValue({
|
||||
userProfile: { email: 'owner@example.com' },
|
||||
currentWorkspace: { name: 'Dify\'s Lab' },
|
||||
})
|
||||
mockUseAppContext.mockReturnValue(mockAppContextState)
|
||||
mockUseMembers.mockReturnValue({
|
||||
data: {
|
||||
accounts: [
|
||||
|
||||
@ -3,9 +3,10 @@ import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { RiGroupLine } from '@remixicon/react'
|
||||
import { produce } from 'immer'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { currentWorkspaceAtom, userProfileEmailAtom } from '@/context/app-context-state'
|
||||
import { useMembers } from '@/service/use-common'
|
||||
import EmailInput from './email-input'
|
||||
import MemberSelector from './member-selector'
|
||||
@ -22,7 +23,8 @@ const Recipient = ({
|
||||
onChange,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { userProfile, currentWorkspace } = useAppContext()
|
||||
const userProfileEmail = useAtomValue(userProfileEmailAtom)
|
||||
const currentWorkspace = useAtomValue(currentWorkspaceAtom)
|
||||
const { data: members } = useMembers()
|
||||
const accounts = members?.accounts || []
|
||||
|
||||
@ -70,14 +72,14 @@ const Recipient = ({
|
||||
<div className="w-[86px]">
|
||||
<MemberSelector
|
||||
value={data.items}
|
||||
email={userProfile.email}
|
||||
email={userProfileEmail}
|
||||
list={accounts}
|
||||
onSelect={handleMemberSelect}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<EmailInput
|
||||
email={userProfile.email}
|
||||
email={userProfileEmail}
|
||||
value={data.items}
|
||||
list={accounts}
|
||||
onDelete={handleDelete}
|
||||
|
||||
@ -10,6 +10,7 @@ import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgeni
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { RiArrowRightSFill } from '@remixicon/react'
|
||||
import { noop, unionBy } from 'es-toolkit/compat'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { memo, useCallback, useMemo, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
@ -24,7 +25,7 @@ import {
|
||||
isSystemVar,
|
||||
} from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { InputVarType, VarType } from '@/app/components/workflow/types'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { currentWorkspaceAtom, userProfileEmailAtom } from '@/context/app-context-state'
|
||||
import { useMembers } from '@/service/use-common'
|
||||
import { useTestEmailSender } from '@/service/use-workflow'
|
||||
import { getHumanInputFormDependencySelectors, isOutput } from '../../utils'
|
||||
@ -123,7 +124,8 @@ const EmailSenderModal = ({
|
||||
availableNodes = [],
|
||||
}: EmailSenderModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { userProfile, currentWorkspace } = useAppContext()
|
||||
const userProfileEmail = useAtomValue(userProfileEmailAtom)
|
||||
const currentWorkspace = useAtomValue(currentWorkspaceAtom)
|
||||
const appDetail = useAppStore(state => state.appDetail)
|
||||
const { mutateAsync: testEmailSender } = useTestEmailSender()
|
||||
|
||||
@ -236,7 +238,7 @@ const EmailSenderModal = ({
|
||||
i18nKey={`${i18nPrefix}.deliveryMethod.emailSender.debugDone`}
|
||||
ns="workflow"
|
||||
components={{ email: <span className="system-md-semibold text-text-secondary"></span> }}
|
||||
values={{ email: userProfile.email }}
|
||||
values={{ email: userProfileEmail }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@ -268,7 +270,7 @@ const EmailSenderModal = ({
|
||||
<div className="mt-4">
|
||||
<EmailInput
|
||||
disabled
|
||||
email={userProfile.email}
|
||||
email={userProfileEmail}
|
||||
value={config?.recipients?.items}
|
||||
list={accounts}
|
||||
onDelete={noop}
|
||||
@ -308,7 +310,7 @@ const EmailSenderModal = ({
|
||||
i18nKey={`${i18nPrefix}.deliveryMethod.emailSender.debugModeTip2`}
|
||||
ns="workflow"
|
||||
components={{ email: <span className="system-sm-semibold text-text-primary"></span> }}
|
||||
values={{ email: userProfile.email }}
|
||||
values={{ email: userProfileEmail }}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
@ -342,7 +344,7 @@ const EmailSenderModal = ({
|
||||
<div className="mt-4">
|
||||
<EmailInput
|
||||
disabled
|
||||
email={userProfile.email}
|
||||
email={userProfileEmail}
|
||||
value={config?.recipients?.items}
|
||||
list={accounts}
|
||||
onDelete={noop}
|
||||
|
||||
@ -141,6 +141,21 @@ vi.mock('@/context/app-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: { id: 'user-1' },
|
||||
workspacePermissionKeys: [] as string[],
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/utils/permission', () => ({
|
||||
DatasetACLPermission: {
|
||||
Readonly: 'dataset.acl.readonly',
|
||||
|
||||
@ -2,10 +2,11 @@
|
||||
import type { FC } from 'react'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import { produce } from 'immer'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector as useAppContextSelector } from '@/context/app-context'
|
||||
import { userProfileIdAtom, workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { getDatasetACLCapabilities } from '@/utils/permission'
|
||||
import Item from './dataset-item'
|
||||
|
||||
@ -29,8 +30,8 @@ const DatasetList: FC<Props> = ({
|
||||
settingsModalHeight,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const currentUserId = useAppContextSelector(s => s.userProfile?.id)
|
||||
const workspacePermissionKeys = useAppContextSelector(s => s.workspacePermissionKeys)
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
|
||||
const handleRemove = useCallback((index: number) => {
|
||||
return () => {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { NoteNodeType } from '../note-node/types'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback } from 'react'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { userProfileAtom } from '@/context/app-context-state'
|
||||
import {
|
||||
CUSTOM_NOTE_NODE,
|
||||
} from '../note-node/constants'
|
||||
@ -11,7 +12,7 @@ import { generateNewNode } from '../utils'
|
||||
|
||||
export const useOperator = () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { userProfile } = useAppContext()
|
||||
const userProfile = useAtomValue(userProfileAtom)
|
||||
const showAuthorStorage = useWorkflowNoteShowAuthorValue()
|
||||
|
||||
const handleAddNote = useCallback(() => {
|
||||
|
||||
@ -7,6 +7,9 @@ const mockHandleCommentResolve = vi.hoisted(() => vi.fn())
|
||||
const mockSetActiveCommentId = vi.hoisted(() => vi.fn())
|
||||
const mockSetControlMode = vi.hoisted(() => vi.fn())
|
||||
const mockSetShowResolvedComments = vi.hoisted(() => vi.fn())
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: { id: 'user-1' },
|
||||
}))
|
||||
|
||||
const commentFixtures: WorkflowCommentList[] = [
|
||||
{
|
||||
@ -70,10 +73,20 @@ vi.mock('@/hooks/use-format-time-from-now', () => ({
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
userProfile: { id: 'user-1' },
|
||||
userProfile: mockAppContextState.userProfile,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/app/components/workflow/store', () => ({
|
||||
useStore: (selector: (state: WorkflowStoreSelectionState) => unknown) => selector({
|
||||
activeCommentId: storeState.activeCommentId,
|
||||
|
||||
@ -2,6 +2,7 @@ import type { WorkflowCommentList } from '@/app/components/workflow/comment/type
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { RiCheckboxCircleFill, RiCheckboxCircleLine, RiCheckLine, RiCloseLine, RiFilter3Line } from '@remixicon/react'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { memo, useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
@ -9,7 +10,7 @@ import { UserAvatarList } from '@/app/components/base/user-avatar-list'
|
||||
import { useWorkflowComment } from '@/app/components/workflow/hooks/use-workflow-comment'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { ControlMode } from '@/app/components/workflow/types'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { userProfileIdAtom } from '@/context/app-context-state'
|
||||
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
|
||||
|
||||
const CommentsPanel = () => {
|
||||
@ -29,16 +30,16 @@ const CommentsPanel = () => {
|
||||
handleCommentIconClick(comment)
|
||||
}, [handleCommentIconClick])
|
||||
|
||||
const { userProfile } = useAppContext()
|
||||
const currentUserId = useAtomValue(userProfileIdAtom)
|
||||
|
||||
const filteredSorted = useMemo(() => {
|
||||
let data = comments
|
||||
if (!showResolvedComments)
|
||||
data = data.filter(c => !c.resolved)
|
||||
if (showOnlyMine)
|
||||
data = data.filter(c => c.created_by === userProfile?.id)
|
||||
data = data.filter(c => c.created_by === currentUserId)
|
||||
return data
|
||||
}, [comments, showOnlyMine, showResolvedComments, userProfile?.id])
|
||||
}, [comments, currentUserId, showOnlyMine, showResolvedComments])
|
||||
|
||||
const handleResolve = useCallback(async (comment: WorkflowCommentList) => {
|
||||
if (comment.resolved)
|
||||
|
||||
@ -18,6 +18,12 @@ const mockEmitRestoreComplete = vi.fn()
|
||||
const mockEmitWorkflowUpdate = vi.fn()
|
||||
let mockPlanType = Plan.professional
|
||||
let mockEnableBilling = true
|
||||
const mockAppContextState = vi.hoisted(() => ({
|
||||
userProfile: {
|
||||
id: 'test-user-id',
|
||||
name: 'Test User',
|
||||
},
|
||||
}))
|
||||
|
||||
const createVersionHistory = (overrides: Partial<VersionHistory> = {}): VersionHistory => ({
|
||||
id: 'version-id',
|
||||
@ -60,9 +66,19 @@ type MockVersionHistoryItemProps = {
|
||||
}
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useSelector: () => ({ id: 'test-user-id' }),
|
||||
useSelector: () => mockAppContextState.userProfile,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/app-context-state', async (importOriginal) => {
|
||||
const { createAppContextStateAtomMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateAtomMock(importOriginal, () => mockAppContextState)
|
||||
})
|
||||
|
||||
vi.mock('jotai', async (importOriginal) => {
|
||||
const { createAppContextStateJotaiMock } = await import('@/__tests__/utils/mock-app-context-state')
|
||||
return createAppContextStateJotaiMock(importOriginal)
|
||||
})
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
plan: { type: mockPlanType },
|
||||
|
||||
@ -3,6 +3,7 @@ import type { VersionHistory } from '@/types/workflow'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { RiArrowDownDoubleLine, RiCloseLine, RiLoader2Line } from '@remixicon/react'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -10,7 +11,7 @@ import VersionInfoModal from '@/app/components/app/app-publisher/version-info-mo
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { PlanUpgradeModal } from '@/app/components/billing/plan-upgrade-modal'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import { useSelector as useAppContextSelector } from '@/context/app-context'
|
||||
import { userProfileAtom } from '@/context/app-context-state'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useDeleteWorkflow, useInvalidAllLastRun, useResetWorkflowVersionHistory, useRestoreWorkflow, useUpdateWorkflow, useWorkflowVersionHistory } from '@/service/use-workflow'
|
||||
import { useDSL, useWorkflowRefreshDraft, useWorkflowRun } from '../../hooks'
|
||||
@ -58,7 +59,7 @@ export const VersionHistoryPanel = ({
|
||||
const setShowWorkflowVersionHistoryPanel = useStore(s => s.setShowWorkflowVersionHistoryPanel)
|
||||
const currentVersion = useStore(s => s.currentVersion)
|
||||
const setCurrentVersion = useStore(s => s.setCurrentVersion)
|
||||
const userProfile = useAppContextSelector(s => s.userProfile)
|
||||
const userProfile = useAtomValue(userProfileAtom)
|
||||
const configsMap = useHooksStore(s => s.configsMap)
|
||||
const canImportExportDSL = useHooksStore(s => s.accessControl.canImportExportDSL)
|
||||
const invalidAllLastRun = useInvalidAllLastRun(configsMap?.flowType, configsMap?.flowId)
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
ContextMenuSeparator,
|
||||
} from '@langgenius/dify-ui/context-menu'
|
||||
import { produce } from 'immer'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import {
|
||||
useCallback,
|
||||
} from 'react'
|
||||
@ -15,7 +16,7 @@ import { useStore as useReactFlowStore } from 'reactflow'
|
||||
import { useCreateSnippetFromSelection } from '@/app/components/snippets/hooks/use-create-snippet-from-selection'
|
||||
import { canCreateAndModifySnippets } from '@/app/components/snippets/utils/permission'
|
||||
import { useCollaborativeWorkflow } from '@/app/components/workflow/hooks/use-collaborative-workflow'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/app-context-state'
|
||||
import { useNodesInteractions, useNodesReadOnly, useNodesSyncDraft } from './hooks'
|
||||
import { useWorkflowHistory, WorkflowHistoryEvent } from './hooks/use-workflow-history'
|
||||
import { ShortcutKbd } from './shortcuts/shortcut-kbd'
|
||||
@ -235,7 +236,7 @@ export function SelectionContextmenu({
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { getNodesReadOnly } = useNodesReadOnly()
|
||||
const workspacePermissionKeys = useAppContextWithSelector(state => state.workspacePermissionKeys)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const { handleNodesCopy, handleNodesDelete, handleNodesDuplicate } = useNodesInteractions()
|
||||
const isSelectionContextMenu = useStore(s => s.contextMenuTarget?.type === 'selection')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user