diff --git a/eslint-suppressions.json b/eslint-suppressions.json index b44db3bc106..d9d06836644 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -3583,11 +3583,6 @@ "count": 2 } }, - "web/app/components/header/account-setting/model-provider-page/model-provider-page-body.tsx": { - "jsx-a11y/anchor-has-content": { - "count": 1 - } - }, "web/app/components/header/account-setting/model-provider-page/provider-added-card/cooldown-timer.tsx": { "react/set-state-in-effect": { "count": 1 diff --git a/web/__tests__/plugins/plugin-page-shell-flow.test.tsx b/web/__tests__/plugins/plugin-page-shell-flow.test.tsx index fde85d7abcd..483df7c8ec7 100644 --- a/web/__tests__/plugins/plugin-page-shell-flow.test.tsx +++ b/web/__tests__/plugins/plugin-page-shell-flow.test.tsx @@ -6,6 +6,9 @@ import PluginPage from '@/app/components/plugins/plugin-page' import { createNuqsTestWrapper } from '@/test/nuqs-testing' const mockFetchManifestFromMarketPlace = vi.fn() +const { mockRouterReplace } = vi.hoisted(() => ({ + mockRouterReplace: vi.fn(), +})) vi.mock('react-i18next', () => ({ useTranslation: () => ({ @@ -26,6 +29,12 @@ vi.mock('@/hooks/use-document-title', () => ({ default: vi.fn(), })) +vi.mock('@/next/navigation', () => ({ + useRouter: () => ({ + replace: mockRouterReplace, + }), +})) + vi.mock('@/context/i18n', () => ({ useDocLink: () => (path: string) => `https://docs.example.com${path}`, })) diff --git a/web/app/(commonLayout)/plugins/page.tsx b/web/app/(commonLayout)/plugins/page.tsx index 55dd57b7ea2..5972cd33ff6 100644 --- a/web/app/(commonLayout)/plugins/page.tsx +++ b/web/app/(commonLayout)/plugins/page.tsx @@ -2,17 +2,65 @@ import type { LegacyPluginsSearchParams } from '@/app/components/plugins/plugin- import Marketplace from '@/app/components/plugins/marketplace' import PluginPage from '@/app/components/plugins/plugin-page' import PluginsPanel from '@/app/components/plugins/plugin-page/plugins-panel' -import { getLegacyPluginRedirectPath } from '@/app/components/plugins/plugin-routes' +import { + getFirstPackageIdFromSearchParams, + getInstallRedirectPathByPluginCategory, + getInstallRedirectPathFromSearchParams, + getLegacyPluginRedirectPath, + shouldResolveInstallCategoryRedirect, +} from '@/app/components/plugins/plugin-routes' +import { MARKETPLACE_API_PREFIX } from '@/config' import { redirect } from '@/next/navigation' type PluginListProps = { searchParams?: Promise } +type MarketplaceManifestCategoryResponse = { + data?: { + plugin?: { + category?: string + } + } +} + +const fetchPluginCategoryFromMarketplace = async (packageId: string) => { + try { + const response = await fetch( + `${MARKETPLACE_API_PREFIX}/plugins/identifier?unique_identifier=${encodeURIComponent(packageId)}`, + { cache: 'no-store' }, + ) + + if (!response.ok) + return undefined + + const payload = await response.json() as MarketplaceManifestCategoryResponse + return payload.data?.plugin?.category + } + catch { + return undefined + } +} + const PluginList = async ({ searchParams, }: PluginListProps) => { - const redirectPath = getLegacyPluginRedirectPath(await searchParams) + const resolvedSearchParams = await searchParams ?? {} + const installRedirectPathFromSearchParams = getInstallRedirectPathFromSearchParams(resolvedSearchParams) + + if (installRedirectPathFromSearchParams) + redirect(installRedirectPathFromSearchParams) + + if (shouldResolveInstallCategoryRedirect(resolvedSearchParams)) { + const packageId = getFirstPackageIdFromSearchParams(resolvedSearchParams) + const category = packageId ? await fetchPluginCategoryFromMarketplace(packageId) : undefined + const installRedirectPath = getInstallRedirectPathByPluginCategory(category, resolvedSearchParams) + + if (installRedirectPath) + redirect(installRedirectPath) + } + + const redirectPath = getLegacyPluginRedirectPath(resolvedSearchParams) if (redirectPath) redirect(redirectPath) diff --git a/web/app/components/header/account-setting/data-source-page-new/__tests__/install-from-marketplace.spec.tsx b/web/app/components/header/account-setting/data-source-page-new/__tests__/install-from-marketplace.spec.tsx index ba73742642f..c8434077ed0 100644 --- a/web/app/components/header/account-setting/data-source-page-new/__tests__/install-from-marketplace.spec.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/__tests__/install-from-marketplace.spec.tsx @@ -180,5 +180,22 @@ describe('InstallFromMarketplace Component', () => { // Assert expect(screen.queryByRole('status')).not.toBeInTheDocument() }) + + it('should use the marketplace callback action when provided', () => { + // Arrange + vi.mocked(useMarketplaceAllPlugins).mockReturnValue({ + plugins: mockPlugins, + isLoading: false, + }) + const onOpenMarketplace = vi.fn() + render() + + // Act + fireEvent.click(screen.getByRole('button', { name: 'plugin.marketplace.difyMarketplace' })) + + // Assert + expect(onOpenMarketplace).toHaveBeenCalledTimes(1) + expect(screen.queryByRole('link', { name: 'plugin.marketplace.difyMarketplace' })).not.toBeInTheDocument() + }) }) }) diff --git a/web/app/components/header/account-setting/data-source-page-new/index.tsx b/web/app/components/header/account-setting/data-source-page-new/index.tsx index a76f33892c8..598aed0241c 100644 --- a/web/app/components/header/account-setting/data-source-page-new/index.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/index.tsx @@ -18,6 +18,7 @@ import InstallFromMarketplace from './install-from-marketplace' type DataSourcePageProps = { layout?: (parts: { body: ReactNode, toolbar: ReactNode }) => ReactNode + onOpenMarketplace?: () => void stickyToolbar?: boolean } @@ -53,6 +54,7 @@ function DataSourceListSkeleton() { const DataSourcePage = ({ layout, + onOpenMarketplace, stickyToolbar, }: DataSourcePageProps) => { const { t } = useTranslation() @@ -164,6 +166,7 @@ const DataSourcePage = ({ ) } diff --git a/web/app/components/header/account-setting/data-source-page-new/install-from-marketplace.tsx b/web/app/components/header/account-setting/data-source-page-new/install-from-marketplace.tsx index 15e1bfeb1d5..e009d99b15e 100644 --- a/web/app/components/header/account-setting/data-source-page-new/install-from-marketplace.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/install-from-marketplace.tsx @@ -1,10 +1,6 @@ import type { DataSourceAuth } from './types' import type { Plugin } from '@/app/components/plugins/types' import { cn } from '@langgenius/dify-ui/cn' -import { - RiArrowDownSLine, - RiArrowRightUpLine, -} from '@remixicon/react' import { useTheme } from 'next-themes' import { memo, @@ -25,10 +21,12 @@ import { } from './hooks' type InstallFromMarketplaceProps = { + onOpenMarketplace?: () => void providers: DataSourceAuth[] searchText: string } const InstallFromMarketplace = ({ + onOpenMarketplace, providers, searchText, }: InstallFromMarketplaceProps) => { @@ -58,15 +56,28 @@ const InstallFromMarketplace = ({ className="flex cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-left system-md-semibold text-text-primary" onClick={() => setCollapse(!collapse)} > -