refactor(web): read workflow profile from query cache (#40469)

This commit is contained in:
yyh 2026-08-11 17:03:56 +08:00 committed by GitHub
parent a64a24b39c
commit df07a0a1de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 288 additions and 256 deletions

View File

@ -1,5 +1,6 @@
import { screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { renderWithAccountProfile } from '@/test/console/account-profile'
import { render } from '@/test/console/render'
import Tips from '../tips'
@ -9,11 +10,6 @@ const mockConsoleState = vi.hoisted(() => ({
},
}))
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => mockConsoleState)
})
describe('Tips', () => {
beforeEach(() => {
vi.clearAllMocks()
@ -29,7 +25,10 @@ describe('Tips', () => {
})
it('should render email tip in debug mode', () => {
render(<Tips showEmailTip={true} isEmailDebugMode={true} showDebugModeTip={false} />)
renderWithAccountProfile(
<Tips showEmailTip={true} isEmailDebugMode={true} showDebugModeTip={false} />,
{ accountProfile: mockConsoleState.userProfile },
)
expect(screen.getByText('workflow.common.humanInputEmailTipInDebugMode')).toBeInTheDocument()
expect(screen.queryByText('workflow.common.humanInputEmailTip')).not.toBeInTheDocument()

View File

@ -1,8 +1,8 @@
import { useAtomValue } from 'jotai'
import { useSuspenseQuery } from '@tanstack/react-query'
import { memo } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import Divider from '@/app/components/base/divider'
import { userProfileEmailAtom } from '@/context/account-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
type TipsProps = {
showEmailTip: boolean
@ -10,9 +10,26 @@ type TipsProps = {
showDebugModeTip: boolean
}
const EmailDebugTip = () => {
const { data: email } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile.email,
})
return (
<div className="system-xs-regular text-text-secondary">
<Trans
i18nKey={($) => $['common.humanInputEmailTipInDebugMode']}
ns="workflow"
components={{ email: <span className="system-xs-semibold"></span> }}
values={{ email }}
/>
</div>
)
}
const Tips = ({ showEmailTip, isEmailDebugMode, showDebugModeTip }: TipsProps) => {
const { t } = useTranslation()
const email = useAtomValue(userProfileEmailAtom)
return (
<>
@ -23,16 +40,7 @@ const Tips = ({ showEmailTip, isEmailDebugMode, showDebugModeTip }: TipsProps) =
{t(($) => $['common.humanInputEmailTip'], { ns: 'workflow' })}
</div>
)}
{showEmailTip && isEmailDebugMode && (
<div className="system-xs-regular text-text-secondary">
<Trans
i18nKey={($) => $['common.humanInputEmailTipInDebugMode']}
ns="workflow"
components={{ email: <span className="system-xs-semibold"></span> }}
values={{ email }}
/>
</div>
)}
{showEmailTip && isEmailDebugMode && <EmailDebugTip />}
{showDebugModeTip && (
<div className="system-xs-medium text-text-warning">
{t(($) => $['common.humanInputWebappTip'], { ns: 'workflow' })}

View File

@ -1,6 +1,7 @@
import type { ReactNode } from 'react'
import type { ReactElement, ReactNode } from 'react'
import { screen, waitFor } from '@testing-library/react'
import { render } from '@/test/console/render'
import { createAccountProfileQueryWrapper } from '@/test/console/account-profile'
import { render as renderWithConsoleState } from '@/test/console/render'
import { AppACLPermission } from '@/utils/permission'
import WorkflowApp from '../index'
@ -59,6 +60,11 @@ let appTriggersState: {
let searchParamsValue: string | null = null
const render = (ui: ReactElement) =>
renderWithConsoleState(ui, {
wrapper: createAccountProfileQueryWrapper(consoleState.userProfile),
})
const mockWorkflowStore = {
setState: mockWorkflowStoreSetState,
getState: () => ({
@ -85,15 +91,6 @@ vi.mock('@/app/components/workflow/store/trigger-status', () => ({
}),
}))
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => ({
isLoadingCurrentWorkspace: consoleState.isLoadingCurrentWorkspace,
currentWorkspace: consoleState.currentWorkspace,
userProfile: consoleState.userProfile,
workspacePermissionKeys: consoleState.workspacePermissionKeys,
}))
})
vi.mock('@/context/workspace-state', async () => {
const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture')
return createWorkspaceStateModuleMock(() => ({

View File

@ -1,15 +1,15 @@
import type { ReactElement } from 'react'
import { screen } from '@testing-library/react'
import { StrictMode } from 'react'
import { useStore as useAppStore } from '@/app/components/app/store'
import { render } from '@/test/console/render'
import { createAccountProfileQueryWrapper } from '@/test/console/account-profile'
import { render as renderWithConsoleState } from '@/test/console/render'
import WorkflowApp from '../index'
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => ({
userProfile: { id: 'user-1' },
}))
})
const render = (ui: ReactElement) =>
renderWithConsoleState(ui, {
wrapper: createAccountProfileQueryWrapper(),
})
vi.mock('@/context/workspace-state', async () => {
const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture')

View File

@ -4,6 +4,7 @@ import type { CollaborationUpdate } from '@/app/components/workflow/collaboratio
import type { Shape as HooksStoreShape } from '@/app/components/workflow/hooks-store/store'
import type { Edge, Node } from '@/app/components/workflow/types'
import type { FetchWorkflowDraftResponse } from '@/types/workflow'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useAtomValue } from 'jotai'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -18,8 +19,8 @@ import { useSetWorkflowVarsWithValue } from '@/app/components/workflow/hooks/use
import { useWorkflowUpdate } from '@/app/components/workflow/hooks/use-workflow-update'
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import { userProfileIdAtom } from '@/context/account-state'
import { workspacePermissionKeysAtom } from '@/context/permission-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { fetchWorkflowDraft } from '@/service/workflow'
import { getAppACLCapabilities } from '@/utils/permission'
import { useAvailableNodesMetaData } from '../hooks/use-available-nodes-meta-data'
@ -89,7 +90,10 @@ const WorkflowMain = ({ nodes, edges, viewport }: WorkflowMainProps) => {
const filteredCursors = Object.fromEntries(
Object.entries(cursors).filter(([userId]) => userId !== myUserId),
)
const currentUserId = useAtomValue(userProfileIdAtom)
const { data: currentUserId } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile.id,
})
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
const appACLCapabilities = useMemo(
() =>

View File

@ -1,7 +1,8 @@
import { waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { BlockEnum } from '@/app/components/workflow/types'
import { renderHook } from '@/test/console/render'
import { createAccountProfileQueryWrapper } from '@/test/console/account-profile'
import { renderHook as renderHookWithConsoleState } from '@/test/console/render'
import { AppACLPermission } from '@/utils/permission'
import { useWorkflowInit } from '../use-workflow-init'
@ -17,6 +18,11 @@ const mockFetchNodesDefaultConfigs = vi.fn()
const mockFetchPublishedWorkflow = vi.fn()
const mockSyncWorkflowDraft = vi.fn()
const renderHook = <Result>(callback: () => Result) =>
renderHookWithConsoleState(callback, {
wrapper: createAccountProfileQueryWrapper({ id: 'user-1' }),
})
let appStoreState: {
appDetail: {
id: string
@ -46,13 +52,6 @@ vi.mock('@/app/components/app/store', () => ({
useStore: <T>(selector: (state: typeof appStoreState) => T): T => selector(appStoreState),
}))
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => ({
userProfile: { id: 'user-1' },
workspacePermissionKeys: ['app.create_and_management'],
}))
})
vi.mock('@/context/permission-state', async () => {
const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture')
return createPermissionStateModuleMock(() => ({

View File

@ -1,13 +1,14 @@
import type { Edge, Node } from '@/app/components/workflow/types'
import type { FileUploadConfigResponse } from '@/models/common'
import type { FetchWorkflowDraftResponse } from '@/types/workflow'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useAtomValue } from 'jotai'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useStore as useAppStore } from '@/app/components/app/store'
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
import { BlockEnum } from '@/app/components/workflow/types'
import { userProfileIdAtom } from '@/context/account-state'
import { workspacePermissionKeysAtom } from '@/context/permission-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { useWorkflowConfig } from '@/service/use-workflow'
import {
fetchNodesDefaultConfigs,
@ -61,7 +62,10 @@ export const useWorkflowInit = () => {
const workflowStore = useWorkflowStore()
const { nodes: nodesTemplate, edges: edgesTemplate } = useWorkflowTemplate()
const appDetail = useAppStore((state) => state.appDetail)!
const currentUserId = useAtomValue(userProfileIdAtom)
const { data: currentUserId } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile.id,
})
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
const appACLCapabilities = useMemo(
() =>

View File

@ -2,6 +2,7 @@
import type { Features as FeaturesData } from '@/app/components/base/features/types'
import type { InjectWorkflowStoreSliceFn } from '@/app/components/workflow/store'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useAtomValue } from 'jotai'
import { useEffect, useMemo } from 'react'
import { useStore as useAppStore } from '@/app/components/app/store'
@ -12,9 +13,9 @@ import { WorkflowContextProvider } from '@/app/components/workflow/context'
import { useWorkflowStore } from '@/app/components/workflow/store'
import { useTriggerStatusStore } from '@/app/components/workflow/store/trigger-status'
import { initialEdges, initialNodes } from '@/app/components/workflow/utils'
import { userProfileIdAtom } from '@/context/account-state'
import { workspacePermissionKeysAtom } from '@/context/permission-state'
import { currentWorkspaceAtom, currentWorkspaceLoadingAtom } from '@/context/workspace-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { useSearchParams } from '@/next/navigation'
import { fetchRunDetail } from '@/service/log'
import { useAppTriggers } from '@/service/use-tools'
@ -31,7 +32,10 @@ const WorkflowAppWithAdditionalContext = () => {
const workflowStore = useWorkflowStore()
const isLoadingCurrentWorkspace = useAtomValue(currentWorkspaceLoadingAtom)
const currentWorkspace = useAtomValue(currentWorkspaceAtom)
const currentUserId = useAtomValue(userProfileIdAtom)
const { data: currentUserId } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile.id,
})
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
// Initialize trigger status at application level

View File

@ -1,7 +1,9 @@
import type { ReactElement } from 'react'
import type { WorkflowCommentList } from '@/app/components/workflow/comment/types'
import { fireEvent, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { render } from '@/test/console/render'
import { createAccountProfileQueryWrapper } from '@/test/console/account-profile'
import { render as renderWithConsoleState } from '@/test/console/render'
import { CommentIcon } from './comment-icon'
type Position = { x: number; y: number }
@ -18,6 +20,14 @@ const mockConsoleState = vi.hoisted(() => ({
const mockFlowToScreenPosition = vi.fn((position: Position) => position)
const mockScreenToFlowPosition = vi.fn((position: Position) => position)
const render = (ui: ReactElement) =>
renderWithConsoleState(ui, {
wrapper: createAccountProfileQueryWrapper({
...mockConsoleState.userProfile,
id: mockUserId,
}),
})
vi.mock('reactflow', () => ({
useReactFlow: () => ({
flowToScreenPosition: mockFlowToScreenPosition,
@ -30,17 +40,6 @@ vi.mock('reactflow', () => ({
}),
}))
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => ({
...mockConsoleState,
userProfile: {
...mockConsoleState.userProfile,
id: mockUserId,
},
}))
})
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>

View File

@ -2,11 +2,11 @@
import type { FC, PointerEvent as ReactPointerEvent } from 'react'
import type { WorkflowCommentList } from '@/app/components/workflow/comment/types'
import { useAtomValue } from 'jotai'
import { useSuspenseQuery } from '@tanstack/react-query'
import { memo, useCallback, useMemo, useRef, useState } from 'react'
import { useReactFlow, useViewport } from 'reactflow'
import { UserAvatarList } from '@/app/components/base/user-avatar-list'
import { userProfileIdAtom } from '@/context/account-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import CommentPreview from './comment-preview'
type CommentIconProps = {
@ -20,7 +20,10 @@ export const CommentIcon: FC<CommentIconProps> = memo(
({ comment, onClick, isActive = false, onPositionUpdate }) => {
const { flowToScreenPosition, screenToFlowPosition } = useReactFlow()
const viewport = useViewport()
const currentUserId = useAtomValue(userProfileIdAtom)
const { data: currentUserId } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile.id,
})
const isAuthor = comment.created_by_account?.id === currentUserId
const [showPreview, setShowPreview] = useState(false)
const [dragPosition, setDragPosition] = useState<{ x: number; y: number } | null>(null)

View File

@ -1,7 +1,8 @@
import type { FC } from 'react'
import type { FC, ReactElement } from 'react'
import { fireEvent, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { render } from '@/test/console/render'
import { createAccountProfileQueryWrapper } from '@/test/console/account-profile'
import { render as renderWithConsoleState } from '@/test/console/render'
import { CommentInput } from './comment-input'
type MentionInputProps = {
@ -26,6 +27,11 @@ const mockConsoleState = vi.hoisted(() => ({
},
}))
const render = (ui: ReactElement) =>
renderWithConsoleState(ui, {
wrapper: createAccountProfileQueryWrapper(mockConsoleState.userProfile),
})
vi.mock('react-i18next', async () => {
const { withSelectorKey } = await import('@/test/i18n-mock')
return {
@ -35,11 +41,6 @@ vi.mock('react-i18next', async () => {
}
})
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => mockConsoleState)
})
vi.mock('./mention-input', () => ({
MentionInput: ((props: MentionInputProps) => {
mentionInputProps = props

View File

@ -1,10 +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 { useSuspenseQuery } from '@tanstack/react-query'
import { memo, useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { userProfileAtom } from '@/context/account-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { MentionInput } from './mention-input'
type CommentInputProps = {
@ -25,7 +25,10 @@ export const CommentInput: FC<CommentInputProps> = memo(
({ position, onSubmit, onCancel, autoFocus = true, disabled = false, onPositionChange }) => {
const [content, setContent] = useState('')
const { t } = useTranslation()
const userProfile = useAtomValue(userProfileAtom)
const { data: userProfile } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile,
})
const dragStateRef = useRef<
{
pointerId: number | null

View File

@ -1,7 +1,9 @@
import type { ReactElement } from 'react'
import type { WorkflowCommentDetail } from '@/app/components/workflow/comment/types'
import { fireEvent, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { render } from '@/test/console/render'
import { createAccountProfileQueryWrapper } from '@/test/console/account-profile'
import { render as renderWithConsoleState } from '@/test/console/render'
import { CommentThread } from './thread'
const mockSetCommentPreviewHovering = vi.hoisted(() => vi.fn())
@ -16,6 +18,11 @@ const mockConsoleState = vi.hoisted(() => ({
},
}))
const render = (ui: ReactElement) =>
renderWithConsoleState(ui, {
wrapper: createAccountProfileQueryWrapper(mockConsoleState.userProfile),
})
const storeState = vi.hoisted(() => ({
mentionableUsersCache: {
'app-1': [
@ -35,11 +42,6 @@ vi.mock('@/hooks/use-format-time-from-now', () => ({
}),
}))
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => mockConsoleState)
})
vi.mock('reactflow', () => ({
useReactFlow: () => ({
flowToScreenPosition: mockFlowToScreenPosition,

View File

@ -22,14 +22,14 @@ import {
RiDeleteBinLine,
RiMoreFill,
} from '@remixicon/react'
import { useAtomValue } from 'jotai'
import { useSuspenseQuery } from '@tanstack/react-query'
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 { userProfileAtom, userProfileIdAtom } from '@/context/account-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
import { useParams } from '@/next/navigation'
import { useStore } from '../store'
@ -68,7 +68,10 @@ const ThreadMessage: FC<{
className?: string
}> = ({ authorId, authorName, avatarUrl, createdAt, content, mentionableNames, className }) => {
const { formatTimeFromNow } = useFormatTimeFromNow()
const currentUserId = useAtomValue(userProfileIdAtom)
const { data: currentUserId } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile.id,
})
const isCurrentUser = authorId === currentUserId
const userColor = isCurrentUser ? undefined : getUserColor(authorId)
@ -179,7 +182,10 @@ export const CommentThread: FC<CommentThreadProps> = memo(
const appId = params.appId as string
const { flowToScreenPosition } = useReactFlow()
const viewport = useViewport()
const userProfile = useAtomValue(userProfileAtom)
const { data: userProfile } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile,
})
const currentUserId = userProfile.id
const { t } = useTranslation()
const [replyContent, setReplyContent] = useState('')

View File

@ -13,17 +13,6 @@ const mockHandleLoadBackupDraft = vi.fn()
const mockHandleRefreshWorkflowDraft = vi.fn()
let mockPlanType = Plan.professional
let mockEnableBilling = true
const mockConsoleState = vi.hoisted(() => ({
userProfile: {
id: '',
name: '',
},
}))
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => mockConsoleState)
})
vi.mock('@/context/provider-context', () => ({
useProviderContext: () => ({

View File

@ -1,5 +1,6 @@
import type { Shape } from '../../store/workflow'
import { fireEvent, screen, waitFor } from '@testing-library/react'
import { createAccountProfileQueryClient } from '@/test/console/account-profile'
import { FlowType } from '@/types/common'
import { renderWorkflowComponent } from '../../__tests__/workflow-test-env'
import { WorkflowVersion } from '../../types'
@ -25,18 +26,6 @@ const mockViewHistory = vi.fn()
let mockNodesReadOnly = false
let mockTheme: 'light' | 'dark' = 'light'
const mockConsoleState = vi.hoisted(() => ({
userProfile: {
id: '',
name: '',
},
}))
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => mockConsoleState)
})
vi.mock('reactflow', () => ({
useNodes: () => mockUseNodes(),
}))
@ -298,6 +287,7 @@ describe('Header layout components', () => {
const onRestoreSettled = vi.fn()
const deleteAllInspectVars = vi.fn()
const currentVersion = createCurrentVersion()
const currentUser = { id: 'user-1', name: 'Alice' }
const { store } = renderWorkflowComponent(
<HeaderInRestoring onRestoreSettled={onRestoreSettled} />,
@ -316,6 +306,7 @@ describe('Header layout components', () => {
fileSettings: {},
},
},
queryClient: createAccountProfileQueryClient(currentUser),
},
)
@ -337,8 +328,8 @@ describe('Header layout components', () => {
expect(mockEmitRestoreIntent).toHaveBeenCalledWith({
versionId: currentVersion.id,
versionName: currentVersion.marked_name,
initiatorUserId: '',
initiatorName: '',
initiatorUserId: currentUser.id,
initiatorName: currentUser.name,
})
expect(mockEmitRestoreComplete).toHaveBeenCalledWith({
versionId: currentVersion.id,

View File

@ -2,13 +2,13 @@ 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 { useSuspenseQuery } from '@tanstack/react-query'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { PlanUpgradeModal } from '@/app/components/billing/plan-upgrade-modal'
import { Plan } from '@/app/components/billing/type'
import { userProfileAtom } from '@/context/account-state'
import { useProviderContext } from '@/context/provider-context'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import useTheme from '@/hooks/use-theme'
import {
useInvalidAllLastRun,
@ -32,7 +32,10 @@ const HeaderInRestoring = ({ onRestoreSettled }: HeaderInRestoringProps) => {
const [isRestorePlanUpgradeModalOpen, setIsRestorePlanUpgradeModalOpen] = useState(false)
const { plan, enableBilling } = useProviderContext()
const workflowStore = useWorkflowStore()
const userProfile = useAtomValue(userProfileAtom)
const { data: userProfile } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile,
})
const configsMap = useHooksStore((s) => s.configsMap)
const invalidAllLastRun = useInvalidAllLastRun(configsMap?.flowType, configsMap?.flowId)
const { deleteAllInspectVars } = workflowStore.getState()

View File

@ -1,15 +1,16 @@
'use client'
import type { OnlineUser } from '../collaboration/types/collaboration'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import { AvatarFallback, AvatarImage, AvatarRoot } from '@langgenius/dify-ui/avatar'
import { cn } from '@langgenius/dify-ui/cn'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useAtomValue } from 'jotai'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useReactFlow } from 'reactflow'
import { userProfileIdAtom } from '@/context/account-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { getAvatar } from '@/service/common'
import { useCollaboration } from '../collaboration/hooks/use-collaboration'
import { getUserColor } from '../collaboration/utils/user-color'
@ -53,7 +54,10 @@ const OnlineUsers = () => {
cursors,
isEnabled: isCollaborationEnabled,
} = useCollaboration(appId as string)
const currentUserId = useAtomValue(userProfileIdAtom)
const { data: currentUserId } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile.id,
})
const reactFlow = useReactFlow()
const [dropdownOpen, setDropdownOpen] = useState(false)
const avatarUrls = useAvatarUrls(onlineUsers || [])

View File

@ -3,6 +3,7 @@ import type {
WorkflowCommentList,
} from '@/app/components/workflow/comment/types'
import { act, waitFor } from '@testing-library/react'
import { seedAccountProfileQuery } from '@/test/console/account-profile'
import { createConsoleQueryClient, seedSystemFeatures } from '@/test/console/query-data'
import { renderWorkflowHook } from '../../__tests__/workflow-test-env'
import { ControlMode } from '../../types'
@ -54,36 +55,36 @@ vi.mock('@/next/navigation', () => ({
useParams: () => ({ appId: 'app-1' }),
}))
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => mockConsoleState)
})
vi.mock('@/service/client', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/service/client')>()
vi.mock('@/service/client', () => ({
consoleClient: {
systemFeatures: {
get: () => ({
enable_collaboration_mode: globalFeatureState.enableCollaboration,
}),
},
apps: {
byAppId: {
workflow: {
comments: {
get: (...args: unknown[]) => mockFetchWorkflowComments(...args),
post: (...args: unknown[]) => mockCreateWorkflowComment(...args),
byCommentId: {
delete: (...args: unknown[]) => mockDeleteWorkflowComment(...args),
get: (...args: unknown[]) => mockFetchWorkflowComment(...args),
put: (...args: unknown[]) => mockUpdateWorkflowComment(...args),
resolve: {
post: (...args: unknown[]) => mockResolveWorkflowComment(...args),
},
replies: {
post: (...args: unknown[]) => mockCreateWorkflowCommentReply(...args),
byReplyId: {
delete: (...args: unknown[]) => mockDeleteWorkflowCommentReply(...args),
put: (...args: unknown[]) => mockUpdateWorkflowCommentReply(...args),
return {
...actual,
consoleClient: {
systemFeatures: {
get: () => ({
enable_collaboration_mode: globalFeatureState.enableCollaboration,
}),
},
apps: {
byAppId: {
workflow: {
comments: {
get: (...args: unknown[]) => mockFetchWorkflowComments(...args),
post: (...args: unknown[]) => mockCreateWorkflowComment(...args),
byCommentId: {
delete: (...args: unknown[]) => mockDeleteWorkflowComment(...args),
get: (...args: unknown[]) => mockFetchWorkflowComment(...args),
put: (...args: unknown[]) => mockUpdateWorkflowComment(...args),
resolve: {
post: (...args: unknown[]) => mockResolveWorkflowComment(...args),
},
replies: {
post: (...args: unknown[]) => mockCreateWorkflowCommentReply(...args),
byReplyId: {
delete: (...args: unknown[]) => mockDeleteWorkflowCommentReply(...args),
put: (...args: unknown[]) => mockUpdateWorkflowCommentReply(...args),
},
},
},
},
@ -91,19 +92,9 @@ vi.mock('@/service/client', () => ({
},
},
},
},
consoleQuery: {
systemFeatures: {
get: {
queryKey: () => ['console', 'systemFeatures', 'get'],
queryOptions: (options: Record<string, unknown> = {}) => ({
queryKey: ['console', 'systemFeatures', 'get'],
...options,
}),
},
},
},
}))
consoleQuery: actual.consoleQuery,
}
})
vi.mock('@/app/components/workflow/collaboration/core/collaboration-manager', () => ({
collaborationManager: {
@ -156,6 +147,7 @@ const baseCommentDetail = (): WorkflowCommentDetail => ({
const createSeededQueryClient = () => {
const queryClient = createConsoleQueryClient()
seedAccountProfileQuery(queryClient, mockConsoleState.userProfile)
seedSystemFeatures(queryClient, {
enable_collaboration_mode: globalFeatureState.enableCollaboration,
})

View File

@ -4,11 +4,10 @@ import type {
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 { userProfileAtom } from '@/context/account-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useParams } from '@/next/navigation'
import { consoleClient } from '@/service/client'
@ -69,7 +68,10 @@ export const useWorkflowComment = () => {
() => new Map(mentionableUsers.map((user) => [user.id, user])),
[mentionableUsers],
)
const userProfile = useAtomValue(userProfileAtom)
const { data: userProfile } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile,
})
const { data: isCollaborationEnabled } = useSuspenseQuery({
...systemFeaturesQueryOptions(),
select: (s) => s.enable_collaboration_mode,

View File

@ -11,20 +11,6 @@ const mockHandleNodeIterationChildSizeChange = vi.fn()
const mockHandleNodeLoopChildSizeChange = vi.fn()
const mockUseNodeResizeObserver = vi.fn()
const mockUseCollaboration = vi.fn()
const mockConsoleState = vi.hoisted(() => ({
userProfile: {
id: 'user-1',
name: 'User',
email: 'user@example.com',
avatar: '',
avatar_url: '',
},
}))
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => mockConsoleState)
})
vi.mock('../../../hooks/use-tool-icon', async (importOriginal) => {
const actual = await importOriginal<typeof import('../../../hooks/use-tool-icon')>()

View File

@ -1,4 +1,5 @@
'use client'
import type { FC } from 'react'
import type { ResourceVarInputs } from '../types'
import type {
@ -18,7 +19,7 @@ import {
SelectItemText,
SelectTrigger,
} from '@langgenius/dify-ui/select'
import { useAtomValue } from 'jotai'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useEffect, useMemo, useState } from 'react'
import { CheckboxList } from '@/app/components/base/checkbox-list'
import Input from '@/app/components/base/input'
@ -32,7 +33,7 @@ import MixedVariableTextInput from '@/app/components/workflow/nodes/tool/compone
import ToolDatePicker from '@/app/components/workflow/nodes/tool/components/tool-date-picker'
import ToolDateRangePicker from '@/app/components/workflow/nodes/tool/components/tool-date-range-picker'
import { VarType } from '@/app/components/workflow/types'
import { userProfileAtom } from '@/context/account-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { useFetchDynamicOptions } from '@/service/use-plugins'
import { useTriggerPluginDynamicOptions } from '@/service/use-triggers'
import { VarKindType } from '../types'
@ -94,7 +95,10 @@ const FormInputItem: FC<Props> = ({
inPanel,
}) => {
const language = useLanguage()
const userProfile = useAtomValue(userProfileAtom)
const { data: userProfile } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile,
})
const timezone = userProfile.timezone ?? 'UTC'
const [toolsOptions, setToolsOptions] = useState<FormOption[] | null>(null)
const [isLoadingToolsOptions, setIsLoadingToolsOptions] = useState(false)

View File

@ -5,8 +5,8 @@ import { cn } from '@langgenius/dify-ui/cn'
import { Tabs, TabsList, TabsPanel, TabsTab } from '@langgenius/dify-ui/tabs'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiCloseLine, RiPlayLargeLine } from '@remixicon/react'
import { useSuspenseQuery } from '@tanstack/react-query'
import { debounce } from 'es-toolkit/compat'
import { useAtomValue } from 'jotai'
import { useQueryState } from 'nuqs'
import * as React from 'react'
import { cloneElement, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
@ -47,7 +47,7 @@ import {
hasRetryNode,
isSupportCustomRunForm,
} from '@/app/components/workflow/utils'
import { userProfileAtom } from '@/context/account-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { useAllBuiltInTools } from '@/service/use-tools'
import { useAllTriggerPlugins } from '@/service/use-triggers'
import { FlowType } from '@/types/common'
@ -96,7 +96,10 @@ const BasePanel: FC<BasePanelProps> = ({ id, data, children }) => {
const { t } = useTranslation()
const language = useLanguage()
const appId = useStore((s) => s.appId)
const userProfile = useAtomValue(userProfileAtom)
const { data: userProfile } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile,
})
const { isConnected, nodePanelPresence } = useCollaboration(appId as string)
const { showMessageLogModal } = useAppStore(
useShallow((state) => ({

View File

@ -2,7 +2,7 @@ import type { FC, ReactElement } from 'react'
import type { WorkflowTranslator } from './node-sections'
import type { NodeProps } from '@/app/components/workflow/types'
import { cn } from '@langgenius/dify-ui/cn'
import { useAtomValue } from 'jotai'
import { useSuspenseQuery } from '@tanstack/react-query'
import { cloneElement, memo, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { UserAvatarList } from '@/app/components/base/user-avatar-list'
@ -15,7 +15,7 @@ import CopyID from '@/app/components/workflow/nodes/tool/components/copy-id'
import { useStore } from '@/app/components/workflow/store'
import { BlockEnum, ControlMode, NodeRunningStatus } from '@/app/components/workflow/types'
import { hasErrorHandleNode, hasRetryNode } from '@/app/components/workflow/utils'
import { userProfileAtom } from '@/context/account-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import useInspectVarsCrud from '../../hooks/use-inspect-vars-crud'
import { useNodePluginInstallation } from '../../hooks/use-node-plugin-installation'
import { useToolIcon } from '../../hooks/use-tool-icon'
@ -57,7 +57,10 @@ const BaseNode: FC<BaseNodeProps> = ({ id, data, children }) => {
const { handleNodeIterationChildSizeChange } = useNodeIterationInteractions()
const { handleNodeLoopChildSizeChange } = useNodeLoopInteractions()
const toolIcon = useToolIcon(data)
const userProfile = useAtomValue(userProfileAtom)
const { data: userProfile } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile,
})
const appId = useStore((s) => s.appId)
const { nodePanelPresence } = useCollaboration(appId as string)
const controlMode = useStore((s) => s.controlMode)

View File

@ -1,6 +1,8 @@
import type { ReactElement } from 'react'
import type { EmailConfig } from '../../../types'
import { fireEvent, screen } from '@testing-library/react'
import { render } from '@/test/console/render'
import { createAccountProfileQueryWrapper } from '@/test/console/account-profile'
import { render as renderWithConsoleState } from '@/test/console/render'
import EmailConfigureModal from '../email-configure-modal'
const mockToastError = vi.hoisted(() => vi.fn())
@ -10,17 +12,17 @@ const mockConsoleState = vi.hoisted(() => ({
},
}))
const render = (ui: ReactElement) =>
renderWithConsoleState(ui, {
wrapper: createAccountProfileQueryWrapper(mockConsoleState.userProfile),
})
vi.mock('@langgenius/dify-ui/toast', () => ({
toast: {
error: (message: string) => mockToastError(message),
},
}))
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => mockConsoleState)
})
vi.mock('../mail-body-input', () => ({
default: ({ value, onChange }: { value: string; onChange: (value: string) => void }) => (
<textarea

View File

@ -1,8 +1,10 @@
import type { ReactElement } from 'react'
import type { EmailConfig, FormInputItem } from '../../../types'
import type { Node, NodeOutPutVar } from '@/app/components/workflow/types'
import { fireEvent, screen, within } from '@testing-library/react'
import { InputVarType } from '@/app/components/workflow/types'
import { render } from '@/test/console/render'
import { createAccountProfileQueryWrapper } from '@/test/console/account-profile'
import { render as renderWithConsoleState } from '@/test/console/render'
import { DeliveryMethodType } from '../../../types'
import DeliveryMethodItem from '../method-item'
@ -27,10 +29,10 @@ const mockConsoleState = vi.hoisted(() => ({
},
}))
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => mockConsoleState)
})
const render = (ui: ReactElement) =>
renderWithConsoleState(ui, {
wrapper: createAccountProfileQueryWrapper(mockConsoleState.userProfile),
})
vi.mock('../email-configure-modal', () => ({
default: (props: EmailConfigureModalProps) => {

View File

@ -16,6 +16,7 @@ import { HooksStoreContext } from '@/app/components/workflow/hooks-store/provide
import { createHooksStore } from '@/app/components/workflow/hooks-store/store'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import { BlockEnum, InputVarType, VarType } from '@/app/components/workflow/types'
import { seedAccountProfileQuery } from '@/test/console/account-profile'
import { render } from '@/test/console/render'
import EmailSenderModal from '../test-email-sender'
@ -38,10 +39,6 @@ const mockConsoleState = vi.hoisted(() => ({
},
}))
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => mockConsoleState)
})
vi.mock('@/context/workspace-state', async () => {
const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture')
return createWorkspaceStateModuleMock(() => mockConsoleState)
@ -67,6 +64,7 @@ const createQueryClient = () =>
const renderWithProviders = (ui: ReactNode) => {
const queryClient = createQueryClient()
seedAccountProfileQuery(queryClient, mockConsoleState.userProfile)
const hooksStore = createHooksStore({})
return render(

View File

@ -5,11 +5,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 { useSuspenseQuery } from '@tanstack/react-query'
import { memo, useCallback, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import Input from '@/app/components/base/input'
import { userProfileEmailAtom } from '@/context/account-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import MailBodyInput from './mail-body-input'
import Recipient from './recipient'
@ -33,7 +33,10 @@ const EmailConfigureModal = ({
availableNodes = [],
}: EmailConfigureModalProps) => {
const { t } = useTranslation()
const email = useAtomValue(userProfileEmailAtom)
const { data: email } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile.email,
})
const [recipients, setRecipients] = useState(
config?.recipients || { whole_workspace: false, items: [] },
)

View File

@ -13,12 +13,12 @@ import {
RiRobot2Fill,
RiSendPlane2Line,
} from '@remixicon/react'
import { useAtomValue } from 'jotai'
import { useSuspenseQuery } from '@tanstack/react-query'
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 { userProfileEmailAtom } from '@/context/account-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { DeliveryMethodType } from '../../types'
import EmailConfigureModal from './email-configure-modal'
import TestEmailSender from './test-email-sender'
@ -49,7 +49,10 @@ const DeliveryMethodItem: FC<DeliveryMethodItemProps> = ({
readonly,
}) => {
const { t } = useTranslation()
const email = useAtomValue(userProfileEmailAtom)
const { data: email } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile.email,
})
const [isHovering, setIsHovering] = useState(false)
const [showEmailModal, setShowEmailModal] = useState(false)
const [showTestEmailModal, setShowTestEmailModal] = useState(false)

View File

@ -1,5 +1,7 @@
import type { ReactElement } from 'react'
import { fireEvent, screen } from '@testing-library/react'
import { render } from '@/test/console/render'
import { createAccountProfileQueryWrapper } from '@/test/console/account-profile'
import { render as renderWithConsoleState } from '@/test/console/render'
import { withSelectorKey } from '@/test/i18n-mock'
import Recipient from '../index'
@ -11,14 +13,15 @@ const mockConsoleState = vi.hoisted(() => ({
currentWorkspace: { name: "Dify's Lab" },
}))
const render = (ui: ReactElement) =>
renderWithConsoleState(ui, {
wrapper: createAccountProfileQueryWrapper(mockConsoleState.userProfile),
})
vi.mock('react-i18next', () => ({
useTranslation: () => mockUseTranslation(),
}))
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => mockConsoleState)
})
vi.mock('@/context/workspace-state', async () => {
const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture')
return createWorkspaceStateModuleMock(() => mockConsoleState)

View File

@ -2,12 +2,13 @@ import type { RecipientData, Recipient as RecipientItem } from '../../../types'
import { cn } from '@langgenius/dify-ui/cn'
import { Switch } from '@langgenius/dify-ui/switch'
import { RiGroupLine } from '@remixicon/react'
import { useSuspenseQuery } from '@tanstack/react-query'
import { produce } from 'immer'
import { useAtomValue } from 'jotai'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import { userProfileEmailAtom } from '@/context/account-state'
import { currentWorkspaceAtom } from '@/context/workspace-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { useMembers } from '@/service/use-common'
import EmailInput from './email-input'
import MemberSelector from './member-selector'
@ -21,7 +22,10 @@ type Props = Readonly<{
const Recipient = ({ data, onChange }: Props) => {
const { t } = useTranslation()
const userProfileEmail = useAtomValue(userProfileEmailAtom)
const { data: userProfileEmail } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile.email,
})
const currentWorkspace = useAtomValue(currentWorkspaceAtom)
const { data: members } = useMembers()
const accounts = members?.accounts || []

View File

@ -5,6 +5,7 @@ import { cn } from '@langgenius/dify-ui/cn'
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
import { toast } from '@langgenius/dify-ui/toast'
import { RiArrowRightSFill } from '@remixicon/react'
import { useSuspenseQuery } from '@tanstack/react-query'
import { noop, unionBy } from 'es-toolkit/compat'
import { useAtomValue } from 'jotai'
import { memo, useCallback, useMemo, useState } from 'react'
@ -21,8 +22,8 @@ import {
isSystemVar,
} from '@/app/components/workflow/nodes/_base/components/variable/utils'
import { InputVarType, VarType } from '@/app/components/workflow/types'
import { userProfileEmailAtom } from '@/context/account-state'
import { currentWorkspaceAtom } from '@/context/workspace-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { useMembers } from '@/service/use-common'
import { useTestEmailSender } from '@/service/use-workflow'
import { getHumanInputFormDependencySelectors, isOutput } from '../../utils'
@ -128,7 +129,10 @@ const EmailSenderModal = ({
availableNodes = [],
}: EmailSenderModalProps) => {
const { t } = useTranslation()
const userProfileEmail = useAtomValue(userProfileEmailAtom)
const { data: userProfileEmail } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile.email,
})
const currentWorkspace = useAtomValue(currentWorkspaceAtom)
const appDetail = useAppStore((state) => state.appDetail)
const { mutateAsync: testEmailSender } = useTestEmailSender()

View File

@ -1,10 +1,14 @@
import type { ReactElement } from 'react'
import type { ComparisonOperator, MetadataFilteringCondition, MetadataShape } from '../types'
import type { DataSet, MetadataInDoc } from '@/models/datasets'
import { fireEvent, screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { useEffect, useRef } from 'react'
import { ChunkingMode, DatasetPermission, DataSourceType } from '@/models/datasets'
import { createAccountProfileQueryClient } from '@/test/console/account-profile'
import {
createAccountProfileQueryClient,
createAccountProfileQueryWrapper,
} from '@/test/console/account-profile'
import { QueryClientTestProvider } from '@/test/console/query-provider'
import { render } from '@/test/console/render'
import { RETRIEVE_METHOD, RETRIEVE_TYPE } from '@/types/app'
@ -34,6 +38,11 @@ import {
MetadataFilteringVariableType,
} from '../types'
const renderWithAccountProfile = (ui: ReactElement) =>
render(ui, {
wrapper: createAccountProfileQueryWrapper(),
})
const mockHasEditPermissionForDataset = vi.fn(
(
_userId: string,
@ -136,10 +145,6 @@ const mockConsoleState = vi.hoisted(() => ({
workspacePermissionKeys: [] as string[],
}))
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => mockConsoleState)
})
vi.mock('@/context/permission-state', async () => {
const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture')
return createPermissionStateModuleMock(() => mockConsoleState)
@ -422,7 +427,7 @@ describe('knowledge-retrieval path', () => {
it('should render empty and populated dataset lists', () => {
const onChange = vi.fn()
const { rerender } = render(<DatasetList list={[]} onChange={onChange} />)
const { rerender } = renderWithAccountProfile(<DatasetList list={[]} onChange={onChange} />)
expect(screen.getByText('appDebug.datasetConfig.knowledgeTip')).toBeInTheDocument()
@ -454,7 +459,7 @@ describe('knowledge-retrieval path', () => {
canAccessConfig: false,
})
render(<DatasetList list={[dataset]} onChange={vi.fn()} />)
renderWithAccountProfile(<DatasetList list={[dataset]} onChange={vi.fn()} />)
const datasetItem = getDatasetItem()
@ -767,7 +772,7 @@ describe('knowledge-retrieval path', () => {
store.getState().updateDatasetsDetail([createDataset()])
const renderNode = (datasetIds: string[]) =>
render(
renderWithAccountProfile(
<DatasetsDetailContext.Provider value={store}>
<Node
id="knowledge-node"

View File

@ -1,13 +1,15 @@
'use client'
import type { FC } from 'react'
import type { DataSet } from '@/models/datasets'
import { useSuspenseQuery } from '@tanstack/react-query'
import { produce } from 'immer'
import { useAtomValue } from 'jotai'
import * as React from 'react'
import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { userProfileIdAtom } from '@/context/account-state'
import { workspacePermissionKeysAtom } from '@/context/permission-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { getDatasetACLCapabilities } from '@/utils/permission'
import Item from './dataset-item'
@ -31,7 +33,10 @@ const DatasetList: FC<Props> = ({
settingsModalHeight,
}) => {
const { t } = useTranslation()
const currentUserId = useAtomValue(userProfileIdAtom)
const { data: currentUserId } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile.id,
})
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
const handleRemove = useCallback(

View File

@ -1,7 +1,7 @@
import type { NoteNodeType } from '../note-node/types'
import { useAtomValue } from 'jotai'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useCallback } from 'react'
import { userProfileAtom } from '@/context/account-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { CUSTOM_NOTE_NODE } from '../note-node/constants'
import { NoteTheme } from '../note-node/types'
import { useWorkflowNoteShowAuthorValue } from '../persistence/local-storage-options'
@ -10,7 +10,10 @@ import { generateNewNode } from '../utils'
export const useOperator = () => {
const workflowStore = useWorkflowStore()
const userProfile = useAtomValue(userProfileAtom)
const { data: userProfile } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile,
})
const showAuthorStorage = useWorkflowNoteShowAuthorValue()
const handleAddNote = useCallback(() => {

View File

@ -1,6 +1,8 @@
import type { ReactElement } from 'react'
import type { WorkflowCommentList } from '@/app/components/workflow/comment/types'
import { fireEvent, screen, waitFor } from '@testing-library/react'
import { render } from '@/test/console/render'
import { createAccountProfileQueryWrapper } from '@/test/console/account-profile'
import { render as renderWithConsoleState } from '@/test/console/render'
import CommentsPanel from '../index'
const mockHandleCommentIconClick = vi.hoisted(() => vi.fn())
@ -12,6 +14,11 @@ const mockConsoleState = vi.hoisted(() => ({
userProfile: { id: 'user-1' },
}))
const render = (ui: ReactElement) =>
renderWithConsoleState(ui, {
wrapper: createAccountProfileQueryWrapper(mockConsoleState.userProfile),
})
const commentFixtures: WorkflowCommentList[] = [
{
id: 'c-1',
@ -65,11 +72,6 @@ vi.mock('@/hooks/use-format-time-from-now', () => ({
}),
}))
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => mockConsoleState)
})
vi.mock('@/app/components/workflow/store', () => ({
useStore: (selector: (state: WorkflowStoreSelectionState) => unknown) =>
selector({

View File

@ -8,14 +8,14 @@ import {
RiCloseLine,
RiFilter3Line,
} from '@remixicon/react'
import { useAtomValue } from 'jotai'
import { useSuspenseQuery } from '@tanstack/react-query'
import { memo, useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Divider from '@/app/components/base/divider'
import { UserAvatarList } from '@/app/components/base/user-avatar-list'
import { useStore } from '@/app/components/workflow/store'
import { ControlMode } from '@/app/components/workflow/types'
import { userProfileIdAtom } from '@/context/account-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
import { useWorkflowComment } from '../../hooks/use-workflow-comment'
@ -39,7 +39,11 @@ const CommentsPanel = () => {
[handleCommentIconClick],
)
const currentUserId = useAtomValue(userProfileIdAtom)
const { data: currentUserId } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile.id,
})
const filteredSorted = useMemo(() => {
let data = comments

View File

@ -19,12 +19,6 @@ const mockEmitRestoreComplete = vi.fn()
const mockEmitWorkflowUpdate = vi.fn()
let mockPlanType = Plan.professional
let mockEnableBilling = true
const mockConsoleState = vi.hoisted(() => ({
userProfile: {
id: 'test-user-id',
name: 'Test User',
},
}))
const createVersionHistory = (overrides: Partial<VersionHistory> = {}): VersionHistory => ({
id: 'version-id',
@ -69,11 +63,6 @@ type MockVersionHistoryItemProps = {
handleClickActionMenuItem: (operation: VersionHistoryContextMenuOptions) => void
}
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => mockConsoleState)
})
vi.mock('@/context/provider-context', () => ({
useProviderContext: () => ({
plan: { type: mockPlanType },

View File

@ -1,9 +1,10 @@
'use client'
import type { VersionHistory } from '@/types/workflow'
import { toast } from '@langgenius/dify-ui/toast'
import { RiArrowDownDoubleLine, RiCloseLine, RiLoader2Line } from '@remixicon/react'
import { useSuspenseQuery } from '@tanstack/react-query'
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'
@ -11,8 +12,8 @@ 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 { userProfileAtom } from '@/context/account-state'
import { useProviderContext } from '@/context/provider-context'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import {
useDeleteWorkflow,
useInvalidAllLastRun,
@ -72,7 +73,10 @@ export const VersionHistoryPanel = ({
const setShowWorkflowVersionHistoryPanel = useStore((s) => s.setShowWorkflowVersionHistoryPanel)
const currentVersion = useStore((s) => s.currentVersion)
const setCurrentVersion = useStore((s) => s.setCurrentVersion)
const userProfile = useAtomValue(userProfileAtom)
const { data: userProfile } = useSuspenseQuery({
...userProfileQueryOptions(),
select: (data) => data.profile,
})
const configsMap = useHooksStore((s) => s.configsMap)
const canImportExportDSL = useHooksStore((s) => s.accessControl.canImportExportDSL)
const invalidAllLastRun = useInvalidAllLastRun(configsMap?.flowType, configsMap?.flowId)

View File

@ -21,11 +21,6 @@ vi.mock('@/service/log', () => ({
fetchTracingList: (...args: unknown[]) => mockFetchTracingList(...args),
}))
vi.mock('@/context/account-state', async () => {
const { createAccountStateModuleMock } = await import('@/test/console/state-fixture')
return createAccountStateModuleMock(() => ({ userProfile: { id: 'account-1' } }))
})
vi.mock('@langgenius/dify-ui/toast', async (importOriginal) => ({
...(await importOriginal()),
toast: {