mirror of
https://github.com/langgenius/dify.git
synced 2026-07-21 02:28:30 +08:00
fix: can not create app from learn dify in app list page (#39187)
This commit is contained in:
parent
83f40b85f4
commit
38045078dc
@ -1,7 +1,9 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import type { App } from '@/models/explore'
|
||||
import type { TryAppSelection } from '@/types/try-app'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import * as React from 'react'
|
||||
import { useContextSelector } from 'use-context-selector'
|
||||
import AppListContext from '@/context/app-list-context'
|
||||
@ -132,7 +134,13 @@ vi.mock('@/next/navigation', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('../list', () => {
|
||||
const MockList = () => {
|
||||
const MockList = ({
|
||||
onCreateLearnDify,
|
||||
onTryLearnDify,
|
||||
}: {
|
||||
onCreateLearnDify?: (app: App) => void
|
||||
onTryLearnDify?: (params: TryAppSelection) => void
|
||||
}) => {
|
||||
const setShowTryAppPanel = useContextSelector(AppListContext, (ctx) => ctx.setShowTryAppPanel)
|
||||
return React.createElement(
|
||||
'div',
|
||||
@ -150,6 +158,19 @@ vi.mock('../list', () => {
|
||||
},
|
||||
'Open Preview',
|
||||
),
|
||||
React.createElement(
|
||||
'button',
|
||||
{
|
||||
type: 'button',
|
||||
onClick: () => onTryLearnDify?.({ appId: mockTemplateApp.app_id, app: mockTemplateApp }),
|
||||
},
|
||||
'Preview Learn Dify template',
|
||||
),
|
||||
React.createElement(
|
||||
'button',
|
||||
{ type: 'button', onClick: () => onCreateLearnDify?.(mockTemplateApp) },
|
||||
'Create Learn Dify template',
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ -365,6 +386,24 @@ describe('Apps', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should open the template preview from Learn Dify', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderWithClient(<Apps />)
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'Preview Learn Dify template' }))
|
||||
|
||||
expect(await screen.findByTestId('try-app-panel')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should open the create modal from Learn Dify', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderWithClient(<Apps />)
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'Create Learn Dify template' }))
|
||||
|
||||
expect(await screen.findByTestId('create-app-modal')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should track template preview creation after confirming a pending import', async () => {
|
||||
mockHandleImportDSL.mockImplementation(
|
||||
async (_payload: unknown, options: { onPending?: () => void }) => {
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen'
|
||||
import type { App } from '@/models/explore'
|
||||
import type { TryAppSelection } from '@/types/try-app'
|
||||
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import * as React from 'react'
|
||||
@ -30,6 +32,36 @@ const mockUseWorkflowOnlineUsers = vi.hoisted(() =>
|
||||
})),
|
||||
)
|
||||
|
||||
const mockLearnDifyApp = vi.hoisted(
|
||||
() =>
|
||||
({
|
||||
app_id: 'learn-dify-template',
|
||||
app: {
|
||||
id: 'learn-dify-template-source',
|
||||
mode: 'chat',
|
||||
icon_type: 'emoji',
|
||||
icon: '🤖',
|
||||
icon_background: '#fff',
|
||||
icon_url: '',
|
||||
name: 'Learn Dify Template',
|
||||
description: 'Learn how to build with Dify',
|
||||
use_icon_as_answer_icon: false,
|
||||
},
|
||||
description: 'Learn how to build with Dify',
|
||||
copyright: '',
|
||||
privacy_policy: null,
|
||||
custom_disclaimer: null,
|
||||
categories: ['Assistant'],
|
||||
position: 1,
|
||||
is_listed: true,
|
||||
install_count: 0,
|
||||
installed: false,
|
||||
editable: false,
|
||||
is_agent: false,
|
||||
can_trial: true,
|
||||
}) satisfies App,
|
||||
)
|
||||
|
||||
const mockReplace = vi.fn()
|
||||
const mockRouter = { replace: mockReplace }
|
||||
let mockSearchParams = new URLSearchParams('')
|
||||
@ -436,7 +468,33 @@ vi.mock('../empty', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/explore/learn-dify', () => ({
|
||||
default: ({ title }: { title?: string }) => React.createElement('section', null, title),
|
||||
default: ({
|
||||
title,
|
||||
onCreate,
|
||||
onTry,
|
||||
}: {
|
||||
title?: string
|
||||
onCreate?: (app: App) => void
|
||||
onTry?: (params: TryAppSelection) => void
|
||||
}) =>
|
||||
React.createElement(
|
||||
'section',
|
||||
null,
|
||||
title,
|
||||
React.createElement(
|
||||
'button',
|
||||
{
|
||||
type: 'button',
|
||||
onClick: () => onTry?.({ appId: mockLearnDifyApp.app_id, app: mockLearnDifyApp }),
|
||||
},
|
||||
'Preview Learn Dify template',
|
||||
),
|
||||
React.createElement(
|
||||
'button',
|
||||
{ type: 'button', onClick: () => onCreate?.(mockLearnDifyApp) },
|
||||
'Create Learn Dify template',
|
||||
),
|
||||
),
|
||||
}))
|
||||
|
||||
const intersectionCallbacks: IntersectionObserverCallback[] = []
|
||||
@ -462,6 +520,8 @@ beforeAll(() => {
|
||||
// Render helper wrapping with shared nuqs testing helper plus a seeded
|
||||
// systemFeatures cache so List can resolve its useSuspenseQuery.
|
||||
type RenderListOptions = {
|
||||
onCreateLearnDify?: (app: App) => void
|
||||
onTryLearnDify?: (params: TryAppSelection) => void
|
||||
systemFeatures?: Partial<GetSystemFeaturesResponse>
|
||||
}
|
||||
|
||||
@ -472,7 +532,7 @@ const renderList = (searchParams = '', options: RenderListOptions = {}) => {
|
||||
})
|
||||
return renderWithNuqs(
|
||||
<SystemFeaturesWrapper>
|
||||
<List />
|
||||
<List onCreateLearnDify={options.onCreateLearnDify} onTryLearnDify={options.onTryLearnDify} />
|
||||
</SystemFeaturesWrapper>,
|
||||
{ searchParams },
|
||||
)
|
||||
@ -768,6 +828,28 @@ describe('List', () => {
|
||||
expect(screen.getByTestId('create-dsl-modal'))!.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should forward Learn Dify template interactions', async () => {
|
||||
const user = userEvent.setup()
|
||||
const onCreateLearnDify = vi.fn()
|
||||
const onTryLearnDify = vi.fn()
|
||||
mockAppData = { pages: [{ data: [], total: 0 }] }
|
||||
|
||||
renderList('', {
|
||||
onCreateLearnDify,
|
||||
onTryLearnDify,
|
||||
systemFeatures: { enable_learn_app: true },
|
||||
})
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'Preview Learn Dify template' }))
|
||||
expect(onTryLearnDify).toHaveBeenCalledWith({
|
||||
appId: mockLearnDifyApp.app_id,
|
||||
app: mockLearnDifyApp,
|
||||
})
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'Create Learn Dify template' }))
|
||||
expect(onCreateLearnDify).toHaveBeenCalledWith(mockLearnDifyApp)
|
||||
})
|
||||
|
||||
it('should pass workflow app ids to online users hook', () => {
|
||||
renderList()
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import type { App } from '@/models/explore'
|
||||
import type { TryAppSelection } from '@/types/try-app'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import LearnDify from '@/app/components/explore/learn-dify'
|
||||
import FirstEmptyActionCard from './action-card'
|
||||
@ -20,12 +22,21 @@ type EmptyCreateAction = {
|
||||
|
||||
type Props = {
|
||||
onCreateBlank: () => void
|
||||
onCreateLearnDify?: (app: App) => void
|
||||
onCreateTemplate: () => void
|
||||
onImportDSL: () => void
|
||||
onTryLearnDify?: (params: TryAppSelection) => void
|
||||
showLearnDify: boolean
|
||||
}
|
||||
|
||||
function FirstEmptyState({ onCreateBlank, onCreateTemplate, onImportDSL, showLearnDify }: Props) {
|
||||
function FirstEmptyState({
|
||||
onCreateBlank,
|
||||
onCreateLearnDify,
|
||||
onCreateTemplate,
|
||||
onImportDSL,
|
||||
onTryLearnDify,
|
||||
showLearnDify,
|
||||
}: Props) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const actions: EmptyCreateAction[] = [
|
||||
@ -109,9 +120,12 @@ function FirstEmptyState({ onCreateBlank, onCreateTemplate, onImportDSL, showLea
|
||||
</div>
|
||||
{showLearnDify && (
|
||||
<LearnDify
|
||||
canCreate
|
||||
className="px-4 pt-2 pb-0 [&_div.grid]:gap-3 [&>div]:mx-0 [&>div]:rounded-t-2xl [&>div]:rounded-b-none [&>div]:px-5 [&>div]:pt-4 [&>div]:pb-5"
|
||||
dismissible={false}
|
||||
itemLimit={4}
|
||||
onCreate={onCreateLearnDify}
|
||||
onTry={onTryLearnDify}
|
||||
showDescription
|
||||
title={t(($) => $['firstEmpty.learnDifyTitle'], { ns: 'app' })}
|
||||
/>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
import type { CreateAppModalProps } from '../explore/create-app-modal'
|
||||
import type { App } from '@/models/explore'
|
||||
import type { TryAppSelection } from '@/types/try-app'
|
||||
import type { TrackCreateAppParams } from '@/utils/create-app-tracking'
|
||||
import { useAtomValue } from 'jotai'
|
||||
@ -60,6 +61,16 @@ const Apps = () => {
|
||||
}
|
||||
const [isShowCreateModal, setIsShowCreateModal] = useState(false)
|
||||
|
||||
const handleTryLearnDify = (params: TryAppSelection) => {
|
||||
setShowTryAppPanel(true, params)
|
||||
}
|
||||
const handleCreateLearnDify = (app: App) => {
|
||||
if (!canCreateApp) return
|
||||
|
||||
setCurrentTryAppParams({ appId: app.app_id, app })
|
||||
setIsShowCreateModal(true)
|
||||
}
|
||||
|
||||
const handleShowFromTryApp = useCallback(() => {
|
||||
if (!canCreateApp) return
|
||||
|
||||
@ -190,7 +201,11 @@ const Apps = () => {
|
||||
}}
|
||||
>
|
||||
<div className="relative flex h-0 shrink-0 grow flex-col overflow-y-auto bg-background-body">
|
||||
<List controlRefreshList={controlRefreshList} />
|
||||
<List
|
||||
controlRefreshList={controlRefreshList}
|
||||
onCreateLearnDify={handleCreateLearnDify}
|
||||
onTryLearnDify={handleTryLearnDify}
|
||||
/>
|
||||
{isShowTryAppPanel && (
|
||||
<TryApp
|
||||
appId={currentTryAppParams?.appId || ''}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import type { GetAppsData } from '@dify/contracts/api/console/apps/types.gen'
|
||||
import type { App } from '@/models/explore'
|
||||
import type { TryAppSelection } from '@/types/try-app'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
keepPreviousData,
|
||||
@ -42,9 +44,11 @@ type AppListSortBy = NonNullable<AppListQuery['sort_by']>
|
||||
|
||||
type Props = Readonly<{
|
||||
controlRefreshList?: number
|
||||
onCreateLearnDify?: (app: App) => void
|
||||
onTryLearnDify?: (params: TryAppSelection) => void
|
||||
}>
|
||||
|
||||
function List({ controlRefreshList = 0 }: Props) {
|
||||
function List({ controlRefreshList = 0, onCreateLearnDify, onTryLearnDify }: Props) {
|
||||
const { t } = useTranslation()
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
@ -270,8 +274,10 @@ function List({ controlRefreshList = 0 }: Props) {
|
||||
{showFirstEmptyState ? (
|
||||
<FirstEmptyState
|
||||
onCreateBlank={openCreateBlankModal}
|
||||
onCreateLearnDify={onCreateLearnDify}
|
||||
onCreateTemplate={openCreateTemplateDialog}
|
||||
onImportDSL={openCreateFromDSLModal}
|
||||
onTryLearnDify={onTryLearnDify}
|
||||
showLearnDify={systemFeatures.enable_learn_app}
|
||||
/>
|
||||
) : (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user