From 64a14dcdbcc79498a22a36f592c711c0f28049f0 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Wed, 24 Dec 2025 18:20:36 +0800 Subject: [PATCH 1/3] fix(web): remove incorrect placeholderData usage in useExploreAppList (#30102) --- .../app/create-app-dialog/app-list/index.tsx | 14 +++++++---- .../explore/app-list/index.spec.tsx | 18 ++++++++++---- web/app/components/explore/app-list/index.tsx | 24 ++++++++++++------- web/service/use-explore.ts | 6 ----- 4 files changed, 37 insertions(+), 25 deletions(-) diff --git a/web/app/components/app/create-app-dialog/app-list/index.tsx b/web/app/components/app/create-app-dialog/app-list/index.tsx index ee64478141..0df13e1ba1 100644 --- a/web/app/components/app/create-app-dialog/app-list/index.tsx +++ b/web/app/components/app/create-app-dialog/app-list/index.tsx @@ -24,7 +24,7 @@ import { useTabSearchParams } from '@/hooks/use-tab-searchparams' import { DSLImportMode } from '@/models/app' import { importDSL } from '@/service/apps' import { fetchAppDetail } from '@/service/explore' -import { exploreAppListInitialData, useExploreAppList } from '@/service/use-explore' +import { useExploreAppList } from '@/service/use-explore' import { AppModeEnum } from '@/types/app' import { getRedirection } from '@/utils/app-redirection' import { cn } from '@/utils/classnames' @@ -70,10 +70,14 @@ const Apps = ({ }) const { - data: { categories, allList } = exploreAppListInitialData, + data, + isLoading, } = useExploreAppList() const filteredList = useMemo(() => { + if (!data) + return [] + const { allList } = data const filteredByCategory = allList.filter((item) => { if (currCategory === allCategoriesEn) return true @@ -94,7 +98,7 @@ const Apps = ({ return true return false }) - }, [currentType, currCategory, allCategoriesEn, allList]) + }, [currentType, currCategory, allCategoriesEn, data]) const searchFilteredList = useMemo(() => { if (!searchKeywords || !filteredList || filteredList.length === 0) @@ -156,7 +160,7 @@ const Apps = ({ } } - if (!categories || categories.length === 0) { + if (isLoading) { return (
@@ -190,7 +194,7 @@ const Apps = ({
{!searchKeywords && (
- { setCurrCategory(category) }} onCreateFromBlank={onCreateFromBlank} /> + { setCurrCategory(category) }} onCreateFromBlank={onCreateFromBlank} />
)}
diff --git a/web/app/components/explore/app-list/index.spec.tsx b/web/app/components/explore/app-list/index.spec.tsx index ebf2c9c075..c84da1931c 100644 --- a/web/app/components/explore/app-list/index.spec.tsx +++ b/web/app/components/explore/app-list/index.spec.tsx @@ -10,7 +10,9 @@ import AppList from './index' const allCategoriesEn = 'explore.apps.allCategories:{"lng":"en"}' let mockTabValue = allCategoriesEn const mockSetTab = vi.fn() -let mockExploreData: { categories: string[], allList: App[] } = { categories: [], allList: [] } +let mockExploreData: { categories: string[], allList: App[] } | undefined = { categories: [], allList: [] } +let mockIsLoading = false +let mockIsError = false const mockHandleImportDSL = vi.fn() const mockHandleImportDSLConfirm = vi.fn() @@ -34,8 +36,11 @@ vi.mock('ahooks', async () => { }) vi.mock('@/service/use-explore', () => ({ - exploreAppListInitialData: { categories: [], allList: [] }, - useExploreAppList: () => ({ data: mockExploreData }), + useExploreAppList: () => ({ + data: mockExploreData, + isLoading: mockIsLoading, + isError: mockIsError, + }), })) vi.mock('@/service/explore', () => ({ @@ -136,13 +141,16 @@ describe('AppList', () => { vi.clearAllMocks() mockTabValue = allCategoriesEn mockExploreData = { categories: [], allList: [] } + mockIsLoading = false + mockIsError = false }) // Rendering: show loading when categories are not ready. describe('Rendering', () => { - it('should render loading when categories are empty', () => { + it('should render loading when the query is loading', () => { // Arrange - mockExploreData = { categories: [], allList: [] } + mockExploreData = undefined + mockIsLoading = true // Act renderWithContext() diff --git a/web/app/components/explore/app-list/index.tsx b/web/app/components/explore/app-list/index.tsx index 244a116e36..5ab68f9b04 100644 --- a/web/app/components/explore/app-list/index.tsx +++ b/web/app/components/explore/app-list/index.tsx @@ -20,7 +20,7 @@ import { DSLImportMode, } from '@/models/app' import { fetchAppDetail } from '@/service/explore' -import { exploreAppListInitialData, useExploreAppList } from '@/service/use-explore' +import { useExploreAppList } from '@/service/use-explore' import { cn } from '@/utils/classnames' import s from './style.module.css' @@ -28,11 +28,6 @@ type AppsProps = { onSuccess?: () => void } -export enum PageType { - EXPLORE = 'explore', - CREATE = 'create', -} - const Apps = ({ onSuccess, }: AppsProps) => { @@ -58,10 +53,16 @@ const Apps = ({ }) const { - data: { categories, allList } = exploreAppListInitialData, + data, + isLoading, + isError, } = useExploreAppList() - const filteredList = allList.filter(item => currCategory === allCategoriesEn || item.category === currCategory) + const filteredList = useMemo(() => { + if (!data) + return [] + return data.allList.filter(item => currCategory === allCategoriesEn || item.category === currCategory) + }, [data, currCategory, allCategoriesEn]) const searchFilteredList = useMemo(() => { if (!searchKeywords || !filteredList || filteredList.length === 0) @@ -119,7 +120,7 @@ const Apps = ({ }) }, [handleImportDSLConfirm, onSuccess]) - if (!categories || categories.length === 0) { + if (isLoading) { return (
@@ -127,6 +128,11 @@ const Apps = ({ ) } + if (isError || !data) + return null + + const { categories } = data + return (
{ return useQuery({ queryKey: [NAME_SPACE, 'appList'], @@ -27,7 +22,6 @@ export const useExploreAppList = () => { allList: [...recommended_apps].sort((a, b) => a.position - b.position), } }, - placeholderData: exploreAppListInitialData, }) } From 5896bc89f5e2d1f939fc5a3b35c63b43341a6121 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Wed, 24 Dec 2025 18:21:01 +0800 Subject: [PATCH 2/3] refactor(web): migrate workflow run history from useSWR to TanStack Query (#30077) --- .../components/rag-pipeline-header/index.tsx | 2 -- .../components/workflow-header/index.spec.tsx | 11 -------- .../components/workflow-header/index.tsx | 4 --- .../workflow/header/view-history.tsx | 11 +++----- web/service/use-workflow.ts | 9 +++++++ web/service/workflow.ts | 27 +++++-------------- 6 files changed, 19 insertions(+), 45 deletions(-) diff --git a/web/app/components/rag-pipeline/components/rag-pipeline-header/index.tsx b/web/app/components/rag-pipeline/components/rag-pipeline-header/index.tsx index fff720469c..fe2490f281 100644 --- a/web/app/components/rag-pipeline/components/rag-pipeline-header/index.tsx +++ b/web/app/components/rag-pipeline/components/rag-pipeline-header/index.tsx @@ -8,7 +8,6 @@ import Header from '@/app/components/workflow/header' import { useStore, } from '@/app/components/workflow/store' -import { fetchWorkflowRunHistory } from '@/service/workflow' import InputFieldButton from './input-field-button' import Publisher from './publisher' import RunMode from './run-mode' @@ -21,7 +20,6 @@ const RagPipelineHeader = () => { const viewHistoryProps = useMemo(() => { return { historyUrl: `/rag/pipelines/${pipelineId}/workflow-runs`, - historyFetcher: fetchWorkflowRunHistory, } }, [pipelineId]) diff --git a/web/app/components/workflow-app/components/workflow-header/index.spec.tsx b/web/app/components/workflow-app/components/workflow-header/index.spec.tsx index 87d7fb30e7..5563af01d3 100644 --- a/web/app/components/workflow-app/components/workflow-header/index.spec.tsx +++ b/web/app/components/workflow-app/components/workflow-header/index.spec.tsx @@ -58,16 +58,12 @@ vi.mock('@/app/components/app/store', () => ({ vi.mock('@/app/components/workflow/header', () => ({ __esModule: true, default: (props: HeaderProps) => { - const historyFetcher = props.normal?.runAndHistoryProps?.viewHistoryProps?.historyFetcher - const hasHistoryFetcher = typeof historyFetcher === 'function' - return (
- - - +
+ + + + + + +
@@ -78,6 +94,14 @@ const SubscriptionCard = ({ data }: Props) => { workflowsInUse={data.workflows_in_use} /> )} + + {isShowEditModal && ( + + )} ) } diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/types.ts b/web/app/components/plugins/plugin-detail-panel/subscription-list/types.ts new file mode 100644 index 0000000000..adfda16547 --- /dev/null +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/types.ts @@ -0,0 +1,9 @@ +export enum SubscriptionListMode { + PANEL = 'panel', + SELECTOR = 'selector', +} + +export type SimpleSubscription = { + id: string + name: string +} diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index 818c2a0388..4aa0326cb4 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -131,7 +131,7 @@ export type ParametersSchema = { scope: any required: boolean multiple: boolean - default?: string[] + default?: string | string[] min: any max: any precision: any diff --git a/web/app/components/workflow/block-selector/types.ts b/web/app/components/workflow/block-selector/types.ts index 6ed4d7f2d5..07efb0d02f 100644 --- a/web/app/components/workflow/block-selector/types.ts +++ b/web/app/components/workflow/block-selector/types.ts @@ -39,9 +39,9 @@ export type TriggerDefaultValue = PluginCommonDefaultValue & { title: string plugin_unique_identifier: string is_team_authorization: boolean - params: Record - paramSchemas: Record[] - output_schema: Record + params: Record + paramSchemas: Record[] + output_schema: Record subscription_id?: string meta?: PluginMeta } @@ -52,9 +52,9 @@ export type ToolDefaultValue = PluginCommonDefaultValue & { tool_description: string title: string is_team_authorization: boolean - params: Record - paramSchemas: Record[] - output_schema?: Record + params: Record + paramSchemas: Record[] + output_schema?: Record credential_id?: string meta?: PluginMeta plugin_id?: string @@ -82,10 +82,10 @@ export type ToolValue = { tool_name: string tool_label: string tool_description?: string - settings?: Record - parameters?: Record + settings?: Record + parameters?: Record enabled?: boolean - extra?: Record + extra?: { description?: string } & Record credential_id?: string } @@ -94,7 +94,7 @@ export type DataSourceItem = { plugin_unique_identifier: string provider: string declaration: { - credentials_schema: any[] + credentials_schema: unknown[] provider_type: string identity: { author: string @@ -113,10 +113,10 @@ export type DataSourceItem = { name: string provider: string } - parameters: any[] + parameters: unknown[] output_schema?: { type: string - properties: Record + properties: Record } }[] } @@ -133,15 +133,15 @@ export type TriggerParameter = { | 'model-selector' | 'app-selector' | 'object' | 'array' | 'dynamic-select' auto_generate?: { type: string - value?: any + value?: unknown } | null template?: { type: string - value?: any + value?: unknown } | null scope?: string | null required?: boolean - default?: any + default?: unknown min?: number | null max?: number | null precision?: number | null @@ -158,7 +158,7 @@ export type TriggerCredentialField = { name: string scope?: string | null required: boolean - default?: string | number | boolean | Array | null + default?: string | number | boolean | Array | null options?: Array<{ value: string label: TypeWithI18N @@ -191,7 +191,7 @@ export type TriggerApiEntity = { identity: TriggerIdentity description: TypeWithI18N parameters: TriggerParameter[] - output_schema?: Record + output_schema?: Record } export type TriggerProviderApiEntity = { @@ -237,32 +237,15 @@ type TriggerSubscriptionStructure = { name: string provider: string credential_type: TriggerCredentialTypeEnum - credentials: TriggerSubCredentials + credentials: Record endpoint: string - parameters: TriggerSubParameters - properties: TriggerSubProperties + parameters: Record + properties: Record workflows_in_use: number } export type TriggerSubscription = TriggerSubscriptionStructure -export type TriggerSubCredentials = { - access_tokens: string -} - -export type TriggerSubParameters = { - repository: string - webhook_secret?: string -} - -export type TriggerSubProperties = { - active: boolean - events: string[] - external_id: string - repository: string - webhook_secret?: string -} - export type TriggerSubscriptionBuilder = TriggerSubscriptionStructure // OAuth configuration types @@ -275,7 +258,7 @@ export type TriggerOAuthConfig = { params: { client_id: string client_secret: string - [key: string]: any + [key: string]: string } system_configured: boolean } diff --git a/web/app/components/workflow/nodes/trigger-plugin/hooks/use-trigger-auth-flow.ts b/web/app/components/workflow/nodes/trigger-plugin/hooks/use-trigger-auth-flow.ts index 36bcbf1cc7..f551f2f420 100644 --- a/web/app/components/workflow/nodes/trigger-plugin/hooks/use-trigger-auth-flow.ts +++ b/web/app/components/workflow/nodes/trigger-plugin/hooks/use-trigger-auth-flow.ts @@ -4,11 +4,11 @@ import { useBuildTriggerSubscription, useCreateTriggerSubscriptionBuilder, useUpdateTriggerSubscriptionBuilder, - useVerifyTriggerSubscriptionBuilder, + useVerifyAndUpdateTriggerSubscriptionBuilder, } from '@/service/use-triggers' // Helper function to serialize complex values to strings for backend encryption -const serializeFormValues = (values: Record): Record => { +const serializeFormValues = (values: Record): Record => { const result: Record = {} for (const [key, value] of Object.entries(values)) { @@ -23,6 +23,17 @@ const serializeFormValues = (values: Record): Record { + if (error instanceof Error && error.message) + return error.message + if (typeof error === 'object' && error && 'message' in error) { + const message = (error as { message?: string }).message + if (typeof message === 'string' && message) + return message + } + return fallback +} + export type AuthFlowStep = 'auth' | 'params' | 'complete' export type AuthFlowState = { @@ -34,8 +45,8 @@ export type AuthFlowState = { export type AuthFlowActions = { startAuth: () => Promise - verifyAuth: (credentials: Record) => Promise - completeConfig: (parameters: Record, properties?: Record, name?: string) => Promise + verifyAuth: (credentials: Record) => Promise + completeConfig: (parameters: Record, properties?: Record, name?: string) => Promise reset: () => void } @@ -47,7 +58,7 @@ export const useTriggerAuthFlow = (provider: TriggerWithProvider): AuthFlowState const createBuilder = useCreateTriggerSubscriptionBuilder() const updateBuilder = useUpdateTriggerSubscriptionBuilder() - const verifyBuilder = useVerifyTriggerSubscriptionBuilder() + const verifyBuilder = useVerifyAndUpdateTriggerSubscriptionBuilder() const buildSubscription = useBuildTriggerSubscription() const startAuth = useCallback(async () => { @@ -64,8 +75,8 @@ export const useTriggerAuthFlow = (provider: TriggerWithProvider): AuthFlowState setBuilderId(response.subscription_builder.id) setStep('auth') } - catch (err: any) { - setError(err.message || 'Failed to start authentication flow') + catch (err: unknown) { + setError(getErrorMessage(err, 'Failed to start authentication flow')) throw err } finally { @@ -73,7 +84,7 @@ export const useTriggerAuthFlow = (provider: TriggerWithProvider): AuthFlowState } }, [provider.name, createBuilder, builderId]) - const verifyAuth = useCallback(async (credentials: Record) => { + const verifyAuth = useCallback(async (credentials: Record) => { if (!builderId) { setError('No builder ID available') return @@ -96,8 +107,8 @@ export const useTriggerAuthFlow = (provider: TriggerWithProvider): AuthFlowState setStep('params') } - catch (err: any) { - setError(err.message || 'Authentication verification failed') + catch (err: unknown) { + setError(getErrorMessage(err, 'Authentication verification failed')) throw err } finally { @@ -106,8 +117,8 @@ export const useTriggerAuthFlow = (provider: TriggerWithProvider): AuthFlowState }, [provider.name, builderId, updateBuilder, verifyBuilder]) const completeConfig = useCallback(async ( - parameters: Record, - properties: Record = {}, + parameters: Record, + properties: Record = {}, name?: string, ) => { if (!builderId) { @@ -134,8 +145,8 @@ export const useTriggerAuthFlow = (provider: TriggerWithProvider): AuthFlowState setStep('complete') } - catch (err: any) { - setError(err.message || 'Configuration failed') + catch (err: unknown) { + setError(getErrorMessage(err, 'Configuration failed')) throw err } finally { diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index 2e378afeda..0117f2ae00 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -21,6 +21,7 @@ const translation = { cancel: 'Cancel', clear: 'Clear', save: 'Save', + saving: 'Saving...', yes: 'Yes', no: 'No', deleteConfirmTitle: 'Delete?', diff --git a/web/i18n/en-US/plugin-trigger.ts b/web/i18n/en-US/plugin-trigger.ts index f1d697e507..16ee1e1362 100644 --- a/web/i18n/en-US/plugin-trigger.ts +++ b/web/i18n/en-US/plugin-trigger.ts @@ -30,6 +30,11 @@ const translation = { unauthorized: 'Manual', }, actions: { + edit: { + title: 'Edit Subscription', + success: 'Subscription updated successfully', + error: 'Failed to update subscription', + }, delete: 'Delete', deleteConfirm: { title: 'Delete {{name}}?', diff --git a/web/service/use-triggers.ts b/web/service/use-triggers.ts index c21d1aa979..6036f5ab34 100644 --- a/web/service/use-triggers.ts +++ b/web/service/use-triggers.ts @@ -1,3 +1,4 @@ +import type { FormOption } from '@/app/components/base/form/types' import type { TriggerLogEntity, TriggerOAuthClientParams, @@ -149,9 +150,9 @@ export const useUpdateTriggerSubscriptionBuilder = () => { provider: string subscriptionBuilderId: string name?: string - properties?: Record - parameters?: Record - credentials?: Record + properties?: Record + parameters?: Record + credentials?: Record }) => { const { provider, subscriptionBuilderId, ...body } = payload return post( @@ -162,17 +163,35 @@ export const useUpdateTriggerSubscriptionBuilder = () => { }) } -export const useVerifyTriggerSubscriptionBuilder = () => { +export const useVerifyAndUpdateTriggerSubscriptionBuilder = () => { return useMutation({ - mutationKey: [NAME_SPACE, 'verify-subscription-builder'], + mutationKey: [NAME_SPACE, 'verify-and-update-subscription-builder'], mutationFn: (payload: { provider: string subscriptionBuilderId: string - credentials?: Record + credentials?: Record }) => { const { provider, subscriptionBuilderId, ...body } = payload return post<{ verified: boolean }>( - `/workspaces/current/trigger-provider/${provider}/subscriptions/builder/verify/${subscriptionBuilderId}`, + `/workspaces/current/trigger-provider/${provider}/subscriptions/builder/verify-and-update/${subscriptionBuilderId}`, + { body }, + { silent: true }, + ) + }, + }) +} + +export const useVerifyTriggerSubscription = () => { + return useMutation({ + mutationKey: [NAME_SPACE, 'verify-subscription'], + mutationFn: (payload: { + provider: string + subscriptionId: string + credentials?: Record + }) => { + const { provider, subscriptionId, ...body } = payload + return post<{ verified: boolean }>( + `/workspaces/current/trigger-provider/${provider}/subscriptions/verify/${subscriptionId}`, { body }, { silent: true }, ) @@ -184,7 +203,7 @@ export type BuildTriggerSubscriptionPayload = { provider: string subscriptionBuilderId: string name?: string - parameters?: Record + parameters?: Record } export const useBuildTriggerSubscription = () => { @@ -211,6 +230,27 @@ export const useDeleteTriggerSubscription = () => { }) } +export type UpdateTriggerSubscriptionPayload = { + subscriptionId: string + name?: string + properties?: Record + parameters?: Record + credentials?: Record +} + +export const useUpdateTriggerSubscription = () => { + return useMutation({ + mutationKey: [NAME_SPACE, 'update-subscription'], + mutationFn: (payload: UpdateTriggerSubscriptionPayload) => { + const { subscriptionId, ...body } = payload + return post<{ result: string, id: string }>( + `/workspaces/current/trigger-provider/${subscriptionId}/subscriptions/update`, + { body }, + ) + }, + }) +} + export const useTriggerSubscriptionBuilderLogs = ( provider: string, subscriptionBuilderId: string, @@ -290,20 +330,45 @@ export const useTriggerPluginDynamicOptions = (payload: { action: string parameter: string credential_id: string - extra?: Record + credentials?: Record + extra?: Record }, enabled = true) => { - return useQuery<{ options: Array<{ value: string, label: any }> }>({ - queryKey: [NAME_SPACE, 'dynamic-options', payload.plugin_id, payload.provider, payload.action, payload.parameter, payload.credential_id, payload.extra], - queryFn: () => get<{ options: Array<{ value: string, label: any }> }>( - '/workspaces/current/plugin/parameters/dynamic-options', - { - params: { - ...payload, - provider_type: 'trigger', // Add required provider_type parameter + return useQuery<{ options: FormOption[] }>({ + queryKey: [NAME_SPACE, 'dynamic-options', payload.plugin_id, payload.provider, payload.action, payload.parameter, payload.credential_id, payload.credentials, payload.extra], + queryFn: () => { + // Use new endpoint with POST when credentials provided (for edit mode) + if (payload.credentials) { + return post<{ options: FormOption[] }>( + '/workspaces/current/plugin/parameters/dynamic-options-with-credentials', + { + body: { + plugin_id: payload.plugin_id, + provider: payload.provider, + action: payload.action, + parameter: payload.parameter, + credential_id: payload.credential_id, + credentials: payload.credentials, + }, + }, + { silent: true }, + ) + } + // Use original GET endpoint for normal cases + return get<{ options: FormOption[] }>( + '/workspaces/current/plugin/parameters/dynamic-options', + { + params: { + plugin_id: payload.plugin_id, + provider: payload.provider, + action: payload.action, + parameter: payload.parameter, + credential_id: payload.credential_id, + provider_type: 'trigger', + }, }, - }, - { silent: true }, - ), + { silent: true }, + ) + }, enabled: enabled && !!payload.plugin_id && !!payload.provider && !!payload.action && !!payload.parameter && !!payload.credential_id, retry: 0, })