chore: update to only SaaS can view template (#36440)

This commit is contained in:
Joel 2026-05-20 16:18:26 +08:00 committed by GitHub
parent be8627233d
commit 848c15a265
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 84 additions and 23 deletions

View File

@ -17,6 +17,20 @@ vi.mock('@/app/components/base/amplitude', () => ({
trackEvent: vi.fn(),
}))
const mockConfig = vi.hoisted(() => ({
isCloudEdition: true,
}))
vi.mock('@/config', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/config')>()
return {
...actual,
get IS_CLOUD_EDITION() {
return mockConfig.isCloudEdition
},
}
})
const mockApp: App = {
can_trial: true,
app: {
@ -70,6 +84,7 @@ describe('AppCard', () => {
}
beforeEach(() => {
mockConfig.isCloudEdition = true
vi.clearAllMocks()
})
@ -261,6 +276,13 @@ describe('AppCard', () => {
app: mockApp,
})
})
it('should hide try button outside cloud edition', () => {
mockConfig.isCloudEdition = false
renderWithProvider(<AppCard {...defaultProps} />)
expect(screen.queryByRole('button', { name: /explore\.appCard\.try/ })).not.toBeInTheDocument()
})
})
describe('Keyboard Accessibility', () => {

View File

@ -4,14 +4,13 @@ import { PlusIcon } from '@heroicons/react/20/solid'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { RiInformation2Line } from '@remixicon/react'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { useContextSelector } from 'use-context-selector'
import { trackEvent } from '@/app/components/base/amplitude'
import AppIcon from '@/app/components/base/app-icon'
import { IS_CLOUD_EDITION } from '@/config'
import AppListContext from '@/context/app-list-context'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { AppTypeIcon, AppTypeLabel } from '../../type-selector'
type AppCardProps = {
@ -27,8 +26,7 @@ const AppCard = ({
}: AppCardProps) => {
const { t } = useTranslation()
const { app: appBasicInfo } = app
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
const isTrialApp = app.can_trial && systemFeatures.enable_trial_app
const canViewApp = IS_CLOUD_EDITION
const setShowTryAppPanel = useContextSelector(AppListContext, ctx => ctx.setShowTryAppPanel)
const handleShowTryAppPanel = useCallback(() => {
trackEvent('preview_template', {
@ -69,19 +67,21 @@ const AppCard = ({
{app.description}
</div>
</div>
{(canCreate || isTrialApp) && (
{(canCreate || canViewApp) && (
<div className={cn('absolute right-0 bottom-0 left-0 hidden bg-linear-to-t from-components-panel-gradient-2 from-[60.27%] to-transparent p-4 pt-8 group-hover:flex')}>
<div className={cn('grid h-8 w-full grid-cols-1 items-center space-x-2', canCreate && 'grid-cols-2')}>
<div className={cn('grid h-8 w-full grid-cols-1 items-center space-x-2', canCreate && canViewApp && 'grid-cols-2')}>
{canCreate && (
<Button variant="primary" onClick={() => onCreate()}>
<PlusIcon className="mr-1 size-4" />
<span className="text-xs">{t('newApp.useTemplate', { ns: 'app' })}</span>
</Button>
)}
<Button onClick={handleShowTryAppPanel}>
<RiInformation2Line className="mr-1 size-4" />
<span>{t('appCard.try', { ns: 'explore' })}</span>
</Button>
{canViewApp && (
<Button onClick={handleShowTryAppPanel}>
<RiInformation2Line className="mr-1 size-4" />
<span>{t('appCard.try', { ns: 'explore' })}</span>
</Button>
)}
</div>
</div>
)}

View File

@ -15,6 +15,20 @@ vi.mock('@/app/components/base/amplitude', () => ({
trackEvent: vi.fn(),
}))
const mockConfig = vi.hoisted(() => ({
isCloudEdition: true,
}))
vi.mock('@/config', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/config')>()
return {
...actual,
get IS_CLOUD_EDITION() {
return mockConfig.isCloudEdition
},
}
})
const createApp = (overrides?: Partial<App>): App => ({
can_trial: true,
app_id: 'app-id',
@ -62,6 +76,7 @@ describe('AppCard', () => {
}
beforeEach(() => {
mockConfig.isCloudEdition = true
vi.clearAllMocks()
})
@ -113,6 +128,13 @@ describe('AppCard', () => {
expect(screen.getByText('explore.appCard.try')).toBeInTheDocument()
})
it('should hide try button outside cloud edition', () => {
mockConfig.isCloudEdition = false
renderComponent({ canCreate: true, isExplore: true })
expect(screen.queryByText('explore.appCard.try')).not.toBeInTheDocument()
})
})
describe('Props', () => {

View File

@ -5,11 +5,10 @@ import { PlusIcon } from '@heroicons/react/20/solid'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { RiInformation2Line } from '@remixicon/react'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import { trackEvent } from '@/app/components/base/amplitude'
import AppIcon from '@/app/components/base/app-icon'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { IS_CLOUD_EDITION } from '@/config'
import { AppModeEnum } from '@/types/app'
import { AppTypeIcon } from '../../app/type-selector'
@ -30,8 +29,7 @@ const AppCard = ({
}: AppCardProps) => {
const { t } = useTranslation()
const { app: appBasicInfo } = app
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
const isTrialApp = app.can_trial && systemFeatures.enable_trial_app
const canViewApp = IS_CLOUD_EDITION
const handleTryApp = () => {
trackEvent('preview_template', {
template_id: app.app_id,
@ -78,9 +76,9 @@ const AppCard = ({
{app.description}
</div>
</div>
{isExplore && (canCreate || isTrialApp) && (
{isExplore && (canCreate || canViewApp) && (
<div className={cn('absolute right-0 bottom-0 left-0 hidden bg-linear-to-t from-components-panel-gradient-2 from-[60.27%] to-transparent p-4 pt-8 group-hover:flex')}>
<div className={cn('grid h-8 w-full grid-cols-1 space-x-2', canCreate && 'grid-cols-2')}>
<div className={cn('grid h-8 w-full grid-cols-1 space-x-2', canCreate && canViewApp && 'grid-cols-2')}>
{
canCreate && (
<Button variant="primary" className="h-7" onClick={() => onCreate()}>
@ -89,10 +87,12 @@ const AppCard = ({
</Button>
)
}
<Button className="h-7" onClick={handleTryApp}>
<RiInformation2Line className="mr-1 size-4" />
<span>{t('appCard.try', { ns: 'explore' })}</span>
</Button>
{canViewApp && (
<Button className="h-7" onClick={handleTryApp}>
<RiInformation2Line className="mr-1 size-4" />
<span>{t('appCard.try', { ns: 'explore' })}</span>
</Button>
)}
</div>
</div>
)}

View File

@ -51,6 +51,20 @@ vi.mock('@/utils/create-app-tracking', () => ({
trackCreateApp: (...args: unknown[]) => mockTrackCreateApp(...args),
}))
const mockConfig = vi.hoisted(() => ({
isCloudEdition: false,
}))
vi.mock('@/config', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/config')>()
return {
...actual,
get IS_CLOUD_EDITION() {
return mockConfig.isCloudEdition
},
}
})
vi.mock('@/app/components/explore/create-app-modal', () => ({
default: (props: CreateAppModalProps) => {
if (!props.show)
@ -137,6 +151,7 @@ const mockMemberRole = (hasEditPermission: boolean) => {
type RenderOptions = {
enableExploreBanner?: boolean
isCloudEdition?: boolean
}
const renderAppList = (
@ -145,6 +160,7 @@ const renderAppList = (
searchParams?: Record<string, string>,
options: RenderOptions = {},
) => {
mockConfig.isCloudEdition = options.isCloudEdition ?? false
mockMemberRole(hasEditPermission)
const { wrapper: SystemFeaturesWrapper, queryClient } = createSystemFeaturesWrapper({
systemFeatures: { enable_explore_banner: options.enableExploreBanner ?? false },
@ -166,6 +182,7 @@ describe('AppList', () => {
mockExploreData = { categories: [], allList: [] }
mockIsLoading = false
mockIsError = false
mockConfig.isCloudEdition = false
})
afterEach(() => {
@ -400,7 +417,7 @@ describe('AppList', () => {
allList: [createApp()],
}
renderAppList(true)
renderAppList(true, undefined, undefined, { isCloudEdition: true })
fireEvent.click(screen.getByText('explore.appCard.try'))
expect(screen.getByTestId('try-app-panel')).toBeInTheDocument()
@ -423,7 +440,7 @@ describe('AppList', () => {
options.onSuccess?.({ app_mode: AppModeEnum.CHAT })
})
renderAppList(true)
renderAppList(true, undefined, undefined, { isCloudEdition: true })
fireEvent.click(screen.getByText('explore.appCard.try'))
fireEvent.click(screen.getByTestId('try-app-create'))
@ -444,7 +461,7 @@ describe('AppList', () => {
allList: [createApp()],
}
renderAppList(true)
renderAppList(true, undefined, undefined, { isCloudEdition: true })
fireEvent.click(screen.getByText('explore.appCard.try'))
expect(screen.getByTestId('try-app-panel')).toBeInTheDocument()