chore: split to single app_dsl_version API (#36864)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
非法操作 2026-05-31 20:13:44 +08:00 committed by GitHub
parent 129af96c23
commit 04f5555580
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 205 additions and 27 deletions

View File

@ -2,13 +2,25 @@ from flask_restx import Resource
from werkzeug.exceptions import Unauthorized
from controllers.common.schema import register_response_schema_models
from fields.base import ResponseModel
from libs.helper import dump_response
from libs.login import current_user, login_required
from services.feature_service import FeatureModel, FeatureService, LimitationModel, SystemFeatureModel
from services.feature_service import (
FeatureModel,
FeatureService,
LimitationModel,
SystemFeatureModel,
)
from . import console_ns
from .wraps import account_initialization_required, cloud_utm_record, setup_required, with_current_tenant_id
register_response_schema_models(console_ns, FeatureModel, LimitationModel, SystemFeatureModel)
class AppDslVersionResponse(ResponseModel):
app_dsl_version: str
register_response_schema_models(console_ns, AppDslVersionResponse, FeatureModel, LimitationModel, SystemFeatureModel)
@console_ns.route("/features")
@ -54,6 +66,23 @@ class FeatureVectorSpaceApi(Resource):
return FeatureService.get_vector_space(current_tenant_id).model_dump()
@console_ns.route("/app-dsl-version")
class AppDslVersionApi(Resource):
@console_ns.doc("get_app_dsl_version")
@console_ns.doc(description="Get current app DSL version")
@console_ns.response(
200,
"Success",
console_ns.models[AppDslVersionResponse.__name__],
)
def get(self):
"""Get current app DSL version for workflow clipboard compatibility."""
return dump_response(
AppDslVersionResponse,
{"app_dsl_version": FeatureService.get_app_dsl_version()},
)
@console_ns.route("/system-features")
class SystemFeatureApi(Resource):
@console_ns.doc("get_system_features")

View File

@ -599,6 +599,23 @@ Update API-based extension
| ---- | ----------- |
| 204 | Binding deleted successfully |
### /app-dsl-version
#### GET
##### Summary
Get current app DSL version for workflow clipboard compatibility
##### Description
Get current app DSL version
##### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | [AppDslVersionResponse](#appdslversionresponse) |
### /app/prompt-templates
#### GET
@ -11379,6 +11396,12 @@ Enum class for api provider schema type.
| use_icon_as_answer_icon | boolean | | No |
| workflow | [WorkflowPartial](#workflowpartial) | | No |
#### AppDslVersionResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| app_dsl_version | string | | Yes |
#### AppExportQuery
| Name | Type | Description | Required |
@ -15250,7 +15273,6 @@ Default configuration for form inputs.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| app_dsl_version | string | | Yes |
| branding | [BrandingModel](#brandingmodel) | | Yes |
| enable_change_email | boolean | | Yes |
| enable_collaboration_mode | boolean | | Yes |

View File

@ -1323,7 +1323,6 @@ Returns Server-Sent Events stream.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| app_dsl_version | string | | Yes |
| branding | [BrandingModel](#brandingmodel) | | Yes |
| enable_change_email | boolean | | Yes |
| enable_collaboration_mode | boolean | | Yes |

View File

@ -160,7 +160,6 @@ class PluginManagerModel(FeatureResponseModel):
class SystemFeatureModel(FeatureResponseModel):
app_dsl_version: str = ""
sso_enforced_for_signin: bool = False
sso_enforced_for_signin_protocol: str = ""
enable_marketplace: bool = False
@ -248,7 +247,6 @@ class FeatureService:
@classmethod
def get_system_features(cls, is_authenticated: bool = False) -> SystemFeatureModel:
system_features = SystemFeatureModel()
system_features.app_dsl_version = CURRENT_APP_DSL_VERSION
cls._fulfill_system_params_from_env(system_features)
@ -267,6 +265,10 @@ class FeatureService:
return system_features
@classmethod
def get_app_dsl_version(cls) -> str:
return CURRENT_APP_DSL_VERSION
@classmethod
def _fulfill_system_params_from_env(cls, system_features: SystemFeatureModel):
system_features.enable_email_code_login = dify_config.ENABLE_EMAIL_CODE_LOGIN

View File

@ -46,6 +46,21 @@ class TestFeatureVectorSpaceApi:
get_vector_space.assert_called_once_with("tenant_123")
class TestAppDslVersionApi:
def test_get_app_dsl_version_success(self, mocker: MockerFixture):
from controllers.console.feature import AppDslVersionApi
get_app_dsl_version = mocker.patch("controllers.console.feature.FeatureService.get_app_dsl_version")
get_app_dsl_version.return_value = "0.6.0"
api = AppDslVersionApi()
result = api.get()
assert result == {"app_dsl_version": "0.6.0"}
get_app_dsl_version.assert_called_once_with()
class TestSystemFeatureApi:
def test_get_system_features_authenticated(self, mocker: MockerFixture):
"""

View File

@ -0,0 +1,14 @@
from constants.dsl_version import CURRENT_APP_DSL_VERSION
from services.feature_service import FeatureService
def test_get_system_features_excludes_app_dsl_version():
result = FeatureService.get_system_features().model_dump()
assert "app_dsl_version" not in result
def test_get_app_dsl_version_returns_current_version():
result = FeatureService.get_app_dsl_version()
assert result == CURRENT_APP_DSL_VERSION

View File

@ -0,0 +1,30 @@
// This file is auto-generated by @hey-api/openapi-ts
import { oc } from '@orpc/contract'
import { zGetAppDslVersionResponse } from './zod.gen'
/**
* Get current app DSL version for workflow clipboard compatibility
*
* Get current app DSL version
*/
export const get = oc
.route({
description: 'Get current app DSL version',
inputStructure: 'detailed',
method: 'GET',
operationId: 'getAppDslVersion',
path: '/app-dsl-version',
summary: 'Get current app DSL version for workflow clipboard compatibility',
tags: ['console'],
})
.output(zGetAppDslVersionResponse)
export const appDslVersion = {
get,
}
export const contract = {
appDslVersion,
}

View File

@ -0,0 +1,22 @@
// This file is auto-generated by @hey-api/openapi-ts
export type ClientOptions = {
baseUrl: `${string}://${string}/console/api` | (string & {})
}
export type AppDslVersionResponse = {
app_dsl_version: string
}
export type GetAppDslVersionData = {
body?: never
path?: never
query?: never
url: '/app-dsl-version'
}
export type GetAppDslVersionResponses = {
200: AppDslVersionResponse
}
export type GetAppDslVersionResponse = GetAppDslVersionResponses[keyof GetAppDslVersionResponses]

View File

@ -0,0 +1,15 @@
// This file is auto-generated by @hey-api/openapi-ts
import * as z from 'zod'
/**
* AppDslVersionResponse
*/
export const zAppDslVersionResponse = z.object({
app_dsl_version: z.string(),
})
/**
* Success
*/
export const zGetAppDslVersionResponse = zAppDslVersionResponse

View File

@ -6,6 +6,7 @@ import { agents } from './agents/orpc.gen'
import { allWorkspaces } from './all-workspaces/orpc.gen'
import { apiBasedExtension } from './api-based-extension/orpc.gen'
import { apiKeyAuth } from './api-key-auth/orpc.gen'
import { appDslVersion } from './app-dsl-version/orpc.gen'
import { app } from './app/orpc.gen'
import { apps } from './apps/orpc.gen'
import { auth } from './auth/orpc.gen'
@ -55,6 +56,7 @@ export const contract = {
apiBasedExtension,
apiKeyAuth,
app,
appDslVersion,
apps,
auth,
billing,

View File

@ -5,7 +5,6 @@ export type ClientOptions = {
}
export type SystemFeatureModel = {
app_dsl_version: string
branding: BrandingModel
enable_change_email: boolean
enable_collaboration_mode: boolean

View File

@ -87,7 +87,6 @@ export const zWebAppAuthModel = z.object({
* SystemFeatureModel
*/
export const zSystemFeatureModel = z.object({
app_dsl_version: z.string().default(''),
branding: zBrandingModel,
enable_change_email: z.boolean().default(true),
enable_collaboration_mode: z.boolean().default(true),

View File

@ -218,7 +218,6 @@ export type SuggestedQuestionsResponse = {
}
export type SystemFeatureModel = {
app_dsl_version: string
branding: BrandingModel
enable_change_email: boolean
enable_collaboration_mode: boolean

View File

@ -364,7 +364,6 @@ export const zWebAppAuthModel = z.object({
* SystemFeatureModel
*/
export const zSystemFeatureModel = z.object({
app_dsl_version: z.string().default(''),
branding: zBrandingModel,
enable_change_email: z.boolean().default(true),
enable_collaboration_mode: z.boolean().default(true),

View File

@ -12,6 +12,22 @@ type DeepPartial<T> = T extends Array<infer U>
? { [K in keyof T]?: DeepPartial<T[K]> }
: T
type QueryKeyProvider = {
queryKey: () => readonly unknown[]
}
type AppDslVersionQueryProvider = {
get?: QueryKeyProvider
}
const fallbackAppDslVersionQueryKey = ['console', 'appDslVersion', 'get'] as const
const getAppDslVersionQueryKey = () => {
const appDslVersionQuery = (consoleQuery as { appDslVersion?: AppDslVersionQueryProvider }).appDslVersion
return appDslVersionQuery?.get?.queryKey() ?? fallbackAppDslVersionQueryKey
}
const buildSystemFeatures = (
overrides: DeepPartial<SystemFeatures> = {},
): SystemFeatures => {
@ -70,6 +86,13 @@ export const seedSystemFeatures = (
return data
}
export const seedAppDslVersion = (
queryClient: QueryClient,
appDslVersion = '0.6.0',
) => {
queryClient.setQueryData(getAppDslVersionQueryKey(), { app_dsl_version: appDslVersion })
}
type SystemFeaturesTestOptions = {
/**
* Partial overrides for the systemFeatures payload. When omitted, the cache
@ -78,6 +101,11 @@ type SystemFeaturesTestOptions = {
* keep the systemFeatures query in the pending state.
*/
systemFeatures?: DeepPartial<SystemFeatures> | null
/**
* Seed the workflow clipboard DSL version query only for tests that need it.
* Omit or pass `null` to leave it unseeded.
*/
appDslVersion?: string | null
queryClient?: QueryClient
}
@ -94,6 +122,8 @@ export const createSystemFeaturesWrapper = (
const systemFeatures = options.systemFeatures === null
? null
: seedSystemFeatures(queryClient, options.systemFeatures)
if (options.appDslVersion !== undefined && options.appDslVersion !== null)
seedAppDslVersion(queryClient, options.appDslVersion)
const wrapper = ({ children }: { children: ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
)
@ -104,9 +134,10 @@ export const renderWithSystemFeatures = (
ui: ReactElement,
options: SystemFeaturesTestOptions & Omit<RenderOptions, 'wrapper'> = {},
): RenderResult & { queryClient: QueryClient, systemFeatures: SystemFeatures | null } => {
const { systemFeatures: sf, queryClient: qc, ...renderOptions } = options
const { systemFeatures: sf, appDslVersion, queryClient: qc, ...renderOptions } = options
const { wrapper, queryClient, systemFeatures } = createSystemFeaturesWrapper({
systemFeatures: sf,
appDslVersion,
queryClient: qc,
})
const rendered = render(ui, { wrapper, ...renderOptions })
@ -117,9 +148,10 @@ export const renderHookWithSystemFeatures = <Result, Props = void>(
callback: (props: Props) => Result,
options: SystemFeaturesTestOptions & Omit<RenderHookOptions<Props>, 'wrapper'> = {},
): RenderHookResult<Result, Props> & { queryClient: QueryClient, systemFeatures: SystemFeatures | null } => {
const { systemFeatures: sf, queryClient: qc, ...hookOptions } = options
const { systemFeatures: sf, appDslVersion, queryClient: qc, ...hookOptions } = options
const { wrapper, queryClient, systemFeatures } = createSystemFeaturesWrapper({
systemFeatures: sf,
appDslVersion,
queryClient: qc,
})
const rendered = renderHook(callback, { wrapper, ...hookOptions })

View File

@ -69,7 +69,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { render, renderHook } from '@testing-library/react'
import * as React from 'react'
import ReactFlow, { ReactFlowProvider } from 'reactflow'
import { seedSystemFeatures } from '@/__tests__/utils/mock-system-features'
import { seedAppDslVersion, seedSystemFeatures } from '@/__tests__/utils/mock-system-features'
import { WorkflowContext } from '../context'
import { HooksStoreContext } from '../hooks-store/provider'
import { createHooksStore } from '../hooks-store/store'
@ -168,6 +168,8 @@ function createWorkflowWrapper(
})
if (!externalQueryClient)
seedSystemFeatures(queryClient)
if (!externalQueryClient)
seedAppDslVersion(queryClient)
return ({ children }: { children: React.ReactNode }) => {
let inner: React.ReactNode = children

View File

@ -14,7 +14,7 @@ import type { VariableAssignerNodeType } from '../nodes/variable-assigner/types'
import type { Edge, Node, OnNodeAdd } from '../types'
import type { RAGPipelineVariables } from '@/models/pipeline'
import { toast } from '@langgenius/dify-ui/toast'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useQuery } from '@tanstack/react-query'
import { produce } from 'immer'
import { useCallback, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -23,7 +23,7 @@ import {
getOutgoers,
useReactFlow,
} from 'reactflow'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { consoleQuery } from '@/service/client'
import { collaborationManager } from '../collaboration/core/collaboration-manager'
import {
CUSTOM_EDGE,
@ -145,10 +145,10 @@ const isNoteLinkClickTarget = (target: EventTarget | null, node: Node) => {
export const useNodesInteractions = () => {
const { t } = useTranslation()
const { data: appDslVersion } = useSuspenseQuery({
...systemFeaturesQueryOptions(),
select: s => s.app_dsl_version,
})
const { data: appDslVersion = '' } = useQuery(consoleQuery.appDslVersion.get.queryOptions({
staleTime: Infinity,
select: data => data.app_dsl_version,
}))
const collaborativeWorkflow = useCollaborativeWorkflow()
const workflowStore = useWorkflowStore()
const reactflow = useReactFlow()

View File

@ -1,16 +1,16 @@
import type { MouseEvent } from 'react'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useQuery } from '@tanstack/react-query'
import { useCallback } from 'react'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { consoleQuery } from '@/service/client'
import { useWorkflowStore } from '../store'
import { readWorkflowClipboard } from '../utils'
export const usePanelInteractions = () => {
const workflowStore = useWorkflowStore()
const { data: appDslVersion } = useSuspenseQuery({
...systemFeaturesQueryOptions(),
select: s => s.app_dsl_version,
})
const { data: appDslVersion = '' } = useQuery(consoleQuery.appDslVersion.get.queryOptions({
staleTime: Infinity,
select: data => data.app_dsl_version,
}))
const handlePaneContextMenu = useCallback((e: MouseEvent) => {
e.preventDefault()

View File

@ -28,7 +28,6 @@ type License = {
}
export type SystemFeatures = {
app_dsl_version: string
trial_models: ModelProviderQuotaGetPaid[]
plugin_installation_permission: {
plugin_installation_scope: InstallationScope
@ -70,7 +69,6 @@ export type SystemFeatures = {
}
export const defaultSystemFeatures: SystemFeatures = {
app_dsl_version: '',
trial_models: [],
plugin_installation_permission: {
plugin_installation_scope: InstallationScope.ALL,