mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 11:04:27 +08:00
fix(web): resolve marketplace plugin ids before install
This commit is contained in:
parent
e9180d38e5
commit
a7a16c1335
@ -1,4 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
import { MARKETPLACE_API_PREFIX } from '@/config'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
fetch: vi.fn(),
|
||||
@ -41,6 +42,41 @@ describe('PluginList', () => {
|
||||
expect(mocks.redirect).toHaveBeenCalledWith(
|
||||
'/integrations/tools/built-in?package-ids=%5B%22langgenius%2Fexample-tool%22%5D',
|
||||
)
|
||||
expect(mocks.fetch).toHaveBeenCalledWith(
|
||||
`${MARKETPLACE_API_PREFIX}/plugins/langgenius/example-tool`,
|
||||
{ cache: 'no-store' },
|
||||
)
|
||||
})
|
||||
|
||||
it('resolves a full package identifier through the identifier endpoint', async () => {
|
||||
const packageId = 'langgenius/example-tool:1.0.0@checksum'
|
||||
mocks.fetch.mockResolvedValue({
|
||||
ok: true,
|
||||
json: vi.fn().mockResolvedValue({
|
||||
data: {
|
||||
plugin: {
|
||||
category: 'tool',
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
const { default: PluginList } = await import('./page')
|
||||
|
||||
await expect(
|
||||
PluginList({
|
||||
searchParams: Promise.resolve({
|
||||
'package-ids': JSON.stringify([packageId]),
|
||||
}),
|
||||
}),
|
||||
).rejects.toThrow('NEXT_REDIRECT')
|
||||
|
||||
expect(mocks.fetch).toHaveBeenCalledWith(
|
||||
`${MARKETPLACE_API_PREFIX}/plugins/identifier?unique_identifier=${encodeURIComponent(packageId)}`,
|
||||
{ cache: 'no-store' },
|
||||
)
|
||||
expect(mocks.redirect).toHaveBeenCalledWith(
|
||||
`/integrations/tools/built-in?package-ids=${encodeURIComponent(JSON.stringify([packageId]))}`,
|
||||
)
|
||||
})
|
||||
|
||||
it('redirects to integrations when category resolution fails', async () => {
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
getInstallRedirectPathByPluginCategory,
|
||||
getInstallRedirectPathFromSearchParams,
|
||||
getLegacyPluginRedirectPath,
|
||||
parseMarketplacePluginId,
|
||||
shouldResolveInstallCategoryRedirect,
|
||||
} from '@/app/components/plugins/plugin-routes'
|
||||
import { MARKETPLACE_API_PREFIX } from '@/config'
|
||||
@ -23,10 +24,11 @@ type MarketplaceManifestCategoryResponse = {
|
||||
|
||||
const fetchPluginCategoryFromMarketplace = async (packageId: string) => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${MARKETPLACE_API_PREFIX}/plugins/identifier?unique_identifier=${encodeURIComponent(packageId)}`,
|
||||
{ cache: 'no-store' },
|
||||
)
|
||||
const pluginId = parseMarketplacePluginId(packageId)
|
||||
const path = pluginId
|
||||
? `/plugins/${encodeURIComponent(pluginId.org)}/${encodeURIComponent(pluginId.name)}`
|
||||
: `/plugins/identifier?unique_identifier=${encodeURIComponent(packageId)}`
|
||||
const response = await fetch(`${MARKETPLACE_API_PREFIX}${path}`, { cache: 'no-store' })
|
||||
|
||||
if (!response.ok) return undefined
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import PluginCategoryPage from '../plugin-category-page'
|
||||
const {
|
||||
mockContainerRef,
|
||||
mockFetchManifestFromMarketPlace,
|
||||
mockFetchPluginInfoFromMarketPlace,
|
||||
mockSetInstallState,
|
||||
mockUseUploader,
|
||||
mockUsePluginInstallation,
|
||||
@ -14,6 +15,7 @@ const {
|
||||
} = vi.hoisted(() => ({
|
||||
mockContainerRef: { current: null },
|
||||
mockFetchManifestFromMarketPlace: vi.fn(),
|
||||
mockFetchPluginInfoFromMarketPlace: vi.fn(),
|
||||
mockSetInstallState: vi.fn(),
|
||||
mockUseUploader: vi.fn((_: unknown) => ({
|
||||
dragging: false,
|
||||
@ -108,6 +110,8 @@ vi.mock('@/hooks/use-query-params', () => ({
|
||||
|
||||
vi.mock('@/service/plugins', () => ({
|
||||
fetchManifestFromMarketPlace: (...args: unknown[]) => mockFetchManifestFromMarketPlace(...args),
|
||||
fetchPluginInfoFromMarketPlace: (...args: unknown[]) =>
|
||||
mockFetchPluginInfoFromMarketPlace(...args),
|
||||
}))
|
||||
|
||||
type UploaderOptions = {
|
||||
@ -280,6 +284,49 @@ describe('PluginCategoryPage', () => {
|
||||
expect(mockSetInstallState).toHaveBeenCalledWith(null)
|
||||
})
|
||||
|
||||
it('resolves a marketplace package name before opening the installer', async () => {
|
||||
const packageId = 'langgenius/confluence_datasource'
|
||||
const uniqueIdentifier =
|
||||
'langgenius/confluence_datasource:0.2.9@923a18de89d8cdb7f419d0dff60bf08a8b81b65fef6bf606cf0ce4b0ee56a9ca'
|
||||
mockUsePluginInstallation.mockReturnValue([
|
||||
{ packageId, bundleInfo: null },
|
||||
mockSetInstallState,
|
||||
])
|
||||
mockFetchPluginInfoFromMarketPlace.mockResolvedValue({
|
||||
data: {
|
||||
plugin: {
|
||||
latest_package_identifier: uniqueIdentifier,
|
||||
},
|
||||
},
|
||||
})
|
||||
mockFetchManifestFromMarketPlace.mockResolvedValue({
|
||||
data: {
|
||||
plugin: {
|
||||
org: 'langgenius',
|
||||
name: 'confluence_datasource',
|
||||
category: PluginCategoryEnum.datasource,
|
||||
},
|
||||
version: { version: '0.2.9' },
|
||||
},
|
||||
})
|
||||
|
||||
render(<PluginCategoryPage category={PluginCategoryEnum.datasource} />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockFetchPluginInfoFromMarketPlace).toHaveBeenCalledWith({
|
||||
name: 'confluence_datasource',
|
||||
org: 'langgenius',
|
||||
})
|
||||
expect(mockFetchManifestFromMarketPlace).toHaveBeenCalledWith(
|
||||
encodeURIComponent(uniqueIdentifier),
|
||||
)
|
||||
expect(screen.getByTestId('install-from-marketplace')).toHaveAttribute(
|
||||
'data-unique-identifier',
|
||||
uniqueIdentifier,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('ignores dropped files when install permission is unavailable', () => {
|
||||
render(<PluginCategoryPage canInstall={false} category={PluginCategoryEnum.agent} />)
|
||||
|
||||
|
||||
@ -2,15 +2,29 @@
|
||||
|
||||
import type { Dependency, PluginDeclaration, PluginManifestInMarket } from '../../types'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { parseMarketplacePluginId } from '@/app/components/plugins/plugin-routes'
|
||||
import { MARKETPLACE_API_PREFIX } from '@/config'
|
||||
import { usePluginInstallation } from '@/hooks/use-query-params'
|
||||
import { fetchBundleInfoFromMarketPlace, fetchManifestFromMarketPlace } from '@/service/plugins'
|
||||
import {
|
||||
fetchBundleInfoFromMarketPlace,
|
||||
fetchManifestFromMarketPlace,
|
||||
fetchPluginInfoFromMarketPlace,
|
||||
} from '@/service/plugins'
|
||||
|
||||
type MarketplaceInstall = {
|
||||
manifest: PluginDeclaration | PluginManifestInMarket
|
||||
packageId: string
|
||||
uniqueIdentifier: string
|
||||
}
|
||||
|
||||
async function resolveMarketplaceUniqueIdentifier(packageId: string) {
|
||||
const pluginId = parseMarketplacePluginId(packageId)
|
||||
if (!pluginId) return packageId
|
||||
|
||||
const { data } = await fetchPluginInfoFromMarketPlace(pluginId)
|
||||
return data.plugin.latest_package_identifier
|
||||
}
|
||||
|
||||
export type UseInstallFromMarketplaceQueryOptions = {
|
||||
canInstallPlugin: boolean
|
||||
isPermissionLoading?: boolean
|
||||
@ -42,7 +56,8 @@ export const useInstallFromMarketplaceQuery = ({
|
||||
const loadMarketplaceInstall = async () => {
|
||||
if (packageId) {
|
||||
try {
|
||||
const { data } = await fetchManifestFromMarketPlace(encodeURIComponent(packageId))
|
||||
const uniqueIdentifier = await resolveMarketplaceUniqueIdentifier(packageId)
|
||||
const { data } = await fetchManifestFromMarketPlace(encodeURIComponent(uniqueIdentifier))
|
||||
if (ignore) return
|
||||
|
||||
const { plugin, version } = data
|
||||
@ -50,7 +65,8 @@ export const useInstallFromMarketplaceQuery = ({
|
||||
if (redirected) return
|
||||
|
||||
setMarketplaceInstall({
|
||||
uniqueIdentifier: packageId,
|
||||
packageId,
|
||||
uniqueIdentifier,
|
||||
manifest: {
|
||||
...plugin,
|
||||
version: version.version,
|
||||
@ -103,7 +119,6 @@ export const useInstallFromMarketplaceQuery = ({
|
||||
dependencies,
|
||||
hideInstallFromMarketplace,
|
||||
isShowInstallFromMarketplace,
|
||||
marketplaceInstall:
|
||||
marketplaceInstall?.uniqueIdentifier === packageId ? marketplaceInstall : null,
|
||||
marketplaceInstall: marketplaceInstall?.packageId === packageId ? marketplaceInstall : null,
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,6 +37,13 @@ const getFirstSearchParamValue = (value: string | string[] | undefined) => {
|
||||
return value
|
||||
}
|
||||
|
||||
export const parseMarketplacePluginId = (packageId: string) => {
|
||||
const [org, name, ...extraParts] = packageId.split('/')
|
||||
if (!org || !name || extraParts.length || name.includes(':')) return null
|
||||
|
||||
return { name, org }
|
||||
}
|
||||
|
||||
const hasInstallSearchParams = (searchParams: LegacyPluginsSearchParams) => {
|
||||
return Object.keys(searchParams).some((key) => installSearchParamKeys.has(key))
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user