mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 11:04:27 +08:00
refactor(web): move app quota refresh out of provider context (#41906)
This commit is contained in:
parent
40f0b8a2a0
commit
3be0cabf0a
@ -20,7 +20,6 @@ export const baseProviderContextValue: ProviderContextState = {
|
|||||||
isFetchedPlanInfo: false,
|
isFetchedPlanInfo: false,
|
||||||
enableBilling: false,
|
enableBilling: false,
|
||||||
enableSkill: false,
|
enableSkill: false,
|
||||||
onPlanInfoChanged: noop,
|
|
||||||
enableReplaceWebAppLogo: false,
|
enableReplaceWebAppLogo: false,
|
||||||
modelLoadBalancingEnabled: false,
|
modelLoadBalancingEnabled: false,
|
||||||
enableEducationPlan: false,
|
enableEducationPlan: false,
|
||||||
@ -38,7 +37,6 @@ export const createMockProviderContextValue = (
|
|||||||
return {
|
return {
|
||||||
...merged,
|
...merged,
|
||||||
refreshModelProviders: merged.refreshModelProviders ?? noop,
|
refreshModelProviders: merged.refreshModelProviders ?? noop,
|
||||||
onPlanInfoChanged: merged.onPlanInfoChanged ?? noop,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { act, renderHook } from '@testing-library/react'
|
import { act, renderHook } from '@testing-library/react'
|
||||||
import { consoleQuery } from '@/service/client'
|
import { consoleQuery } from '@/service/client'
|
||||||
import { AppModeEnum } from '@/types/app'
|
import { AppModeEnum } from '@/types/app'
|
||||||
|
import { getRedirection } from '@/utils/app-redirection'
|
||||||
import { useAppInfoActions } from '../use-app-info-actions'
|
import { useAppInfoActions } from '../use-app-info-actions'
|
||||||
|
|
||||||
const toastMocks = vi.hoisted(() => {
|
const toastMocks = vi.hoisted(() => {
|
||||||
@ -16,7 +17,6 @@ const toastMocks = vi.hoisted(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
const mockReplace = vi.fn()
|
const mockReplace = vi.fn()
|
||||||
const mockOnPlanInfoChanged = vi.fn()
|
|
||||||
const mockInvalidateQueries = vi.fn()
|
const mockInvalidateQueries = vi.fn()
|
||||||
const mockSetAppDetail = vi.fn()
|
const mockSetAppDetail = vi.fn()
|
||||||
const mockUpdateAppInfo = vi.fn()
|
const mockUpdateAppInfo = vi.fn()
|
||||||
@ -47,10 +47,6 @@ vi.mock('@/next/navigation', () => ({
|
|||||||
useRouter: () => ({ replace: mockReplace }),
|
useRouter: () => ({ replace: mockReplace }),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('@/context/provider-context', () => ({
|
|
||||||
useProviderContext: () => ({ onPlanInfoChanged: mockOnPlanInfoChanged }),
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mock('@/app/components/app/store', () => ({
|
vi.mock('@/app/components/app/store', () => ({
|
||||||
useStore: (selector: (state: Record<string, unknown>) => unknown) =>
|
useStore: (selector: (state: Record<string, unknown>) => unknown) =>
|
||||||
selector({
|
selector({
|
||||||
@ -87,6 +83,13 @@ vi.mock('@tanstack/react-query', () => ({
|
|||||||
useSuspenseQuery: () => ({
|
useSuspenseQuery: () => ({
|
||||||
data: { rbac_enabled: true },
|
data: { rbac_enabled: true },
|
||||||
}),
|
}),
|
||||||
|
useMutation: ({ mutationKey }: { mutationKey: unknown }) => ({
|
||||||
|
mutateAsync:
|
||||||
|
JSON.stringify(mutationKey) ===
|
||||||
|
JSON.stringify(consoleQuery.apps.byAppId.copy.post.mutationOptions().mutationKey)
|
||||||
|
? mockCopyApp
|
||||||
|
: mockDeleteApp,
|
||||||
|
}),
|
||||||
useQueryClient: () => ({
|
useQueryClient: () => ({
|
||||||
invalidateQueries: mockInvalidateQueries,
|
invalidateQueries: mockInvalidateQueries,
|
||||||
setQueryData: mockSetQueryData,
|
setQueryData: mockSetQueryData,
|
||||||
@ -95,8 +98,6 @@ vi.mock('@tanstack/react-query', () => ({
|
|||||||
|
|
||||||
vi.mock('@/service/apps', () => ({
|
vi.mock('@/service/apps', () => ({
|
||||||
updateAppInfo: (...args: unknown[]) => mockUpdateAppInfo(...args),
|
updateAppInfo: (...args: unknown[]) => mockUpdateAppInfo(...args),
|
||||||
copyApp: (...args: unknown[]) => mockCopyApp(...args),
|
|
||||||
deleteApp: (...args: unknown[]) => mockDeleteApp(...args),
|
|
||||||
fetchAppDetail: (...args: unknown[]) => mockFetchAppDetail(...args),
|
fetchAppDetail: (...args: unknown[]) => mockFetchAppDetail(...args),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -306,29 +307,50 @@ describe('useAppInfoActions', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('onCopy', () => {
|
describe('onCopy', () => {
|
||||||
it('should copy app and redirect on success', async () => {
|
it.each(['completed', 'pending'] as const)(
|
||||||
const newApp = { id: 'app-2', name: 'Copy', mode: 'chat' }
|
'should redirect only when the copy is completed (%s)',
|
||||||
mockCopyApp.mockResolvedValue(newApp)
|
async (status) => {
|
||||||
|
const newApp = { id: 'app-2', name: 'Copy', mode: 'chat' }
|
||||||
|
mockCopyApp.mockResolvedValue(
|
||||||
|
status === 'completed'
|
||||||
|
? newApp
|
||||||
|
: {
|
||||||
|
id: 'import-1',
|
||||||
|
status: 'pending',
|
||||||
|
current_dsl_version: '1.0.0',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const { result } = renderHook(() => useAppInfoActions({}))
|
const { result } = renderHook(() => useAppInfoActions({}))
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await result.current.onCopy({
|
await result.current.onCopy({
|
||||||
name: 'Copy',
|
name: 'Copy',
|
||||||
icon_type: 'emoji',
|
icon_type: 'emoji',
|
||||||
icon: '🤖',
|
icon: '🤖',
|
||||||
icon_background: '#fff',
|
icon_background: '#fff',
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
expect(mockCopyApp).toHaveBeenCalled()
|
expect(mockCopyApp).toHaveBeenCalledWith({
|
||||||
expect(mockInvalidateQueries).toHaveBeenCalledTimes(3)
|
params: { app_id: 'app-1' },
|
||||||
expect(toastMocks.call).toHaveBeenCalledWith({
|
body: { name: 'Copy', icon_type: 'emoji', icon: '🤖', icon_background: '#fff' },
|
||||||
type: 'success',
|
})
|
||||||
message: 'app.newApp.appCreated',
|
if (status === 'completed') {
|
||||||
})
|
expect(toastMocks.call).toHaveBeenCalledWith({
|
||||||
expect(mockOnPlanInfoChanged).toHaveBeenCalled()
|
type: 'success',
|
||||||
})
|
message: 'app.newApp.appCreated',
|
||||||
|
})
|
||||||
|
expect(getRedirection).toHaveBeenCalledWith(newApp, mockReplace, { isRbacEnabled: true })
|
||||||
|
} else {
|
||||||
|
expect(toastMocks.call).toHaveBeenCalledWith({
|
||||||
|
type: 'error',
|
||||||
|
message: 'app.newApp.appCreateFailed',
|
||||||
|
})
|
||||||
|
expect(getRedirection).not.toHaveBeenCalled()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
it('should notify error on copy failure', async () => {
|
it('should notify error on copy failure', async () => {
|
||||||
mockCopyApp.mockRejectedValue(new Error('fail'))
|
mockCopyApp.mockRejectedValue(new Error('fail'))
|
||||||
@ -507,12 +529,11 @@ describe('useAppInfoActions', () => {
|
|||||||
await result.current.onConfirmDelete()
|
await result.current.onConfirmDelete()
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(mockDeleteApp).toHaveBeenCalledWith('app-1')
|
expect(mockDeleteApp).toHaveBeenCalledWith({ params: { app_id: 'app-1' } })
|
||||||
expect(mockMarkAppDeletionStarted).toHaveBeenCalledWith('app-1')
|
expect(mockMarkAppDeletionStarted).toHaveBeenCalledWith('app-1')
|
||||||
expect(mockMarkAppDeletionSucceeded).toHaveBeenCalledWith('app-1')
|
expect(mockMarkAppDeletionSucceeded).toHaveBeenCalledWith('app-1')
|
||||||
expect(mockMarkAppDeletionFailed).not.toHaveBeenCalled()
|
expect(mockMarkAppDeletionFailed).not.toHaveBeenCalled()
|
||||||
expect(toastMocks.call).toHaveBeenCalledWith({ type: 'success', message: 'app.appDeleted' })
|
expect(toastMocks.call).toHaveBeenCalledWith({ type: 'success', message: 'app.appDeleted' })
|
||||||
expect(mockInvalidateQueries).toHaveBeenCalledTimes(3)
|
|
||||||
expect(mockReplace).toHaveBeenCalledWith('/apps')
|
expect(mockReplace).toHaveBeenCalledWith('/apps')
|
||||||
expect(mockSetAppDetail).toHaveBeenCalledWith()
|
expect(mockSetAppDetail).toHaveBeenCalledWith()
|
||||||
})
|
})
|
||||||
|
|||||||
@ -7,12 +7,11 @@ import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-moda
|
|||||||
import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
|
import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
|
||||||
import type { App } from '@/types/app'
|
import type { App } from '@/types/app'
|
||||||
import { toast } from '@langgenius/dify-ui/toast'
|
import { toast } from '@langgenius/dify-ui/toast'
|
||||||
import { useQueryClient, useSuspenseQuery } from '@tanstack/react-query'
|
import { useMutation, useQueryClient, useSuspenseQuery } from '@tanstack/react-query'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||||
import { useExportAppDsl, useExportWorkflowAppDsl } from '@/app/components/app/use-export-app-dsl'
|
import { useExportAppDsl, useExportWorkflowAppDsl } from '@/app/components/app/use-export-app-dsl'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
|
||||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||||
import { useRouter } from '@/next/navigation'
|
import { useRouter } from '@/next/navigation'
|
||||||
import {
|
import {
|
||||||
@ -20,7 +19,7 @@ import {
|
|||||||
markAppDeletionStarted,
|
markAppDeletionStarted,
|
||||||
markAppDeletionSucceeded,
|
markAppDeletionSucceeded,
|
||||||
} from '@/service/app-deletion'
|
} from '@/service/app-deletion'
|
||||||
import { copyApp, deleteApp, fetchAppDetail, updateAppInfo } from '@/service/apps'
|
import { fetchAppDetail, updateAppInfo } from '@/service/apps'
|
||||||
import { consoleQuery } from '@/service/client'
|
import { consoleQuery } from '@/service/client'
|
||||||
import { AppModeEnum } from '@/types/app'
|
import { AppModeEnum } from '@/types/app'
|
||||||
import { getRedirection } from '@/utils/app-redirection'
|
import { getRedirection } from '@/utils/app-redirection'
|
||||||
@ -94,7 +93,10 @@ export function useAppInfoActions({ resetKey }: UseAppInfoActionsParams) {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { replace } = useRouter()
|
const { replace } = useRouter()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const { onPlanInfoChanged } = useProviderContext()
|
const { mutateAsync: copyApp } = useMutation(
|
||||||
|
consoleQuery.apps.byAppId.copy.post.mutationOptions(),
|
||||||
|
)
|
||||||
|
const { mutateAsync: deleteApp } = useMutation(consoleQuery.apps.byAppId.delete.mutationOptions())
|
||||||
const appDetail = useAppStore((state) => state.appDetail)
|
const appDetail = useAppStore((state) => state.appDetail)
|
||||||
const setAppDetail = useAppStore((state) => state.setAppDetail)
|
const setAppDetail = useAppStore((state) => state.setAppDetail)
|
||||||
const { exportAppDsl, isExporting: isAppDslExporting } = useExportAppDsl()
|
const { exportAppDsl, isExporting: isAppDslExporting } = useExportAppDsl()
|
||||||
@ -251,22 +253,21 @@ export function useAppInfoActions({ resetKey }: UseAppInfoActionsParams) {
|
|||||||
if (!appDetail) return
|
if (!appDetail) return
|
||||||
try {
|
try {
|
||||||
const newApp = await copyApp({
|
const newApp = await copyApp({
|
||||||
appID: appDetail.id,
|
params: { app_id: appDetail.id },
|
||||||
name,
|
body: { name, icon_type, icon, icon_background },
|
||||||
icon_type,
|
|
||||||
icon,
|
|
||||||
icon_background,
|
|
||||||
mode: appDetail.mode,
|
|
||||||
})
|
})
|
||||||
|
if (!('mode' in newApp)) {
|
||||||
|
toast(
|
||||||
|
t(($) => $['newApp.appCreateFailed'], { ns: 'app' }),
|
||||||
|
{ type: 'error' },
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
closeModal()
|
closeModal()
|
||||||
toast(
|
toast(
|
||||||
t(($) => $['newApp.appCreated'], { ns: 'app' }),
|
t(($) => $['newApp.appCreated'], { ns: 'app' }),
|
||||||
{ type: 'success' },
|
{ type: 'success' },
|
||||||
)
|
)
|
||||||
void queryClient.invalidateQueries({ queryKey: consoleQuery.apps.get.key() })
|
|
||||||
void queryClient.invalidateQueries({ queryKey: consoleQuery.apps.starred.get.key() })
|
|
||||||
void queryClient.invalidateQueries({ queryKey: consoleQuery.apps.recent.get.key() })
|
|
||||||
onPlanInfoChanged()
|
|
||||||
getRedirection(newApp, replace, { isRbacEnabled })
|
getRedirection(newApp, replace, { isRbacEnabled })
|
||||||
} catch {
|
} catch {
|
||||||
toast(
|
toast(
|
||||||
@ -275,7 +276,7 @@ export function useAppInfoActions({ resetKey }: UseAppInfoActionsParams) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[appDetail, closeModal, isRbacEnabled, onPlanInfoChanged, queryClient, replace, t],
|
[appDetail, closeModal, copyApp, isRbacEnabled, replace, t],
|
||||||
)
|
)
|
||||||
|
|
||||||
const onExport = useCallback(
|
const onExport = useCallback(
|
||||||
@ -313,16 +314,12 @@ export function useAppInfoActions({ resetKey }: UseAppInfoActionsParams) {
|
|||||||
if (!appDetail) return
|
if (!appDetail) return
|
||||||
markAppDeletionStarted(appDetail.id)
|
markAppDeletionStarted(appDetail.id)
|
||||||
try {
|
try {
|
||||||
await deleteApp(appDetail.id)
|
await deleteApp({ params: { app_id: appDetail.id } })
|
||||||
markAppDeletionSucceeded(appDetail.id)
|
markAppDeletionSucceeded(appDetail.id)
|
||||||
toast(
|
toast(
|
||||||
t(($) => $.appDeleted, { ns: 'app' }),
|
t(($) => $.appDeleted, { ns: 'app' }),
|
||||||
{ type: 'success' },
|
{ type: 'success' },
|
||||||
)
|
)
|
||||||
void queryClient.invalidateQueries({ queryKey: consoleQuery.apps.get.key() })
|
|
||||||
void queryClient.invalidateQueries({ queryKey: consoleQuery.apps.starred.get.key() })
|
|
||||||
void queryClient.invalidateQueries({ queryKey: consoleQuery.apps.recent.get.key() })
|
|
||||||
onPlanInfoChanged()
|
|
||||||
setAppDetail()
|
setAppDetail()
|
||||||
replace('/apps')
|
replace('/apps')
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
@ -333,7 +330,7 @@ export function useAppInfoActions({ resetKey }: UseAppInfoActionsParams) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
closeModal()
|
closeModal()
|
||||||
}, [appDetail, closeModal, onPlanInfoChanged, queryClient, replace, setAppDetail, t])
|
}, [appDetail, closeModal, deleteApp, replace, setAppDetail, t])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
appDetail,
|
appDetail,
|
||||||
|
|||||||
@ -125,7 +125,6 @@ function createMockProviderContext(
|
|||||||
ttsDefaultModel: null,
|
ttsDefaultModel: null,
|
||||||
agentThoughtDefaultModel: null,
|
agentThoughtDefaultModel: null,
|
||||||
updateModelList: vi.fn(),
|
updateModelList: vi.fn(),
|
||||||
onPlanInfoChanged: vi.fn(),
|
|
||||||
refreshModelProviders: vi.fn(),
|
refreshModelProviders: vi.fn(),
|
||||||
...overrides,
|
...overrides,
|
||||||
} as ProviderContextState
|
} as ProviderContextState
|
||||||
|
|||||||
@ -5,14 +5,14 @@ import CreateAppTemplateDialog from '../index'
|
|||||||
vi.mock('../app-list', () => ({
|
vi.mock('../app-list', () => ({
|
||||||
default: function MockAppList({
|
default: function MockAppList({
|
||||||
onCreateFromBlank,
|
onCreateFromBlank,
|
||||||
onSuccess,
|
onClose,
|
||||||
}: {
|
}: {
|
||||||
onCreateFromBlank?: () => void
|
onCreateFromBlank?: () => void
|
||||||
onSuccess: () => void
|
onClose: () => void
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div role="region" aria-label="App list">
|
<div role="region" aria-label="App list">
|
||||||
<button type="button" onClick={onSuccess}>
|
<button type="button" onClick={onClose}>
|
||||||
Success
|
Success
|
||||||
</button>
|
</button>
|
||||||
{onCreateFromBlank && (
|
{onCreateFromBlank && (
|
||||||
@ -28,7 +28,6 @@ vi.mock('../app-list', () => ({
|
|||||||
describe('CreateAppTemplateDialog', () => {
|
describe('CreateAppTemplateDialog', () => {
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
show: false,
|
show: false,
|
||||||
onSuccess: vi.fn(),
|
|
||||||
onClose: vi.fn(),
|
onClose: vi.fn(),
|
||||||
onCreateFromBlank: vi.fn(),
|
onCreateFromBlank: vi.fn(),
|
||||||
}
|
}
|
||||||
@ -103,21 +102,12 @@ describe('CreateAppTemplateDialog', () => {
|
|||||||
expect(screen.getByRole('button', { name: 'Success' }))!.toBeInTheDocument()
|
expect(screen.getByRole('button', { name: 'Success' }))!.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call both onSuccess and onClose when app list success is triggered', () => {
|
it('should close when an app is created from the list', () => {
|
||||||
const mockOnSuccess = vi.fn()
|
|
||||||
const mockOnClose = vi.fn()
|
const mockOnClose = vi.fn()
|
||||||
render(
|
render(<CreateAppTemplateDialog {...defaultProps} show={true} onClose={mockOnClose} />)
|
||||||
<CreateAppTemplateDialog
|
|
||||||
{...defaultProps}
|
|
||||||
show={true}
|
|
||||||
onSuccess={mockOnSuccess}
|
|
||||||
onClose={mockOnClose}
|
|
||||||
/>,
|
|
||||||
)
|
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: 'Success' }))
|
fireEvent.click(screen.getByRole('button', { name: 'Success' }))
|
||||||
|
|
||||||
expect(mockOnSuccess).toHaveBeenCalledTimes(1)
|
|
||||||
expect(mockOnClose).toHaveBeenCalledTimes(1)
|
expect(mockOnClose).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -143,7 +133,6 @@ describe('CreateAppTemplateDialog', () => {
|
|||||||
render(
|
render(
|
||||||
<CreateAppTemplateDialog
|
<CreateAppTemplateDialog
|
||||||
show={true}
|
show={true}
|
||||||
onSuccess={vi.fn()}
|
|
||||||
onClose={vi.fn()}
|
onClose={vi.fn()}
|
||||||
// onCreateFromBlank is undefined
|
// onCreateFromBlank is undefined
|
||||||
/>,
|
/>,
|
||||||
@ -154,12 +143,7 @@ describe('CreateAppTemplateDialog', () => {
|
|||||||
it('should handle undefined props gracefully', () => {
|
it('should handle undefined props gracefully', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
render(
|
render(
|
||||||
<CreateAppTemplateDialog
|
<CreateAppTemplateDialog show={true} onClose={vi.fn()} onCreateFromBlank={undefined} />,
|
||||||
show={true}
|
|
||||||
onSuccess={vi.fn()}
|
|
||||||
onClose={vi.fn()}
|
|
||||||
onCreateFromBlank={undefined}
|
|
||||||
/>,
|
|
||||||
)
|
)
|
||||||
}).not.toThrow()
|
}).not.toThrow()
|
||||||
})
|
})
|
||||||
@ -193,7 +177,6 @@ describe('CreateAppTemplateDialog', () => {
|
|||||||
it('should work with all required props only', () => {
|
it('should work with all required props only', () => {
|
||||||
const requiredProps = {
|
const requiredProps = {
|
||||||
show: true,
|
show: true,
|
||||||
onSuccess: vi.fn(),
|
|
||||||
onClose: vi.fn(),
|
onClose: vi.fn(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -247,7 +247,7 @@ describe('Apps', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('renders template cards when data is available', () => {
|
it('renders template cards when data is available', () => {
|
||||||
render(<Apps />)
|
render(<Apps onClose={vi.fn()} />)
|
||||||
|
|
||||||
expect(screen.getAllByTestId('app-card')).toHaveLength(6)
|
expect(screen.getAllByTestId('app-card')).toHaveLength(6)
|
||||||
expect(screen.getByText('Alpha'))!.toBeInTheDocument()
|
expect(screen.getByText('Alpha'))!.toBeInTheDocument()
|
||||||
@ -255,7 +255,7 @@ describe('Apps', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('opens create modal when a template card is clicked', () => {
|
it('opens create modal when a template card is clicked', () => {
|
||||||
render(<Apps />)
|
render(<Apps onClose={vi.fn()} />)
|
||||||
|
|
||||||
fireEvent.click(screen.getAllByTestId('app-card')[0]!)
|
fireEvent.click(screen.getAllByTestId('app-card')[0]!)
|
||||||
expect(screen.getByTestId('create-from-template-modal'))!.toBeInTheDocument()
|
expect(screen.getByTestId('create-from-template-modal'))!.toBeInTheDocument()
|
||||||
@ -264,7 +264,7 @@ describe('Apps', () => {
|
|||||||
it('passes app.create_and_management permission to template cards even when user is not a workspace editor', () => {
|
it('passes app.create_and_management permission to template cards even when user is not a workspace editor', () => {
|
||||||
mockWorkspacePermissionKeys = ['app.create_and_management']
|
mockWorkspacePermissionKeys = ['app.create_and_management']
|
||||||
|
|
||||||
render(<Apps />)
|
render(<Apps onClose={vi.fn()} />)
|
||||||
|
|
||||||
expect(screen.getAllByTestId('app-card')[0]).toHaveAttribute('data-can-create', 'true')
|
expect(screen.getAllByTestId('app-card')[0]).toHaveAttribute('data-can-create', 'true')
|
||||||
})
|
})
|
||||||
@ -272,7 +272,7 @@ describe('Apps', () => {
|
|||||||
it('does not allow template creation when app.create_and_management permission is missing', () => {
|
it('does not allow template creation when app.create_and_management permission is missing', () => {
|
||||||
mockWorkspacePermissionKeys = []
|
mockWorkspacePermissionKeys = []
|
||||||
|
|
||||||
render(<Apps />)
|
render(<Apps onClose={vi.fn()} />)
|
||||||
|
|
||||||
expect(screen.getAllByTestId('app-card')[0]).toHaveAttribute('data-can-create', 'false')
|
expect(screen.getAllByTestId('app-card')[0]).toHaveAttribute('data-can-create', 'false')
|
||||||
})
|
})
|
||||||
@ -283,14 +283,14 @@ describe('Apps', () => {
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
render(<Apps />)
|
render(<Apps onClose={vi.fn()} />)
|
||||||
|
|
||||||
expect(screen.getByText('app.newApp.noTemplateFound'))!.toBeInTheDocument()
|
expect(screen.getByText('app.newApp.noTemplateFound'))!.toBeInTheDocument()
|
||||||
expect(screen.getByText('app.newApp.noTemplateFoundTip'))!.toBeInTheDocument()
|
expect(screen.getByText('app.newApp.noTemplateFoundTip'))!.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('filters templates by keyword and selected app type', async () => {
|
it('filters templates by keyword and selected app type', async () => {
|
||||||
render(<Apps />)
|
render(<Apps onClose={vi.fn()} />)
|
||||||
|
|
||||||
fireEvent.change(screen.getByPlaceholderText('app.newAppFromTemplate.searchAllTemplate'), {
|
fireEvent.change(screen.getByPlaceholderText('app.newAppFromTemplate.searchAllTemplate'), {
|
||||||
target: { value: 'Bravo' },
|
target: { value: 'Bravo' },
|
||||||
@ -314,9 +314,9 @@ describe('Apps', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('creates an app from a template and redirects after import succeeds', async () => {
|
it('creates an app from a template and redirects after import succeeds', async () => {
|
||||||
const onSuccess = vi.fn()
|
const onClose = vi.fn()
|
||||||
|
|
||||||
render(<Apps onSuccess={onSuccess} />)
|
render(<Apps onClose={onClose} />)
|
||||||
|
|
||||||
fireEvent.click(screen.getAllByTestId('app-card')[0]!)
|
fireEvent.click(screen.getAllByTestId('app-card')[0]!)
|
||||||
fireEvent.click(screen.getByTestId('confirm-create'))
|
fireEvent.click(screen.getByTestId('confirm-create'))
|
||||||
@ -337,7 +337,7 @@ describe('Apps', () => {
|
|||||||
templateId: 'Alpha',
|
templateId: 'Alpha',
|
||||||
})
|
})
|
||||||
expect(mockToastSuccess).toHaveBeenCalledWith('app.newApp.appCreated')
|
expect(mockToastSuccess).toHaveBeenCalledWith('app.newApp.appCreated')
|
||||||
expect(onSuccess).toHaveBeenCalled()
|
expect(onClose).toHaveBeenCalledTimes(1)
|
||||||
expect(mockHandleCheckPluginDependencies).toHaveBeenCalledWith('created-app-id')
|
expect(mockHandleCheckPluginDependencies).toHaveBeenCalledWith('created-app-id')
|
||||||
expect(mockGetRedirection).toHaveBeenCalledWith(
|
expect(mockGetRedirection).toHaveBeenCalledWith(
|
||||||
{
|
{
|
||||||
@ -361,7 +361,7 @@ describe('Apps', () => {
|
|||||||
app_mode: AppModeEnum.WORKFLOW,
|
app_mode: AppModeEnum.WORKFLOW,
|
||||||
})
|
})
|
||||||
|
|
||||||
render(<Apps />)
|
render(<Apps onClose={vi.fn()} />)
|
||||||
|
|
||||||
fireEvent.click(screen.getAllByTestId('app-card')[0]!)
|
fireEvent.click(screen.getAllByTestId('app-card')[0]!)
|
||||||
fireEvent.click(screen.getByTestId('confirm-create'))
|
fireEvent.click(screen.getByTestId('confirm-create'))
|
||||||
@ -387,7 +387,7 @@ describe('Apps', () => {
|
|||||||
it('shows an error toast when importing the template fails', async () => {
|
it('shows an error toast when importing the template fails', async () => {
|
||||||
mockImportDSL.mockRejectedValueOnce(new Error('failed'))
|
mockImportDSL.mockRejectedValueOnce(new Error('failed'))
|
||||||
|
|
||||||
render(<Apps />)
|
render(<Apps onClose={vi.fn()} />)
|
||||||
|
|
||||||
fireEvent.click(screen.getAllByTestId('app-card')[0]!)
|
fireEvent.click(screen.getAllByTestId('app-card')[0]!)
|
||||||
fireEvent.click(screen.getByTestId('confirm-create'))
|
fireEvent.click(screen.getByTestId('confirm-create'))
|
||||||
@ -400,7 +400,7 @@ describe('Apps', () => {
|
|||||||
it('forwards the create-from-blank action from the sidebar', () => {
|
it('forwards the create-from-blank action from the sidebar', () => {
|
||||||
const onCreateFromBlank = vi.fn()
|
const onCreateFromBlank = vi.fn()
|
||||||
|
|
||||||
render(<Apps onCreateFromBlank={onCreateFromBlank} />)
|
render(<Apps onClose={vi.fn()} onCreateFromBlank={onCreateFromBlank} />)
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('app.newApp.startFromBlank'))
|
fireEvent.click(screen.getByText('app.newApp.startFromBlank'))
|
||||||
|
|
||||||
@ -413,7 +413,7 @@ describe('Apps', () => {
|
|||||||
isLoading: true,
|
isLoading: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
render(<Apps />)
|
render(<Apps onClose={vi.fn()} />)
|
||||||
|
|
||||||
expect(screen.getByRole('status'))!.toBeInTheDocument()
|
expect(screen.getByRole('status'))!.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
@ -424,13 +424,13 @@ describe('Apps', () => {
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
render(<Apps />)
|
render(<Apps onClose={vi.fn()} />)
|
||||||
|
|
||||||
expect(screen.getByText('app.newApp.noTemplateFound'))!.toBeInTheDocument()
|
expect(screen.getByText('app.newApp.noTemplateFound'))!.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should filter templates by category and the remaining app modes', async () => {
|
it('should filter templates by category and the remaining app modes', async () => {
|
||||||
render(<Apps />)
|
render(<Apps onClose={vi.fn()} />)
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('Cat C'))
|
fireEvent.click(screen.getByText('Cat C'))
|
||||||
expect(screen.queryByText('Alpha')).not.toBeInTheDocument()
|
expect(screen.queryByText('Alpha')).not.toBeInTheDocument()
|
||||||
@ -473,7 +473,7 @@ describe('Apps', () => {
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
render(<Apps />)
|
render(<Apps onClose={vi.fn()} />)
|
||||||
|
|
||||||
expect(screen.getByText('Cat A'))!.toBeInTheDocument()
|
expect(screen.getByText('Cat A'))!.toBeInTheDocument()
|
||||||
expect(screen.queryByRole('button', { name: 'v' })).not.toBeInTheDocument()
|
expect(screen.queryByRole('button', { name: 'v' })).not.toBeInTheDocument()
|
||||||
@ -481,7 +481,7 @@ describe('Apps', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should clear the search, hide the sidebar during search, and close the modal when requested', async () => {
|
it('should clear the search, hide the sidebar during search, and close the modal when requested', async () => {
|
||||||
render(<Apps />)
|
render(<Apps onClose={vi.fn()} />)
|
||||||
|
|
||||||
const searchInput = screen.getByPlaceholderText('app.newAppFromTemplate.searchAllTemplate')
|
const searchInput = screen.getByPlaceholderText('app.newAppFromTemplate.searchAllTemplate')
|
||||||
fireEvent.change(searchInput, {
|
fireEvent.change(searchInput, {
|
||||||
@ -510,7 +510,7 @@ describe('Apps', () => {
|
|||||||
|
|
||||||
it('clears an active search immediately and returns focus to the searchbox', async () => {
|
it('clears an active search immediately and returns focus to the searchbox', async () => {
|
||||||
const user = userEvent.setup()
|
const user = userEvent.setup()
|
||||||
render(<Apps />)
|
render(<Apps onClose={vi.fn()} />)
|
||||||
|
|
||||||
const searchInput = screen.getByRole('searchbox', {
|
const searchInput = screen.getByRole('searchbox', {
|
||||||
name: 'app.newAppFromTemplate.searchAllTemplate',
|
name: 'app.newAppFromTemplate.searchAllTemplate',
|
||||||
|
|||||||
@ -32,7 +32,7 @@ import AppCard from '../app-card'
|
|||||||
import Sidebar, { AppCategories, AppCategoryLabel } from './sidebar'
|
import Sidebar, { AppCategories, AppCategoryLabel } from './sidebar'
|
||||||
|
|
||||||
type AppsProps = {
|
type AppsProps = {
|
||||||
onSuccess?: () => void
|
onClose: () => void
|
||||||
onCreateFromBlank?: () => void
|
onCreateFromBlank?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ type AppsProps = {
|
|||||||
// CREATE = 'create',
|
// CREATE = 'create',
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const Apps = ({ onSuccess, onCreateFromBlank }: AppsProps) => {
|
const Apps = ({ onClose, onCreateFromBlank }: AppsProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||||
const { data: currentUserId } = useSuspenseQuery({
|
const { data: currentUserId } = useSuspenseQuery({
|
||||||
@ -151,7 +151,7 @@ const Apps = ({ onSuccess, onCreateFromBlank }: AppsProps) => {
|
|||||||
|
|
||||||
setIsShowCreateModal(false)
|
setIsShowCreateModal(false)
|
||||||
toast.success(t(($) => $['newApp.appCreated'], { ns: 'app' }))
|
toast.success(t(($) => $['newApp.appCreated'], { ns: 'app' }))
|
||||||
if (onSuccess) onSuccess()
|
onClose()
|
||||||
await handleCheckPluginDependencies(app.app_id)
|
await handleCheckPluginDependencies(app.app_id)
|
||||||
getRedirection(
|
getRedirection(
|
||||||
{ id: app.app_id, mode: app.app_mode, permission_keys: app.permission_keys },
|
{ id: app.app_id, mode: app.app_mode, permission_keys: app.permission_keys },
|
||||||
|
|||||||
@ -5,17 +5,11 @@ import AppList from './app-list'
|
|||||||
|
|
||||||
type CreateAppDialogProps = {
|
type CreateAppDialogProps = {
|
||||||
show: boolean
|
show: boolean
|
||||||
onSuccess: () => void
|
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onCreateFromBlank?: () => void
|
onCreateFromBlank?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const CreateAppTemplateDialog = ({
|
const CreateAppTemplateDialog = ({ show, onClose, onCreateFromBlank }: CreateAppDialogProps) => {
|
||||||
show,
|
|
||||||
onSuccess,
|
|
||||||
onClose,
|
|
||||||
onCreateFromBlank,
|
|
||||||
}: CreateAppDialogProps) => {
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -24,13 +18,7 @@ const CreateAppTemplateDialog = ({
|
|||||||
title={t(($) => $['newApp.startFromTemplate'], { ns: 'app' })}
|
title={t(($) => $['newApp.startFromTemplate'], { ns: 'app' })}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
>
|
>
|
||||||
<AppList
|
<AppList onCreateFromBlank={onCreateFromBlank} onClose={onClose} />
|
||||||
onCreateFromBlank={onCreateFromBlank}
|
|
||||||
onSuccess={() => {
|
|
||||||
onSuccess()
|
|
||||||
onClose()
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</CreateAppDialogShell>
|
</CreateAppDialogShell>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -125,18 +125,16 @@ const defaultPlanUsage = {
|
|||||||
|
|
||||||
const renderModal = () => {
|
const renderModal = () => {
|
||||||
const onClose = vi.fn()
|
const onClose = vi.fn()
|
||||||
const onSuccess = vi.fn()
|
|
||||||
const onCreateFromTemplate = vi.fn()
|
const onCreateFromTemplate = vi.fn()
|
||||||
render(
|
render(
|
||||||
<CreateAppModal
|
<CreateAppModal
|
||||||
show
|
show
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onSuccess={onSuccess}
|
|
||||||
onCreateFromTemplate={onCreateFromTemplate}
|
onCreateFromTemplate={onCreateFromTemplate}
|
||||||
defaultAppMode={AppModeEnum.ADVANCED_CHAT}
|
defaultAppMode={AppModeEnum.ADVANCED_CHAT}
|
||||||
/>,
|
/>,
|
||||||
)
|
)
|
||||||
return { onClose, onSuccess, onCreateFromTemplate }
|
return { onClose, onCreateFromTemplate }
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('CreateAppModal', () => {
|
describe('CreateAppModal', () => {
|
||||||
@ -168,7 +166,7 @@ describe('CreateAppModal', () => {
|
|||||||
maintainer: 'user-1',
|
maintainer: 'user-1',
|
||||||
}
|
}
|
||||||
mockCreateApp.mockResolvedValue(mockApp as App)
|
mockCreateApp.mockResolvedValue(mockApp as App)
|
||||||
const { onClose, onSuccess } = renderModal()
|
const { onClose } = renderModal()
|
||||||
|
|
||||||
const nameInput = screen.getByPlaceholderText('app.newApp.appNamePlaceholder')
|
const nameInput = screen.getByPlaceholderText('app.newApp.appNamePlaceholder')
|
||||||
fireEvent.change(nameInput, { target: { value: 'My App' } })
|
fireEvent.change(nameInput, { target: { value: 'My App' } })
|
||||||
@ -190,8 +188,7 @@ describe('CreateAppModal', () => {
|
|||||||
appMode: AppModeEnum.ADVANCED_CHAT,
|
appMode: AppModeEnum.ADVANCED_CHAT,
|
||||||
})
|
})
|
||||||
expect(mockToastSuccess).toHaveBeenCalledWith('app.newApp.appCreated')
|
expect(mockToastSuccess).toHaveBeenCalledWith('app.newApp.appCreated')
|
||||||
expect(onSuccess).toHaveBeenCalled()
|
expect(onClose).toHaveBeenCalledTimes(1)
|
||||||
expect(onClose).toHaveBeenCalled()
|
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(mockGetRedirection).toHaveBeenCalledWith(mockApp, mockPush, {
|
expect(mockGetRedirection).toHaveBeenCalledWith(mockApp, mockPush, {
|
||||||
currentUserId: 'user-1',
|
currentUserId: 'user-1',
|
||||||
|
|||||||
@ -34,7 +34,6 @@ import AppIconPicker from '../../base/app-icon-picker'
|
|||||||
import { CreateAppDialogShell } from '../create-app-dialog-shell'
|
import { CreateAppDialogShell } from '../create-app-dialog-shell'
|
||||||
|
|
||||||
type CreateAppProps = {
|
type CreateAppProps = {
|
||||||
onSuccess: () => void
|
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onCreateFromTemplate?: () => void
|
onCreateFromTemplate?: () => void
|
||||||
defaultAppMode?: AppModeEnum
|
defaultAppMode?: AppModeEnum
|
||||||
@ -50,7 +49,7 @@ const shouldExpandBeginnerAppTypes = (appMode?: AppModeEnum) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }: CreateAppProps) {
|
function CreateApp({ onClose, onCreateFromTemplate, defaultAppMode }: CreateAppProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { push } = useRouter()
|
const { push } = useRouter()
|
||||||
const nameInputId = useId()
|
const nameInputId = useId()
|
||||||
@ -120,7 +119,6 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }:
|
|||||||
}
|
}
|
||||||
|
|
||||||
toast.success(t(($) => $['newApp.appCreated'], { ns: 'app' }))
|
toast.success(t(($) => $['newApp.appCreated'], { ns: 'app' }))
|
||||||
onSuccess()
|
|
||||||
onClose()
|
onClose()
|
||||||
getRedirection(app, push, {
|
getRedirection(app, push, {
|
||||||
currentUserId,
|
currentUserId,
|
||||||
@ -146,7 +144,6 @@ function CreateApp({ onClose, onSuccess, onCreateFromTemplate, defaultAppMode }:
|
|||||||
appMode,
|
appMode,
|
||||||
appIcon,
|
appIcon,
|
||||||
description,
|
description,
|
||||||
onSuccess,
|
|
||||||
onClose,
|
onClose,
|
||||||
push,
|
push,
|
||||||
workspacePermissionKeys,
|
workspacePermissionKeys,
|
||||||
@ -420,7 +417,6 @@ type CreateAppDialogProps = CreateAppProps & {
|
|||||||
const CreateAppModal = ({
|
const CreateAppModal = ({
|
||||||
show,
|
show,
|
||||||
onClose,
|
onClose,
|
||||||
onSuccess,
|
|
||||||
onCreateFromTemplate,
|
onCreateFromTemplate,
|
||||||
defaultAppMode,
|
defaultAppMode,
|
||||||
}: CreateAppDialogProps) => {
|
}: CreateAppDialogProps) => {
|
||||||
@ -435,7 +431,6 @@ const CreateAppModal = ({
|
|||||||
>
|
>
|
||||||
<CreateApp
|
<CreateApp
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onSuccess={onSuccess}
|
|
||||||
onCreateFromTemplate={onCreateFromTemplate}
|
onCreateFromTemplate={onCreateFromTemplate}
|
||||||
defaultAppMode={defaultAppMode}
|
defaultAppMode={defaultAppMode}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import type { DeploymentEdition } from '@dify/contracts/api/console/system-featu
|
|||||||
import type { RenderOptions } from '@testing-library/react'
|
import type { RenderOptions } from '@testing-library/react'
|
||||||
import type { MockedFunction } from 'vite-plus/test'
|
import type { MockedFunction } from 'vite-plus/test'
|
||||||
import { fireEvent, screen } from '@testing-library/react'
|
import { fireEvent, screen } from '@testing-library/react'
|
||||||
import { noop } from 'es-toolkit/function'
|
|
||||||
import { defaultPlan } from '@/app/components/billing/config'
|
import { defaultPlan } from '@/app/components/billing/config'
|
||||||
import { useProviderContext as actualUseProviderContext } from '@/context/provider-context'
|
import { useProviderContext as actualUseProviderContext } from '@/context/provider-context'
|
||||||
import { renderWithConsoleQuery } from '@/test/console/query-data'
|
import { renderWithConsoleQuery } from '@/test/console/query-data'
|
||||||
@ -51,7 +50,6 @@ const defaultProviderContext = {
|
|||||||
isFetchedPlanInfo: false,
|
isFetchedPlanInfo: false,
|
||||||
enableBilling: false,
|
enableBilling: false,
|
||||||
enableSkill: false,
|
enableSkill: false,
|
||||||
onPlanInfoChanged: noop,
|
|
||||||
enableReplaceWebAppLogo: false,
|
enableReplaceWebAppLogo: false,
|
||||||
modelLoadBalancingEnabled: false,
|
modelLoadBalancingEnabled: false,
|
||||||
enableEducationPlan: false,
|
enableEducationPlan: false,
|
||||||
|
|||||||
@ -185,14 +185,6 @@ vi.mock('@/context/permission-state', async () => {
|
|||||||
return createPermissionStateModuleMock(() => mockConsoleState)
|
return createPermissionStateModuleMock(() => mockConsoleState)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Mock provider context
|
|
||||||
const mockOnPlanInfoChanged = vi.fn()
|
|
||||||
vi.mock('@/context/provider-context', () => ({
|
|
||||||
useProviderContext: () => ({
|
|
||||||
onPlanInfoChanged: mockOnPlanInfoChanged,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
// systemFeatures is seeded into the QueryClient via the local render helper.
|
// systemFeatures is seeded into the QueryClient via the local render helper.
|
||||||
|
|
||||||
vi.mock('@/service/apps', () => ({
|
vi.mock('@/service/apps', () => ({
|
||||||
@ -1163,25 +1155,6 @@ describe('AppCard', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should call onPlanInfoChanged after successful duplication', async () => {
|
|
||||||
render(<AppCard app={mockApp} />)
|
|
||||||
|
|
||||||
fireEvent.click(getOperationsTrigger())
|
|
||||||
await waitFor(() => {
|
|
||||||
fireEvent.click(screen.getByText('app.duplicate'))
|
|
||||||
})
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByTestId('duplicate-modal')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
fireEvent.click(screen.getByTestId('confirm-duplicate-modal'))
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(mockOnPlanInfoChanged).toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle copy failure', async () => {
|
it('should handle copy failure', async () => {
|
||||||
mockCopyApp.mockRejectedValueOnce(new Error('Copy failed'))
|
mockCopyApp.mockRejectedValueOnce(new Error('Copy failed'))
|
||||||
|
|
||||||
|
|||||||
@ -131,12 +131,6 @@ vi.mock('@/context/permission-state', async () => {
|
|||||||
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
workspacePermissionKeys: mockWorkspacePermissionKeys,
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
const mockOnPlanInfoChanged = vi.fn()
|
|
||||||
vi.mock('@/context/provider-context', () => ({
|
|
||||||
useProviderContext: () => ({
|
|
||||||
onPlanInfoChanged: mockOnPlanInfoChanged,
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mock('@/service/use-common', () => ({
|
vi.mock('@/service/use-common', () => ({
|
||||||
useMembers: () => ({
|
useMembers: () => ({
|
||||||
@ -288,11 +282,9 @@ vi.mock('@/next/dynamic', () => ({
|
|||||||
return function MockCreateFromDSLModal({
|
return function MockCreateFromDSLModal({
|
||||||
show,
|
show,
|
||||||
onClose,
|
onClose,
|
||||||
onSuccess,
|
|
||||||
}: {
|
}: {
|
||||||
show: boolean
|
show: boolean
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onSuccess: () => void
|
|
||||||
}) {
|
}) {
|
||||||
if (!show) return null
|
if (!show) return null
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
@ -305,7 +297,7 @@ vi.mock('@/next/dynamic', () => ({
|
|||||||
),
|
),
|
||||||
React.createElement(
|
React.createElement(
|
||||||
'button',
|
'button',
|
||||||
{ onClick: onSuccess, 'data-testid': 'success-dsl-modal' },
|
{ onClick: onClose, 'data-testid': 'success-dsl-modal' },
|
||||||
'Success',
|
'Success',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -315,12 +307,10 @@ vi.mock('@/next/dynamic', () => ({
|
|||||||
return function MockCreateAppModal({
|
return function MockCreateAppModal({
|
||||||
show,
|
show,
|
||||||
onClose,
|
onClose,
|
||||||
onSuccess,
|
|
||||||
onCreateFromTemplate,
|
onCreateFromTemplate,
|
||||||
}: {
|
}: {
|
||||||
show: boolean
|
show: boolean
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onSuccess: () => void
|
|
||||||
onCreateFromTemplate: () => void
|
onCreateFromTemplate: () => void
|
||||||
}) {
|
}) {
|
||||||
if (!show) return null
|
if (!show) return null
|
||||||
@ -334,7 +324,7 @@ vi.mock('@/next/dynamic', () => ({
|
|||||||
),
|
),
|
||||||
React.createElement(
|
React.createElement(
|
||||||
'button',
|
'button',
|
||||||
{ onClick: onSuccess, 'data-testid': 'success-create-modal' },
|
{ onClick: onClose, 'data-testid': 'success-create-modal' },
|
||||||
'Success',
|
'Success',
|
||||||
),
|
),
|
||||||
React.createElement(
|
React.createElement(
|
||||||
@ -349,12 +339,10 @@ vi.mock('@/next/dynamic', () => ({
|
|||||||
return function MockCreateAppTemplateDialog({
|
return function MockCreateAppTemplateDialog({
|
||||||
show,
|
show,
|
||||||
onClose,
|
onClose,
|
||||||
onSuccess,
|
|
||||||
onCreateFromBlank,
|
onCreateFromBlank,
|
||||||
}: {
|
}: {
|
||||||
show: boolean
|
show: boolean
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onSuccess: () => void
|
|
||||||
onCreateFromBlank: () => void
|
onCreateFromBlank: () => void
|
||||||
}) {
|
}) {
|
||||||
if (!show) return null
|
if (!show) return null
|
||||||
@ -368,7 +356,7 @@ vi.mock('@/next/dynamic', () => ({
|
|||||||
),
|
),
|
||||||
React.createElement(
|
React.createElement(
|
||||||
'button',
|
'button',
|
||||||
{ onClick: onSuccess, 'data-testid': 'success-template-dialog' },
|
{ onClick: onClose, 'data-testid': 'success-template-dialog' },
|
||||||
'Success',
|
'Success',
|
||||||
),
|
),
|
||||||
React.createElement(
|
React.createElement(
|
||||||
|
|||||||
@ -51,7 +51,6 @@ import {
|
|||||||
useStepByStepTourControlledDropdown,
|
useStepByStepTourControlledDropdown,
|
||||||
} from '@/app/components/step-by-step-tour/dropdown-menu'
|
} from '@/app/components/step-by-step-tour/dropdown-menu'
|
||||||
import { workspacePermissionKeysAtom } from '@/context/permission-state'
|
import { workspacePermissionKeysAtom } from '@/context/permission-state'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
|
||||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||||
import { useAsyncWindowOpen } from '@/hooks/use-async-window-open'
|
import { useAsyncWindowOpen } from '@/hooks/use-async-window-open'
|
||||||
@ -276,7 +275,6 @@ export function AppCardInteractions({
|
|||||||
})
|
})
|
||||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||||
const isRbacEnabled = systemFeatures.rbac_enabled
|
const isRbacEnabled = systemFeatures.rbac_enabled
|
||||||
const { onPlanInfoChanged } = useProviderContext()
|
|
||||||
const { push } = useRouter()
|
const { push } = useRouter()
|
||||||
const { mutate: copyApp } = useMutation(consoleQuery.apps.byAppId.copy.post.mutationOptions())
|
const { mutate: copyApp } = useMutation(consoleQuery.apps.byAppId.copy.post.mutationOptions())
|
||||||
const { mutateAsync: updateApp } = useMutation(consoleQuery.apps.byAppId.put.mutationOptions())
|
const { mutateAsync: updateApp } = useMutation(consoleQuery.apps.byAppId.put.mutationOptions())
|
||||||
@ -329,7 +327,6 @@ export function AppCardInteractions({
|
|||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(t(($) => $.appDeleted, { ns: 'app' }))
|
toast.success(t(($) => $.appDeleted, { ns: 'app' }))
|
||||||
onPlanInfoChanged()
|
|
||||||
setActiveDialog(null)
|
setActiveDialog(null)
|
||||||
setConfirmDeleteInput('')
|
setConfirmDeleteInput('')
|
||||||
},
|
},
|
||||||
@ -345,7 +342,7 @@ export function AppCardInteractions({
|
|||||||
const message = error instanceof Error ? error.message : ''
|
const message = error instanceof Error ? error.message : ''
|
||||||
toast.error(`${t(($) => $.appDeleteFailed, { ns: 'app' })}${message ? `: ${message}` : ''}`)
|
toast.error(`${t(($) => $.appDeleteFailed, { ns: 'app' })}${message ? `: ${message}` : ''}`)
|
||||||
}
|
}
|
||||||
}, [app.id, deleteApp, onPlanInfoChanged, t])
|
}, [app.id, deleteApp, t])
|
||||||
|
|
||||||
const onDeleteDialogOpenChange = useCallback(
|
const onDeleteDialogOpenChange = useCallback(
|
||||||
(open: boolean) => {
|
(open: boolean) => {
|
||||||
@ -461,7 +458,6 @@ export function AppCardInteractions({
|
|||||||
|
|
||||||
setActiveDialog(null)
|
setActiveDialog(null)
|
||||||
toast.success(t(($) => $['newApp.appCreated'], { ns: 'app' }))
|
toast.success(t(($) => $['newApp.appCreated'], { ns: 'app' }))
|
||||||
onPlanInfoChanged()
|
|
||||||
getRedirection(newApp, push, {
|
getRedirection(newApp, push, {
|
||||||
currentUserId,
|
currentUserId,
|
||||||
resourceMaintainer: newApp.maintainer ?? undefined,
|
resourceMaintainer: newApp.maintainer ?? undefined,
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import type { AppListUrlQuery } from './query-params'
|
import type { AppListUrlQuery } from './query-params'
|
||||||
import { zPostAppsBody } from '@dify/contracts/api/console/apps/zod.gen'
|
import { zPostAppsBody } from '@dify/contracts/api/console/apps/zod.gen'
|
||||||
import { useProviderContext } from '@/context/provider-context'
|
|
||||||
import dynamic from '@/next/dynamic'
|
import dynamic from '@/next/dynamic'
|
||||||
|
|
||||||
type AppListCategory = AppListUrlQuery['category']
|
type AppListCategory = AppListUrlQuery['category']
|
||||||
@ -38,40 +37,24 @@ export function AppListCreationModals({
|
|||||||
onOpenBlank: () => void
|
onOpenBlank: () => void
|
||||||
onOpenTemplate: () => void
|
onOpenTemplate: () => void
|
||||||
}) {
|
}) {
|
||||||
const { onPlanInfoChanged } = useProviderContext()
|
|
||||||
|
|
||||||
if (!canCreateApp) return null
|
if (!canCreateApp) return null
|
||||||
const defaultAppModeResult = zPostAppsBody.shape.mode.safeParse(category)
|
const defaultAppModeResult = zPostAppsBody.shape.mode.safeParse(category)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{dialog?.type === 'dsl' && (
|
{dialog?.type === 'dsl' && (
|
||||||
<CreateFromDSLModal
|
<CreateFromDSLModal show onClose={onClose} droppedFile={dialog.droppedFile} />
|
||||||
show
|
|
||||||
onClose={onClose}
|
|
||||||
onSuccess={() => {
|
|
||||||
onClose()
|
|
||||||
onPlanInfoChanged()
|
|
||||||
}}
|
|
||||||
droppedFile={dialog.droppedFile}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{dialog?.type === 'blank' && (
|
{dialog?.type === 'blank' && (
|
||||||
<CreateAppModal
|
<CreateAppModal
|
||||||
show
|
show
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onSuccess={onPlanInfoChanged}
|
|
||||||
onCreateFromTemplate={onOpenTemplate}
|
onCreateFromTemplate={onOpenTemplate}
|
||||||
defaultAppMode={defaultAppModeResult.success ? defaultAppModeResult.data : undefined}
|
defaultAppMode={defaultAppModeResult.success ? defaultAppModeResult.data : undefined}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{dialog?.type === 'template' && (
|
{dialog?.type === 'template' && (
|
||||||
<CreateAppTemplateDialog
|
<CreateAppTemplateDialog show onClose={onClose} onCreateFromBlank={onOpenBlank} />
|
||||||
show
|
|
||||||
onClose={onClose}
|
|
||||||
onSuccess={onPlanInfoChanged}
|
|
||||||
onCreateFromBlank={onOpenBlank}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -218,7 +218,6 @@ vi.mock('@/context/provider-context', () => ({
|
|||||||
hasSettedApiKey: true,
|
hasSettedApiKey: true,
|
||||||
plan: { type: 'free' },
|
plan: { type: 'free' },
|
||||||
enableBilling: false,
|
enableBilling: false,
|
||||||
onPlanInfoChanged: vi.fn(),
|
|
||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|||||||
@ -60,11 +60,6 @@ export const ProviderContextProvider = ({ children }: ProviderContextProviderPro
|
|||||||
queryClient.invalidateQueries({ queryKey: commonQueryKeys.modelProviderDetails }),
|
queryClient.invalidateQueries({ queryKey: commonQueryKeys.modelProviderDetails }),
|
||||||
]).then(() => undefined)
|
]).then(() => undefined)
|
||||||
|
|
||||||
const refreshFeatures = () =>
|
|
||||||
queryClient
|
|
||||||
.invalidateQueries({ queryKey: consoleQuery.features.get.key() })
|
|
||||||
.then(() => undefined)
|
|
||||||
|
|
||||||
// #region Zendesk conversation fields
|
// #region Zendesk conversation fields
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ZENDESK_FIELD_IDS.PLAN && plan.type) {
|
if (ZENDESK_FIELD_IDS.PLAN && plan.type) {
|
||||||
@ -99,7 +94,6 @@ export const ProviderContextProvider = ({ children }: ProviderContextProviderPro
|
|||||||
isFetchedPlanInfo,
|
isFetchedPlanInfo,
|
||||||
enableBilling,
|
enableBilling,
|
||||||
enableSkill,
|
enableSkill,
|
||||||
onPlanInfoChanged: refreshFeatures,
|
|
||||||
enableReplaceWebAppLogo,
|
enableReplaceWebAppLogo,
|
||||||
modelLoadBalancingEnabled,
|
modelLoadBalancingEnabled,
|
||||||
enableEducationPlan,
|
enableEducationPlan,
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import type {
|
|||||||
import type { UsagePlanInfo, UsageResetInfo } from '@/app/components/billing/type'
|
import type { UsagePlanInfo, UsageResetInfo } from '@/app/components/billing/type'
|
||||||
import type { Model } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
import type { Model } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||||
import type { RETRIEVE_METHOD } from '@/types/app'
|
import type { RETRIEVE_METHOD } from '@/types/app'
|
||||||
import { noop } from 'es-toolkit/function'
|
|
||||||
import { createContext, useContext, useContextSelector } from 'use-context-selector'
|
import { createContext, useContext, useContextSelector } from 'use-context-selector'
|
||||||
import { defaultPlan } from '@/app/components/billing/config'
|
import { defaultPlan } from '@/app/components/billing/config'
|
||||||
|
|
||||||
@ -31,7 +30,6 @@ export type ProviderContextState = {
|
|||||||
isFetchedPlanInfo: boolean
|
isFetchedPlanInfo: boolean
|
||||||
enableBilling: boolean
|
enableBilling: boolean
|
||||||
enableSkill: boolean
|
enableSkill: boolean
|
||||||
onPlanInfoChanged: () => void
|
|
||||||
enableReplaceWebAppLogo: boolean
|
enableReplaceWebAppLogo: boolean
|
||||||
modelLoadBalancingEnabled: boolean
|
modelLoadBalancingEnabled: boolean
|
||||||
enableEducationPlan: boolean
|
enableEducationPlan: boolean
|
||||||
@ -55,7 +53,6 @@ export const baseProviderContextValue: ProviderContextState = {
|
|||||||
isFetchedPlanInfo: false,
|
isFetchedPlanInfo: false,
|
||||||
enableBilling: false,
|
enableBilling: false,
|
||||||
enableSkill: false,
|
enableSkill: false,
|
||||||
onPlanInfoChanged: noop,
|
|
||||||
enableReplaceWebAppLogo: false,
|
enableReplaceWebAppLogo: false,
|
||||||
modelLoadBalancingEnabled: false,
|
modelLoadBalancingEnabled: false,
|
||||||
enableEducationPlan: false,
|
enableEducationPlan: false,
|
||||||
|
|||||||
@ -85,28 +85,6 @@ export const updateAppInfo = ({
|
|||||||
return put<AppDetailResponse>(`apps/${appID}`, { body })
|
return put<AppDetailResponse>(`apps/${appID}`, { body })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const copyApp = ({
|
|
||||||
appID,
|
|
||||||
name,
|
|
||||||
icon_type,
|
|
||||||
icon,
|
|
||||||
icon_background,
|
|
||||||
mode,
|
|
||||||
description,
|
|
||||||
}: {
|
|
||||||
appID: string
|
|
||||||
name: string
|
|
||||||
icon_type: AppIconType
|
|
||||||
icon: string
|
|
||||||
icon_background?: string | null
|
|
||||||
mode: AppModeEnum
|
|
||||||
description?: string
|
|
||||||
}): Promise<AppDetailResponse> => {
|
|
||||||
return post<AppDetailResponse>(`apps/${appID}/copy`, {
|
|
||||||
body: { name, icon_type, icon, icon_background, mode, description },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const exportAppConfig = ({
|
export const exportAppConfig = ({
|
||||||
appID,
|
appID,
|
||||||
include = false,
|
include = false,
|
||||||
|
|||||||
@ -778,39 +778,51 @@ describe('consoleQuery app mutation defaults', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should invalidate app lists without blocking a directly completed import', async () => {
|
it.each([undefined, 'existing-app'])(
|
||||||
const consoleQuery = await loadConsoleQuery()
|
'should refresh completed import data without blocking (overwrite: %s)',
|
||||||
const queryClient = new QueryClient()
|
async (appId) => {
|
||||||
const invalidateQueries = vi
|
const consoleQuery = await loadConsoleQuery()
|
||||||
.spyOn(queryClient, 'invalidateQueries')
|
const queryClient = new QueryClient()
|
||||||
.mockImplementation(() => new Promise(() => {}))
|
const invalidateQueries = vi
|
||||||
const mutationOptions = consoleQuery.apps.imports.post.mutationOptions()
|
.spyOn(queryClient, 'invalidateQueries')
|
||||||
|
.mockImplementation(() => new Promise(() => {}))
|
||||||
|
const mutationOptions = consoleQuery.apps.imports.post.mutationOptions()
|
||||||
|
|
||||||
const result = mutationOptions.onSuccess?.(
|
const result = mutationOptions.onSuccess?.(
|
||||||
{
|
{
|
||||||
id: 'import-1',
|
id: 'import-1',
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
app_id: 'app-1',
|
app_id: 'app-1',
|
||||||
current_dsl_version: '',
|
current_dsl_version: '',
|
||||||
imported_dsl_version: '',
|
imported_dsl_version: '',
|
||||||
error: '',
|
error: '',
|
||||||
},
|
},
|
||||||
{ body: { mode: 'yaml-content', yaml_content: 'app: demo' } },
|
{ body: { mode: 'yaml-content', yaml_content: 'app: demo', app_id: appId } },
|
||||||
undefined,
|
undefined,
|
||||||
createMutationContext(queryClient),
|
createMutationContext(queryClient),
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(result).toBeUndefined()
|
if (appId) {
|
||||||
expect(invalidateQueries).toHaveBeenCalledWith({
|
expect(invalidateQueries).not.toHaveBeenCalledWith({
|
||||||
queryKey: consoleQuery.apps.get.key(),
|
queryKey: consoleQuery.features.get.key(),
|
||||||
})
|
})
|
||||||
expect(invalidateQueries).toHaveBeenCalledWith({
|
} else {
|
||||||
queryKey: consoleQuery.apps.starred.get.key(),
|
expect(invalidateQueries).toHaveBeenCalledWith({
|
||||||
})
|
queryKey: consoleQuery.features.get.key(),
|
||||||
expect(invalidateQueries).toHaveBeenCalledWith({
|
})
|
||||||
queryKey: consoleQuery.apps.recent.get.key(),
|
}
|
||||||
})
|
expect(result).toBeUndefined()
|
||||||
})
|
expect(invalidateQueries).toHaveBeenCalledWith({
|
||||||
|
queryKey: consoleQuery.apps.get.key(),
|
||||||
|
})
|
||||||
|
expect(invalidateQueries).toHaveBeenCalledWith({
|
||||||
|
queryKey: consoleQuery.apps.starred.get.key(),
|
||||||
|
})
|
||||||
|
expect(invalidateQueries).toHaveBeenCalledWith({
|
||||||
|
queryKey: consoleQuery.apps.recent.get.key(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
it('should keep app lists intact while an import awaits confirmation', async () => {
|
it('should keep app lists intact while an import awaits confirmation', async () => {
|
||||||
const consoleQuery = await loadConsoleQuery()
|
const consoleQuery = await loadConsoleQuery()
|
||||||
@ -873,16 +885,20 @@ describe('consoleQuery app mutation defaults', () => {
|
|||||||
expect(synchronized).toBe(true)
|
expect(synchronized).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should keep a delete mutation pending until every app list synchronizes', async () => {
|
it('should wait for deleted app lists to synchronize without waiting for quota', async () => {
|
||||||
const consoleQuery = await loadConsoleQuery()
|
const consoleQuery = await loadConsoleQuery()
|
||||||
const queryClient = new QueryClient()
|
const queryClient = new QueryClient()
|
||||||
const invalidationResolvers: Array<() => void> = []
|
const invalidationResolvers: Array<() => void> = []
|
||||||
const invalidateQueries = vi.spyOn(queryClient, 'invalidateQueries').mockImplementation(
|
const invalidateQueries = vi
|
||||||
() =>
|
.spyOn(queryClient, 'invalidateQueries')
|
||||||
new Promise<void>((resolve) => {
|
.mockImplementation((filters) => {
|
||||||
|
if (JSON.stringify(filters?.queryKey) === JSON.stringify(consoleQuery.features.get.key()))
|
||||||
|
return new Promise<void>(() => {})
|
||||||
|
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
invalidationResolvers.push(resolve)
|
invalidationResolvers.push(resolve)
|
||||||
}),
|
})
|
||||||
)
|
})
|
||||||
const mutationOptions = consoleQuery.apps.byAppId.delete.mutationOptions()
|
const mutationOptions = consoleQuery.apps.byAppId.delete.mutationOptions()
|
||||||
|
|
||||||
const synchronization = mutationOptions.onSuccess?.(
|
const synchronization = mutationOptions.onSuccess?.(
|
||||||
@ -907,7 +923,8 @@ describe('consoleQuery app mutation defaults', () => {
|
|||||||
expect(invalidateQueries).toHaveBeenCalledWith({
|
expect(invalidateQueries).toHaveBeenCalledWith({
|
||||||
queryKey: consoleQuery.apps.recent.get.key(),
|
queryKey: consoleQuery.apps.recent.get.key(),
|
||||||
})
|
})
|
||||||
expect(invalidateQueries).toHaveBeenCalledTimes(3)
|
expect(invalidateQueries).toHaveBeenCalledWith({ queryKey: consoleQuery.features.get.key() })
|
||||||
|
expect(invalidateQueries).toHaveBeenCalledTimes(4)
|
||||||
|
|
||||||
invalidationResolvers.forEach((resolve) => resolve())
|
invalidationResolvers.forEach((resolve) => resolve())
|
||||||
await synchronization
|
await synchronization
|
||||||
|
|||||||
@ -583,6 +583,7 @@ export const consoleQuery: RouterUtils<typeof consoleClient> = createTanstackQue
|
|||||||
post: {
|
post: {
|
||||||
mutationOptions: {
|
mutationOptions: {
|
||||||
onSuccess: (_data, _variables, _onMutateResult, context) => {
|
onSuccess: (_data, _variables, _onMutateResult, context) => {
|
||||||
|
void context.client.invalidateQueries({ queryKey: consoleQuery.features.get.key() })
|
||||||
void context.client.invalidateQueries({ queryKey: consoleQuery.apps.get.key() })
|
void context.client.invalidateQueries({ queryKey: consoleQuery.apps.get.key() })
|
||||||
void context.client.invalidateQueries({
|
void context.client.invalidateQueries({
|
||||||
queryKey: consoleQuery.apps.starred.get.key(),
|
queryKey: consoleQuery.apps.starred.get.key(),
|
||||||
@ -596,9 +597,14 @@ export const consoleQuery: RouterUtils<typeof consoleClient> = createTanstackQue
|
|||||||
imports: {
|
imports: {
|
||||||
post: {
|
post: {
|
||||||
mutationOptions: {
|
mutationOptions: {
|
||||||
onSuccess: (data, _variables, _onMutateResult, context) => {
|
onSuccess: (data, variables, _onMutateResult, context) => {
|
||||||
if (data.status !== 'completed' && data.status !== 'completed-with-warnings') return
|
if (data.status !== 'completed' && data.status !== 'completed-with-warnings') return
|
||||||
|
|
||||||
|
if (!variables.body.app_id) {
|
||||||
|
void context.client.invalidateQueries({
|
||||||
|
queryKey: consoleQuery.features.get.key(),
|
||||||
|
})
|
||||||
|
}
|
||||||
void context.client.invalidateQueries({ queryKey: consoleQuery.apps.get.key() })
|
void context.client.invalidateQueries({ queryKey: consoleQuery.apps.get.key() })
|
||||||
void context.client.invalidateQueries({
|
void context.client.invalidateQueries({
|
||||||
queryKey: consoleQuery.apps.starred.get.key(),
|
queryKey: consoleQuery.apps.starred.get.key(),
|
||||||
@ -617,6 +623,9 @@ export const consoleQuery: RouterUtils<typeof consoleClient> = createTanstackQue
|
|||||||
if (data.status !== 'completed' && data.status !== 'completed-with-warnings')
|
if (data.status !== 'completed' && data.status !== 'completed-with-warnings')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
void context.client.invalidateQueries({
|
||||||
|
queryKey: consoleQuery.features.get.key(),
|
||||||
|
})
|
||||||
void context.client.invalidateQueries({
|
void context.client.invalidateQueries({
|
||||||
queryKey: consoleQuery.apps.get.key(),
|
queryKey: consoleQuery.apps.get.key(),
|
||||||
})
|
})
|
||||||
@ -651,8 +660,9 @@ export const consoleQuery: RouterUtils<typeof consoleClient> = createTanstackQue
|
|||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
mutationOptions: {
|
mutationOptions: {
|
||||||
onSuccess: (_data, _variables, _onMutateResult, context) =>
|
onSuccess: (_data, _variables, _onMutateResult, context) => {
|
||||||
Promise.all([
|
void context.client.invalidateQueries({ queryKey: consoleQuery.features.get.key() })
|
||||||
|
return Promise.all([
|
||||||
context.client.invalidateQueries({ queryKey: consoleQuery.apps.get.key() }),
|
context.client.invalidateQueries({ queryKey: consoleQuery.apps.get.key() }),
|
||||||
context.client.invalidateQueries({
|
context.client.invalidateQueries({
|
||||||
queryKey: consoleQuery.apps.starred.get.key(),
|
queryKey: consoleQuery.apps.starred.get.key(),
|
||||||
@ -660,7 +670,8 @@ export const consoleQuery: RouterUtils<typeof consoleClient> = createTanstackQue
|
|||||||
context.client.invalidateQueries({
|
context.client.invalidateQueries({
|
||||||
queryKey: consoleQuery.apps.recent.get.key(),
|
queryKey: consoleQuery.apps.recent.get.key(),
|
||||||
}),
|
}),
|
||||||
]),
|
])
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
put: {
|
put: {
|
||||||
@ -732,7 +743,12 @@ export const consoleQuery: RouterUtils<typeof consoleClient> = createTanstackQue
|
|||||||
copy: {
|
copy: {
|
||||||
post: {
|
post: {
|
||||||
mutationOptions: {
|
mutationOptions: {
|
||||||
onSuccess: (_data, _variables, _onMutateResult, context) => {
|
onSuccess: (data, _variables, _onMutateResult, context) => {
|
||||||
|
if (!('mode' in data)) return
|
||||||
|
|
||||||
|
void context.client.invalidateQueries({
|
||||||
|
queryKey: consoleQuery.features.get.key(),
|
||||||
|
})
|
||||||
void context.client.invalidateQueries({ queryKey: consoleQuery.apps.get.key() })
|
void context.client.invalidateQueries({ queryKey: consoleQuery.apps.get.key() })
|
||||||
void context.client.invalidateQueries({
|
void context.client.invalidateQueries({
|
||||||
queryKey: consoleQuery.apps.starred.get.key(),
|
queryKey: consoleQuery.apps.starred.get.key(),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user