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,
})