From 0bdd21bc17015f3d32f6a53545e9fa8a2aa64239 Mon Sep 17 00:00:00 2001 From: yyh Date: Fri, 27 Feb 2026 11:58:16 +0800 Subject: [PATCH] refactor(web): replace query option tunneling with queryOptions factories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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()) from three no-input GET contracts—oc already defaults TInputSchema to Schema. --- .../sandbox-provider-page/index.tsx | 5 +- .../variable-inspect/artifacts-tab.spec.tsx | 49 ++++++++----------- .../variable-inspect/artifacts-tab.tsx | 8 +-- .../workflow/variable-inspect/panel.tsx | 8 +-- web/contract/console/billing.ts | 1 - web/contract/console/sandbox-provider.ts | 1 - web/contract/console/system.ts | 1 - web/service/use-sandbox-file.ts | 37 +++++--------- web/service/use-sandbox-provider.ts | 8 --- 9 files changed, 44 insertions(+), 74 deletions(-) diff --git a/web/app/components/header/account-setting/sandbox-provider-page/index.tsx b/web/app/components/header/account-setting/sandbox-provider-page/index.tsx index e1d80cdd5f8..e4699d75e73 100644 --- a/web/app/components/header/account-setting/sandbox-provider-page/index.tsx +++ b/web/app/components/header/account-setting/sandbox-provider-page/index.tsx @@ -1,11 +1,12 @@ 'use client' import type { SandboxProvider } from '@/types/sandbox-provider' +import { useQuery } from '@tanstack/react-query' import { memo, useState } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' import { useAppContext } from '@/context/app-context' -import { useGetSandboxProviderList } from '@/service/use-sandbox-provider' +import { consoleQuery } from '@/service/client' import ConfigModal from './config-modal' import ProviderCard from './provider-card' import SwitchModal from './switch-modal' @@ -13,7 +14,7 @@ import SwitchModal from './switch-modal' const SandboxProviderPage = () => { const { t } = useTranslation() const { isCurrentWorkspaceManager, isLoadingCurrentWorkspace } = useAppContext() - const { data: providers, isLoading } = useGetSandboxProviderList() + const { data: providers, isLoading } = useQuery(consoleQuery.sandboxProvider.getSandboxProviderList.queryOptions()) const [configModalProvider, setConfigModalProvider] = useState(null) const [switchModalProvider, setSwitchModalProvider] = useState(null) diff --git a/web/app/components/workflow/variable-inspect/artifacts-tab.spec.tsx b/web/app/components/workflow/variable-inspect/artifacts-tab.spec.tsx index 78ca2be5260..404f418ab47 100644 --- a/web/app/components/workflow/variable-inspect/artifacts-tab.spec.tsx +++ b/web/app/components/workflow/variable-inspect/artifacts-tab.spec.tsx @@ -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 ArtifactsTab from './artifacts-tab' import { InspectTab } from './types' @@ -21,9 +21,7 @@ const mocks = vi.hoisted(() => ({ isResponding: false, bottomPanelWidth: 640, } as MockStoreState, - treeData: undefined as SandboxFileTreeNode[] | undefined, flatData: [] as SandboxFileNode[], - hasFiles: false, isLoading: false, fetchDownloadUrl: vi.fn(), mockUseQuery: vi.fn(), @@ -31,6 +29,10 @@ const mocks = vi.hoisted(() => ({ queryKey: ['sandboxFile', 'downloadFile'], queryFn: vi.fn(), }), + mockTreeOptions: vi.fn().mockReturnValue({ + queryKey: ['sandboxFile', 'listFiles'], + queryFn: vi.fn(), + }), })) vi.mock('../store', () => ({ @@ -42,14 +44,10 @@ vi.mock('@tanstack/react-query', async importOriginal => ({ useQuery: (options: unknown) => mocks.mockUseQuery(options), })) -vi.mock('@/service/use-sandbox-file', () => ({ +vi.mock('@/service/use-sandbox-file', async importOriginal => ({ + ...(await importOriginal()), sandboxFileDownloadUrlOptions: (...args: unknown[]) => mocks.mockDownloadUrlOptions(...args), - useSandboxFilesTree: () => ({ - data: mocks.treeData, - flatData: mocks.flatData, - hasFiles: mocks.hasFiles, - isLoading: mocks.isLoading, - }), + sandboxFilesTreeOptions: (...args: unknown[]) => mocks.mockTreeOptions(...args), useDownloadSandboxFile: () => ({ mutateAsync: mocks.fetchDownloadUrl, isPending: false, @@ -74,18 +72,6 @@ vi.mock('@/utils/download', () => ({ downloadUrl: vi.fn(), })) -const createTreeFileNode = (overrides: Partial = {}): 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 => ({ path: 'a.txt', is_dir: false, @@ -103,13 +89,20 @@ describe('ArtifactsTab', () => { mocks.storeState.isResponding = false mocks.storeState.bottomPanelWidth = 640 - mocks.treeData = [createTreeFileNode()] mocks.flatData = [createFlatFileNode()] - mocks.hasFiles = true mocks.isLoading = false - mocks.mockUseQuery.mockReturnValue({ - data: undefined, - isLoading: false, + mocks.mockUseQuery.mockImplementation((options: { queryKey?: unknown }) => { + const treeKey = mocks.mockTreeOptions.mock.results.at(-1)?.value?.queryKey + 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') }) - mocks.treeData = undefined mocks.flatData = [] - mocks.hasFiles = false rerender() diff --git a/web/app/components/workflow/variable-inspect/artifacts-tab.tsx b/web/app/components/workflow/variable-inspect/artifacts-tab.tsx index e90808062a9..595ba1018a6 100644 --- a/web/app/components/workflow/variable-inspect/artifacts-tab.tsx +++ b/web/app/components/workflow/variable-inspect/artifacts-tab.tsx @@ -11,7 +11,7 @@ import Loading from '@/app/components/base/loading' 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 { 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 { downloadUrl } from '@/utils/download' import { useStore } from '../store' @@ -67,10 +67,12 @@ const ArtifactsTab = (headerProps: InspectHeaderProps) => { ) const isResponding = useStore(s => s.isResponding) - const { data: treeData, flatData, hasFiles, isLoading } = useSandboxFilesTree(appId, { - enabled: !!appId, + const { data: flatData, isLoading } = useQuery({ + ...sandboxFilesTreeOptions(appId), 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 [selectedFile, setSelectedFile] = useState(null) const selectedFilePath = useMemo(() => { diff --git a/web/app/components/workflow/variable-inspect/panel.tsx b/web/app/components/workflow/variable-inspect/panel.tsx index fabce360d3c..32803e5a13f 100644 --- a/web/app/components/workflow/variable-inspect/panel.tsx +++ b/web/app/components/workflow/variable-inspect/panel.tsx @@ -1,9 +1,10 @@ import type { FC } from 'react' +import { useQuery } from '@tanstack/react-query' import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Button from '@/app/components/base/button' 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 { useStore } from '../store' import ArtifactsTab from './artifacts-tab' @@ -28,9 +29,8 @@ const VariablesPanel: FC<{ onClose: () => void }> = ({ onClose }) => { return [...environmentVariables, ...conversationVars, ...systemVars, ...nodesWithInspectVars].length === 0 }, [environmentVariables, conversationVars, systemVars, nodesWithInspectVars]) - const { hasFiles: hasArtifacts } = useSandboxFilesTree(appId, { - enabled: !!appId && sandboxEnabled, - }) + const { data: sandboxFiles } = useQuery(sandboxFilesTreeOptions(sandboxEnabled ? appId : undefined)) + const hasArtifacts = (sandboxFiles?.length ?? 0) > 0 const handleClear = useCallback(() => { deleteAllInspectorVars() diff --git a/web/contract/console/billing.ts b/web/contract/console/billing.ts index 08e1d0668f4..d286ff3b420 100644 --- a/web/contract/console/billing.ts +++ b/web/contract/console/billing.ts @@ -6,7 +6,6 @@ export const invoicesContract = base path: '/billing/invoices', method: 'GET', }) - .input(type()) .output(type<{ url: string }>()) export const bindPartnerStackContract = base diff --git a/web/contract/console/sandbox-provider.ts b/web/contract/console/sandbox-provider.ts index a4a618f6bb0..5a71941d051 100644 --- a/web/contract/console/sandbox-provider.ts +++ b/web/contract/console/sandbox-provider.ts @@ -7,7 +7,6 @@ export const getSandboxProviderListContract = base path: '/workspaces/current/sandbox-providers', method: 'GET', }) - .input(type()) .output(type()) export const saveSandboxProviderConfigContract = base diff --git a/web/contract/console/system.ts b/web/contract/console/system.ts index bce0a8226e9..73e920b8044 100644 --- a/web/contract/console/system.ts +++ b/web/contract/console/system.ts @@ -7,5 +7,4 @@ export const systemFeaturesContract = base path: '/system-features', method: 'GET', }) - .input(type()) .output(type()) diff --git a/web/service/use-sandbox-file.ts b/web/service/use-sandbox-file.ts index c4734c889ca..4fd59bc2353 100644 --- a/web/service/use-sandbox-file.ts +++ b/web/service/use-sandbox-file.ts @@ -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 = { 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() const roots: SandboxFileTreeNode[] = [] @@ -86,25 +94,8 @@ function buildTreeFromFlatList(nodes: SandboxFileNode[]): SandboxFileTreeNode[] return roots } -type UseSandboxFilesTreeOptions = { - enabled?: boolean - 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, - }) +export function useSandboxFilesTree(appId: string | undefined) { + const { data, isLoading, error } = useQuery(sandboxFilesTreeOptions(appId)) const treeData = useMemo(() => { if (!data) @@ -112,14 +103,10 @@ export function useSandboxFilesTree( return buildTreeFromFlatList(data) }, [data]) - const hasFiles = useMemo(() => { - return (data?.length ?? 0) > 0 - }, [data]) - return { data: treeData, flatData: data, - hasFiles, + hasFiles: (data?.length ?? 0) > 0, isLoading, error, } diff --git a/web/service/use-sandbox-provider.ts b/web/service/use-sandbox-provider.ts index b038b80ed44..3381dbca335 100644 --- a/web/service/use-sandbox-provider.ts +++ b/web/service/use-sandbox-provider.ts @@ -1,17 +1,9 @@ import { useMutation, - useQuery, useQueryClient, } from '@tanstack/react-query' import { consoleClient, consoleQuery } from '@/service/client' -export const useGetSandboxProviderList = () => { - return useQuery({ - queryKey: consoleQuery.sandboxProvider.getSandboxProviderList.queryKey(), - queryFn: () => consoleClient.sandboxProvider.getSandboxProviderList(), - }) -} - export const useSaveSandboxProviderConfig = () => { const queryClient = useQueryClient() return useMutation({