mirror of https://github.com/langgenius/dify.git
refactor(web): migrate tools/MCP/external-knowledge toast usage to UI toast and add i18n (#33797)
This commit is contained in:
parent
f35a4e5249
commit
4d538c3727
|
|
@ -164,7 +164,7 @@ describe('ExternalKnowledgeBaseConnector', () => {
|
|||
// Verify success notification
|
||||
expect(mockNotify).toHaveBeenCalledWith({
|
||||
type: 'success',
|
||||
title: 'External Knowledge Base Connected Successfully',
|
||||
title: 'dataset.externalKnowledgeForm.connectedSuccess',
|
||||
})
|
||||
|
||||
// Verify navigation back
|
||||
|
|
@ -206,7 +206,7 @@ describe('ExternalKnowledgeBaseConnector', () => {
|
|||
await waitFor(() => {
|
||||
expect(mockNotify).toHaveBeenCalledWith({
|
||||
type: 'error',
|
||||
title: 'Failed to connect External Knowledge Base',
|
||||
title: 'dataset.externalKnowledgeForm.connectedFailed',
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -228,7 +228,7 @@ describe('ExternalKnowledgeBaseConnector', () => {
|
|||
await waitFor(() => {
|
||||
expect(mockNotify).toHaveBeenCalledWith({
|
||||
type: 'error',
|
||||
title: 'Failed to connect External Knowledge Base',
|
||||
title: 'dataset.externalKnowledgeForm.connectedFailed',
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -274,7 +274,7 @@ describe('ExternalKnowledgeBaseConnector', () => {
|
|||
await waitFor(() => {
|
||||
expect(mockNotify).toHaveBeenCalledWith({
|
||||
type: 'success',
|
||||
title: 'External Knowledge Base Connected Successfully',
|
||||
title: 'dataset.externalKnowledgeForm.connectedSuccess',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import type { CreateKnowledgeBaseReq } from '@/app/components/datasets/external-knowledge-base/create/declarations'
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { trackEvent } from '@/app/components/base/amplitude'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import ExternalKnowledgeBaseCreate from '@/app/components/datasets/external-knowledge-base/create'
|
||||
|
|
@ -12,13 +13,14 @@ import { createExternalKnowledgeBase } from '@/service/datasets'
|
|||
const ExternalKnowledgeBaseConnector = () => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleConnect = async (formValue: CreateKnowledgeBaseReq) => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const result = await createExternalKnowledgeBase({ body: formValue })
|
||||
if (result && result.id) {
|
||||
toast.add({ type: 'success', title: 'External Knowledge Base Connected Successfully' })
|
||||
toast.add({ type: 'success', title: t('externalKnowledgeForm.connectedSuccess', { ns: 'dataset' }) })
|
||||
trackEvent('create_external_knowledge_base', {
|
||||
provider: formValue.provider,
|
||||
name: formValue.name,
|
||||
|
|
@ -29,7 +31,7 @@ const ExternalKnowledgeBaseConnector = () => {
|
|||
}
|
||||
catch (error) {
|
||||
console.error('Error creating external knowledge base:', error)
|
||||
toast.add({ type: 'error', title: 'Failed to connect External Knowledge Base' })
|
||||
toast.add({ type: 'error', title: t('externalKnowledgeForm.connectedFailed', { ns: 'dataset' }) })
|
||||
}
|
||||
setLoading(false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,24 @@
|
|||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { importSchemaFromURL } from '@/service/tools'
|
||||
import Toast from '../../../base/toast'
|
||||
import examples from '../examples'
|
||||
import GetSchema from '../get-schema'
|
||||
|
||||
vi.mock('@/service/tools', () => ({
|
||||
importSchemaFromURL: vi.fn(),
|
||||
}))
|
||||
const mockToastAdd = vi.hoisted(() => vi.fn())
|
||||
vi.mock('@/app/components/base/ui/toast', () => ({
|
||||
toast: {
|
||||
add: mockToastAdd,
|
||||
},
|
||||
}))
|
||||
const importSchemaFromURLMock = vi.mocked(importSchemaFromURL)
|
||||
|
||||
describe('GetSchema', () => {
|
||||
const notifySpy = vi.spyOn(Toast, 'notify')
|
||||
const mockOnChange = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
notifySpy.mockClear()
|
||||
importSchemaFromURLMock.mockReset()
|
||||
render(<GetSchema onChange={mockOnChange} />)
|
||||
})
|
||||
|
|
@ -27,9 +30,9 @@ describe('GetSchema', () => {
|
|||
fireEvent.change(input, { target: { value: 'ftp://invalid' } })
|
||||
fireEvent.click(screen.getByText('common.operation.ok'))
|
||||
|
||||
expect(notifySpy).toHaveBeenCalledWith({
|
||||
expect(mockToastAdd).toHaveBeenCalledWith({
|
||||
type: 'error',
|
||||
message: 'tools.createTool.urlError',
|
||||
title: 'tools.createTool.urlError',
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ import { useState } from 'react'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { importSchemaFromURL } from '@/service/tools'
|
||||
import Toast from '../../base/toast'
|
||||
import examples from './examples'
|
||||
|
||||
type Props = {
|
||||
|
|
@ -27,9 +27,9 @@ const GetSchema: FC<Props> = ({
|
|||
const [isParsing, setIsParsing] = useState(false)
|
||||
const handleImportFromUrl = async () => {
|
||||
if (!importUrl.startsWith('http://') && !importUrl.startsWith('https://')) {
|
||||
Toast.notify({
|
||||
toast.add({
|
||||
type: 'error',
|
||||
message: t('createTool.urlError', { ns: 'tools' }),
|
||||
title: t('createTool.urlError', { ns: 'tools' }),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type { ToolWithProvider } from '@/app/components/workflow/types'
|
|||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import MCPModal from '../modal'
|
||||
|
||||
// Mock the service API
|
||||
|
|
@ -48,7 +48,18 @@ vi.mock('@/service/use-plugins', () => ({
|
|||
}),
|
||||
}))
|
||||
|
||||
const mockToastAdd = vi.hoisted(() => vi.fn())
|
||||
vi.mock('@/app/components/base/ui/toast', () => ({
|
||||
toast: {
|
||||
add: mockToastAdd,
|
||||
},
|
||||
}))
|
||||
|
||||
describe('MCPModal', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
const createWrapper = () => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
|
|
@ -299,6 +310,10 @@ describe('MCPModal', () => {
|
|||
// Wait a bit and verify onConfirm was not called
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
expect(onConfirm).not.toHaveBeenCalled()
|
||||
expect(mockToastAdd).toHaveBeenCalledWith({
|
||||
type: 'error',
|
||||
title: 'tools.mcp.modal.invalidServerUrl',
|
||||
})
|
||||
})
|
||||
|
||||
it('should not call onConfirm with invalid server identifier', async () => {
|
||||
|
|
@ -320,6 +335,10 @@ describe('MCPModal', () => {
|
|||
// Wait a bit and verify onConfirm was not called
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
expect(onConfirm).not.toHaveBeenCalled()
|
||||
expect(mockToastAdd).toHaveBeenCalledWith({
|
||||
type: 'error',
|
||||
title: 'tools.mcp.modal.invalidServerIdentifier',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { Mcp } from '@/app/components/base/icons/src/vender/other'
|
|||
import Input from '@/app/components/base/input'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import TabSlider from '@/app/components/base/tab-slider'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { MCPAuthMethod } from '@/app/components/tools/types'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { shouldUseMcpIconForAppIcon } from '@/utils/mcp'
|
||||
|
|
@ -82,11 +82,11 @@ const MCPModalContent: FC<MCPModalContentProps> = ({
|
|||
|
||||
const submit = async () => {
|
||||
if (!isValidUrl(state.url)) {
|
||||
Toast.notify({ type: 'error', message: 'invalid server url' })
|
||||
toast.add({ type: 'error', title: t('mcp.modal.invalidServerUrl', { ns: 'tools' }) })
|
||||
return
|
||||
}
|
||||
if (!isValidServerID(state.serverIdentifier.trim())) {
|
||||
Toast.notify({ type: 'error', message: 'invalid server identifier' })
|
||||
toast.add({ type: 'error', title: t('mcp.modal.invalidServerIdentifier', { ns: 'tools' }) })
|
||||
return
|
||||
}
|
||||
const formattedHeaders = state.headers.reduce((acc, item) => {
|
||||
|
|
|
|||
|
|
@ -70,11 +70,11 @@ vi.mock('@/app/components/tools/edit-custom-collection-modal', () => ({
|
|||
},
|
||||
}))
|
||||
|
||||
// Mock Toast
|
||||
// Mock toast
|
||||
const mockToastNotify = vi.fn()
|
||||
vi.mock('@/app/components/base/toast', () => ({
|
||||
default: {
|
||||
notify: (options: { type: string, message: string }) => mockToastNotify(options),
|
||||
vi.mock('@/app/components/base/ui/toast', () => ({
|
||||
toast: {
|
||||
add: (options: { type: string, title: string }) => mockToastNotify(options),
|
||||
},
|
||||
}))
|
||||
|
||||
|
|
@ -200,7 +200,7 @@ describe('CustomCreateCard', () => {
|
|||
await waitFor(() => {
|
||||
expect(mockToastNotify).toHaveBeenCalledWith({
|
||||
type: 'success',
|
||||
message: expect.any(String),
|
||||
title: expect.any(String),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -92,8 +92,9 @@ vi.mock('@/app/components/base/confirm', () => ({
|
|||
: null,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/toast', () => ({
|
||||
default: { notify: vi.fn() },
|
||||
const mockToastAdd = vi.hoisted(() => vi.fn())
|
||||
vi.mock('@/app/components/base/ui/toast', () => ({
|
||||
toast: { add: mockToastAdd },
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/header/indicator', () => ({
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
} from '@remixicon/react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { createCustomCollection } from '@/service/tools'
|
||||
|
|
@ -21,9 +21,9 @@ const Contribute = ({ onRefreshData }: Props) => {
|
|||
const [isShowEditCollectionToolModal, setIsShowEditCustomCollectionModal] = useState(false)
|
||||
const doCreateCustomToolCollection = async (data: CustomCollectionBackend) => {
|
||||
await createCustomCollection(data)
|
||||
Toast.notify({
|
||||
toast.add({
|
||||
type: 'success',
|
||||
message: t('api.actionSuccess', { ns: 'common' }),
|
||||
title: t('api.actionSuccess', { ns: 'common' }),
|
||||
})
|
||||
setIsShowEditCustomCollectionModal(false)
|
||||
onRefreshData()
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import Confirm from '@/app/components/base/confirm'
|
|||
import Drawer from '@/app/components/base/drawer'
|
||||
import { LinkExternal02, Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { toast } from '@/app/components/base/ui/toast'
|
||||
import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import Icon from '@/app/components/plugins/card/base/card-icon'
|
||||
|
|
@ -122,18 +122,18 @@ const ProviderDetail = ({
|
|||
await getCustomProvider()
|
||||
// Use fresh data from form submission to avoid race condition with collection.labels
|
||||
setCustomCollection(prev => prev ? { ...prev, labels: data.labels } : null)
|
||||
Toast.notify({
|
||||
toast.add({
|
||||
type: 'success',
|
||||
message: t('api.actionSuccess', { ns: 'common' }),
|
||||
title: t('api.actionSuccess', { ns: 'common' }),
|
||||
})
|
||||
setIsShowEditCustomCollectionModal(false)
|
||||
}
|
||||
const doRemoveCustomToolCollection = async () => {
|
||||
await removeCustomCollection(collection?.name as string)
|
||||
onRefreshData()
|
||||
Toast.notify({
|
||||
toast.add({
|
||||
type: 'success',
|
||||
message: t('api.actionSuccess', { ns: 'common' }),
|
||||
title: t('api.actionSuccess', { ns: 'common' }),
|
||||
})
|
||||
setIsShowEditCustomCollectionModal(false)
|
||||
}
|
||||
|
|
@ -161,9 +161,9 @@ const ProviderDetail = ({
|
|||
const removeWorkflowToolProvider = async () => {
|
||||
await deleteWorkflowTool(collection.id)
|
||||
onRefreshData()
|
||||
Toast.notify({
|
||||
toast.add({
|
||||
type: 'success',
|
||||
message: t('api.actionSuccess', { ns: 'common' }),
|
||||
title: t('api.actionSuccess', { ns: 'common' }),
|
||||
})
|
||||
setIsShowEditWorkflowToolModal(false)
|
||||
}
|
||||
|
|
@ -175,9 +175,9 @@ const ProviderDetail = ({
|
|||
invalidateAllWorkflowTools()
|
||||
onRefreshData()
|
||||
getWorkflowToolProvider()
|
||||
Toast.notify({
|
||||
toast.add({
|
||||
type: 'success',
|
||||
message: t('api.actionSuccess', { ns: 'common' }),
|
||||
title: t('api.actionSuccess', { ns: 'common' }),
|
||||
})
|
||||
setIsShowEditWorkflowToolModal(false)
|
||||
}
|
||||
|
|
@ -385,18 +385,18 @@ const ProviderDetail = ({
|
|||
onCancel={() => setShowSettingAuth(false)}
|
||||
onSaved={async (value) => {
|
||||
await updateBuiltInToolCredential(collection.name, value)
|
||||
Toast.notify({
|
||||
toast.add({
|
||||
type: 'success',
|
||||
message: t('api.actionSuccess', { ns: 'common' }),
|
||||
title: t('api.actionSuccess', { ns: 'common' }),
|
||||
})
|
||||
await onRefreshData()
|
||||
setShowSettingAuth(false)
|
||||
}}
|
||||
onRemove={async () => {
|
||||
await removeBuiltInToolCredential(collection.name)
|
||||
Toast.notify({
|
||||
toast.add({
|
||||
type: 'success',
|
||||
message: t('api.actionSuccess', { ns: 'common' }),
|
||||
title: t('api.actionSuccess', { ns: 'common' }),
|
||||
})
|
||||
await onRefreshData()
|
||||
setShowSettingAuth(false)
|
||||
|
|
|
|||
|
|
@ -5928,9 +5928,6 @@
|
|||
}
|
||||
},
|
||||
"app/components/tools/edit-custom-collection-modal/get-schema.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 3
|
||||
},
|
||||
|
|
@ -6056,7 +6053,7 @@
|
|||
},
|
||||
"app/components/tools/mcp/modal.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
},
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 7
|
||||
|
|
@ -6097,16 +6094,13 @@
|
|||
}
|
||||
},
|
||||
"app/components/tools/provider/custom-create-card.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/tools/provider/detail.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
},
|
||||
"tailwindcss/enforce-consistent-class-order": {
|
||||
"count": 10
|
||||
|
|
|
|||
|
|
@ -77,6 +77,8 @@
|
|||
"externalKnowledgeDescriptionPlaceholder": "Describe what's in this Knowledge Base (optional)",
|
||||
"externalKnowledgeForm.cancel": "Cancel",
|
||||
"externalKnowledgeForm.connect": "Connect",
|
||||
"externalKnowledgeForm.connectedFailed": "Failed to connect External Knowledge Base",
|
||||
"externalKnowledgeForm.connectedSuccess": "External Knowledge Base Connected Successfully",
|
||||
"externalKnowledgeId": "External Knowledge ID",
|
||||
"externalKnowledgeIdPlaceholder": "Please enter the Knowledge ID",
|
||||
"externalKnowledgeName": "External Knowledge Name",
|
||||
|
|
|
|||
|
|
@ -126,6 +126,8 @@
|
|||
"mcp.modal.headerValuePlaceholder": "e.g., Bearer token123",
|
||||
"mcp.modal.headers": "Headers",
|
||||
"mcp.modal.headersTip": "Additional HTTP headers to send with MCP server requests",
|
||||
"mcp.modal.invalidServerIdentifier": "Please enter a valid server identifier",
|
||||
"mcp.modal.invalidServerUrl": "Please enter a valid server URL",
|
||||
"mcp.modal.maskedHeadersTip": "Header values are masked for security. Changes will update the actual values.",
|
||||
"mcp.modal.name": "Name & Icon",
|
||||
"mcp.modal.namePlaceholder": "Name your MCP server",
|
||||
|
|
|
|||
Loading…
Reference in New Issue