mirror of
https://github.com/langgenius/dify.git
synced 2026-07-01 19:36:52 +08:00
Compare commits
1 Commits
codex/migr
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| bf46b82303 |
@ -31,7 +31,7 @@ from controllers.console.wraps import (
|
||||
with_current_user_id,
|
||||
)
|
||||
from core.helper.position_helper import is_filtered
|
||||
from core.plugin.entities.plugin import PluginCategory, PluginInstallation, PluginInstallationSource
|
||||
from core.plugin.entities.plugin import PluginCategory, PluginInstallationSource
|
||||
from core.plugin.impl.exc import PluginDaemonClientSideError
|
||||
from core.plugin.plugin_service import PluginService
|
||||
from core.tools.builtin_tool.providers._positions import BuiltinToolProviderSort
|
||||
@ -309,8 +309,6 @@ class PluginInstallationItemResponse(ResponseModel):
|
||||
id: str
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
name: str
|
||||
installation_id: str
|
||||
tenant_id: str
|
||||
endpoints_setups: int
|
||||
endpoints_active: int
|
||||
@ -320,45 +318,14 @@ class PluginInstallationItemResponse(ResponseModel):
|
||||
plugin_id: str
|
||||
plugin_unique_identifier: str
|
||||
version: str
|
||||
latest_version: str
|
||||
latest_unique_identifier: str
|
||||
status: Literal["active", "deleted"]
|
||||
deprecated_reason: str
|
||||
alternative_plugin_id: str
|
||||
checksum: str
|
||||
declaration: PluginDeclarationResponse
|
||||
declaration: Mapping[str, Any]
|
||||
|
||||
|
||||
class PluginInstallationsResponse(ResponseModel):
|
||||
plugins: list[PluginInstallationItemResponse]
|
||||
|
||||
|
||||
def _plugin_installation_response(plugin: PluginInstallation) -> PluginInstallationItemResponse:
|
||||
return PluginInstallationItemResponse(
|
||||
id=plugin.id,
|
||||
created_at=plugin.created_at,
|
||||
updated_at=plugin.updated_at,
|
||||
name=plugin.declaration.name,
|
||||
installation_id=plugin.id,
|
||||
tenant_id=plugin.tenant_id,
|
||||
endpoints_setups=plugin.endpoints_setups,
|
||||
endpoints_active=plugin.endpoints_active,
|
||||
runtime_type=plugin.runtime_type,
|
||||
source=plugin.source,
|
||||
meta=plugin.meta,
|
||||
plugin_id=plugin.plugin_id,
|
||||
plugin_unique_identifier=plugin.plugin_unique_identifier,
|
||||
version=plugin.version,
|
||||
latest_version=plugin.version,
|
||||
latest_unique_identifier=plugin.plugin_unique_identifier,
|
||||
status="active",
|
||||
deprecated_reason="",
|
||||
alternative_plugin_id="",
|
||||
checksum=plugin.checksum,
|
||||
declaration=PluginDeclarationResponse.model_validate(jsonable_encoder(plugin.declaration)),
|
||||
)
|
||||
|
||||
|
||||
class PluginManifestResponse(ResponseModel):
|
||||
manifest: Any
|
||||
|
||||
@ -621,10 +588,7 @@ class PluginListInstallationsFromIdsApi(Resource):
|
||||
except PluginDaemonClientSideError as e:
|
||||
return {"code": "plugin_error", "message": e.description}, 400
|
||||
|
||||
return dump_response(
|
||||
PluginInstallationsResponse,
|
||||
{"plugins": [_plugin_installation_response(plugin) for plugin in plugins]},
|
||||
)
|
||||
return jsonable_encoder({"plugins": plugins})
|
||||
|
||||
|
||||
@console_ns.route("/workspaces/current/plugin/icon")
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from typing import Any, Literal
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field, computed_field, model_validator
|
||||
|
||||
@ -32,9 +32,7 @@ class MarketplacePluginDeclaration(BaseModel):
|
||||
latest_package_identifier: str = Field(
|
||||
..., description="Unique identifier for the latest package release of the plugin"
|
||||
)
|
||||
status: Literal["active", "deleted"] = Field(
|
||||
..., description="Indicate the status of marketplace plugin, enum from `active` `deleted`"
|
||||
)
|
||||
status: str = Field(..., description="Indicate the status of marketplace plugin, enum from `active` `deleted`")
|
||||
deprecated_reason: str = Field(
|
||||
..., description="Not empty when status='deleted', indicates the reason why this plugin is deleted(deprecated)"
|
||||
)
|
||||
|
||||
@ -16,7 +16,7 @@ import logging
|
||||
import time
|
||||
from collections.abc import Mapping, Sequence
|
||||
from mimetypes import guess_type
|
||||
from typing import Any, ClassVar, Literal
|
||||
from typing import Any, ClassVar
|
||||
|
||||
from pydantic import BaseModel, TypeAdapter, ValidationError
|
||||
from redis import RedisError
|
||||
@ -75,7 +75,7 @@ class PluginService:
|
||||
plugin_id: str
|
||||
version: str
|
||||
unique_identifier: str
|
||||
status: Literal["active", "deleted"]
|
||||
status: str
|
||||
deprecated_reason: str
|
||||
alternative_plugin_id: str
|
||||
|
||||
|
||||
@ -18093,7 +18093,7 @@ Enum class for large language model mode.
|
||||
| alternative_plugin_id | string | | Yes |
|
||||
| deprecated_reason | string | | Yes |
|
||||
| plugin_id | string | | Yes |
|
||||
| status | string, <br>**Available values:** "active", "deleted" | *Enum:* `"active"`, `"deleted"` | Yes |
|
||||
| status | string | | Yes |
|
||||
| unique_identifier | string | | Yes |
|
||||
| version | string | | Yes |
|
||||
|
||||
@ -19588,24 +19588,17 @@ Shared permission levels for resources (datasets, credentials, etc.)
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| alternative_plugin_id | string | | Yes |
|
||||
| checksum | string | | Yes |
|
||||
| created_at | dateTime | | Yes |
|
||||
| declaration | [PluginDeclarationResponse](#plugindeclarationresponse) | | Yes |
|
||||
| deprecated_reason | string | | Yes |
|
||||
| declaration | object | | Yes |
|
||||
| endpoints_active | integer | | Yes |
|
||||
| endpoints_setups | integer | | Yes |
|
||||
| id | string | | Yes |
|
||||
| installation_id | string | | Yes |
|
||||
| latest_unique_identifier | string | | Yes |
|
||||
| latest_version | string | | Yes |
|
||||
| meta | object | | Yes |
|
||||
| name | string | | Yes |
|
||||
| plugin_id | string | | Yes |
|
||||
| plugin_unique_identifier | string | | Yes |
|
||||
| runtime_type | string | | Yes |
|
||||
| source | [PluginInstallationSource](#plugininstallationsource) | | Yes |
|
||||
| status | string, <br>**Available values:** "active", "deleted" | *Enum:* `"active"`, `"deleted"` | Yes |
|
||||
| tenant_id | string | | Yes |
|
||||
| updated_at | dateTime | | Yes |
|
||||
| version | string | | Yes |
|
||||
|
||||
@ -42,7 +42,6 @@ from controllers.console.workspace.plugin import (
|
||||
PluginUploadFromGithubApi,
|
||||
PluginUploadFromPkgApi,
|
||||
)
|
||||
from core.plugin.entities.plugin import PluginInstallation
|
||||
from core.plugin.impl.exc import PluginDaemonClientSideError
|
||||
from models.account import Account, TenantAccountRole, TenantPluginAutoUpgradeStrategy, TenantPluginPermission
|
||||
|
||||
@ -479,19 +478,12 @@ class TestPluginListInstallationsFromIdsApi:
|
||||
app.test_request_context("/", json=payload),
|
||||
patch(
|
||||
"controllers.console.workspace.plugin.PluginService.list_installations_from_ids",
|
||||
return_value=[PluginInstallation.model_validate(_plugin_category_list_item())],
|
||||
return_value=[{"id": "p1"}],
|
||||
),
|
||||
):
|
||||
result = method(api, "t1")
|
||||
|
||||
assert result["plugins"][0]["id"] == "entity-1"
|
||||
assert result["plugins"][0]["name"] == "test-plugin"
|
||||
assert result["plugins"][0]["installation_id"] == "entity-1"
|
||||
assert result["plugins"][0]["latest_version"] == "1.0.0"
|
||||
assert result["plugins"][0]["latest_unique_identifier"] == "test-author/test-plugin:1.0.0@checksum"
|
||||
assert result["plugins"][0]["status"] == "active"
|
||||
assert result["plugins"][0]["deprecated_reason"] == ""
|
||||
assert result["plugins"][0]["alternative_plugin_id"] == ""
|
||||
assert "plugins" in result
|
||||
|
||||
def test_daemon_error(self, app: Flask):
|
||||
api = PluginListInstallationsFromIdsApi()
|
||||
|
||||
@ -1132,26 +1132,21 @@ export type PluginAutoUpgradeSettingsResponseModel = {
|
||||
}
|
||||
|
||||
export type PluginInstallationItemResponse = {
|
||||
alternative_plugin_id: string
|
||||
checksum: string
|
||||
created_at: string
|
||||
declaration: PluginDeclarationResponse
|
||||
deprecated_reason: string
|
||||
declaration: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
endpoints_active: number
|
||||
endpoints_setups: number
|
||||
id: string
|
||||
installation_id: string
|
||||
latest_unique_identifier: string
|
||||
latest_version: string
|
||||
meta: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
name: string
|
||||
plugin_id: string
|
||||
plugin_unique_identifier: string
|
||||
runtime_type: string
|
||||
source: PluginInstallationSource
|
||||
status: 'active' | 'deleted'
|
||||
tenant_id: string
|
||||
updated_at: string
|
||||
version: string
|
||||
@ -1161,7 +1156,7 @@ export type LatestPluginCache = {
|
||||
alternative_plugin_id: string
|
||||
deprecated_reason: string
|
||||
plugin_id: string
|
||||
status: 'active' | 'deleted'
|
||||
status: string
|
||||
unique_identifier: string
|
||||
version: string
|
||||
}
|
||||
@ -1484,6 +1479,39 @@ export type StrategySetting = 'disabled' | 'fix_only' | 'latest'
|
||||
|
||||
export type UpgradeMode = 'all' | 'exclude' | 'partial'
|
||||
|
||||
export type PluginInstallationSource = 'github' | 'marketplace' | 'package' | 'remote'
|
||||
|
||||
export type CoreToolsEntitiesCommonEntitiesI18nObject = {
|
||||
en_US: string
|
||||
ja_JP?: string | null
|
||||
pt_BR?: string | null
|
||||
zh_Hans?: string | null
|
||||
}
|
||||
|
||||
export type PluginCategoryBuiltinToolResponse = {
|
||||
author: string
|
||||
description: CoreToolsEntitiesCommonEntitiesI18nObject
|
||||
label: CoreToolsEntitiesCommonEntitiesI18nObject
|
||||
labels: Array<string>
|
||||
name: string
|
||||
output_schema: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
parameters?: Array<{
|
||||
[key: string]: unknown
|
||||
}> | null
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type ToolProviderType
|
||||
= | 'api'
|
||||
| 'app'
|
||||
| 'builtin'
|
||||
| 'dataset-retrieval'
|
||||
| 'mcp'
|
||||
| 'plugin'
|
||||
| 'workflow'
|
||||
|
||||
export type PluginDeclarationResponse = {
|
||||
agent_strategy?: {
|
||||
[key: string]: unknown
|
||||
@ -1524,39 +1552,6 @@ export type PluginDeclarationResponse = {
|
||||
version: string
|
||||
}
|
||||
|
||||
export type PluginInstallationSource = 'github' | 'marketplace' | 'package' | 'remote'
|
||||
|
||||
export type CoreToolsEntitiesCommonEntitiesI18nObject = {
|
||||
en_US: string
|
||||
ja_JP?: string | null
|
||||
pt_BR?: string | null
|
||||
zh_Hans?: string | null
|
||||
}
|
||||
|
||||
export type PluginCategoryBuiltinToolResponse = {
|
||||
author: string
|
||||
description: CoreToolsEntitiesCommonEntitiesI18nObject
|
||||
label: CoreToolsEntitiesCommonEntitiesI18nObject
|
||||
labels: Array<string>
|
||||
name: string
|
||||
output_schema: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
parameters?: Array<{
|
||||
[key: string]: unknown
|
||||
}> | null
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type ToolProviderType
|
||||
= | 'api'
|
||||
| 'app'
|
||||
| 'builtin'
|
||||
| 'dataset-retrieval'
|
||||
| 'mcp'
|
||||
| 'plugin'
|
||||
| 'workflow'
|
||||
|
||||
export type RbacRoleAccount = {
|
||||
account_id: string
|
||||
account_name?: string
|
||||
|
||||
@ -1114,7 +1114,7 @@ export const zLatestPluginCache = z.object({
|
||||
alternative_plugin_id: z.string(),
|
||||
deprecated_reason: z.string(),
|
||||
plugin_id: z.string(),
|
||||
status: z.enum(['active', 'deleted']),
|
||||
status: z.string(),
|
||||
unique_identifier: z.string(),
|
||||
version: z.string(),
|
||||
})
|
||||
@ -1729,6 +1729,33 @@ export const zPluginAutoUpgradeFetchResponse = z.object({
|
||||
*/
|
||||
export const zPluginInstallationSource = z.enum(['github', 'marketplace', 'package', 'remote'])
|
||||
|
||||
/**
|
||||
* PluginInstallationItemResponse
|
||||
*/
|
||||
export const zPluginInstallationItemResponse = z.object({
|
||||
checksum: z.string(),
|
||||
created_at: z.iso.datetime(),
|
||||
declaration: z.record(z.string(), z.unknown()),
|
||||
endpoints_active: z.int(),
|
||||
endpoints_setups: z.int(),
|
||||
id: z.string(),
|
||||
meta: z.record(z.string(), z.unknown()),
|
||||
plugin_id: z.string(),
|
||||
plugin_unique_identifier: z.string(),
|
||||
runtime_type: z.string(),
|
||||
source: zPluginInstallationSource,
|
||||
tenant_id: z.string(),
|
||||
updated_at: z.iso.datetime(),
|
||||
version: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* PluginInstallationsResponse
|
||||
*/
|
||||
export const zPluginInstallationsResponse = z.object({
|
||||
plugins: z.array(zPluginInstallationItemResponse),
|
||||
})
|
||||
|
||||
/**
|
||||
* I18nObject
|
||||
*
|
||||
@ -2288,40 +2315,6 @@ export const zPluginDeclarationResponse = z.object({
|
||||
version: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* PluginInstallationItemResponse
|
||||
*/
|
||||
export const zPluginInstallationItemResponse = z.object({
|
||||
alternative_plugin_id: z.string(),
|
||||
checksum: z.string(),
|
||||
created_at: z.iso.datetime(),
|
||||
declaration: zPluginDeclarationResponse,
|
||||
deprecated_reason: z.string(),
|
||||
endpoints_active: z.int(),
|
||||
endpoints_setups: z.int(),
|
||||
id: z.string(),
|
||||
installation_id: z.string(),
|
||||
latest_unique_identifier: z.string(),
|
||||
latest_version: z.string(),
|
||||
meta: z.record(z.string(), z.unknown()),
|
||||
name: z.string(),
|
||||
plugin_id: z.string(),
|
||||
plugin_unique_identifier: z.string(),
|
||||
runtime_type: z.string(),
|
||||
source: zPluginInstallationSource,
|
||||
status: z.enum(['active', 'deleted']),
|
||||
tenant_id: z.string(),
|
||||
updated_at: z.iso.datetime(),
|
||||
version: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* PluginInstallationsResponse
|
||||
*/
|
||||
export const zPluginInstallationsResponse = z.object({
|
||||
plugins: z.array(zPluginInstallationItemResponse),
|
||||
})
|
||||
|
||||
/**
|
||||
* PluginCategoryInstalledPluginResponse
|
||||
*/
|
||||
|
||||
@ -215,7 +215,7 @@ describe('App Publisher Flow', () => {
|
||||
fireEvent.click(screen.getByText('common.openInExplore'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockToastError).toHaveBeenCalledWith('No app found in Explore')
|
||||
expect(mockToastError).toHaveBeenCalledWith('notPublishedYet')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -562,7 +562,7 @@ describe('AppPublisher', () => {
|
||||
fireEvent.click(screen.getByText('publisher-open-in-explore'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockToastError).toHaveBeenCalledWith('No app found in Explore')
|
||||
expect(mockToastError).toHaveBeenCalledWith('notPublishedYet')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -231,7 +231,7 @@ export function AppPublisher({
|
||||
const { installed_apps } = await fetchInstalledAppList(appDetail.id)
|
||||
if (installed_apps?.length > 0)
|
||||
return `${basePath}${buildInstalledAppPath(installed_apps[0]!.id)}`
|
||||
throw new Error('No app found in Explore')
|
||||
throw new Error(t('notPublishedYet', { ns: 'app' }))
|
||||
}, {
|
||||
onError: (err) => {
|
||||
toast.error(`${err.message || err}`)
|
||||
|
||||
@ -14,6 +14,10 @@ import { StarredAppCard } from '../starred-app-card'
|
||||
|
||||
let mockWebappAuthEnabled = false
|
||||
let mockRbacEnabled = true
|
||||
const mockUserCanAccessApp = vi.hoisted(() => ({
|
||||
result: true as boolean | undefined,
|
||||
isLoading: false,
|
||||
}))
|
||||
|
||||
const render = (ui: React.ReactElement) => renderWithSystemFeatures(ui, {
|
||||
systemFeatures: {
|
||||
@ -118,15 +122,15 @@ vi.mock('@/service/explore', () => ({
|
||||
|
||||
vi.mock('@/service/access-control', () => ({
|
||||
useGetUserCanAccessApp: () => ({
|
||||
data: { result: true },
|
||||
isLoading: false,
|
||||
data: mockUserCanAccessApp.result === undefined ? undefined : { result: mockUserCanAccessApp.result },
|
||||
isLoading: mockUserCanAccessApp.isLoading,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/access-control/use-app-access-control', () => ({
|
||||
useGetUserCanAccessApp: () => ({
|
||||
data: { result: true },
|
||||
isLoading: false,
|
||||
data: mockUserCanAccessApp.result === undefined ? undefined : { result: mockUserCanAccessApp.result },
|
||||
isLoading: mockUserCanAccessApp.isLoading,
|
||||
}),
|
||||
}))
|
||||
|
||||
@ -380,6 +384,8 @@ describe('AppCard', () => {
|
||||
mockOpenAsyncWindow.mockReset()
|
||||
mockWebappAuthEnabled = false
|
||||
mockRbacEnabled = true
|
||||
mockUserCanAccessApp.result = true
|
||||
mockUserCanAccessApp.isLoading = false
|
||||
mockDeleteMutationPending = false
|
||||
mockToggleStarMutationPending = false
|
||||
mockAppContext.isCurrentWorkspaceEditor = true
|
||||
@ -1733,6 +1739,28 @@ describe('AppCard', () => {
|
||||
})
|
||||
|
||||
describe('Open in Explore - No App Found', () => {
|
||||
it('should tell workflow users to publish before opening in explore', async () => {
|
||||
const workflowApp = createMockApp({
|
||||
mode: AppModeEnum.WORKFLOW,
|
||||
workflow: undefined,
|
||||
})
|
||||
render(<AppCard app={workflowApp} />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('dropdown-menu-trigger'))
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('app.openInExplore')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('app.openInExplore'))
|
||||
|
||||
expect(mockOpenAsyncWindow).not.toHaveBeenCalled()
|
||||
expect(exploreService.fetchInstalledAppList).not.toHaveBeenCalled()
|
||||
expect(toastMocks.record).toHaveBeenCalledWith({
|
||||
type: 'error',
|
||||
message: 'app.notPublishedYet',
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle case when installed_apps is empty array', async () => {
|
||||
(exploreService.fetchInstalledAppList as Mock).mockResolvedValueOnce({ installed_apps: [] })
|
||||
|
||||
@ -1756,6 +1784,10 @@ describe('AppCard', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(exploreService.fetchInstalledAppList).toHaveBeenCalled()
|
||||
expect(toastMocks.record).toHaveBeenCalledWith({
|
||||
type: 'error',
|
||||
message: 'app.notPublishedYet',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1944,6 +1976,31 @@ describe('AppCard', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should keep open in explore visible for unpublished workflow apps while access check is pending', async () => {
|
||||
mockUserCanAccessApp.result = false
|
||||
mockUserCanAccessApp.isLoading = true
|
||||
const workflowApp = createMockApp({
|
||||
mode: AppModeEnum.WORKFLOW,
|
||||
workflow: undefined,
|
||||
})
|
||||
|
||||
render(<AppCard app={workflowApp} />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('dropdown-menu-trigger'))
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('app.openInExplore')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('app.openInExplore'))
|
||||
|
||||
expect(mockOpenAsyncWindow).not.toHaveBeenCalled()
|
||||
expect(exploreService.fetchInstalledAppList).not.toHaveBeenCalled()
|
||||
expect(toastMocks.record).toHaveBeenCalledWith({
|
||||
type: 'error',
|
||||
message: 'app.notPublishedYet',
|
||||
})
|
||||
})
|
||||
|
||||
it('should close access control modal when onClose is called', async () => {
|
||||
render(<AppCard app={mockApp} />)
|
||||
|
||||
|
||||
@ -90,6 +90,11 @@ const ACCESS_MODE_LABEL_KEYS = {
|
||||
[AccessMode.EXTERNAL_MEMBERS]: 'accessItemsDescription.external',
|
||||
} as const
|
||||
|
||||
const APP_MODES_REQUIRING_PUBLISHED_WORKFLOW_IN_EXPLORE = new Set<AppModeEnum>([
|
||||
AppModeEnum.ADVANCED_CHAT,
|
||||
AppModeEnum.WORKFLOW,
|
||||
])
|
||||
|
||||
type AppCardProps = {
|
||||
app: App
|
||||
onlineUsers?: WorkflowOnlineUser[]
|
||||
@ -103,6 +108,10 @@ type AppAccessModeIconProps = {
|
||||
|
||||
const getAppResourceMaintainer = (app: App) => app.maintainer
|
||||
|
||||
function requiresPublishedWorkflowInExplore(app: App) {
|
||||
return APP_MODES_REQUIRING_PUBLISHED_WORKFLOW_IN_EXPLORE.has(app.mode)
|
||||
}
|
||||
|
||||
function AppAccessModeIcon({ accessMode }: AppAccessModeIconProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -182,12 +191,17 @@ function AppCardOperationsMenu({
|
||||
async function handleOpenInstalledApp(e: MouseEvent<HTMLElement>) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
if (requiresPublishedWorkflowInExplore(app) && !app.workflow?.id) {
|
||||
toast.error(t('notPublishedYet', { ns: 'app' }))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await openAsyncWindow(async () => {
|
||||
const { installed_apps } = await fetchInstalledAppList(app.id)
|
||||
if (installed_apps?.length > 0)
|
||||
return `${basePath}${buildInstalledAppPath(installed_apps[0]!.id)}`
|
||||
throw new Error('No app found in Explore')
|
||||
throw new Error(t('notPublishedYet', { ns: 'app' }))
|
||||
}, {
|
||||
onError: (err) => {
|
||||
toast.error(`${err.message || err}`)
|
||||
@ -272,10 +286,12 @@ function AppCardOperationsMenuContent(props: AppCardOperationsMenuContentProps)
|
||||
appId: props.app.id,
|
||||
enabled: systemFeatures.webapp_auth.enabled,
|
||||
})
|
||||
const needsPublishBeforeExplore = requiresPublishedWorkflowInExplore(props.app) && !props.app.workflow?.id
|
||||
|
||||
const shouldShowOpenInExploreOption = !props.app.has_draft_trigger
|
||||
&& (
|
||||
!systemFeatures.webapp_auth.enabled
|
||||
needsPublishBeforeExplore
|
||||
|| !systemFeatures.webapp_auth.enabled
|
||||
|| (!isGettingUserCanAccessApp && Boolean(userCanAccessApp?.result))
|
||||
)
|
||||
|
||||
|
||||
@ -84,9 +84,6 @@ vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-plugins', () => ({
|
||||
useCheckInstalled: () => ({
|
||||
data: { plugins: [] },
|
||||
}),
|
||||
usePluginAutoUpgradeSettings: () => ({
|
||||
data: {
|
||||
category: 'model',
|
||||
@ -114,40 +111,25 @@ vi.mock('@/app/components/plugins/reference-setting-modal', () => ({
|
||||
|
||||
vi.mock('@/service/client', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/service/client')>()
|
||||
const originalWorkspaces = actual.consoleQuery.workspaces
|
||||
const originalPlugins = actual.consoleQuery.plugins as unknown as Record<string, unknown>
|
||||
return {
|
||||
...actual,
|
||||
consoleQuery: new Proxy(actual.consoleQuery, {
|
||||
get(target, prop) {
|
||||
if (prop === 'workspaces') {
|
||||
if (prop === 'plugins') {
|
||||
return {
|
||||
...originalWorkspaces,
|
||||
current: {
|
||||
...originalWorkspaces.current,
|
||||
plugin: {
|
||||
...originalWorkspaces.current.plugin,
|
||||
list: {
|
||||
...originalWorkspaces.current.plugin.list,
|
||||
installations: {
|
||||
ids: {
|
||||
post: {
|
||||
queryOptions: () => ({
|
||||
queryKey: ['workspaces', 'current', 'plugin', 'list', 'installations', 'ids', 'post'],
|
||||
queryFn: () => new Promise(() => {}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
latestVersions: {
|
||||
post: {
|
||||
queryOptions: () => ({
|
||||
queryKey: ['workspaces', 'current', 'plugin', 'list', 'latestVersions', 'post'],
|
||||
queryFn: () => new Promise(() => {}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
...originalPlugins,
|
||||
checkInstalled: {
|
||||
queryOptions: () => ({
|
||||
queryKey: ['plugins', 'checkInstalled'],
|
||||
queryFn: () => new Promise(() => {}),
|
||||
}),
|
||||
},
|
||||
latestVersions: {
|
||||
queryOptions: () => ({
|
||||
queryKey: ['plugins', 'latestVersions'],
|
||||
queryFn: () => new Promise(() => {}),
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,9 +163,6 @@ vi.mock('@/service/use-plugins', () => ({
|
||||
useInstalledPluginList: () => ({
|
||||
data: { plugins: [] },
|
||||
}),
|
||||
useCheckInstalled: () => ({
|
||||
data: { plugins: [] },
|
||||
}),
|
||||
usePluginAutoUpgradeSettings: () => ({
|
||||
data: mockReferenceSetting.auto_upgrade
|
||||
? {
|
||||
@ -233,40 +230,25 @@ vi.mock('@/app/components/base/date-and-time-picker/time-picker', () => ({
|
||||
|
||||
vi.mock('@/service/client', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/service/client')>()
|
||||
const originalWorkspaces = actual.consoleQuery.workspaces
|
||||
const originalPlugins = actual.consoleQuery.plugins as unknown as Record<string, unknown>
|
||||
return {
|
||||
...actual,
|
||||
consoleQuery: new Proxy(actual.consoleQuery, {
|
||||
get(target, prop) {
|
||||
if (prop === 'workspaces') {
|
||||
if (prop === 'plugins') {
|
||||
return {
|
||||
...originalWorkspaces,
|
||||
current: {
|
||||
...originalWorkspaces.current,
|
||||
plugin: {
|
||||
...originalWorkspaces.current.plugin,
|
||||
list: {
|
||||
...originalWorkspaces.current.plugin.list,
|
||||
installations: {
|
||||
ids: {
|
||||
post: {
|
||||
queryOptions: () => ({
|
||||
queryKey: ['workspaces', 'current', 'plugin', 'list', 'installations', 'ids', 'post'],
|
||||
queryFn: () => new Promise(() => {}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
latestVersions: {
|
||||
post: {
|
||||
queryOptions: () => ({
|
||||
queryKey: ['workspaces', 'current', 'plugin', 'list', 'latestVersions', 'post'],
|
||||
queryFn: () => new Promise(() => {}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
...originalPlugins,
|
||||
checkInstalled: {
|
||||
queryOptions: () => ({
|
||||
queryKey: ['plugins', 'checkInstalled'],
|
||||
queryFn: () => new Promise(() => {}),
|
||||
}),
|
||||
},
|
||||
latestVersions: {
|
||||
queryOptions: () => ({
|
||||
queryKey: ['plugins', 'latestVersions'],
|
||||
queryFn: () => new Promise(() => {}),
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import type {
|
||||
ModelProvider,
|
||||
} from './declarations'
|
||||
import type { PluginDetail } from '@/app/components/plugins/types'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useDebounce } from 'ahooks'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
import { useMemo } from 'react'
|
||||
@ -14,7 +14,7 @@ import { usePluginSettingsAccess } from '@/app/components/plugins/plugin-page/us
|
||||
import { PluginCategoryEnum } from '@/app/components/plugins/types'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { useCheckInstalled } from '@/service/use-plugins'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import UpdateSettingDialog from '../update-setting-dialog'
|
||||
import {
|
||||
CustomConfigurationStatusEnum,
|
||||
@ -64,10 +64,11 @@ const ModelProviderPage = ({
|
||||
const allPluginIds = useMemo(() => {
|
||||
return [...new Set(providers.map(p => providerToPluginId(p.provider)).filter(Boolean))]
|
||||
}, [providers])
|
||||
const { data: installedPlugins } = useCheckInstalled({
|
||||
pluginIds: allPluginIds,
|
||||
const { data: installedPlugins } = useQuery(consoleQuery.plugins.checkInstalled.queryOptions({
|
||||
input: { body: { plugin_ids: allPluginIds } },
|
||||
enabled: allPluginIds.length > 0,
|
||||
})
|
||||
staleTime: 0,
|
||||
}))
|
||||
const enrichedPlugins = usePluginsWithLatestVersion(installedPlugins?.plugins)
|
||||
const pluginDetailMap = useMemo(() => {
|
||||
const map = new Map<string, PluginDetail>()
|
||||
|
||||
@ -11,17 +11,9 @@ vi.mock('@tanstack/react-query', () => ({
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleQuery: {
|
||||
workspaces: {
|
||||
current: {
|
||||
plugin: {
|
||||
list: {
|
||||
latestVersions: {
|
||||
post: {
|
||||
queryOptions: vi.fn((options: unknown) => options),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
latestVersions: {
|
||||
queryOptions: vi.fn((options: unknown) => options),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -63,7 +55,7 @@ describe('usePluginsWithLatestVersion', () => {
|
||||
|
||||
const { result } = renderHook(() => usePluginsWithLatestVersion(plugins))
|
||||
|
||||
expect(consoleQuery.workspaces.current.plugin.list.latestVersions.post.queryOptions).toHaveBeenCalledWith({
|
||||
expect(consoleQuery.plugins.latestVersions.queryOptions).toHaveBeenCalledWith({
|
||||
input: { body: { plugin_ids: [] } },
|
||||
enabled: false,
|
||||
})
|
||||
@ -117,7 +109,7 @@ describe('usePluginsWithLatestVersion', () => {
|
||||
|
||||
const { result } = renderHook(() => usePluginsWithLatestVersion(plugins))
|
||||
|
||||
expect(consoleQuery.workspaces.current.plugin.list.latestVersions.post.queryOptions).toHaveBeenCalledWith({
|
||||
expect(consoleQuery.plugins.latestVersions.queryOptions).toHaveBeenCalledWith({
|
||||
input: { body: { plugin_ids: ['plugin-1'] } },
|
||||
enabled: true,
|
||||
})
|
||||
|
||||
@ -109,7 +109,7 @@ export function usePluginsWithLatestVersion(plugins: PluginDetail[] = EMPTY_PLUG
|
||||
[plugins],
|
||||
)
|
||||
|
||||
const { data: latestVersionData } = useQuery(consoleQuery.workspaces.current.plugin.list.latestVersions.post.queryOptions({
|
||||
const { data: latestVersionData } = useQuery(consoleQuery.plugins.latestVersions.queryOptions({
|
||||
input: { body: { plugin_ids: marketplacePluginIds } },
|
||||
enabled: !!marketplacePluginIds.length,
|
||||
}))
|
||||
|
||||
@ -450,6 +450,18 @@ export type InstalledPluginCategoryListResponse = {
|
||||
has_more: boolean
|
||||
}
|
||||
|
||||
export type InstalledLatestVersionResponse = {
|
||||
versions: {
|
||||
[plugin_id: string]: {
|
||||
unique_identifier: string
|
||||
version: string
|
||||
status: 'active' | 'deleted'
|
||||
deprecated_reason: string
|
||||
alternative_plugin_id: string
|
||||
} | null
|
||||
}
|
||||
}
|
||||
|
||||
export type UninstallPluginResponse = {
|
||||
success: boolean
|
||||
}
|
||||
|
||||
32
web/contract/console/plugins.ts
Normal file
32
web/contract/console/plugins.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import type { InstalledLatestVersionResponse, PluginDetail } from '@/app/components/plugins/types'
|
||||
import { type } from '@orpc/contract'
|
||||
import { base } from '../base'
|
||||
|
||||
export const pluginCheckInstalledContract = base
|
||||
.route({
|
||||
path: '/workspaces/current/plugin/list/installations/ids',
|
||||
method: 'POST',
|
||||
})
|
||||
.input(type<{
|
||||
body: {
|
||||
plugin_ids: string[]
|
||||
}
|
||||
}>())
|
||||
.output(type<{ plugins: PluginDetail[] }>())
|
||||
|
||||
export const pluginLatestVersionsContract = base
|
||||
.route({
|
||||
path: '/workspaces/current/plugin/list/latest-versions',
|
||||
method: 'POST',
|
||||
})
|
||||
.input(type<{
|
||||
body: {
|
||||
plugin_ids: string[]
|
||||
}
|
||||
}>())
|
||||
.output(type<InstalledLatestVersionResponse>())
|
||||
|
||||
export const pluginsRouterContract = {
|
||||
checkInstalled: pluginCheckInstalledContract,
|
||||
latestVersions: pluginLatestVersionsContract,
|
||||
}
|
||||
@ -48,6 +48,7 @@ import { contract as enterpriseContract } from '@dify/contracts/enterprise/orpc.
|
||||
import { rbacAccessConfigContract } from './console/access-control'
|
||||
import { exploreRouterContract } from './console/explore'
|
||||
import { modelProvidersRouterContract } from './console/model-providers'
|
||||
import { pluginsRouterContract } from './console/plugins'
|
||||
import { snippetsRouterContract } from './console/snippets'
|
||||
import { triggersRouterContract } from './console/trigger'
|
||||
import { trialAppsRouterContract } from './console/try-app'
|
||||
@ -106,6 +107,7 @@ export const consoleRouterContract = {
|
||||
...communityContract,
|
||||
explore: exploreRouterContract,
|
||||
modelProviders: modelProvidersRouterContract,
|
||||
plugins: pluginsRouterContract,
|
||||
rbacAccessConfig: rbacAccessConfigContract,
|
||||
snippets: snippetsRouterContract,
|
||||
triggers: triggersRouterContract,
|
||||
|
||||
@ -16,6 +16,7 @@ const customConsoleContractLoaders: Record<string, () => Promise<AnyContractRout
|
||||
explore: () => import('@/contract/console/explore').then(({ exploreRouterContract }) => wrapConsoleContract('explore', exploreRouterContract)),
|
||||
modelProviders: () =>
|
||||
import('@/contract/console/model-providers').then(({ modelProvidersRouterContract }) => wrapConsoleContract('modelProviders', modelProvidersRouterContract)),
|
||||
plugins: () => import('@/contract/console/plugins').then(({ pluginsRouterContract }) => wrapConsoleContract('plugins', pluginsRouterContract)),
|
||||
rbacAccessConfig: () =>
|
||||
import('@/contract/console/access-control').then(({ rbacAccessConfigContract }) => wrapConsoleContract('rbacAccessConfig', rbacAccessConfigContract)),
|
||||
snippets: () => import('@/contract/console/snippets').then(({ snippetsRouterContract }) => wrapConsoleContract('snippets', snippetsRouterContract)),
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import type { PluginInstallationItemResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { MutateOptions, QueryClient, QueryOptions } from '@tanstack/react-query'
|
||||
import type {
|
||||
FormOption,
|
||||
@ -15,12 +14,10 @@ import type {
|
||||
InstalledPluginListWithTotalResponse,
|
||||
InstallPackageResponse,
|
||||
InstallStatusResponse,
|
||||
MetaData,
|
||||
PackageDependency,
|
||||
Permissions,
|
||||
Plugin,
|
||||
PluginDeclaration,
|
||||
PluginDetail,
|
||||
PluginInfoFromMarketPlace,
|
||||
PluginsFromMarketplaceByInfoResponse,
|
||||
PluginsFromMarketplaceResponse,
|
||||
@ -41,7 +38,7 @@ import { cloneDeep } from 'es-toolkit/object'
|
||||
import { useCallback, useEffect, useRef } from 'react'
|
||||
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
|
||||
import { getFormattedPlugin } from '@/app/components/plugins/marketplace/utils'
|
||||
import { PluginCategoryEnum, PluginSource, TaskStatus } from '@/app/components/plugins/types'
|
||||
import { PluginCategoryEnum, TaskStatus } from '@/app/components/plugins/types'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { fetchModelProviderModelList } from '@/service/common'
|
||||
import { fetchPluginInfoFromMarketPlace, uninstallPlugin } from '@/service/plugins'
|
||||
@ -59,165 +56,6 @@ type PluginTaskListResponse = {
|
||||
tasks: PluginTask[]
|
||||
}
|
||||
|
||||
const getString = (value: unknown) => {
|
||||
return typeof value === 'string' ? value : ''
|
||||
}
|
||||
|
||||
const getI18nValue = (value: object | null | undefined, key: string) => {
|
||||
return value ? getString(Object.entries(value).find(([itemKey]) => itemKey === key)?.[1]) : ''
|
||||
}
|
||||
|
||||
const normalizeI18nObject = (value: object | null | undefined, fallback = ''): PluginDeclaration['label'] => {
|
||||
const en = getI18nValue(value, 'en_US') || getI18nValue(value, 'en-US') || fallback
|
||||
const zhHans = getI18nValue(value, 'zh_Hans') || getI18nValue(value, 'zh-Hans') || en
|
||||
const ja = getI18nValue(value, 'ja_JP') || getI18nValue(value, 'ja-JP') || en
|
||||
const ptBr = getI18nValue(value, 'pt_BR') || getI18nValue(value, 'pt-BR') || en
|
||||
|
||||
return {
|
||||
'en-US': en,
|
||||
'zh-Hans': zhHans,
|
||||
'zh-Hant': en,
|
||||
'pt-BR': ptBr,
|
||||
'es-ES': en,
|
||||
'fr-FR': en,
|
||||
'de-DE': en,
|
||||
'ja-JP': ja,
|
||||
'ko-KR': en,
|
||||
'ru-RU': en,
|
||||
'it-IT': en,
|
||||
'th-TH': en,
|
||||
'uk-UA': en,
|
||||
'vi-VN': en,
|
||||
'ro-RO': en,
|
||||
'pl-PL': en,
|
||||
'hi-IN': en,
|
||||
'tr-TR': en,
|
||||
'fa-IR': en,
|
||||
'sl-SI': en,
|
||||
'id-ID': en,
|
||||
'nl-NL': en,
|
||||
'ar-TN': en,
|
||||
'en_US': en,
|
||||
'zh_Hans': zhHans,
|
||||
'ja_JP': ja,
|
||||
}
|
||||
}
|
||||
|
||||
const normalizePluginCategory = (category: PluginInstallationItemResponse['declaration']['category']): PluginCategoryEnum => {
|
||||
switch (category) {
|
||||
case PluginCategoryEnum.tool:
|
||||
return PluginCategoryEnum.tool
|
||||
case PluginCategoryEnum.model:
|
||||
return PluginCategoryEnum.model
|
||||
case PluginCategoryEnum.datasource:
|
||||
return PluginCategoryEnum.datasource
|
||||
case PluginCategoryEnum.trigger:
|
||||
return PluginCategoryEnum.trigger
|
||||
case PluginCategoryEnum.agent:
|
||||
return PluginCategoryEnum.agent
|
||||
case PluginCategoryEnum.extension:
|
||||
return PluginCategoryEnum.extension
|
||||
}
|
||||
return PluginCategoryEnum.extension
|
||||
}
|
||||
|
||||
const normalizePluginSource = (source: PluginInstallationItemResponse['source']): PluginSource => {
|
||||
switch (source) {
|
||||
case PluginSource.github:
|
||||
return PluginSource.github
|
||||
case PluginSource.marketplace:
|
||||
return PluginSource.marketplace
|
||||
case PluginSource.local:
|
||||
return PluginSource.local
|
||||
case PluginSource.debugging:
|
||||
return PluginSource.debugging
|
||||
}
|
||||
return PluginSource.marketplace
|
||||
}
|
||||
|
||||
const normalizePluginMeta = (meta: Record<string, unknown>): MetaData => {
|
||||
return {
|
||||
repo: getString(meta.repo),
|
||||
version: getString(meta.version),
|
||||
package: getString(meta.package),
|
||||
}
|
||||
}
|
||||
|
||||
const createEmptyTrigger = (name: string): PluginDeclaration['trigger'] => ({
|
||||
events: [],
|
||||
identity: {
|
||||
author: '',
|
||||
name,
|
||||
label: normalizeI18nObject(undefined, name),
|
||||
description: normalizeI18nObject(undefined),
|
||||
icon: '',
|
||||
tags: [],
|
||||
},
|
||||
subscription_constructor: {
|
||||
credentials_schema: [],
|
||||
oauth_schema: {
|
||||
client_schema: [],
|
||||
credentials_schema: [],
|
||||
},
|
||||
parameters: [],
|
||||
},
|
||||
subscription_schema: [],
|
||||
})
|
||||
|
||||
const normalizePluginDeclaration = (plugin: PluginInstallationItemResponse): PluginDeclaration => {
|
||||
const { declaration } = plugin
|
||||
return {
|
||||
plugin_unique_identifier: plugin.plugin_unique_identifier,
|
||||
version: declaration.version,
|
||||
author: declaration.author ?? '',
|
||||
icon: declaration.icon,
|
||||
icon_dark: declaration.icon_dark ?? undefined,
|
||||
name: declaration.name,
|
||||
category: normalizePluginCategory(declaration.category),
|
||||
label: normalizeI18nObject(declaration.label, declaration.name),
|
||||
description: normalizeI18nObject(declaration.description, declaration.name),
|
||||
created_at: declaration.created_at,
|
||||
resource: declaration.resource,
|
||||
plugins: declaration.plugins,
|
||||
verified: declaration.verified ?? false,
|
||||
endpoint: undefined,
|
||||
tool: undefined,
|
||||
datasource: undefined,
|
||||
model: declaration.model,
|
||||
tags: declaration.tags ?? [],
|
||||
agent_strategy: declaration.agent_strategy,
|
||||
meta: {
|
||||
version: getString(declaration.meta.version) || declaration.version,
|
||||
minimum_dify_version: getString(declaration.meta.minimum_dify_version) || undefined,
|
||||
},
|
||||
trigger: createEmptyTrigger(declaration.name),
|
||||
}
|
||||
}
|
||||
|
||||
const normalizeInstalledPluginDetail = (plugin: PluginInstallationItemResponse): PluginDetail => {
|
||||
return {
|
||||
id: plugin.id,
|
||||
created_at: plugin.created_at,
|
||||
updated_at: plugin.updated_at,
|
||||
name: plugin.name,
|
||||
plugin_id: plugin.plugin_id,
|
||||
plugin_unique_identifier: plugin.plugin_unique_identifier,
|
||||
declaration: normalizePluginDeclaration(plugin),
|
||||
installation_id: plugin.installation_id,
|
||||
tenant_id: plugin.tenant_id,
|
||||
endpoints_setups: plugin.endpoints_setups,
|
||||
endpoints_active: plugin.endpoints_active,
|
||||
version: plugin.version,
|
||||
latest_version: plugin.latest_version,
|
||||
latest_unique_identifier: plugin.latest_unique_identifier,
|
||||
source: normalizePluginSource(plugin.source),
|
||||
meta: normalizePluginMeta(plugin.meta),
|
||||
status: plugin.status,
|
||||
deprecated_reason: plugin.deprecated_reason,
|
||||
alternative_plugin_id: plugin.alternative_plugin_id,
|
||||
}
|
||||
}
|
||||
|
||||
const isUnfinishedPluginTask = (task: PluginTask) => task.status === TaskStatus.pending || task.status === TaskStatus.running
|
||||
|
||||
const normalizeStartedPluginTask = (task: PluginTaskStart): PluginTask => ({
|
||||
@ -276,13 +114,10 @@ export const useCheckInstalled = ({
|
||||
pluginIds: string[]
|
||||
enabled: boolean
|
||||
}) => {
|
||||
return useQuery(consoleQuery.workspaces.current.plugin.list.installations.ids.post.queryOptions({
|
||||
return useQuery(consoleQuery.plugins.checkInstalled.queryOptions({
|
||||
input: { body: { plugin_ids: pluginIds } },
|
||||
enabled,
|
||||
staleTime: 0,
|
||||
select: response => ({
|
||||
plugins: response.plugins.map(normalizeInstalledPluginDetail),
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
@ -290,7 +125,7 @@ export const useInvalidateCheckInstalled = () => {
|
||||
const queryClient = useQueryClient()
|
||||
return () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: consoleQuery.workspaces.current.plugin.list.installations.ids.post.key(),
|
||||
queryKey: consoleQuery.plugins.checkInstalled.key(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user