Browse Source

refactor: add API endpoint to list latest plugin versions and query it in a asynchronous way (#17695)

tags/1.3.0
Yeuoly 6 months ago
parent
commit
33324ee23d
No account linked to committer's email address

+ 18
- 0
api/controllers/console/workspace/plugin.py View File

return jsonable_encoder({"plugins": plugins}) return jsonable_encoder({"plugins": plugins})




class PluginListLatestVersionsApi(Resource):
@setup_required
@login_required
@account_initialization_required
def post(self):
req = reqparse.RequestParser()
req.add_argument("plugin_ids", type=list, required=True, location="json")
args = req.parse_args()

try:
versions = PluginService.list_latest_versions(args["plugin_ids"])
except PluginDaemonClientSideError as e:
raise ValueError(e)

return jsonable_encoder({"versions": versions})


class PluginListInstallationsFromIdsApi(Resource): class PluginListInstallationsFromIdsApi(Resource):
@setup_required @setup_required
@login_required @login_required


api.add_resource(PluginDebuggingKeyApi, "/workspaces/current/plugin/debugging-key") api.add_resource(PluginDebuggingKeyApi, "/workspaces/current/plugin/debugging-key")
api.add_resource(PluginListApi, "/workspaces/current/plugin/list") api.add_resource(PluginListApi, "/workspaces/current/plugin/list")
api.add_resource(PluginListLatestVersionsApi, "/workspaces/current/plugin/list/latest-versions")
api.add_resource(PluginListInstallationsFromIdsApi, "/workspaces/current/plugin/list/installations/ids") api.add_resource(PluginListInstallationsFromIdsApi, "/workspaces/current/plugin/list/installations/ids")
api.add_resource(PluginIconApi, "/workspaces/current/plugin/icon") api.add_resource(PluginIconApi, "/workspaces/current/plugin/icon")
api.add_resource(PluginUploadFromPkgApi, "/workspaces/current/plugin/upload/pkg") api.add_resource(PluginUploadFromPkgApi, "/workspaces/current/plugin/upload/pkg")

+ 0
- 2
api/core/plugin/entities/plugin.py View File

name: str name: str
installation_id: str installation_id: str
version: str version: str
latest_version: Optional[str] = None
latest_unique_identifier: Optional[str] = None


@model_validator(mode="after") @model_validator(mode="after")
def set_plugin_id(self): def set_plugin_id(self):

+ 7
- 16
api/services/plugin/plugin_service.py View File

manager = PluginDebuggingManager() manager = PluginDebuggingManager()
return manager.get_debugging_key(tenant_id) return manager.get_debugging_key(tenant_id)


@staticmethod
def list_latest_versions(plugin_ids: Sequence[str]) -> Mapping[str, Optional[LatestPluginCache]]:
"""
List the latest versions of the plugins
"""
return PluginService.fetch_latest_plugin_version(plugin_ids)

@staticmethod @staticmethod
def list(tenant_id: str) -> list[PluginEntity]: def list(tenant_id: str) -> list[PluginEntity]:
""" """
""" """
manager = PluginInstallationManager() manager = PluginInstallationManager()
plugins = manager.list_plugins(tenant_id) plugins = manager.list_plugins(tenant_id)
plugin_ids = [plugin.plugin_id for plugin in plugins if plugin.source == PluginInstallationSource.Marketplace]
try:
manifests = PluginService.fetch_latest_plugin_version(plugin_ids)
except Exception:
manifests = {}
logger.exception("failed to fetch plugin manifests")

for plugin in plugins:
if plugin.source == PluginInstallationSource.Marketplace:
if plugin.plugin_id in manifests:
latest_plugin_cache = manifests[plugin.plugin_id]
if latest_plugin_cache:
# set latest_version
plugin.latest_version = latest_plugin_cache.version
plugin.latest_unique_identifier = latest_plugin_cache.unique_identifier

return plugins return plugins


@staticmethod @staticmethod

+ 19
- 5
web/app/components/plugins/plugin-page/plugins-panel.tsx View File

import type { FilterState } from './filter-management' import type { FilterState } from './filter-management'
import FilterManagement from './filter-management' import FilterManagement from './filter-management'
import List from './list' import List from './list'
import { useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins'
import { useInstalledLatestVersion, useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins'
import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel' import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel'
import { usePluginPageContext } from './context' import { usePluginPageContext } from './context'
import { useDebounceFn } from 'ahooks' import { useDebounceFn } from 'ahooks'
import Empty from './empty' import Empty from './empty'
import Loading from '../../base/loading' import Loading from '../../base/loading'
import { PluginSource } from '../types'


const PluginsPanel = () => { const PluginsPanel = () => {
const filters = usePluginPageContext(v => v.filters) as FilterState const filters = usePluginPageContext(v => v.filters) as FilterState
const setFilters = usePluginPageContext(v => v.setFilters) const setFilters = usePluginPageContext(v => v.setFilters)
const { data: pluginList, isLoading: isPluginListLoading } = useInstalledPluginList() const { data: pluginList, isLoading: isPluginListLoading } = useInstalledPluginList()
const { data: installedLatestVersion } = useInstalledLatestVersion(
pluginList?.plugins
.filter(plugin => plugin.source === PluginSource.marketplace)
.map(plugin => plugin.plugin_id) ?? [],
)
const invalidateInstalledPluginList = useInvalidateInstalledPluginList() const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
const currentPluginID = usePluginPageContext(v => v.currentPluginID) const currentPluginID = usePluginPageContext(v => v.currentPluginID)
const setCurrentPluginID = usePluginPageContext(v => v.setCurrentPluginID) const setCurrentPluginID = usePluginPageContext(v => v.setCurrentPluginID)
setFilters(filters) setFilters(filters)
}, { wait: 500 }) }, { wait: 500 })


const pluginListWithLatestVersion = useMemo(() => {
return pluginList?.plugins.map(plugin => ({
...plugin,
latest_version: installedLatestVersion?.versions[plugin.plugin_id]?.version ?? '',
latest_unique_identifier: installedLatestVersion?.versions[plugin.plugin_id]?.unique_identifier ?? '',
})) || []
}, [pluginList, installedLatestVersion])

const filteredList = useMemo(() => { const filteredList = useMemo(() => {
const { categories, searchQuery, tags } = filters const { categories, searchQuery, tags } = filters
const filteredList = pluginList?.plugins.filter((plugin) => {
const filteredList = pluginListWithLatestVersion.filter((plugin) => {
return ( return (
(categories.length === 0 || categories.includes(plugin.declaration.category)) (categories.length === 0 || categories.includes(plugin.declaration.category))
&& (tags.length === 0 || tags.some(tag => plugin.declaration.tags.includes(tag))) && (tags.length === 0 || tags.some(tag => plugin.declaration.tags.includes(tag)))
) )
}) })
return filteredList return filteredList
}, [pluginList, filters])
}, [pluginListWithLatestVersion, filters])


const currentPluginDetail = useMemo(() => { const currentPluginDetail = useMemo(() => {
const detail = pluginList?.plugins.find(plugin => plugin.plugin_id === currentPluginID)
const detail = pluginListWithLatestVersion.find(plugin => plugin.plugin_id === currentPluginID)
return detail return detail
}, [currentPluginID, pluginList?.plugins])
}, [currentPluginID, pluginListWithLatestVersion])


const handleHide = () => setCurrentPluginID(undefined) const handleHide = () => setCurrentPluginID(undefined)



+ 9
- 0
web/app/components/plugins/types.ts View File

plugins: PluginDetail[] plugins: PluginDetail[]
} }


