mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 02:18:08 +08:00
refactor(web): replace query option tunneling with queryOptions factories
Extract sandboxFilesTreeOptions and buildTreeFromFlatList from useSandboxFilesTree so callers that need custom TanStack Query behavior (e.g. refetchInterval) can compose at the call site instead of tunneling options through the hook. Remove the thin useGetSandboxProviderList wrapper in favor of inline oRPC queryOptions in the component. Also remove redundant .input(type<unknown>()) from three no-input GET contracts—oc already defaults TInputSchema to Schema<unknown, unknown>.
This commit is contained in:
@ -1,11 +1,12 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import type { SandboxProvider } from '@/types/sandbox-provider'
|
import type { SandboxProvider } from '@/types/sandbox-provider'
|
||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { memo, useState } from 'react'
|
import { memo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Loading from '@/app/components/base/loading'
|
import Loading from '@/app/components/base/loading'
|
||||||
import { useAppContext } from '@/context/app-context'
|
import { useAppContext } from '@/context/app-context'
|
||||||
import { useGetSandboxProviderList } from '@/service/use-sandbox-provider'
|
import { consoleQuery } from '@/service/client'
|
||||||
import ConfigModal from './config-modal'
|
import ConfigModal from './config-modal'
|
||||||
import ProviderCard from './provider-card'
|
import ProviderCard from './provider-card'
|
||||||
import SwitchModal from './switch-modal'
|
import SwitchModal from './switch-modal'
|
||||||
@ -13,7 +14,7 @@ import SwitchModal from './switch-modal'
|
|||||||
const SandboxProviderPage = () => {
|
const SandboxProviderPage = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { isCurrentWorkspaceManager, isLoadingCurrentWorkspace } = useAppContext()
|
const { isCurrentWorkspaceManager, isLoadingCurrentWorkspace } = useAppContext()
|
||||||
const { data: providers, isLoading } = useGetSandboxProviderList()
|
const { data: providers, isLoading } = useQuery(consoleQuery.sandboxProvider.getSandboxProviderList.queryOptions())
|
||||||
|
|
||||||
const [configModalProvider, setConfigModalProvider] = useState<SandboxProvider | null>(null)
|
const [configModalProvider, setConfigModalProvider] = useState<SandboxProvider | null>(null)
|
||||||
const [switchModalProvider, setSwitchModalProvider] = useState<SandboxProvider | null>(null)
|
const [switchModalProvider, setSwitchModalProvider] = useState<SandboxProvider | null>(null)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { SandboxFileNode, SandboxFileTreeNode } from '@/types/sandbox-file'
|
import type { SandboxFileNode } from '@/types/sandbox-file'
|
||||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||||
import ArtifactsTab from './artifacts-tab'
|
import ArtifactsTab from './artifacts-tab'
|
||||||
import { InspectTab } from './types'
|
import { InspectTab } from './types'
|
||||||
@ -21,9 +21,7 @@ const mocks = vi.hoisted(() => ({
|
|||||||
isResponding: false,
|
isResponding: false,
|
||||||
bottomPanelWidth: 640,
|
bottomPanelWidth: 640,
|
||||||
} as MockStoreState,
|
} as MockStoreState,
|
||||||
treeData: undefined as SandboxFileTreeNode[] | undefined,
|
|
||||||
flatData: [] as SandboxFileNode[],
|
flatData: [] as SandboxFileNode[],
|
||||||
hasFiles: false,
|
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
fetchDownloadUrl: vi.fn(),
|
fetchDownloadUrl: vi.fn(),
|
||||||
mockUseQuery: vi.fn(),
|
mockUseQuery: vi.fn(),
|
||||||
@ -31,6 +29,10 @@ const mocks = vi.hoisted(() => ({
|
|||||||
queryKey: ['sandboxFile', 'downloadFile'],
|
queryKey: ['sandboxFile', 'downloadFile'],
|
||||||
queryFn: vi.fn(),
|
queryFn: vi.fn(),
|
||||||
}),
|
}),
|
||||||
|
mockTreeOptions: vi.fn().mockReturnValue({
|
||||||
|
queryKey: ['sandboxFile', 'listFiles'],
|
||||||
|
queryFn: vi.fn(),
|
||||||
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('../store', () => ({
|
vi.mock('../store', () => ({
|
||||||
@ -42,14 +44,10 @@ vi.mock('@tanstack/react-query', async importOriginal => ({
|
|||||||
useQuery: (options: unknown) => mocks.mockUseQuery(options),
|
useQuery: (options: unknown) => mocks.mockUseQuery(options),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/service/use-sandbox-file', () => ({
|
vi.mock('@/service/use-sandbox-file', async importOriginal => ({
|
||||||
|
...(await importOriginal<typeof import('@/service/use-sandbox-file')>()),
|
||||||
sandboxFileDownloadUrlOptions: (...args: unknown[]) => mocks.mockDownloadUrlOptions(...args),
|
sandboxFileDownloadUrlOptions: (...args: unknown[]) => mocks.mockDownloadUrlOptions(...args),
|
||||||
useSandboxFilesTree: () => ({
|
sandboxFilesTreeOptions: (...args: unknown[]) => mocks.mockTreeOptions(...args),
|
||||||
data: mocks.treeData,
|
|
||||||
flatData: mocks.flatData,
|
|
||||||
hasFiles: mocks.hasFiles,
|
|
||||||
isLoading: mocks.isLoading,
|
|
||||||
}),
|
|
||||||
useDownloadSandboxFile: () => ({
|
useDownloadSandboxFile: () => ({
|
||||||
mutateAsync: mocks.fetchDownloadUrl,
|
mutateAsync: mocks.fetchDownloadUrl,
|
||||||
isPending: false,
|
isPending: false,
|
||||||
@ -74,18 +72,6 @@ vi.mock('@/utils/download', () => ({
|
|||||||
downloadUrl: vi.fn(),
|
downloadUrl: vi.fn(),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const createTreeFileNode = (overrides: Partial<SandboxFileTreeNode> = {}): SandboxFileTreeNode => ({
|
|
||||||
id: 'a.txt',
|
|
||||||
name: 'a.txt',
|
|
||||||
path: 'a.txt',
|
|
||||||
node_type: 'file',
|
|
||||||
size: 128,
|
|
||||||
mtime: 1700000000,
|
|
||||||
extension: 'txt',
|
|
||||||
children: [],
|
|
||||||
...overrides,
|
|
||||||
})
|
|
||||||
|
|
||||||
const createFlatFileNode = (overrides: Partial<SandboxFileNode> = {}): SandboxFileNode => ({
|
const createFlatFileNode = (overrides: Partial<SandboxFileNode> = {}): SandboxFileNode => ({
|
||||||
path: 'a.txt',
|
path: 'a.txt',
|
||||||
is_dir: false,
|
is_dir: false,
|
||||||
@ -103,13 +89,20 @@ describe('ArtifactsTab', () => {
|
|||||||
mocks.storeState.isResponding = false
|
mocks.storeState.isResponding = false
|
||||||
mocks.storeState.bottomPanelWidth = 640
|
mocks.storeState.bottomPanelWidth = 640
|
||||||
|
|
||||||
mocks.treeData = [createTreeFileNode()]
|
|
||||||
mocks.flatData = [createFlatFileNode()]
|
mocks.flatData = [createFlatFileNode()]
|
||||||
mocks.hasFiles = true
|
|
||||||
mocks.isLoading = false
|
mocks.isLoading = false
|
||||||
mocks.mockUseQuery.mockReturnValue({
|
mocks.mockUseQuery.mockImplementation((options: { queryKey?: unknown }) => {
|
||||||
data: undefined,
|
const treeKey = mocks.mockTreeOptions.mock.results.at(-1)?.value?.queryKey
|
||||||
isLoading: false,
|
if (treeKey && options.queryKey === treeKey) {
|
||||||
|
return {
|
||||||
|
data: mocks.flatData,
|
||||||
|
isLoading: mocks.isLoading,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
data: undefined,
|
||||||
|
isLoading: false,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -128,9 +121,7 @@ describe('ArtifactsTab', () => {
|
|||||||
expect(mocks.mockDownloadUrlOptions).toHaveBeenCalledWith('app-1', 'a.txt')
|
expect(mocks.mockDownloadUrlOptions).toHaveBeenCalledWith('app-1', 'a.txt')
|
||||||
})
|
})
|
||||||
|
|
||||||
mocks.treeData = undefined
|
|
||||||
mocks.flatData = []
|
mocks.flatData = []
|
||||||
mocks.hasFiles = false
|
|
||||||
|
|
||||||
rerender(<ArtifactsTab {...headerProps} />)
|
rerender(<ArtifactsTab {...headerProps} />)
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import Loading from '@/app/components/base/loading'
|
|||||||
import ArtifactsTree from '@/app/components/workflow/skill/file-tree/artifacts/artifacts-tree'
|
import ArtifactsTree from '@/app/components/workflow/skill/file-tree/artifacts/artifacts-tree'
|
||||||
import ReadOnlyFilePreview from '@/app/components/workflow/skill/viewer/read-only-file-preview'
|
import ReadOnlyFilePreview from '@/app/components/workflow/skill/viewer/read-only-file-preview'
|
||||||
import { useDocLink } from '@/context/i18n'
|
import { useDocLink } from '@/context/i18n'
|
||||||
import { sandboxFileDownloadUrlOptions, useDownloadSandboxFile, useSandboxFilesTree } from '@/service/use-sandbox-file'
|
import { buildTreeFromFlatList, sandboxFileDownloadUrlOptions, sandboxFilesTreeOptions, useDownloadSandboxFile } from '@/service/use-sandbox-file'
|
||||||
import { cn } from '@/utils/classnames'
|
import { cn } from '@/utils/classnames'
|
||||||
import { downloadUrl } from '@/utils/download'
|
import { downloadUrl } from '@/utils/download'
|
||||||
import { useStore } from '../store'
|
import { useStore } from '../store'
|
||||||
@ -67,10 +67,12 @@ const ArtifactsTab = (headerProps: InspectHeaderProps) => {
|
|||||||
)
|
)
|
||||||
const isResponding = useStore(s => s.isResponding)
|
const isResponding = useStore(s => s.isResponding)
|
||||||
|
|
||||||
const { data: treeData, flatData, hasFiles, isLoading } = useSandboxFilesTree(appId, {
|
const { data: flatData, isLoading } = useQuery({
|
||||||
enabled: !!appId,
|
...sandboxFilesTreeOptions(appId),
|
||||||
refetchInterval: (isWorkflowRunning || isResponding) ? 5000 : false,
|
refetchInterval: (isWorkflowRunning || isResponding) ? 5000 : false,
|
||||||
})
|
})
|
||||||
|
const treeData = useMemo(() => flatData ? buildTreeFromFlatList(flatData) : undefined, [flatData])
|
||||||
|
const hasFiles = (flatData?.length ?? 0) > 0
|
||||||
const { mutateAsync: fetchDownloadUrl, isPending: isDownloading } = useDownloadSandboxFile(appId)
|
const { mutateAsync: fetchDownloadUrl, isPending: isDownloading } = useDownloadSandboxFile(appId)
|
||||||
const [selectedFile, setSelectedFile] = useState<SandboxFileTreeNode | null>(null)
|
const [selectedFile, setSelectedFile] = useState<SandboxFileTreeNode | null>(null)
|
||||||
const selectedFilePath = useMemo(() => {
|
const selectedFilePath = useMemo(() => {
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
import { useFeatures } from '@/app/components/base/features/hooks'
|
import { useFeatures } from '@/app/components/base/features/hooks'
|
||||||
import { useSandboxFilesTree } from '@/service/use-sandbox-file'
|
import { sandboxFilesTreeOptions } from '@/service/use-sandbox-file'
|
||||||
import useCurrentVars from '../hooks/use-inspect-vars-crud'
|
import useCurrentVars from '../hooks/use-inspect-vars-crud'
|
||||||
import { useStore } from '../store'
|
import { useStore } from '../store'
|
||||||
import ArtifactsTab from './artifacts-tab'
|
import ArtifactsTab from './artifacts-tab'
|
||||||
@ -28,9 +29,8 @@ const VariablesPanel: FC<{ onClose: () => void }> = ({ onClose }) => {
|
|||||||
return [...environmentVariables, ...conversationVars, ...systemVars, ...nodesWithInspectVars].length === 0
|
return [...environmentVariables, ...conversationVars, ...systemVars, ...nodesWithInspectVars].length === 0
|
||||||
}, [environmentVariables, conversationVars, systemVars, nodesWithInspectVars])
|
}, [environmentVariables, conversationVars, systemVars, nodesWithInspectVars])
|
||||||
|
|
||||||
const { hasFiles: hasArtifacts } = useSandboxFilesTree(appId, {
|
const { data: sandboxFiles } = useQuery(sandboxFilesTreeOptions(sandboxEnabled ? appId : undefined))
|
||||||
enabled: !!appId && sandboxEnabled,
|
const hasArtifacts = (sandboxFiles?.length ?? 0) > 0
|
||||||
})
|
|
||||||
|
|
||||||
const handleClear = useCallback(() => {
|
const handleClear = useCallback(() => {
|
||||||
deleteAllInspectorVars()
|
deleteAllInspectorVars()
|
||||||
|
|||||||
@ -6,7 +6,6 @@ export const invoicesContract = base
|
|||||||
path: '/billing/invoices',
|
path: '/billing/invoices',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
.input(type<unknown>())
|
|
||||||
.output(type<{ url: string }>())
|
.output(type<{ url: string }>())
|
||||||
|
|
||||||
export const bindPartnerStackContract = base
|
export const bindPartnerStackContract = base
|
||||||
|
|||||||
@ -7,7 +7,6 @@ export const getSandboxProviderListContract = base
|
|||||||
path: '/workspaces/current/sandbox-providers',
|
path: '/workspaces/current/sandbox-providers',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
.input(type<unknown>())
|
|
||||||
.output(type<SandboxProvider[]>())
|
.output(type<SandboxProvider[]>())
|
||||||
|
|
||||||
export const saveSandboxProviderConfigContract = base
|
export const saveSandboxProviderConfigContract = base
|
||||||
|
|||||||
@ -7,5 +7,4 @@ export const systemFeaturesContract = base
|
|||||||
path: '/system-features',
|
path: '/system-features',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
.input(type<unknown>())
|
|
||||||
.output(type<SystemFeatures>())
|
.output(type<SystemFeatures>())
|
||||||
|
|||||||
@ -14,6 +14,14 @@ export function sandboxFileDownloadUrlOptions(appId: string | undefined, path: s
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sandboxFilesTreeOptions(appId: string | undefined) {
|
||||||
|
return consoleQuery.sandboxFile.listFiles.queryOptions({
|
||||||
|
input: appId
|
||||||
|
? { params: { appId }, query: { recursive: true } }
|
||||||
|
: skipToken,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type InvalidateSandboxFilesOptions = {
|
type InvalidateSandboxFilesOptions = {
|
||||||
refetchDownloadFile?: boolean
|
refetchDownloadFile?: boolean
|
||||||
}
|
}
|
||||||
@ -47,7 +55,7 @@ export function useDownloadSandboxFile(appId: string | undefined) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTreeFromFlatList(nodes: SandboxFileNode[]): SandboxFileTreeNode[] {
|
export function buildTreeFromFlatList(nodes: SandboxFileNode[]): SandboxFileTreeNode[] {
|
||||||
const nodeMap = new Map<string, SandboxFileTreeNode>()
|
const nodeMap = new Map<string, SandboxFileTreeNode>()
|
||||||
const roots: SandboxFileTreeNode[] = []
|
const roots: SandboxFileTreeNode[] = []
|
||||||
|
|
||||||
@ -86,25 +94,8 @@ function buildTreeFromFlatList(nodes: SandboxFileNode[]): SandboxFileTreeNode[]
|
|||||||
return roots
|
return roots
|
||||||
}
|
}
|
||||||
|
|
||||||
type UseSandboxFilesTreeOptions = {
|
export function useSandboxFilesTree(appId: string | undefined) {
|
||||||
enabled?: boolean
|
const { data, isLoading, error } = useQuery(sandboxFilesTreeOptions(appId))
|
||||||
refetchInterval?: number | false
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useSandboxFilesTree(
|
|
||||||
appId: string | undefined,
|
|
||||||
options?: UseSandboxFilesTreeOptions,
|
|
||||||
) {
|
|
||||||
const input = appId && (options?.enabled ?? true)
|
|
||||||
? { params: { appId }, query: { recursive: true } }
|
|
||||||
: skipToken
|
|
||||||
|
|
||||||
const { data, isLoading, error } = useQuery({
|
|
||||||
...consoleQuery.sandboxFile.listFiles.queryOptions({
|
|
||||||
input,
|
|
||||||
}),
|
|
||||||
refetchInterval: options?.refetchInterval,
|
|
||||||
})
|
|
||||||
|
|
||||||
const treeData = useMemo(() => {
|
const treeData = useMemo(() => {
|
||||||
if (!data)
|
if (!data)
|
||||||
@ -112,14 +103,10 @@ export function useSandboxFilesTree(
|
|||||||
return buildTreeFromFlatList(data)
|
return buildTreeFromFlatList(data)
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
const hasFiles = useMemo(() => {
|
|
||||||
return (data?.length ?? 0) > 0
|
|
||||||
}, [data])
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: treeData,
|
data: treeData,
|
||||||
flatData: data,
|
flatData: data,
|
||||||
hasFiles,
|
hasFiles: (data?.length ?? 0) > 0,
|
||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
useMutation,
|
useMutation,
|
||||||
useQuery,
|
|
||||||
useQueryClient,
|
useQueryClient,
|
||||||
} from '@tanstack/react-query'
|
} from '@tanstack/react-query'
|
||||||
import { consoleClient, consoleQuery } from '@/service/client'
|
import { consoleClient, consoleQuery } from '@/service/client'
|
||||||
|
|
||||||
export const useGetSandboxProviderList = () => {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: consoleQuery.sandboxProvider.getSandboxProviderList.queryKey(),
|
|
||||||
queryFn: () => consoleClient.sandboxProvider.getSandboxProviderList(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useSaveSandboxProviderConfig = () => {
|
export const useSaveSandboxProviderConfig = () => {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
return useMutation({
|
return useMutation({
|
||||||
|
|||||||
Reference in New Issue
Block a user