mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 10:28:10 +08:00
refactor(web): replace query hooks with queryOptions factories (#32520)
This commit is contained in:
@ -1,5 +1,6 @@
|
|||||||
import type { FileAppearanceType } from '@/app/components/base/file-uploader/types'
|
import type { FileAppearanceType } from '@/app/components/base/file-uploader/types'
|
||||||
import type { AppAssetTreeView } from '@/types/app-asset'
|
import type { AppAssetTreeView } from '@/types/app-asset'
|
||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import { useCallback, useMemo } from 'react'
|
import { useCallback, useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -11,7 +12,7 @@ import SkillEditor from '@/app/components/workflow/skill/editor/skill-editor'
|
|||||||
import { useFileTypeInfo } from '@/app/components/workflow/skill/hooks/use-file-type-info'
|
import { useFileTypeInfo } from '@/app/components/workflow/skill/hooks/use-file-type-info'
|
||||||
import { getFileIconType } from '@/app/components/workflow/skill/utils/file-utils'
|
import { getFileIconType } from '@/app/components/workflow/skill/utils/file-utils'
|
||||||
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 { useGetAppAssetFileContent, useGetAppAssetFileDownloadUrl } from '@/service/use-app-asset'
|
import { appAssetFileContentOptions, appAssetFileDownloadUrlOptions } from '@/service/use-app-asset'
|
||||||
import { cn } from '@/utils/classnames'
|
import { cn } from '@/utils/classnames'
|
||||||
|
|
||||||
type FilePreviewPanelProps = {
|
type FilePreviewPanelProps = {
|
||||||
@ -36,7 +37,8 @@ const FilePreviewPanel = ({ resourceId, currentNode, className, style, onClose }
|
|||||||
data: fileContent,
|
data: fileContent,
|
||||||
isLoading: isContentLoading,
|
isLoading: isContentLoading,
|
||||||
error: contentError,
|
error: contentError,
|
||||||
} = useGetAppAssetFileContent(appId, resourceId, {
|
} = useQuery({
|
||||||
|
...appAssetFileContentOptions(appId, resourceId),
|
||||||
enabled: isMarkdownPreview,
|
enabled: isMarkdownPreview,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -44,7 +46,8 @@ const FilePreviewPanel = ({ resourceId, currentNode, className, style, onClose }
|
|||||||
data: downloadUrlData,
|
data: downloadUrlData,
|
||||||
isLoading: isDownloadLoading,
|
isLoading: isDownloadLoading,
|
||||||
error: downloadError,
|
error: downloadError,
|
||||||
} = useGetAppAssetFileDownloadUrl(appId, resourceId, {
|
} = useQuery({
|
||||||
|
...appAssetFileDownloadUrlOptions(appId, resourceId),
|
||||||
enabled: isReadOnlyPreview,
|
enabled: isReadOnlyPreview,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -8,12 +8,22 @@ import {
|
|||||||
useSkillAssetTreeData,
|
useSkillAssetTreeData,
|
||||||
} from './use-skill-asset-tree'
|
} from './use-skill-asset-tree'
|
||||||
|
|
||||||
const { mockUseGetAppAssetTree } = vi.hoisted(() => ({
|
const { mockUseQuery, mockAppAssetTreeOptions } = vi.hoisted(() => ({
|
||||||
mockUseGetAppAssetTree: vi.fn(),
|
mockUseQuery: vi.fn(),
|
||||||
|
mockAppAssetTreeOptions: vi.fn().mockReturnValue({
|
||||||
|
queryKey: ['test', 'tree'],
|
||||||
|
queryFn: vi.fn(),
|
||||||
|
enabled: true,
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@tanstack/react-query', async importOriginal => ({
|
||||||
|
...await importOriginal<typeof import('@tanstack/react-query')>(),
|
||||||
|
useQuery: (options: unknown) => mockUseQuery(options),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/service/use-app-asset', () => ({
|
vi.mock('@/service/use-app-asset', () => ({
|
||||||
useGetAppAssetTree: (...args: unknown[]) => mockUseGetAppAssetTree(...args),
|
appAssetTreeOptions: (...args: unknown[]) => mockAppAssetTreeOptions(...args),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const createTreeNode = (
|
const createTreeNode = (
|
||||||
@ -34,22 +44,21 @@ describe('useSkillAssetTree', () => {
|
|||||||
useAppStore.setState({
|
useAppStore.setState({
|
||||||
appDetail: { id: 'app-1' } as App & Partial<AppSSO>,
|
appDetail: { id: 'app-1' } as App & Partial<AppSSO>,
|
||||||
})
|
})
|
||||||
mockUseGetAppAssetTree.mockReturnValue({
|
mockUseQuery.mockReturnValue({
|
||||||
data: null,
|
data: null,
|
||||||
isPending: false,
|
isPending: false,
|
||||||
error: null,
|
error: null,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Scenario: should pass app id from app store to the data query hook.
|
|
||||||
describe('useSkillAssetTreeData', () => {
|
describe('useSkillAssetTreeData', () => {
|
||||||
it('should request tree data with current app id', () => {
|
it('should request tree data with current app id', () => {
|
||||||
const expectedResult = { data: { children: [] }, isPending: false }
|
const expectedResult = { data: { children: [] }, isPending: false }
|
||||||
mockUseGetAppAssetTree.mockReturnValue(expectedResult)
|
mockUseQuery.mockReturnValue(expectedResult)
|
||||||
|
|
||||||
const { result } = renderHook(() => useSkillAssetTreeData())
|
const { result } = renderHook(() => useSkillAssetTreeData())
|
||||||
|
|
||||||
expect(mockUseGetAppAssetTree).toHaveBeenCalledWith('app-1')
|
expect(mockAppAssetTreeOptions).toHaveBeenCalledWith('app-1')
|
||||||
expect(result.current).toBe(expectedResult)
|
expect(result.current).toBe(expectedResult)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -58,16 +67,15 @@ describe('useSkillAssetTree', () => {
|
|||||||
|
|
||||||
renderHook(() => useSkillAssetTreeData())
|
renderHook(() => useSkillAssetTreeData())
|
||||||
|
|
||||||
expect(mockUseGetAppAssetTree).toHaveBeenCalledWith('')
|
expect(mockAppAssetTreeOptions).toHaveBeenCalledWith('')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Scenario: should expose a select transform that builds node lookup maps.
|
|
||||||
describe('useSkillAssetNodeMap', () => {
|
describe('useSkillAssetNodeMap', () => {
|
||||||
it('should build a map including nested nodes', () => {
|
it('should build a map including nested nodes', () => {
|
||||||
renderHook(() => useSkillAssetNodeMap())
|
renderHook(() => useSkillAssetNodeMap())
|
||||||
|
|
||||||
const options = mockUseGetAppAssetTree.mock.calls[0][1] as {
|
const options = mockUseQuery.mock.calls[0][0] as {
|
||||||
select: (data: AppAssetTreeResponse) => Map<string, AppAssetTreeView>
|
select: (data: AppAssetTreeResponse) => Map<string, AppAssetTreeView>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,7 +105,7 @@ describe('useSkillAssetTree', () => {
|
|||||||
it('should return an empty map when tree response has no children', () => {
|
it('should return an empty map when tree response has no children', () => {
|
||||||
renderHook(() => useSkillAssetNodeMap())
|
renderHook(() => useSkillAssetNodeMap())
|
||||||
|
|
||||||
const options = mockUseGetAppAssetTree.mock.calls[0][1] as {
|
const options = mockUseQuery.mock.calls[0][0] as {
|
||||||
select: (data: AppAssetTreeResponse) => Map<string, AppAssetTreeView>
|
select: (data: AppAssetTreeResponse) => Map<string, AppAssetTreeView>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,12 +115,11 @@ describe('useSkillAssetTree', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Scenario: should expose root-level existing skill folder names.
|
|
||||||
describe('useExistingSkillNames', () => {
|
describe('useExistingSkillNames', () => {
|
||||||
it('should collect only root folder names', () => {
|
it('should collect only root folder names', () => {
|
||||||
renderHook(() => useExistingSkillNames())
|
renderHook(() => useExistingSkillNames())
|
||||||
|
|
||||||
const options = mockUseGetAppAssetTree.mock.calls[0][1] as {
|
const options = mockUseQuery.mock.calls[0][0] as {
|
||||||
select: (data: AppAssetTreeResponse) => Set<string>
|
select: (data: AppAssetTreeResponse) => Set<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +160,7 @@ describe('useSkillAssetTree', () => {
|
|||||||
it('should return an empty set when tree response has no children', () => {
|
it('should return an empty set when tree response has no children', () => {
|
||||||
renderHook(() => useExistingSkillNames())
|
renderHook(() => useExistingSkillNames())
|
||||||
|
|
||||||
const options = mockUseGetAppAssetTree.mock.calls[0][1] as {
|
const options = mockUseQuery.mock.calls[0][0] as {
|
||||||
select: (data: AppAssetTreeResponse) => Set<string>
|
select: (data: AppAssetTreeResponse) => Set<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,33 +1,23 @@
|
|||||||
import type { AppAssetTreeResponse, AppAssetTreeView } from '@/types/app-asset'
|
import type { AppAssetTreeResponse, AppAssetTreeView } from '@/types/app-asset'
|
||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
import { useGetAppAssetTree } from '@/service/use-app-asset'
|
import { appAssetTreeOptions } from '@/service/use-app-asset'
|
||||||
import { buildNodeMap } from '../../../utils/tree-utils'
|
import { buildNodeMap } from '../../../utils/tree-utils'
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current app ID from the app store.
|
|
||||||
* Used internally by skill asset tree hooks.
|
|
||||||
*/
|
|
||||||
function useSkillAppId(): string {
|
function useSkillAppId(): string {
|
||||||
const appDetail = useAppStore(s => s.appDetail)
|
const appDetail = useAppStore(s => s.appDetail)
|
||||||
return appDetail?.id || ''
|
return appDetail?.id || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to get the asset tree data for the current skill app.
|
|
||||||
* Returns the raw tree data along with loading and error states.
|
|
||||||
*/
|
|
||||||
export function useSkillAssetTreeData() {
|
export function useSkillAssetTreeData() {
|
||||||
const appId = useSkillAppId()
|
const appId = useSkillAppId()
|
||||||
return useGetAppAssetTree(appId)
|
return useQuery(appAssetTreeOptions(appId))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to get the node map (id -> node) for the current skill app.
|
|
||||||
* Uses TanStack Query's select option to compute and cache the map.
|
|
||||||
*/
|
|
||||||
export function useSkillAssetNodeMap() {
|
export function useSkillAssetNodeMap() {
|
||||||
const appId = useSkillAppId()
|
const appId = useSkillAppId()
|
||||||
return useGetAppAssetTree(appId, {
|
return useQuery({
|
||||||
|
...appAssetTreeOptions(appId),
|
||||||
select: (data: AppAssetTreeResponse): Map<string, AppAssetTreeView> => {
|
select: (data: AppAssetTreeResponse): Map<string, AppAssetTreeView> => {
|
||||||
if (!data?.children)
|
if (!data?.children)
|
||||||
return new Map()
|
return new Map()
|
||||||
@ -36,13 +26,10 @@ export function useSkillAssetNodeMap() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to get the set of root-level folder names in the skill asset tree.
|
|
||||||
* Useful for checking whether a skill template has already been added.
|
|
||||||
*/
|
|
||||||
export function useExistingSkillNames() {
|
export function useExistingSkillNames() {
|
||||||
const appId = useSkillAppId()
|
const appId = useSkillAppId()
|
||||||
return useGetAppAssetTree(appId, {
|
return useQuery({
|
||||||
|
...appAssetTreeOptions(appId),
|
||||||
select: (data: AppAssetTreeResponse): Set<string> => {
|
select: (data: AppAssetTreeResponse): Set<string> => {
|
||||||
if (!data?.children)
|
if (!data?.children)
|
||||||
return new Set()
|
return new Set()
|
||||||
|
|||||||
@ -1,28 +1,32 @@
|
|||||||
import { renderHook } from '@testing-library/react'
|
import { renderHook } from '@testing-library/react'
|
||||||
import { useSkillFileData } from './use-skill-file-data'
|
import { useSkillFileData } from './use-skill-file-data'
|
||||||
|
|
||||||
const {
|
const { mockUseQuery, mockContentOptions, mockDownloadUrlOptions } = vi.hoisted(() => ({
|
||||||
mockUseGetAppAssetFileContent,
|
mockUseQuery: vi.fn(),
|
||||||
mockUseGetAppAssetFileDownloadUrl,
|
mockContentOptions: vi.fn().mockReturnValue({
|
||||||
} = vi.hoisted(() => ({
|
queryKey: ['test', 'content'],
|
||||||
mockUseGetAppAssetFileContent: vi.fn(),
|
queryFn: vi.fn(),
|
||||||
mockUseGetAppAssetFileDownloadUrl: vi.fn(),
|
}),
|
||||||
|
mockDownloadUrlOptions: vi.fn().mockReturnValue({
|
||||||
|
queryKey: ['test', 'downloadUrl'],
|
||||||
|
queryFn: vi.fn(),
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@tanstack/react-query', async importOriginal => ({
|
||||||
|
...await importOriginal<typeof import('@tanstack/react-query')>(),
|
||||||
|
useQuery: (options: unknown) => mockUseQuery(options),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/service/use-app-asset', () => ({
|
vi.mock('@/service/use-app-asset', () => ({
|
||||||
useGetAppAssetFileContent: (...args: unknown[]) => mockUseGetAppAssetFileContent(...args),
|
appAssetFileContentOptions: (...args: unknown[]) => mockContentOptions(...args),
|
||||||
useGetAppAssetFileDownloadUrl: (...args: unknown[]) => mockUseGetAppAssetFileDownloadUrl(...args),
|
appAssetFileDownloadUrlOptions: (...args: unknown[]) => mockDownloadUrlOptions(...args),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe('useSkillFileData', () => {
|
describe('useSkillFileData', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
mockUseGetAppAssetFileContent.mockReturnValue({
|
mockUseQuery.mockReturnValue({
|
||||||
data: undefined,
|
|
||||||
isLoading: false,
|
|
||||||
error: null,
|
|
||||||
})
|
|
||||||
mockUseGetAppAssetFileDownloadUrl.mockReturnValue({
|
|
||||||
data: undefined,
|
data: undefined,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: null,
|
||||||
@ -33,51 +37,62 @@ describe('useSkillFileData', () => {
|
|||||||
it('should disable both queries when mode is none', () => {
|
it('should disable both queries when mode is none', () => {
|
||||||
const { result } = renderHook(() => useSkillFileData('app-1', 'node-1', 'none'))
|
const { result } = renderHook(() => useSkillFileData('app-1', 'node-1', 'none'))
|
||||||
|
|
||||||
expect(mockUseGetAppAssetFileContent).toHaveBeenCalledWith('app-1', 'node-1', { enabled: false })
|
expect(mockContentOptions).toHaveBeenCalledWith('app-1', 'node-1')
|
||||||
expect(mockUseGetAppAssetFileDownloadUrl).toHaveBeenCalledWith('app-1', 'node-1', { enabled: false })
|
expect(mockDownloadUrlOptions).toHaveBeenCalledWith('app-1', 'node-1')
|
||||||
|
expect(mockUseQuery.mock.calls[0][0].enabled).toBe(false)
|
||||||
|
expect(mockUseQuery.mock.calls[1][0].enabled).toBe(false)
|
||||||
expect(result.current.isLoading).toBe(false)
|
expect(result.current.isLoading).toBe(false)
|
||||||
expect(result.current.error).toBeNull()
|
expect(result.current.error).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should fetch content data when mode is content', () => {
|
it('should fetch content data when mode is content', () => {
|
||||||
const contentError = new Error('content-error')
|
const contentError = new Error('content-error')
|
||||||
mockUseGetAppAssetFileContent.mockReturnValue({
|
mockUseQuery
|
||||||
data: { content: 'hello' },
|
.mockReturnValueOnce({
|
||||||
isLoading: true,
|
data: { content: 'hello' },
|
||||||
error: contentError,
|
isLoading: true,
|
||||||
})
|
error: contentError,
|
||||||
mockUseGetAppAssetFileDownloadUrl.mockReturnValue({
|
})
|
||||||
data: { download_url: 'https://example.com/file' },
|
.mockReturnValueOnce({
|
||||||
isLoading: true,
|
data: { download_url: 'https://example.com/file' },
|
||||||
error: new Error('download-error'),
|
isLoading: true,
|
||||||
})
|
error: new Error('download-error'),
|
||||||
|
})
|
||||||
|
|
||||||
const { result } = renderHook(() => useSkillFileData('app-1', 'node-1', 'content'))
|
const { result } = renderHook(() => useSkillFileData('app-1', 'node-1', 'content'))
|
||||||
|
|
||||||
expect(mockUseGetAppAssetFileContent).toHaveBeenCalledWith('app-1', 'node-1', { enabled: true })
|
expect(mockUseQuery.mock.calls[0][0].enabled).toBe(true)
|
||||||
expect(mockUseGetAppAssetFileDownloadUrl).toHaveBeenCalledWith('app-1', 'node-1', { enabled: false })
|
expect(mockUseQuery.mock.calls[1][0].enabled).toBe(false)
|
||||||
expect(result.current.fileContent).toEqual({ content: 'hello' })
|
expect(result.current.fileContent).toEqual({ content: 'hello' })
|
||||||
expect(result.current.isLoading).toBe(true)
|
expect(result.current.isLoading).toBe(true)
|
||||||
expect(result.current.error).toBe(contentError)
|
expect(result.current.error).toBe(contentError)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should disable content query when nodeId is null even if mode is content', () => {
|
||||||
|
const { result } = renderHook(() => useSkillFileData('app-1', null, 'content'))
|
||||||
|
|
||||||
|
expect(mockUseQuery.mock.calls[0][0].enabled).toBe(false)
|
||||||
|
expect(result.current.isLoading).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
it('should fetch download URL data when mode is download', () => {
|
it('should fetch download URL data when mode is download', () => {
|
||||||
const downloadError = new Error('download-error')
|
const downloadError = new Error('download-error')
|
||||||
mockUseGetAppAssetFileContent.mockReturnValue({
|
mockUseQuery
|
||||||
data: { content: 'hello' },
|
.mockReturnValueOnce({
|
||||||
isLoading: true,
|
data: { content: 'hello' },
|
||||||
error: new Error('content-error'),
|
isLoading: true,
|
||||||
})
|
error: new Error('content-error'),
|
||||||
mockUseGetAppAssetFileDownloadUrl.mockReturnValue({
|
})
|
||||||
data: { download_url: 'https://example.com/file' },
|
.mockReturnValueOnce({
|
||||||
isLoading: true,
|
data: { download_url: 'https://example.com/file' },
|
||||||
error: downloadError,
|
isLoading: true,
|
||||||
})
|
error: downloadError,
|
||||||
|
})
|
||||||
|
|
||||||
const { result } = renderHook(() => useSkillFileData('app-1', 'node-1', 'download'))
|
const { result } = renderHook(() => useSkillFileData('app-1', 'node-1', 'download'))
|
||||||
|
|
||||||
expect(mockUseGetAppAssetFileContent).toHaveBeenCalledWith('app-1', 'node-1', { enabled: false })
|
expect(mockUseQuery.mock.calls[0][0].enabled).toBe(false)
|
||||||
expect(mockUseGetAppAssetFileDownloadUrl).toHaveBeenCalledWith('app-1', 'node-1', { enabled: true })
|
expect(mockUseQuery.mock.calls[1][0].enabled).toBe(true)
|
||||||
expect(result.current.downloadUrlData).toEqual({ download_url: 'https://example.com/file' })
|
expect(result.current.downloadUrlData).toEqual({ download_url: 'https://example.com/file' })
|
||||||
expect(result.current.isLoading).toBe(true)
|
expect(result.current.isLoading).toBe(true)
|
||||||
expect(result.current.error).toBe(downloadError)
|
expect(result.current.error).toBe(downloadError)
|
||||||
|
|||||||
@ -1,40 +1,29 @@
|
|||||||
import { useGetAppAssetFileContent, useGetAppAssetFileDownloadUrl } from '@/service/use-app-asset'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
|
import { appAssetFileContentOptions, appAssetFileDownloadUrlOptions } from '@/service/use-app-asset'
|
||||||
|
|
||||||
export type SkillFileDataMode = 'none' | 'content' | 'download'
|
export type SkillFileDataMode = 'none' | 'content' | 'download'
|
||||||
|
|
||||||
export type SkillFileDataResult = {
|
|
||||||
fileContent: ReturnType<typeof useGetAppAssetFileContent>['data']
|
|
||||||
downloadUrlData: ReturnType<typeof useGetAppAssetFileDownloadUrl>['data']
|
|
||||||
isLoading: boolean
|
|
||||||
error: Error | null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook to fetch file data for skill documents.
|
|
||||||
* Uses explicit mode to control data fetching:
|
|
||||||
* - 'content': fetch editable file content
|
|
||||||
* - 'download': fetch non-editable file download URL
|
|
||||||
* - 'none': skip file-related requests while node metadata is unresolved
|
|
||||||
*/
|
|
||||||
export function useSkillFileData(
|
export function useSkillFileData(
|
||||||
appId: string,
|
appId: string,
|
||||||
nodeId: string | null | undefined,
|
nodeId: string | null | undefined,
|
||||||
mode: SkillFileDataMode,
|
mode: SkillFileDataMode,
|
||||||
): SkillFileDataResult {
|
) {
|
||||||
const {
|
const {
|
||||||
data: fileContent,
|
data: fileContent,
|
||||||
isLoading: isContentLoading,
|
isLoading: isContentLoading,
|
||||||
error: contentError,
|
error: contentError,
|
||||||
} = useGetAppAssetFileContent(appId, nodeId || '', {
|
} = useQuery({
|
||||||
enabled: mode === 'content',
|
...appAssetFileContentOptions(appId, nodeId || ''),
|
||||||
|
enabled: mode === 'content' && !!appId && !!nodeId,
|
||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: downloadUrlData,
|
data: downloadUrlData,
|
||||||
isLoading: isDownloadUrlLoading,
|
isLoading: isDownloadUrlLoading,
|
||||||
error: downloadUrlError,
|
error: downloadUrlError,
|
||||||
} = useGetAppAssetFileDownloadUrl(appId, nodeId || '', {
|
} = useQuery({
|
||||||
enabled: mode === 'download' && !!nodeId,
|
...appAssetFileDownloadUrlOptions(appId, nodeId || ''),
|
||||||
|
enabled: mode === 'download' && !!appId && !!nodeId,
|
||||||
})
|
})
|
||||||
|
|
||||||
const isLoading = mode === 'content'
|
const isLoading = mode === 'content'
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
import ArtifactContentPanel from './artifact-content-panel'
|
import ArtifactContentPanel from './artifact-content-panel'
|
||||||
|
|
||||||
@ -12,39 +11,32 @@ const mocks = vi.hoisted(() => ({
|
|||||||
activeTabId: 'artifact:/assets/report.bin',
|
activeTabId: 'artifact:/assets/report.bin',
|
||||||
appId: 'app-1',
|
appId: 'app-1',
|
||||||
} as WorkflowStoreState,
|
} as WorkflowStoreState,
|
||||||
useSandboxFileDownloadUrl: vi.fn(),
|
mockUseQuery: vi.fn(),
|
||||||
|
mockDownloadUrlOptions: vi.fn().mockReturnValue({
|
||||||
|
queryKey: ['sandboxFile', 'downloadFile'],
|
||||||
|
queryFn: vi.fn(),
|
||||||
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/app/components/workflow/store', () => ({
|
vi.mock('@/app/components/workflow/store', () => ({
|
||||||
useStore: (selector: (state: WorkflowStoreState) => unknown) => selector(mocks.workflowState),
|
useStore: (selector: (state: WorkflowStoreState) => unknown) => selector(mocks.workflowState),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/service/use-sandbox-file', () => ({
|
vi.mock('@tanstack/react-query', async importOriginal => ({
|
||||||
useSandboxFileDownloadUrl: (...args: unknown[]) => mocks.useSandboxFileDownloadUrl(...args),
|
...await importOriginal<typeof import('@tanstack/react-query')>(),
|
||||||
|
useQuery: (options: unknown) => mocks.mockUseQuery(options),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const renderPanel = () => {
|
vi.mock('@/service/use-sandbox-file', () => ({
|
||||||
const queryClient = new QueryClient({
|
sandboxFileDownloadUrlOptions: (...args: unknown[]) => mocks.mockDownloadUrlOptions(...args),
|
||||||
defaultOptions: {
|
}))
|
||||||
queries: {
|
|
||||||
retry: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return render(
|
|
||||||
<QueryClientProvider client={queryClient}>
|
|
||||||
<ArtifactContentPanel />
|
|
||||||
</QueryClientProvider>,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('ArtifactContentPanel', () => {
|
describe('ArtifactContentPanel', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
mocks.workflowState.activeTabId = 'artifact:/assets/report.bin'
|
mocks.workflowState.activeTabId = 'artifact:/assets/report.bin'
|
||||||
mocks.workflowState.appId = 'app-1'
|
mocks.workflowState.appId = 'app-1'
|
||||||
mocks.useSandboxFileDownloadUrl.mockReturnValue({
|
mocks.mockUseQuery.mockReturnValue({
|
||||||
data: { download_url: 'https://example.com/report.bin' },
|
data: { download_url: 'https://example.com/report.bin' },
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
})
|
})
|
||||||
@ -52,38 +44,30 @@ describe('ArtifactContentPanel', () => {
|
|||||||
|
|
||||||
describe('Rendering', () => {
|
describe('Rendering', () => {
|
||||||
it('should show loading indicator when download ticket is loading', () => {
|
it('should show loading indicator when download ticket is loading', () => {
|
||||||
// Arrange
|
mocks.mockUseQuery.mockReturnValue({
|
||||||
mocks.useSandboxFileDownloadUrl.mockReturnValue({
|
|
||||||
data: undefined,
|
data: undefined,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Act
|
render(<ArtifactContentPanel />)
|
||||||
renderPanel()
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show load error message when download url is unavailable', () => {
|
it('should show load error message when download url is unavailable', () => {
|
||||||
// Arrange
|
mocks.mockUseQuery.mockReturnValue({
|
||||||
mocks.useSandboxFileDownloadUrl.mockReturnValue({
|
|
||||||
data: { download_url: '' },
|
data: { download_url: '' },
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Act
|
render(<ArtifactContentPanel />)
|
||||||
renderPanel()
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(screen.getByText('workflow.skillSidebar.loadError')).toBeInTheDocument()
|
expect(screen.getByText('workflow.skillSidebar.loadError')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should render preview panel when ticket contains download url', () => {
|
it('should render preview panel when ticket contains download url', () => {
|
||||||
// Act
|
render(<ArtifactContentPanel />)
|
||||||
renderPanel()
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(screen.getByText('report.bin')).toBeInTheDocument()
|
expect(screen.getByText('report.bin')).toBeInTheDocument()
|
||||||
expect(screen.getByRole('button', { name: /common\.operation\.download/i })).toBeInTheDocument()
|
expect(screen.getByRole('button', { name: /common\.operation\.download/i })).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
@ -91,25 +75,19 @@ describe('ArtifactContentPanel', () => {
|
|||||||
|
|
||||||
describe('Data flow', () => {
|
describe('Data flow', () => {
|
||||||
it('should request ticket using app id and artifact path when tab is selected', () => {
|
it('should request ticket using app id and artifact path when tab is selected', () => {
|
||||||
// Act
|
render(<ArtifactContentPanel />)
|
||||||
renderPanel()
|
|
||||||
|
|
||||||
// Assert
|
expect(mocks.mockDownloadUrlOptions).toHaveBeenCalledWith('app-1', '/assets/report.bin')
|
||||||
expect(mocks.useSandboxFileDownloadUrl).toHaveBeenCalledTimes(1)
|
|
||||||
expect(mocks.useSandboxFileDownloadUrl).toHaveBeenCalledWith('app-1', '/assets/report.bin')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Edge Cases', () => {
|
describe('Edge Cases', () => {
|
||||||
it('should request ticket with undefined path when active tab id is null', () => {
|
it('should pass undefined path to options factory when active tab id is null', () => {
|
||||||
// Arrange
|
|
||||||
mocks.workflowState.activeTabId = null
|
mocks.workflowState.activeTabId = null
|
||||||
|
|
||||||
// Act
|
render(<ArtifactContentPanel />)
|
||||||
renderPanel()
|
|
||||||
|
|
||||||
// Assert
|
expect(mocks.mockDownloadUrlOptions).toHaveBeenCalledWith('app-1', undefined)
|
||||||
expect(mocks.useSandboxFileDownloadUrl).toHaveBeenCalledWith('app-1', undefined)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import * as React from 'react'
|
import * as React 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 { useStore } from '@/app/components/workflow/store'
|
import { useStore } from '@/app/components/workflow/store'
|
||||||
import { useSandboxFileDownloadUrl } from '@/service/use-sandbox-file'
|
import { sandboxFileDownloadUrlOptions } from '@/service/use-sandbox-file'
|
||||||
import { getArtifactPath } from '../../constants'
|
import { getArtifactPath } from '../../constants'
|
||||||
import { getFileExtension } from '../../utils/file-utils'
|
import { getFileExtension } from '../../utils/file-utils'
|
||||||
import ReadOnlyFilePreview from '../../viewer/read-only-file-preview'
|
import ReadOnlyFilePreview from '../../viewer/read-only-file-preview'
|
||||||
@ -18,7 +19,7 @@ const ArtifactContentPanel = () => {
|
|||||||
const fileName = path?.split('/').pop() ?? ''
|
const fileName = path?.split('/').pop() ?? ''
|
||||||
const extension = getFileExtension(fileName)
|
const extension = getFileExtension(fileName)
|
||||||
|
|
||||||
const { data: ticket, isLoading } = useSandboxFileDownloadUrl(appId, path)
|
const { data: ticket, isLoading } = useQuery(sandboxFileDownloadUrlOptions(appId, path))
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -26,14 +26,24 @@ const mocks = vi.hoisted(() => ({
|
|||||||
hasFiles: false,
|
hasFiles: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
fetchDownloadUrl: vi.fn(),
|
fetchDownloadUrl: vi.fn(),
|
||||||
useSandboxFileDownloadUrl: vi.fn(),
|
mockUseQuery: vi.fn(),
|
||||||
|
mockDownloadUrlOptions: vi.fn().mockReturnValue({
|
||||||
|
queryKey: ['sandboxFile', 'downloadFile'],
|
||||||
|
queryFn: vi.fn(),
|
||||||
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('../store', () => ({
|
vi.mock('../store', () => ({
|
||||||
useStore: (selector: (state: MockStoreState) => unknown) => selector(mocks.storeState),
|
useStore: (selector: (state: MockStoreState) => unknown) => selector(mocks.storeState),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@tanstack/react-query', async importOriginal => ({
|
||||||
|
...await importOriginal<typeof import('@tanstack/react-query')>(),
|
||||||
|
useQuery: (options: unknown) => mocks.mockUseQuery(options),
|
||||||
|
}))
|
||||||
|
|
||||||
vi.mock('@/service/use-sandbox-file', () => ({
|
vi.mock('@/service/use-sandbox-file', () => ({
|
||||||
|
sandboxFileDownloadUrlOptions: (...args: unknown[]) => mocks.mockDownloadUrlOptions(...args),
|
||||||
useSandboxFilesTree: () => ({
|
useSandboxFilesTree: () => ({
|
||||||
data: mocks.treeData,
|
data: mocks.treeData,
|
||||||
flatData: mocks.flatData,
|
flatData: mocks.flatData,
|
||||||
@ -44,7 +54,6 @@ vi.mock('@/service/use-sandbox-file', () => ({
|
|||||||
mutateAsync: mocks.fetchDownloadUrl,
|
mutateAsync: mocks.fetchDownloadUrl,
|
||||||
isPending: false,
|
isPending: false,
|
||||||
}),
|
}),
|
||||||
useSandboxFileDownloadUrl: (...args: unknown[]) => mocks.useSandboxFileDownloadUrl(...args),
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/context/i18n', () => ({
|
vi.mock('@/context/i18n', () => ({
|
||||||
@ -98,7 +107,7 @@ describe('ArtifactsTab', () => {
|
|||||||
mocks.flatData = [createFlatFileNode()]
|
mocks.flatData = [createFlatFileNode()]
|
||||||
mocks.hasFiles = true
|
mocks.hasFiles = true
|
||||||
mocks.isLoading = false
|
mocks.isLoading = false
|
||||||
mocks.useSandboxFileDownloadUrl.mockReturnValue({
|
mocks.mockUseQuery.mockReturnValue({
|
||||||
data: undefined,
|
data: undefined,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
})
|
})
|
||||||
@ -116,11 +125,7 @@ describe('ArtifactsTab', () => {
|
|||||||
fireEvent.click(screen.getByRole('button', { name: 'a.txt' }))
|
fireEvent.click(screen.getByRole('button', { name: 'a.txt' }))
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(mocks.useSandboxFileDownloadUrl).toHaveBeenCalledWith(
|
expect(mocks.mockDownloadUrlOptions).toHaveBeenCalledWith('app-1', 'a.txt')
|
||||||
'app-1',
|
|
||||||
'a.txt',
|
|
||||||
{ retry: false },
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
mocks.treeData = undefined
|
mocks.treeData = undefined
|
||||||
@ -130,12 +135,8 @@ describe('ArtifactsTab', () => {
|
|||||||
rerender(<ArtifactsTab {...headerProps} />)
|
rerender(<ArtifactsTab {...headerProps} />)
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const lastCall = mocks.useSandboxFileDownloadUrl.mock.calls.at(-1)
|
const lastCall = mocks.mockDownloadUrlOptions.mock.calls.at(-1)
|
||||||
expect(lastCall).toEqual([
|
expect(lastCall).toEqual(['app-1', undefined])
|
||||||
'app-1',
|
|
||||||
undefined,
|
|
||||||
{ retry: false },
|
|
||||||
])
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type { InspectHeaderProps } from './inspect-layout'
|
import type { InspectHeaderProps } from './inspect-layout'
|
||||||
import type { DocPathWithoutLang } from '@/types/doc-paths'
|
import type { DocPathWithoutLang } from '@/types/doc-paths'
|
||||||
import type { SandboxFileTreeNode } from '@/types/sandbox-file'
|
import type { SandboxFileTreeNode } from '@/types/sandbox-file'
|
||||||
|
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 ActionButton from '@/app/components/base/action-button'
|
import ActionButton from '@/app/components/base/action-button'
|
||||||
@ -10,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 { useDownloadSandboxFile, useSandboxFileDownloadUrl, useSandboxFilesTree } from '@/service/use-sandbox-file'
|
import { sandboxFileDownloadUrlOptions, useDownloadSandboxFile, useSandboxFilesTree } 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'
|
||||||
@ -83,11 +84,10 @@ const ArtifactsTab = (headerProps: InspectHeaderProps) => {
|
|||||||
return selectedExists ? selectedFile.path : undefined
|
return selectedExists ? selectedFile.path : undefined
|
||||||
}, [flatData, selectedFile])
|
}, [flatData, selectedFile])
|
||||||
|
|
||||||
const { data: downloadUrlData, isLoading: isDownloadUrlLoading } = useSandboxFileDownloadUrl(
|
const { data: downloadUrlData, isLoading: isDownloadUrlLoading } = useQuery({
|
||||||
appId,
|
...sandboxFileDownloadUrlOptions(appId, selectedFilePath),
|
||||||
selectedFilePath,
|
retry: false,
|
||||||
{ retry: false },
|
})
|
||||||
)
|
|
||||||
|
|
||||||
const handleFileSelect = useCallback((node: SandboxFileTreeNode) => {
|
const handleFileSelect = useCallback((node: SandboxFileTreeNode) => {
|
||||||
if (node.node_type === 'file')
|
if (node.node_type === 'file')
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import type {
|
import type {
|
||||||
AppAssetNode,
|
AppAssetNode,
|
||||||
AppAssetTreeResponse,
|
|
||||||
BatchUploadNodeInput,
|
BatchUploadNodeInput,
|
||||||
BatchUploadNodeOutput,
|
BatchUploadNodeOutput,
|
||||||
CreateFolderPayload,
|
CreateFolderPayload,
|
||||||
@ -12,26 +11,36 @@ import type {
|
|||||||
} from '@/types/app-asset'
|
} from '@/types/app-asset'
|
||||||
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'
|
||||||
import { upload } from './base'
|
import { upload } from './base'
|
||||||
import { uploadToPresignedUrl } from './upload-to-presigned-url'
|
import { uploadToPresignedUrl } from './upload-to-presigned-url'
|
||||||
|
|
||||||
type UseGetAppAssetTreeOptions<TData = AppAssetTreeResponse> = {
|
export function appAssetTreeOptions(appId: string) {
|
||||||
select?: (data: AppAssetTreeResponse) => TData
|
return consoleQuery.appAsset.tree.queryOptions({
|
||||||
|
input: { params: { appId } },
|
||||||
|
enabled: !!appId,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGetAppAssetTree<TData = AppAssetTreeResponse>(
|
export function appAssetFileContentOptions(appId: string, nodeId: string) {
|
||||||
appId: string,
|
return consoleQuery.appAsset.getFileContent.queryOptions({
|
||||||
options?: UseGetAppAssetTreeOptions<TData>,
|
input: { params: { appId, nodeId } },
|
||||||
) {
|
select: (data) => {
|
||||||
return useQuery({
|
try {
|
||||||
queryKey: consoleQuery.appAsset.tree.queryKey({ input: { params: { appId } } }),
|
return JSON.parse(data.content)
|
||||||
queryFn: () => consoleClient.appAsset.tree({ params: { appId } }),
|
}
|
||||||
enabled: !!appId,
|
catch {
|
||||||
select: options?.select,
|
return { content: data.content }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function appAssetFileDownloadUrlOptions(appId: string, nodeId: string) {
|
||||||
|
return consoleQuery.appAsset.getFileDownloadUrl.queryOptions({
|
||||||
|
input: { params: { appId, nodeId } },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,31 +62,6 @@ export const useCreateAppAssetFolder = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useGetAppAssetFileContent = (appId: string, nodeId: string, options?: { enabled?: boolean }) => {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: consoleQuery.appAsset.getFileContent.queryKey({ input: { params: { appId, nodeId } } }),
|
|
||||||
queryFn: () => consoleClient.appAsset.getFileContent({ params: { appId, nodeId } }),
|
|
||||||
select: (data) => {
|
|
||||||
try {
|
|
||||||
const result = JSON.parse(data.content)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
return { content: data.content }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enabled: (options?.enabled ?? true) && !!appId && !!nodeId,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useGetAppAssetFileDownloadUrl = (appId: string, nodeId: string, options?: { enabled?: boolean }) => {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: consoleQuery.appAsset.getFileDownloadUrl.queryKey({ input: { params: { appId, nodeId } } }),
|
|
||||||
queryFn: () => consoleClient.appAsset.getFileDownloadUrl({ params: { appId, nodeId } }),
|
|
||||||
enabled: (options?.enabled ?? true) && !!appId && !!nodeId,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useUpdateAppAssetFileContent = () => {
|
export const useUpdateAppAssetFileContent = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: consoleQuery.appAsset.updateFileContent.mutationKey(),
|
mutationKey: consoleQuery.appAsset.updateFileContent.mutationKey(),
|
||||||
|
|||||||
@ -1,68 +1,23 @@
|
|||||||
import type {
|
import type {
|
||||||
SandboxFileListQuery,
|
|
||||||
SandboxFileNode,
|
SandboxFileNode,
|
||||||
SandboxFileTreeNode,
|
SandboxFileTreeNode,
|
||||||
} from '@/types/sandbox-file'
|
} from '@/types/sandbox-file'
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
import { skipToken, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { useCallback, useMemo } from 'react'
|
import { useCallback, useMemo } from 'react'
|
||||||
import { consoleClient, consoleQuery } from '@/service/client'
|
import { consoleClient, consoleQuery } from '@/service/client'
|
||||||
|
|
||||||
type UseGetSandboxFilesOptions = {
|
export function sandboxFileDownloadUrlOptions(appId: string | undefined, path: string | undefined) {
|
||||||
path?: string
|
return consoleQuery.sandboxFile.downloadFile.queryOptions({
|
||||||
recursive?: boolean
|
input: appId && path
|
||||||
enabled?: boolean
|
? { params: { appId }, body: { path } }
|
||||||
refetchInterval?: number | false
|
: skipToken,
|
||||||
}
|
})
|
||||||
|
|
||||||
type UseSandboxFileDownloadUrlOptions = {
|
|
||||||
enabled?: boolean
|
|
||||||
retry?: boolean | number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type InvalidateSandboxFilesOptions = {
|
type InvalidateSandboxFilesOptions = {
|
||||||
refetchDownloadFile?: boolean
|
refetchDownloadFile?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useGetSandboxFiles(
|
|
||||||
appId: string | undefined,
|
|
||||||
options?: UseGetSandboxFilesOptions,
|
|
||||||
) {
|
|
||||||
const query: SandboxFileListQuery = {
|
|
||||||
path: options?.path,
|
|
||||||
recursive: options?.recursive,
|
|
||||||
}
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
queryKey: consoleQuery.sandboxFile.listFiles.queryKey({
|
|
||||||
input: { params: { appId: appId! }, query },
|
|
||||||
}),
|
|
||||||
queryFn: () => consoleClient.sandboxFile.listFiles({
|
|
||||||
params: { appId: appId! },
|
|
||||||
query,
|
|
||||||
}),
|
|
||||||
enabled: !!appId && (options?.enabled ?? true),
|
|
||||||
refetchInterval: options?.refetchInterval,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useSandboxFileDownloadUrl(
|
|
||||||
appId: string | undefined,
|
|
||||||
path: string | undefined,
|
|
||||||
options?: UseSandboxFileDownloadUrlOptions,
|
|
||||||
) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: consoleQuery.sandboxFile.downloadFile.queryKey({
|
|
||||||
input: { params: { appId: appId! }, body: { path: path! } },
|
|
||||||
}),
|
|
||||||
queryFn: () => consoleClient.sandboxFile.downloadFile({
|
|
||||||
params: { appId: appId! },
|
|
||||||
body: { path: path! },
|
|
||||||
}),
|
|
||||||
enabled: !!appId && !!path && (options?.enabled ?? true),
|
|
||||||
retry: options?.retry,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useInvalidateSandboxFiles() {
|
export function useInvalidateSandboxFiles() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
return useCallback((options?: InvalidateSandboxFilesOptions) => {
|
return useCallback((options?: InvalidateSandboxFilesOptions) => {
|
||||||
@ -131,13 +86,21 @@ function buildTreeFromFlatList(nodes: SandboxFileNode[]): SandboxFileTreeNode[]
|
|||||||
return roots
|
return roots
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UseSandboxFilesTreeOptions = {
|
||||||
|
enabled?: boolean
|
||||||
|
refetchInterval?: number | false
|
||||||
|
}
|
||||||
|
|
||||||
export function useSandboxFilesTree(
|
export function useSandboxFilesTree(
|
||||||
appId: string | undefined,
|
appId: string | undefined,
|
||||||
options?: UseGetSandboxFilesOptions,
|
options?: UseSandboxFilesTreeOptions,
|
||||||
) {
|
) {
|
||||||
const { data, isLoading, error, refetch } = useGetSandboxFiles(appId, {
|
const { data, isLoading, error, refetch } = useQuery({
|
||||||
...options,
|
...consoleQuery.sandboxFile.listFiles.queryOptions({
|
||||||
recursive: true,
|
input: { params: { appId: appId! }, query: { recursive: true } },
|
||||||
|
}),
|
||||||
|
enabled: !!appId && (options?.enabled ?? true),
|
||||||
|
refetchInterval: options?.refetchInterval,
|
||||||
})
|
})
|
||||||
|
|
||||||
const treeData = useMemo(() => {
|
const treeData = useMemo(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user