export type InstalledLatestVersionResponse = {
versions: {
[plugin_id: string]: {
unique_identifier: string
version: string
} | null
}
}

export type UninstallPluginResponse = { export type UninstallPluginResponse = {
success: boolean success: boolean
} }

+ 14
- 0
web/service/use-plugins.ts View File

Dependency, Dependency,
GitHubItemAndMarketPlaceDependency, GitHubItemAndMarketPlaceDependency,
InstallPackageResponse, InstallPackageResponse,
InstalledLatestVersionResponse,
InstalledPluginListResponse, InstalledPluginListResponse,
PackageDependency, PackageDependency,
Permissions, Permissions,
}) })
} }


export const useInstalledLatestVersion = (pluginIds: string[]) => {
return useQuery<InstalledLatestVersionResponse>({
queryKey: [NAME_SPACE, 'installedLatestVersion', pluginIds],
queryFn: () => post<InstalledLatestVersionResponse>('/workspaces/current/plugin/list/latest-versions', {
body: {
plugin_ids: pluginIds,
},
}),
enabled: !!pluginIds.length,
initialData: pluginIds.length ? undefined : { versions: {} },
})
}

export const useInvalidateInstalledPluginList = () => { export const useInvalidateInstalledPluginList = () => {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const invalidateAllBuiltInTools = useInvalidateAllBuiltInTools() const invalidateAllBuiltInTools = useInvalidateAllBuiltInTools()

Loading…
Cancel
Save