From b283a2b3d95d7d32b8370e5a23b48b666664376e Mon Sep 17 00:00:00 2001 From: Harry Date: Mon, 13 Oct 2025 16:50:12 +0800 Subject: [PATCH] feat(trigger): add API endpoint to retrieve trigger plugin icons and enhance workflow response handling - Introduced `TriggerProviderIconApi` to fetch icons for trigger plugins based on tenant and provider ID. - Updated `WorkflowResponseConverter` to include trigger plugin icons in the response. - Implemented `get_trigger_plugin_icon` method in `TriggerManager` for icon retrieval logic. - Adjusted `Node` class to correctly set provider information for trigger plugins. - Modified TypeScript types to accommodate new provider ID field in workflow nodes. --- .../console/workspace/trigger_providers.py | 13 ++++++++++ .../common/workflow_response_converter.py | 6 +++++ api/core/trigger/entities/entities.py | 3 ++- api/core/trigger/trigger_manager.py | 24 ++++++++++++++++++- api/core/workflow/nodes/base/node.py | 6 +++++ .../hooks/use-dynamic-test-run-options.tsx | 9 ++++--- web/app/components/workflow/types.ts | 1 + 7 files changed, 55 insertions(+), 7 deletions(-) diff --git a/api/controllers/console/workspace/trigger_providers.py b/api/controllers/console/workspace/trigger_providers.py index a69c6a48bb..6544071076 100644 --- a/api/controllers/console/workspace/trigger_providers.py +++ b/api/controllers/console/workspace/trigger_providers.py @@ -25,6 +25,18 @@ from services.trigger.workflow_plugin_trigger_service import WorkflowPluginTrigg logger = logging.getLogger(__name__) +class TriggerProviderIconApi(Resource): + @setup_required + @login_required + @account_initialization_required + def get(self, provider): + user = current_user + assert isinstance(user, Account) + assert user.current_tenant_id is not None + + return TriggerManager.get_trigger_plugin_icon(tenant_id=user.current_tenant_id, provider_id=provider) + + class TriggerProviderListApi(Resource): @setup_required @login_required @@ -534,6 +546,7 @@ class TriggerOAuthClientManageApi(Resource): # Trigger Subscription +api.add_resource(TriggerProviderIconApi, "/workspaces/current/trigger-provider//icon") api.add_resource(TriggerProviderListApi, "/workspaces/current/triggers") api.add_resource(TriggerProviderInfoApi, "/workspaces/current/trigger-provider//info") api.add_resource(TriggerSubscriptionListApi, "/workspaces/current/trigger-provider//subscriptions/list") diff --git a/api/core/app/apps/common/workflow_response_converter.py b/api/core/app/apps/common/workflow_response_converter.py index 7c7a4fd6ac..8672fb136b 100644 --- a/api/core/app/apps/common/workflow_response_converter.py +++ b/api/core/app/apps/common/workflow_response_converter.py @@ -38,6 +38,7 @@ from core.file import FILE_MODEL_IDENTITY, File from core.plugin.impl.datasource import PluginDatasourceManager from core.tools.entities.tool_entities import ToolProviderType from core.tools.tool_manager import ToolManager +from core.trigger.trigger_manager import TriggerManager from core.variables.segments import ArrayFileSegment, FileSegment, Segment from core.workflow.entities import WorkflowExecution, WorkflowNodeExecution from core.workflow.enums import NodeType, WorkflowNodeExecutionStatus @@ -181,6 +182,11 @@ class WorkflowResponseConverter: response.data.extras["icon"] = provider_entity.declaration.identity.generate_datasource_icon_url( self._application_generate_entity.app_config.tenant_id ) + elif event.node_type == NodeType.TRIGGER_PLUGIN: + response.data.extras["icon"] = TriggerManager.get_trigger_plugin_icon( + self._application_generate_entity.app_config.tenant_id, + event.provider_id, + ) return response diff --git a/api/core/trigger/entities/entities.py b/api/core/trigger/entities/entities.py index 9fa219dd93..74a2dbcc49 100644 --- a/api/core/trigger/entities/entities.py +++ b/api/core/trigger/entities/entities.py @@ -155,7 +155,8 @@ class TriggerProviderEntity(BaseModel): default_factory=list, description="The configuration schema stored in the subscription entity", ) - subscription_constructor: SubscriptionConstructor = Field( + subscription_constructor: SubscriptionConstructor | None = Field( + default=None, description="The subscription constructor of the trigger provider", ) events: list[EventEntity] = Field(default=[], description="The events of the trigger provider") diff --git a/api/core/trigger/trigger_manager.py b/api/core/trigger/trigger_manager.py index f6237bfb9f..618402019c 100644 --- a/api/core/trigger/trigger_manager.py +++ b/api/core/trigger/trigger_manager.py @@ -8,9 +8,11 @@ from threading import Lock from typing import Any from flask import Request +from yarl import URL import contexts -from core.plugin.entities.plugin_daemon import CredentialType +from configs import dify_config +from core.plugin.entities.plugin_daemon import CredentialType, PluginTriggerProviderEntity from core.plugin.entities.request import TriggerInvokeEventResponse from core.plugin.impl.exc import PluginInvokeError from core.plugin.impl.trigger import PluginTriggerManager @@ -30,6 +32,26 @@ class TriggerManager: Manager for trigger providers and triggers """ + @classmethod + def get_trigger_plugin_icon(cls, tenant_id: str, provider_id: str) -> str: + """ + Get the icon of a trigger plugin + """ + manager = PluginTriggerManager() + provider: PluginTriggerProviderEntity = manager.fetch_trigger_provider( + tenant_id=tenant_id, provider_id=TriggerProviderID(provider_id) + ) + return str( + URL(dify_config.CONSOLE_API_URL or "/") + / "console" + / "api" + / "workspaces" + / "current" + / "plugin" + / "icon" + % {"tenant_id": tenant_id, "filename": provider.declaration.identity.icon} + ) + @classmethod def list_plugin_trigger_providers(cls, tenant_id: str) -> list[PluginTriggerProviderController]: """ diff --git a/api/core/workflow/nodes/base/node.py b/api/core/workflow/nodes/base/node.py index 41212abb0e..15186e69a7 100644 --- a/api/core/workflow/nodes/base/node.py +++ b/api/core/workflow/nodes/base/node.py @@ -122,6 +122,12 @@ class Node: start_event.provider_id = f"{plugin_id}/{provider_name}" start_event.provider_type = getattr(self.get_base_node_data(), "provider_type", "") + + from core.workflow.nodes.trigger_plugin.trigger_plugin_node import TriggerPluginNode + + if isinstance(self, TriggerPluginNode): + start_event.provider_id = getattr(self.get_base_node_data(), "provider_id", "") + start_event.provider_type = getattr(self.get_base_node_data(), "provider_type", "") from typing import cast diff --git a/web/app/components/workflow/hooks/use-dynamic-test-run-options.tsx b/web/app/components/workflow/hooks/use-dynamic-test-run-options.tsx index aca9a3389f..c2ed15fb7c 100644 --- a/web/app/components/workflow/hooks/use-dynamic-test-run-options.tsx +++ b/web/app/components/workflow/hooks/use-dynamic-test-run-options.tsx @@ -7,7 +7,6 @@ import type { TestRunOptions, TriggerOption } from '../header/test-run-menu' import { TriggerAll } from '@/app/components/base/icons/src/vender/workflow' import BlockIcon from '../block-icon' import { useStore } from '../store' -import { canFindTool } from '@/utils' import { useAllTriggerPlugins } from '@/service/use-triggers' export const useDynamicTestRunOptions = (): TestRunOptions => { @@ -74,18 +73,18 @@ export const useDynamicTestRunOptions = (): TestRunOptions => { }) } else if (nodeData.type === BlockEnum.TriggerPlugin) { - let toolIcon: string | any + let triggerIcon: string | any if (nodeData.provider_id) { - const targetTools = triggerPlugins || [] - toolIcon = targetTools.find(toolWithProvider => canFindTool(toolWithProvider.id, nodeData.provider_id!))?.icon + const targetTriggers = triggerPlugins || [] + triggerIcon = targetTriggers.find(toolWithProvider => toolWithProvider.name === nodeData.provider_id)?.icon } const icon = ( ) diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 7cb1cf9770..e29a61cb3a 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -104,6 +104,7 @@ export type CommonNodeType = { default_value?: DefaultValueForm[] credential_id?: string subscription_id?: string + provider_id?: string _dimmed?: boolean } & T & Partial