mirror of
https://github.com/langgenius/dify.git
synced 2026-07-31 09:19:29 +08:00
refactor(web): localize dataset external API state (#39791)
This commit is contained in:
parent
3d01e8389a
commit
71ae1d5511
@ -5974,16 +5974,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/context/external-api-panel-context.tsx": {
|
||||
"react/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/context/external-knowledge-api-context.tsx": {
|
||||
"react/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/context/hooks/use-trigger-events-limit-modal.ts": {
|
||||
"eslint-react/set-state-in-effect": {
|
||||
"count": 3
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { screen, waitFor } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { render } from '@/test/console/render'
|
||||
@ -7,7 +6,6 @@ import DatasetsLayout from './layout'
|
||||
const mockReplace = vi.fn()
|
||||
const mockConsoleStateReader = vi.fn()
|
||||
let mockPathname = '/datasets'
|
||||
let mockExternalKnowledgeApiProviderEnabled: boolean | undefined
|
||||
|
||||
vi.mock('@/next/navigation', () => ({
|
||||
useRouter: () => ({
|
||||
@ -27,23 +25,6 @@ vi.mock('@/context/permission-state', async () => {
|
||||
return createPermissionStateModuleMock(() => mockConsoleStateReader())
|
||||
})
|
||||
|
||||
vi.mock('@/context/external-api-panel-context', () => ({
|
||||
ExternalApiPanelProvider: ({ children }: { children: ReactNode }) => <>{children}</>,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/external-knowledge-api-context', () => ({
|
||||
ExternalKnowledgeApiProvider: ({
|
||||
children,
|
||||
enabled,
|
||||
}: {
|
||||
children: ReactNode
|
||||
enabled?: boolean
|
||||
}) => {
|
||||
mockExternalKnowledgeApiProviderEnabled = enabled
|
||||
return <>{children}</>
|
||||
},
|
||||
}))
|
||||
|
||||
type ConsoleStateFixture = {
|
||||
isCurrentWorkspaceEditor: boolean
|
||||
isCurrentWorkspaceDatasetOperator: boolean
|
||||
@ -77,7 +58,6 @@ describe('DatasetsLayout', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockPathname = '/datasets'
|
||||
mockExternalKnowledgeApiProviderEnabled = undefined
|
||||
setConsoleState()
|
||||
})
|
||||
|
||||
@ -241,32 +221,4 @@ describe('DatasetsLayout', () => {
|
||||
expect(mockReplace).not.toHaveBeenCalled()
|
||||
},
|
||||
)
|
||||
|
||||
it('should disable external knowledge API queries without dataset.external.connect', () => {
|
||||
setConsoleState({
|
||||
workspacePermissionKeys: [],
|
||||
})
|
||||
|
||||
render(
|
||||
<DatasetsLayout>
|
||||
<div>datasets</div>
|
||||
</DatasetsLayout>,
|
||||
)
|
||||
|
||||
expect(mockExternalKnowledgeApiProviderEnabled).toBe(false)
|
||||
})
|
||||
|
||||
it('should enable external knowledge API queries with dataset.external.connect', () => {
|
||||
setConsoleState({
|
||||
workspacePermissionKeys: ['dataset.external.connect'],
|
||||
})
|
||||
|
||||
render(
|
||||
<DatasetsLayout>
|
||||
<div>datasets</div>
|
||||
</DatasetsLayout>,
|
||||
)
|
||||
|
||||
expect(mockExternalKnowledgeApiProviderEnabled).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@ -3,8 +3,6 @@
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useEffect } from 'react'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { ExternalApiPanelProvider } from '@/context/external-api-panel-context'
|
||||
import { ExternalKnowledgeApiProvider } from '@/context/external-knowledge-api-context'
|
||||
import {
|
||||
workspacePermissionKeysAtom,
|
||||
workspacePermissionKeysLoadingAtom,
|
||||
@ -60,9 +58,5 @@ export default function DatasetsLayout({ children }: { children: React.ReactNode
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<ExternalKnowledgeApiProvider enabled={canConnectExternalDataset}>
|
||||
<ExternalApiPanelProvider>{children}</ExternalApiPanelProvider>
|
||||
</ExternalKnowledgeApiProvider>
|
||||
)
|
||||
return children
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ExternalAPIItem } from '@/models/datasets'
|
||||
import type { ExternalKnowledgeApiResponse } from '@dify/contracts/api/console/datasets/types.gen'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import ExternalAPIPanel from '../index'
|
||||
@ -7,11 +7,11 @@ vi.mock('@/context/i18n', () => ({
|
||||
useDocLink: () => (path: string) => `https://docs.example.com${path}`,
|
||||
}))
|
||||
|
||||
// Mock external contexts (only mock context providers, not base components)
|
||||
const mockSetShowExternalKnowledgeAPIModal = vi.fn()
|
||||
const mockMutateExternalKnowledgeApis = vi.fn()
|
||||
const mockInvalidateQueries = vi.fn()
|
||||
const externalKnowledgeApiQueryKey = ['console', 'datasets', 'externalKnowledgeApi', 'get']
|
||||
let mockIsLoading = false
|
||||
let mockExternalKnowledgeApiList: ExternalAPIItem[] = []
|
||||
let mockExternalKnowledgeApiList: ExternalKnowledgeApiResponse[] = []
|
||||
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
useModalContext: () => ({
|
||||
@ -19,12 +19,30 @@ vi.mock('@/context/modal-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/external-knowledge-api-context', () => ({
|
||||
useExternalKnowledgeApi: () => ({
|
||||
externalKnowledgeApiList: mockExternalKnowledgeApiList,
|
||||
mutateExternalKnowledgeApis: mockMutateExternalKnowledgeApis,
|
||||
isLoading: mockIsLoading,
|
||||
}),
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const original = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...original,
|
||||
useQuery: () => ({
|
||||
data: { data: mockExternalKnowledgeApiList },
|
||||
isLoading: mockIsLoading,
|
||||
}),
|
||||
useQueryClient: () => ({ invalidateQueries: mockInvalidateQueries }),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleQuery: {
|
||||
datasets: {
|
||||
externalKnowledgeApi: {
|
||||
get: {
|
||||
queryOptions: () => ({
|
||||
queryKey: ['console', 'datasets', 'externalKnowledgeApi', 'get'],
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock the ExternalKnowledgeAPICard to avoid mocking its internal dependencies
|
||||
@ -33,7 +51,7 @@ vi.mock('../../external-knowledge-api-card', () => ({
|
||||
api,
|
||||
canManageExternalKnowledgeApi,
|
||||
}: {
|
||||
api: ExternalAPIItem
|
||||
api: ExternalKnowledgeApiResponse
|
||||
canManageExternalKnowledgeApi: boolean
|
||||
}) => (
|
||||
<div
|
||||
@ -178,26 +196,28 @@ describe('ExternalAPIPanel', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should call mutateExternalKnowledgeApis in onSaveCallback', async () => {
|
||||
it('should invalidate the generated external API query after creation', async () => {
|
||||
render(<ExternalAPIPanel {...defaultProps} />)
|
||||
const createButton = screen.getByText('dataset.createExternalAPI').closest('button')!
|
||||
fireEvent.click(createButton)
|
||||
|
||||
const callArgs = mockSetShowExternalKnowledgeAPIModal.mock.calls[0]![0]
|
||||
callArgs.onSaveCallback()
|
||||
await callArgs.onSaveCallback()
|
||||
|
||||
expect(mockMutateExternalKnowledgeApis).toHaveBeenCalled()
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: externalKnowledgeApiQueryKey,
|
||||
})
|
||||
})
|
||||
|
||||
it('should call mutateExternalKnowledgeApis in onCancelCallback', async () => {
|
||||
it('should not refresh the query when creation is canceled', async () => {
|
||||
render(<ExternalAPIPanel {...defaultProps} />)
|
||||
const createButton = screen.getByText('dataset.createExternalAPI').closest('button')!
|
||||
fireEvent.click(createButton)
|
||||
|
||||
const callArgs = mockSetShowExternalKnowledgeAPIModal.mock.calls[0]![0]
|
||||
callArgs.onCancelCallback()
|
||||
|
||||
expect(mockMutateExternalKnowledgeApis).toHaveBeenCalled()
|
||||
expect(callArgs.onCancelCallback).toBeUndefined()
|
||||
expect(mockInvalidateQueries).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { RiAddLine, RiBookOpenLine, RiCloseLine } from '@remixicon/react'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { useExternalKnowledgeApi } from '@/context/external-knowledge-api-context'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import ExternalKnowledgeAPICard from '../external-knowledge-api-card'
|
||||
|
||||
type ExternalAPIPanelProps = {
|
||||
@ -22,8 +23,11 @@ const ExternalAPIPanel: React.FC<ExternalAPIPanelProps> = ({
|
||||
const { t } = useTranslation()
|
||||
const docLink = useDocLink()
|
||||
const { setShowExternalKnowledgeAPIModal } = useModalContext()
|
||||
const { externalKnowledgeApiList, mutateExternalKnowledgeApis, isLoading } =
|
||||
useExternalKnowledgeApi()
|
||||
const queryClient = useQueryClient()
|
||||
const externalKnowledgeApiQueryOptions =
|
||||
consoleQuery.datasets.externalKnowledgeApi.get.queryOptions({ input: {} })
|
||||
const { data, isLoading } = useQuery(externalKnowledgeApiQueryOptions)
|
||||
const externalKnowledgeApiList = data?.data ?? []
|
||||
|
||||
const handleOpenExternalAPIModal = () => {
|
||||
if (!canManageExternalKnowledgeApi) return
|
||||
@ -32,10 +36,9 @@ const ExternalAPIPanel: React.FC<ExternalAPIPanelProps> = ({
|
||||
payload: { name: '', settings: { endpoint: '', api_key: '' } },
|
||||
datasetBindings: [],
|
||||
onSaveCallback: () => {
|
||||
mutateExternalKnowledgeApis()
|
||||
},
|
||||
onCancelCallback: () => {
|
||||
mutateExternalKnowledgeApis()
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: externalKnowledgeApiQueryOptions.queryKey,
|
||||
})
|
||||
},
|
||||
isEditMode: false,
|
||||
})
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
import type { ExternalKnowledgeApiResponse } from '@dify/contracts/api/console/datasets/types.gen'
|
||||
import type { ExternalAPIItem } from '@/models/datasets'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
// Import mocked services
|
||||
import { checkUsageExternalAPI, deleteExternalAPI, fetchExternalAPI } from '@/service/datasets'
|
||||
import {
|
||||
checkUsageExternalAPI,
|
||||
deleteExternalAPI,
|
||||
fetchExternalAPI,
|
||||
updateExternalAPI,
|
||||
} from '@/service/datasets'
|
||||
import ExternalKnowledgeAPICard from '../index'
|
||||
|
||||
// Mock API services
|
||||
@ -15,7 +21,8 @@ vi.mock('@/service/datasets', () => ({
|
||||
|
||||
// Mock contexts
|
||||
const mockSetShowExternalKnowledgeAPIModal = vi.fn()
|
||||
const mockMutateExternalKnowledgeApis = vi.fn()
|
||||
const mockInvalidateQueries = vi.fn()
|
||||
const externalKnowledgeApiQueryKey = ['console', 'datasets', 'externalKnowledgeApi', 'get']
|
||||
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
useModalContext: () => ({
|
||||
@ -23,14 +30,30 @@ vi.mock('@/context/modal-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/external-knowledge-api-context', () => ({
|
||||
useExternalKnowledgeApi: () => ({
|
||||
mutateExternalKnowledgeApis: mockMutateExternalKnowledgeApis,
|
||||
}),
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const original = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...original,
|
||||
useQueryClient: () => ({ invalidateQueries: mockInvalidateQueries }),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleQuery: {
|
||||
datasets: {
|
||||
externalKnowledgeApi: {
|
||||
get: {
|
||||
queryOptions: () => ({
|
||||
queryKey: ['console', 'datasets', 'externalKnowledgeApi', 'get'],
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
describe('ExternalKnowledgeAPICard', () => {
|
||||
const mockApi: ExternalAPIItem = {
|
||||
const mockApi: ExternalKnowledgeApiResponse = {
|
||||
id: 'api-123',
|
||||
tenant_id: 'tenant-1',
|
||||
name: 'Test External API',
|
||||
@ -146,7 +169,7 @@ describe('ExternalKnowledgeAPICard', () => {
|
||||
consoleSpy.mockRestore()
|
||||
})
|
||||
|
||||
it('should call mutate on save callback', async () => {
|
||||
it('should invalidate the generated list query after editing', async () => {
|
||||
const mockResponse: ExternalAPIItem = {
|
||||
id: 'api-123',
|
||||
tenant_id: 'tenant-1',
|
||||
@ -171,14 +194,31 @@ describe('ExternalKnowledgeAPICard', () => {
|
||||
expect(mockSetShowExternalKnowledgeAPIModal).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// Simulate save callback
|
||||
const modalCall = mockSetShowExternalKnowledgeAPIModal.mock.calls[0]![0]
|
||||
modalCall.onSaveCallback()
|
||||
await modalCall.onEditCallback({
|
||||
name: 'Updated External API',
|
||||
settings: {
|
||||
endpoint: 'https://updated.example.com/knowledge',
|
||||
api_key: 'updated-secret-key',
|
||||
},
|
||||
})
|
||||
|
||||
expect(mockMutateExternalKnowledgeApis).toHaveBeenCalled()
|
||||
expect(updateExternalAPI).toHaveBeenCalledWith({
|
||||
apiTemplateId: 'api-123',
|
||||
body: expect.objectContaining({
|
||||
name: 'Updated External API',
|
||||
settings: {
|
||||
endpoint: 'https://updated.example.com/knowledge',
|
||||
api_key: 'updated-secret-key',
|
||||
},
|
||||
}),
|
||||
})
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: externalKnowledgeApiQueryKey,
|
||||
})
|
||||
})
|
||||
|
||||
it('should call mutate on cancel callback', async () => {
|
||||
it('should not refresh the list query when editing is canceled', async () => {
|
||||
const mockResponse: ExternalAPIItem = {
|
||||
id: 'api-123',
|
||||
tenant_id: 'tenant-1',
|
||||
@ -203,11 +243,10 @@ describe('ExternalKnowledgeAPICard', () => {
|
||||
expect(mockSetShowExternalKnowledgeAPIModal).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// Simulate cancel callback
|
||||
const modalCall = mockSetShowExternalKnowledgeAPIModal.mock.calls[0]![0]
|
||||
modalCall.onCancelCallback()
|
||||
|
||||
expect(mockMutateExternalKnowledgeApis).toHaveBeenCalled()
|
||||
expect(modalCall.onCancelCallback).toBeUndefined()
|
||||
expect(mockInvalidateQueries).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@ -262,7 +301,9 @@ describe('ExternalKnowledgeAPICard', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(deleteExternalAPI).toHaveBeenCalledWith({ apiTemplateId: 'api-123' })
|
||||
expect(mockMutateExternalKnowledgeApis).toHaveBeenCalled()
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: externalKnowledgeApiQueryKey,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -335,7 +376,7 @@ describe('ExternalKnowledgeAPICard', () => {
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle API with empty endpoint', () => {
|
||||
const apiWithEmptyEndpoint: ExternalAPIItem = {
|
||||
const apiWithEmptyEndpoint: ExternalKnowledgeApiResponse = {
|
||||
...mockApi,
|
||||
settings: { endpoint: '', api_key: 'key' },
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { ExternalKnowledgeApiResponse } from '@dify/contracts/api/console/datasets/types.gen'
|
||||
import type { CreateExternalAPIReq } from '../declarations'
|
||||
import type { ExternalAPIItem } from '@/models/datasets'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
@ -10,13 +10,14 @@ import {
|
||||
AlertDialogTitle,
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { RiDeleteBinLine, RiEditLine } from '@remixicon/react'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { useExternalKnowledgeApi } from '@/context/external-knowledge-api-context'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import {
|
||||
checkUsageExternalAPI,
|
||||
deleteExternalAPI,
|
||||
@ -25,7 +26,7 @@ import {
|
||||
} from '@/service/datasets'
|
||||
|
||||
type ExternalKnowledgeAPICardProps = {
|
||||
api: ExternalAPIItem
|
||||
api: ExternalKnowledgeApiResponse
|
||||
canManageExternalKnowledgeApi: boolean
|
||||
}
|
||||
|
||||
@ -37,7 +38,12 @@ const ExternalKnowledgeAPICard: React.FC<ExternalKnowledgeAPICardProps> = ({
|
||||
const [showConfirm, setShowConfirm] = useState(false)
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
const [usageCount, setUsageCount] = useState(0)
|
||||
const { mutateExternalKnowledgeApis } = useExternalKnowledgeApi()
|
||||
const queryClient = useQueryClient()
|
||||
const externalKnowledgeApiQueryKey = consoleQuery.datasets.externalKnowledgeApi.get.queryOptions({
|
||||
input: {},
|
||||
}).queryKey
|
||||
const endpoint =
|
||||
api.settings && typeof api.settings.endpoint === 'string' ? api.settings.endpoint : ''
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -56,12 +62,6 @@ const ExternalKnowledgeAPICard: React.FC<ExternalKnowledgeAPICardProps> = ({
|
||||
|
||||
setShowExternalKnowledgeAPIModal({
|
||||
payload: formValue,
|
||||
onSaveCallback: () => {
|
||||
mutateExternalKnowledgeApis()
|
||||
},
|
||||
onCancelCallback: () => {
|
||||
mutateExternalKnowledgeApis()
|
||||
},
|
||||
isEditMode: true,
|
||||
datasetBindings: response.dataset_bindings,
|
||||
onEditCallback: async (updatedData: CreateExternalAPIReq) => {
|
||||
@ -78,7 +78,7 @@ const ExternalKnowledgeAPICard: React.FC<ExternalKnowledgeAPICardProps> = ({
|
||||
},
|
||||
},
|
||||
})
|
||||
mutateExternalKnowledgeApis()
|
||||
await queryClient.invalidateQueries({ queryKey: externalKnowledgeApiQueryKey })
|
||||
} catch (error) {
|
||||
console.error('Error updating external knowledge API:', error)
|
||||
}
|
||||
@ -109,7 +109,7 @@ const ExternalKnowledgeAPICard: React.FC<ExternalKnowledgeAPICardProps> = ({
|
||||
const response = await deleteExternalAPI({ apiTemplateId: api.id })
|
||||
if (response && response.result === 'success') {
|
||||
setShowConfirm(false)
|
||||
mutateExternalKnowledgeApis()
|
||||
await queryClient.invalidateQueries({ queryKey: externalKnowledgeApiQueryKey })
|
||||
} else {
|
||||
console.error('Failed to delete external API')
|
||||
}
|
||||
@ -128,9 +128,7 @@ const ExternalKnowledgeAPICard: React.FC<ExternalKnowledgeAPICardProps> = ({
|
||||
<ApiConnectionMod className="size-4" />
|
||||
<div className="system-sm-medium">{api.name}</div>
|
||||
</div>
|
||||
<div className="self-stretch system-xs-regular text-text-tertiary">
|
||||
{api.settings.endpoint}
|
||||
</div>
|
||||
<div className="self-stretch system-xs-regular text-text-tertiary">{endpoint}</div>
|
||||
</div>
|
||||
{canManageExternalKnowledgeApi && (
|
||||
<div className="flex items-start gap-1">
|
||||
|
||||
@ -79,12 +79,27 @@ const createDefaultMockApiList = (): ExternalAPIItem[] => [
|
||||
|
||||
let mockExternalKnowledgeApiList: ExternalAPIItem[] = createDefaultMockApiList()
|
||||
|
||||
vi.mock('@/context/external-knowledge-api-context', () => ({
|
||||
useExternalKnowledgeApi: () => ({
|
||||
externalKnowledgeApiList: mockExternalKnowledgeApiList,
|
||||
mutateExternalKnowledgeApis: vi.fn(),
|
||||
isLoading: false,
|
||||
}),
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const original = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...original,
|
||||
useQuery: () => ({ data: { data: mockExternalKnowledgeApiList } }),
|
||||
useQueryClient: () => ({ invalidateQueries: vi.fn() }),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleQuery: {
|
||||
datasets: {
|
||||
externalKnowledgeApi: {
|
||||
get: {
|
||||
queryOptions: () => ({
|
||||
queryKey: ['console', 'datasets', 'externalKnowledgeApi', 'get'],
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
// Suppress console.error helper
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { RiAddLine, RiArrowDownSLine } from '@remixicon/react'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { useExternalKnowledgeApi } from '@/context/external-knowledge-api-context'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
|
||||
type ApiItem = {
|
||||
value: string
|
||||
@ -26,7 +27,10 @@ const ExternalApiSelect: React.FC<ExternalApiSelectProps> = ({ items, value, onS
|
||||
items.find((item) => item.value === value) || null,
|
||||
)
|
||||
const { setShowExternalKnowledgeAPIModal } = useModalContext()
|
||||
const { mutateExternalKnowledgeApis } = useExternalKnowledgeApi()
|
||||
const queryClient = useQueryClient()
|
||||
const externalKnowledgeApiQueryKey = consoleQuery.datasets.externalKnowledgeApi.get.queryOptions({
|
||||
input: {},
|
||||
}).queryKey
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
@ -38,12 +42,9 @@ const ExternalApiSelect: React.FC<ExternalApiSelectProps> = ({ items, value, onS
|
||||
setShowExternalKnowledgeAPIModal({
|
||||
payload: { name: '', settings: { endpoint: '', api_key: '' } },
|
||||
onSaveCallback: async () => {
|
||||
mutateExternalKnowledgeApis()
|
||||
await queryClient.invalidateQueries({ queryKey: externalKnowledgeApiQueryKey })
|
||||
router.refresh()
|
||||
},
|
||||
onCancelCallback: () => {
|
||||
mutateExternalKnowledgeApis()
|
||||
},
|
||||
isEditMode: false,
|
||||
})
|
||||
}
|
||||
|
||||
@ -2,13 +2,14 @@
|
||||
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { useExternalKnowledgeApi } from '@/context/external-knowledge-api-context'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import ExternalApiSelect from './ExternalApiSelect'
|
||||
|
||||
type ExternalApiSelectionProps = {
|
||||
@ -24,15 +25,18 @@ const ExternalApiSelection: React.FC<ExternalApiSelectionProps> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
const { externalKnowledgeApiList } = useExternalKnowledgeApi()
|
||||
const queryClient = useQueryClient()
|
||||
const externalKnowledgeApiQueryOptions =
|
||||
consoleQuery.datasets.externalKnowledgeApi.get.queryOptions({ input: {} })
|
||||
const { data } = useQuery(externalKnowledgeApiQueryOptions)
|
||||
const externalKnowledgeApiList = data?.data ?? []
|
||||
const [selectedApiId, setSelectedApiId] = useState(external_knowledge_api_id)
|
||||
const { setShowExternalKnowledgeAPIModal } = useModalContext()
|
||||
const { mutateExternalKnowledgeApis } = useExternalKnowledgeApi()
|
||||
|
||||
const apiItems = externalKnowledgeApiList.map((api) => ({
|
||||
value: api.id,
|
||||
name: api.name,
|
||||
url: api.settings.endpoint,
|
||||
url: api.settings && typeof api.settings.endpoint === 'string' ? api.settings.endpoint : '',
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
@ -48,12 +52,11 @@ const ExternalApiSelection: React.FC<ExternalApiSelectionProps> = ({
|
||||
setShowExternalKnowledgeAPIModal({
|
||||
payload: { name: '', settings: { endpoint: '', api_key: '' } },
|
||||
onSaveCallback: async () => {
|
||||
mutateExternalKnowledgeApis()
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: externalKnowledgeApiQueryOptions.queryKey,
|
||||
})
|
||||
router.refresh()
|
||||
},
|
||||
onCancelCallback: () => {
|
||||
mutateExternalKnowledgeApis()
|
||||
},
|
||||
isEditMode: false,
|
||||
})
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ const mocks = vi.hoisted(() => ({
|
||||
push: vi.fn(),
|
||||
refresh: vi.fn(),
|
||||
setShowExternalKnowledgeAPIModal: vi.fn(),
|
||||
mutateExternalKnowledgeApis: vi.fn(),
|
||||
invalidateQueries: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/next/navigation', () => ({
|
||||
@ -22,10 +22,28 @@ vi.mock('@/context/modal-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/external-knowledge-api-context', () => ({
|
||||
useExternalKnowledgeApi: () => ({
|
||||
mutateExternalKnowledgeApis: mocks.mutateExternalKnowledgeApis,
|
||||
}),
|
||||
const externalKnowledgeApiQueryKey = ['console', 'datasets', 'externalKnowledgeApi', 'get']
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const original = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...original,
|
||||
useQueryClient: () => ({ invalidateQueries: mocks.invalidateQueries }),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleQuery: {
|
||||
datasets: {
|
||||
externalKnowledgeApi: {
|
||||
get: {
|
||||
queryOptions: () => ({
|
||||
queryKey: ['console', 'datasets', 'externalKnowledgeApi', 'get'],
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/icons/src/vender/solid/development', () => ({
|
||||
@ -87,11 +105,19 @@ describe('ExternalApiSelect', () => {
|
||||
expect(screen.getByText('dataset.createNewExternalAPI')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call setShowExternalKnowledgeAPIModal when add new clicked', () => {
|
||||
it('should invalidate the generated query after creating an external API', async () => {
|
||||
render(<ExternalApiSelect items={items} onSelect={onSelect} />)
|
||||
fireEvent.click(screen.getByText('dataset.selectExternalKnowledgeAPI.placeholder'))
|
||||
fireEvent.click(screen.getByText('dataset.createNewExternalAPI'))
|
||||
expect(mocks.setShowExternalKnowledgeAPIModal).toHaveBeenCalledOnce()
|
||||
|
||||
const modalConfig = mocks.setShowExternalKnowledgeAPIModal.mock.calls[0]![0]
|
||||
await modalConfig.onSaveCallback()
|
||||
|
||||
expect(mocks.invalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: externalKnowledgeApiQueryKey,
|
||||
})
|
||||
expect(mocks.refresh).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('should show item URLs in dropdown', () => {
|
||||
|
||||
@ -6,6 +6,7 @@ const mocks = vi.hoisted(() => ({
|
||||
push: vi.fn(),
|
||||
refresh: vi.fn(),
|
||||
setShowExternalKnowledgeAPIModal: vi.fn(),
|
||||
invalidateQueries: vi.fn(),
|
||||
externalKnowledgeApiList: [] as Array<{
|
||||
id: string
|
||||
name: string
|
||||
@ -23,10 +24,29 @@ vi.mock('@/context/modal-context', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/external-knowledge-api-context', () => ({
|
||||
useExternalKnowledgeApi: () => ({
|
||||
externalKnowledgeApiList: mocks.externalKnowledgeApiList,
|
||||
}),
|
||||
const externalKnowledgeApiQueryKey = ['console', 'datasets', 'externalKnowledgeApi', 'get']
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const original = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...original,
|
||||
useQuery: () => ({ data: { data: mocks.externalKnowledgeApiList } }),
|
||||
useQueryClient: () => ({ invalidateQueries: mocks.invalidateQueries }),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleQuery: {
|
||||
datasets: {
|
||||
externalKnowledgeApi: {
|
||||
get: {
|
||||
queryOptions: () => ({
|
||||
queryKey: ['console', 'datasets', 'externalKnowledgeApi', 'get'],
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('../ExternalApiSelect', () => ({
|
||||
@ -108,4 +128,19 @@ describe('ExternalApiSelection', () => {
|
||||
|
||||
expect(mocks.setShowExternalKnowledgeAPIModal).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('invalidates the generated query after creating an external API', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.externalKnowledgeApiList = []
|
||||
render(<ExternalApiSelection {...defaultProps} />)
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.noExternalKnowledge' }))
|
||||
const modalConfig = mocks.setShowExternalKnowledgeAPIModal.mock.calls[0]![0]
|
||||
await modalConfig.onSaveCallback()
|
||||
|
||||
expect(mocks.invalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: externalKnowledgeApiQueryKey,
|
||||
})
|
||||
expect(mocks.refresh).toHaveBeenCalledOnce()
|
||||
})
|
||||
})
|
||||
|
||||
@ -21,7 +21,7 @@ vi.mock('@/context/i18n', () => ({
|
||||
`https://docs.dify.ai/en${path?.startsWith('/use-dify/') ? `/cloud${path}` : path || ''}`,
|
||||
}))
|
||||
|
||||
// Mock external context providers (these are external dependencies)
|
||||
// Mock the shared modal owner.
|
||||
const mockSetShowExternalKnowledgeAPIModal = vi.fn()
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
useModalContext: () => ({
|
||||
@ -59,15 +59,31 @@ const createDefaultMockApiList = (): ExternalAPIItem[] => [
|
||||
}),
|
||||
]
|
||||
|
||||
const mockMutateExternalKnowledgeApis = vi.fn()
|
||||
const mockInvalidateQueries = vi.fn()
|
||||
const externalKnowledgeApiQueryKey = ['console', 'datasets', 'externalKnowledgeApi', 'get']
|
||||
let mockExternalKnowledgeApiList: ExternalAPIItem[] = createDefaultMockApiList()
|
||||
|
||||
vi.mock('@/context/external-knowledge-api-context', () => ({
|
||||
useExternalKnowledgeApi: () => ({
|
||||
externalKnowledgeApiList: mockExternalKnowledgeApiList,
|
||||
mutateExternalKnowledgeApis: mockMutateExternalKnowledgeApis,
|
||||
isLoading: false,
|
||||
}),
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const original = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...original,
|
||||
useQuery: () => ({ data: { data: mockExternalKnowledgeApiList } }),
|
||||
useQueryClient: () => ({ invalidateQueries: mockInvalidateQueries }),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleQuery: {
|
||||
datasets: {
|
||||
externalKnowledgeApi: {
|
||||
get: {
|
||||
queryOptions: () => ({
|
||||
queryKey: ['console', 'datasets', 'externalKnowledgeApi', 'get'],
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
// Helper to render component with default props
|
||||
@ -523,7 +539,7 @@ describe('ExternalKnowledgeBaseCreate', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('should call mutate and router.refresh on modal save callback', async () => {
|
||||
it('should invalidate the generated query and refresh after modal save', async () => {
|
||||
const user = userEvent.setup()
|
||||
// Set empty API list
|
||||
mockExternalKnowledgeApiList = []
|
||||
@ -536,11 +552,13 @@ describe('ExternalKnowledgeBaseCreate', () => {
|
||||
const modalCall = mockSetShowExternalKnowledgeAPIModal.mock.calls[0]![0]
|
||||
await modalCall.onSaveCallback()
|
||||
|
||||
expect(mockMutateExternalKnowledgeApis).toHaveBeenCalled()
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: externalKnowledgeApiQueryKey,
|
||||
})
|
||||
expect(mockRefresh).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call mutate on modal cancel callback', async () => {
|
||||
it('should not invalidate the generated query when the modal is canceled', async () => {
|
||||
const user = userEvent.setup()
|
||||
// Set empty API list
|
||||
mockExternalKnowledgeApiList = []
|
||||
@ -551,9 +569,8 @@ describe('ExternalKnowledgeBaseCreate', () => {
|
||||
|
||||
// Get the callback and invoke it
|
||||
const modalCall = mockSetShowExternalKnowledgeAPIModal.mock.calls[0]![0]
|
||||
modalCall.onCancelCallback()
|
||||
|
||||
expect(mockMutateExternalKnowledgeApis).toHaveBeenCalled()
|
||||
expect(modalCall.onCancelCallback).toBeUndefined()
|
||||
expect(mockInvalidateQueries).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should display API URL in dropdown', async () => {
|
||||
@ -600,7 +617,7 @@ describe('ExternalKnowledgeBaseCreate', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('should call mutate and refresh on save callback from ExternalApiSelect dropdown', async () => {
|
||||
it('should invalidate and refresh after saving from the API dropdown', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderComponent()
|
||||
|
||||
@ -614,11 +631,13 @@ describe('ExternalKnowledgeBaseCreate', () => {
|
||||
const modalCall = mockSetShowExternalKnowledgeAPIModal.mock.calls[0]![0]
|
||||
await modalCall.onSaveCallback()
|
||||
|
||||
expect(mockMutateExternalKnowledgeApis).toHaveBeenCalled()
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({
|
||||
queryKey: externalKnowledgeApiQueryKey,
|
||||
})
|
||||
expect(mockRefresh).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call mutate on cancel callback from ExternalApiSelect dropdown', async () => {
|
||||
it('should not invalidate after canceling from the API dropdown', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderComponent()
|
||||
|
||||
@ -630,9 +649,8 @@ describe('ExternalKnowledgeBaseCreate', () => {
|
||||
|
||||
// Get the callback from the modal call and invoke it
|
||||
const modalCall = mockSetShowExternalKnowledgeAPIModal.mock.calls[0]![0]
|
||||
modalCall.onCancelCallback()
|
||||
|
||||
expect(mockMutateExternalKnowledgeApis).toHaveBeenCalled()
|
||||
expect(modalCall.onCancelCallback).toBeUndefined()
|
||||
expect(mockInvalidateQueries).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should close dropdown after selecting an API', async () => {
|
||||
|
||||
@ -126,15 +126,6 @@ const renderList = (
|
||||
const render = (ui: ReactElement) => renderList(ui)
|
||||
const renderWithNuqs = renderList
|
||||
|
||||
// Mock external api panel context
|
||||
const mockSetShowExternalApiPanel = vi.fn()
|
||||
vi.mock('@/context/external-api-panel-context', () => ({
|
||||
useExternalApiPanel: () => ({
|
||||
showExternalApiPanel: false,
|
||||
setShowExternalApiPanel: mockSetShowExternalApiPanel,
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock useDocumentTitle hook
|
||||
vi.mock('@/hooks/use-document-title', () => ({
|
||||
default: vi.fn(),
|
||||
@ -336,6 +327,24 @@ describe('List', () => {
|
||||
expect(onUrlUpdate.mock.calls.at(-1)?.[0].searchParams.get('view')).toBe('new')
|
||||
})
|
||||
|
||||
it('should reset each view panel when its owning list unmounts', async () => {
|
||||
const user = userEvent.setup()
|
||||
mockConsoleState.knowledgeFsEnabled = true
|
||||
renderWithNuqs(<List />)
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.externalAPIPanelTitle' }))
|
||||
expect(screen.getByTestId('external-api-panel')).toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.new' }))
|
||||
expect(screen.queryByTestId('external-api-panel')).not.toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.externalAPIPanelTitle' }))
|
||||
expect(screen.getByTestId('external-api-panel')).toBeInTheDocument()
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.newKnowledge.legacy' }))
|
||||
expect(screen.queryByTestId('external-api-panel')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should restore the New Knowledge view from the URL', () => {
|
||||
mockConsoleState.knowledgeFsEnabled = true
|
||||
|
||||
@ -435,6 +444,7 @@ describe('List', () => {
|
||||
render(<List />)
|
||||
|
||||
expect(screen.queryByText(/externalAPIPanelTitle/)).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('external-api-panel')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -483,7 +493,7 @@ describe('List', () => {
|
||||
const button = screen.getByText(/externalAPIPanelTitle/)
|
||||
fireEvent.click(button)
|
||||
|
||||
expect(mockSetShowExternalApiPanel).toHaveBeenCalledWith(true)
|
||||
expect(screen.getByTestId('external-api-panel')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should update search input value', () => {
|
||||
@ -654,69 +664,15 @@ describe('List', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should show ExternalAPIPanel when showExternalApiPanel is true', async () => {
|
||||
// Re-mock to show external API panel
|
||||
vi.doMock('@/context/external-api-panel-context', () => ({
|
||||
useExternalApiPanel: () => ({
|
||||
showExternalApiPanel: true,
|
||||
setShowExternalApiPanel: mockSetShowExternalApiPanel,
|
||||
}),
|
||||
}))
|
||||
it('should close ExternalAPIPanel when onClose is called', () => {
|
||||
render(<List />)
|
||||
|
||||
vi.resetModules()
|
||||
const { default: ListComponent } = await import('../index')
|
||||
|
||||
render(<ListComponent />)
|
||||
|
||||
expect(screen.getByTestId('external-api-panel')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('external-api-panel')).toHaveAttribute(
|
||||
'data-can-manage-external-knowledge-api',
|
||||
'true',
|
||||
)
|
||||
})
|
||||
|
||||
it('should not show ExternalAPIPanel without dataset.external.connect even when panel state is open', async () => {
|
||||
mockConsoleState = {
|
||||
isCurrentWorkspaceEditor: true,
|
||||
isCurrentWorkspaceManager: true,
|
||||
isCurrentWorkspaceOwner: true,
|
||||
knowledgeFsEnabled: false,
|
||||
workspacePermissionKeys: ['dataset.create_and_management'],
|
||||
}
|
||||
vi.doMock('@/context/external-api-panel-context', () => ({
|
||||
useExternalApiPanel: () => ({
|
||||
showExternalApiPanel: true,
|
||||
setShowExternalApiPanel: mockSetShowExternalApiPanel,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.resetModules()
|
||||
const { default: ListComponent } = await import('../index')
|
||||
|
||||
render(<ListComponent />)
|
||||
fireEvent.click(screen.getByText(/externalAPIPanelTitle/))
|
||||
fireEvent.click(screen.getByText('Close Panel'))
|
||||
|
||||
expect(screen.queryByTestId('external-api-panel')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should close ExternalAPIPanel when onClose is called', async () => {
|
||||
vi.doMock('@/context/external-api-panel-context', () => ({
|
||||
useExternalApiPanel: () => ({
|
||||
showExternalApiPanel: true,
|
||||
setShowExternalApiPanel: mockSetShowExternalApiPanel,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.resetModules()
|
||||
const { default: ListComponent } = await import('../index')
|
||||
|
||||
render(<ListComponent />)
|
||||
|
||||
const closeButton = screen.getByText('Close Panel')
|
||||
fireEvent.click(closeButton)
|
||||
|
||||
expect(mockSetShowExternalApiPanel).toHaveBeenCalledWith(false)
|
||||
})
|
||||
|
||||
it('should show TagManagementModal when tag management is opened', () => {
|
||||
render(<List />)
|
||||
fireEvent.click(screen.getByText('Manage Tags'))
|
||||
|
||||
@ -18,7 +18,6 @@ import {
|
||||
getStepByStepTourGuides,
|
||||
STEP_BY_STEP_TOUR_TARGETS,
|
||||
} from '@/app/components/step-by-step-tour/target-registry'
|
||||
import { useExternalApiPanel } from '@/context/external-api-panel-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/permission-state'
|
||||
import { isCurrentWorkspaceOwnerAtom } from '@/context/workspace-state'
|
||||
import { NewKnowledgeList } from '@/features/new-rag/new-knowledge-list'
|
||||
@ -50,7 +49,7 @@ function LegacyList({
|
||||
const { push } = useRouter()
|
||||
const isCurrentWorkspaceOwner = useAtomValue(isCurrentWorkspaceOwnerAtom)
|
||||
const [showTagManagementModal, setShowTagManagementModal] = useState(false)
|
||||
const { showExternalApiPanel, setShowExternalApiPanel } = useExternalApiPanel()
|
||||
const [showExternalApiPanel, setShowExternalApiPanel] = useState(false)
|
||||
const [includeAll, { toggle: toggleIncludeAll }] = useBoolean(false)
|
||||
const invalidDatasetList = useInvalidDatasetList()
|
||||
useDocumentTitle(t(($) => $.knowledge, { ns: 'dataset' }))
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { createContext, use, useState } from 'react'
|
||||
|
||||
type ExternalApiPanelContextType = {
|
||||
showExternalApiPanel: boolean
|
||||
setShowExternalApiPanel: (show: boolean) => void
|
||||
}
|
||||
|
||||
const ExternalApiPanelContext = createContext<ExternalApiPanelContextType | undefined>(undefined)
|
||||
|
||||
export const ExternalApiPanelProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [showExternalApiPanel, setShowExternalApiPanel] = useState(false)
|
||||
|
||||
return (
|
||||
<ExternalApiPanelContext.Provider value={{ showExternalApiPanel, setShowExternalApiPanel }}>
|
||||
{children}
|
||||
</ExternalApiPanelContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useExternalApiPanel = () => {
|
||||
const context = use(ExternalApiPanelContext)
|
||||
if (context === undefined)
|
||||
throw new Error('useExternalApiPanel must be used within an ExternalApiPanelProvider')
|
||||
|
||||
return context
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import type { FC, ReactNode } from 'react'
|
||||
import type { ExternalAPIItem, ExternalAPIListResponse } from '@/models/datasets'
|
||||
import { createContext, use, useCallback, useMemo } from 'react'
|
||||
import { useExternalKnowledgeApiList } from '@/service/knowledge/use-dataset'
|
||||
|
||||
type ExternalKnowledgeApiContextType = {
|
||||
externalKnowledgeApiList: ExternalAPIItem[]
|
||||
mutateExternalKnowledgeApis: () => Promise<ExternalAPIListResponse | undefined>
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
const ExternalKnowledgeApiContext = createContext<ExternalKnowledgeApiContextType | undefined>(
|
||||
undefined,
|
||||
)
|
||||
|
||||
type ExternalKnowledgeApiProviderProps = {
|
||||
children: ReactNode
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
export const ExternalKnowledgeApiProvider: FC<ExternalKnowledgeApiProviderProps> = ({
|
||||
children,
|
||||
enabled = true,
|
||||
}) => {
|
||||
const { data, refetch, isLoading } = useExternalKnowledgeApiList({ enabled })
|
||||
|
||||
const mutateExternalKnowledgeApis = useCallback(() => {
|
||||
if (!enabled) return Promise.resolve(undefined)
|
||||
|
||||
return refetch().then((res) => res.data)
|
||||
}, [enabled, refetch])
|
||||
|
||||
const contextValue = useMemo<ExternalKnowledgeApiContextType>(
|
||||
() => ({
|
||||
externalKnowledgeApiList: data?.data || [],
|
||||
mutateExternalKnowledgeApis,
|
||||
isLoading,
|
||||
}),
|
||||
[data, mutateExternalKnowledgeApis, isLoading],
|
||||
)
|
||||
|
||||
return (
|
||||
<ExternalKnowledgeApiContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</ExternalKnowledgeApiContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useExternalKnowledgeApi = () => {
|
||||
const context = use(ExternalKnowledgeApiContext)
|
||||
if (context === undefined)
|
||||
throw new Error('useExternalKnowledgeApi must be used within a ExternalKnowledgeApiProvider')
|
||||
|
||||
return context
|
||||
}
|
||||
@ -16,10 +16,6 @@ type ListKnowledgeSpacesInfiniteOptions = {
|
||||
}
|
||||
}
|
||||
|
||||
const externalApiPanelMock = vi.hoisted(() => ({
|
||||
open: false,
|
||||
setOpen: vi.fn(),
|
||||
}))
|
||||
const toastInfoMock = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
@ -46,13 +42,6 @@ const permissionStateMock = vi.hoisted(() => ({
|
||||
workspacePermissionKeysAtom: Symbol('workspacePermissionKeysAtom'),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/external-api-panel-context', () => ({
|
||||
useExternalApiPanel: () => ({
|
||||
showExternalApiPanel: externalApiPanelMock.open,
|
||||
setShowExternalApiPanel: externalApiPanelMock.setOpen,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/knowledge/use-dataset', () => ({
|
||||
useDatasetApiBaseUrl: () => ({ data: { api_base_url: 'https://api.example.com' } }),
|
||||
}))
|
||||
@ -62,7 +51,14 @@ vi.mock('@/app/components/datasets/extra-info/service-api', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/datasets/external-api/external-api-panel', () => ({
|
||||
default: () => <div>external API panel</div>,
|
||||
default: ({ onClose }: { onClose: () => void }) => (
|
||||
<div>
|
||||
external API panel
|
||||
<button type="button" onClick={onClose}>
|
||||
close external API panel
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
@ -109,7 +105,6 @@ const setResolvedPage = (items: KnowledgeSpaceList['items'] = []) => {
|
||||
describe('NewKnowledgeList', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
externalApiPanelMock.open = false
|
||||
queryMock.data = undefined
|
||||
queryMock.error = null
|
||||
queryMock.hasNextPage = false
|
||||
@ -219,7 +214,9 @@ describe('NewKnowledgeList', () => {
|
||||
renderWithNuqs(<NewKnowledgeList view="new" onViewChange={vi.fn()} />)
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'dataset.externalAPIPanelTitle' }))
|
||||
expect(externalApiPanelMock.setOpen).toHaveBeenCalledWith(true)
|
||||
expect(screen.getByText('external API panel')).toBeInTheDocument()
|
||||
await user.click(screen.getByRole('button', { name: 'close external API panel' }))
|
||||
expect(screen.queryByText('external API panel')).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'dataset.serviceApi.title' })).toBeInTheDocument()
|
||||
|
||||
const tags = screen.getByRole('button', { name: 'dataset.newKnowledge.tags' })
|
||||
@ -296,6 +293,10 @@ describe('NewKnowledgeList', () => {
|
||||
expect(
|
||||
screen.queryByRole('link', { name: /common\.operation\.create/ }),
|
||||
).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('button', { name: 'dataset.externalAPIPanelTitle' }),
|
||||
).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('external API panel')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('dataset.newKnowledge.readOnlyEmpty')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
|
||||
@ -9,7 +9,6 @@ import { useTranslation } from 'react-i18next'
|
||||
import { SearchInput } from '@/app/components/base/search-input'
|
||||
import ExternalAPIPanel from '@/app/components/datasets/external-api/external-api-panel'
|
||||
import ServiceApi from '@/app/components/datasets/extra-info/service-api'
|
||||
import { useExternalApiPanel } from '@/context/external-api-panel-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/permission-state'
|
||||
import Link from '@/next/link'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
@ -60,7 +59,7 @@ export function NewKnowledgeList({
|
||||
const { t } = useTranslation('dataset')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const { data: apiBaseInfo } = useDatasetApiBaseUrl()
|
||||
const { showExternalApiPanel, setShowExternalApiPanel } = useExternalApiPanel()
|
||||
const [showExternalApiPanel, setShowExternalApiPanel] = useState(false)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const canCreate = hasPermission(workspacePermissionKeys, 'dataset.create_and_management')
|
||||
const canConnect = hasPermission(workspacePermissionKeys, 'dataset.external.connect')
|
||||
|
||||
@ -223,14 +223,6 @@ export type DataSetListResponse = {
|
||||
total: number
|
||||
}
|
||||
|
||||
export type ExternalAPIListResponse = {
|
||||
data: ExternalAPIItem[]
|
||||
has_more: boolean
|
||||
limit: number
|
||||
page: number
|
||||
total: number
|
||||
}
|
||||
|
||||
export type QA = {
|
||||
question: string
|
||||
answer: string
|
||||
|
||||
@ -6,7 +6,6 @@ import type {
|
||||
DatasetListRequest,
|
||||
DataSetListResponse,
|
||||
ErrorDocsResponse,
|
||||
ExternalAPIListResponse,
|
||||
FetchDatasetsParams,
|
||||
HitTestingRecordsResponse,
|
||||
IndexingStatusBatchRequest,
|
||||
@ -189,14 +188,6 @@ export const useInvalidateDatasetApiKeys = () => {
|
||||
}
|
||||
}
|
||||
|
||||
export const useExternalKnowledgeApiList = (options?: { enabled?: boolean }) => {
|
||||
return useQuery<ExternalAPIListResponse>({
|
||||
queryKey: [NAME_SPACE, 'external-knowledge-api'],
|
||||
queryFn: () => get<ExternalAPIListResponse>('/datasets/external-knowledge-api'),
|
||||
enabled: options?.enabled ?? true,
|
||||
})
|
||||
}
|
||||
|
||||
export const useDatasetTestingRecords = (
|
||||
datasetId?: string,
|
||||
params?: { page: number; limit: number },
|
||||
|
||||
Loading…
Reference in New Issue
Block a user