refactor(api): migrate dataset rag pipeline endpoints to BaseModel (#37958)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Asuka Minato <i@asukaminato.eu.org>
This commit is contained in:
chariri 2026-07-06 16:00:04 +09:00 committed by GitHub
parent c3b1508712
commit 77ae583b44
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 787 additions and 245 deletions

View File

@ -6,7 +6,7 @@ from pydantic import BaseModel, Field
from werkzeug.exceptions import Forbidden, NotFound from werkzeug.exceptions import Forbidden, NotFound
from configs import dify_config from configs import dify_config
from controllers.common.fields import RedirectResponse, SimpleResultResponse from controllers.common.fields import SimpleResultResponse
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
from controllers.console import console_ns from controllers.console import console_ns
from controllers.console.wraps import ( from controllers.console.wraps import (
@ -19,11 +19,13 @@ from controllers.console.wraps import (
with_current_tenant_id, with_current_tenant_id,
with_current_user, with_current_user,
) )
from core.entities.provider_entities import ProviderConfig
from core.plugin.entities.plugin_daemon import PluginOAuthAuthorizationUrlResponse from core.plugin.entities.plugin_daemon import PluginOAuthAuthorizationUrlResponse
from core.plugin.impl.oauth import OAuthHandler from core.plugin.impl.oauth import OAuthHandler
from core.tools.entities.common_entities import I18nObject
from fields.base import ResponseModel from fields.base import ResponseModel
from graphon.model_runtime.errors.validate import CredentialsValidateFailedError from graphon.model_runtime.errors.validate import CredentialsValidateFailedError
from graphon.model_runtime.utils.encoders import jsonable_encoder from libs.helper import dump_response
from libs.login import login_required from libs.login import login_required
from models import Account from models import Account
from models.provider_ids import DatasourceProviderID from models.provider_ids import DatasourceProviderID
@ -33,7 +35,9 @@ from services.plugin.oauth_service import OAuthProxyService
class DatasourceCredentialPayload(BaseModel): class DatasourceCredentialPayload(BaseModel):
name: str | None = Field(default=None, max_length=100) name: str | None = Field(default=None, max_length=100)
credentials: dict[str, Any] credentials: dict[str, Any] = Field(
description="Plugin-defined credential parameters. The schema is declared by the datasource provider."
)
class DatasourceCredentialDeletePayload(BaseModel): class DatasourceCredentialDeletePayload(BaseModel):
@ -43,11 +47,17 @@ class DatasourceCredentialDeletePayload(BaseModel):
class DatasourceCredentialUpdatePayload(BaseModel): class DatasourceCredentialUpdatePayload(BaseModel):
credential_id: str credential_id: str
name: str | None = Field(default=None, max_length=100) name: str | None = Field(default=None, max_length=100)
credentials: dict[str, Any] | None = Field(default=None) credentials: dict[str, Any] | None = Field(
default=None,
description="Plugin-defined credential parameters. The schema is declared by the datasource provider.",
)
class DatasourceCustomClientPayload(BaseModel): class DatasourceCustomClientPayload(BaseModel):
client_params: dict[str, Any] | None = Field(default=None) client_params: dict[str, Any] | None = Field(
default=None,
description="Plugin-defined OAuth client parameters. The schema is declared by the datasource provider.",
)
enable_oauth_custom_client: bool | None = None enable_oauth_custom_client: bool | None = None
@ -71,8 +81,48 @@ class DatasourceOAuthCallbackQuery(BaseModel):
context_id: str | None = Field(default=None, description="OAuth proxy context ID") context_id: str | None = Field(default=None, description="OAuth proxy context ID")
class DatasourceCredentialsResponse(ResponseModel): class DatasourceCredentialResponse(ResponseModel):
result: Any credential: dict[str, Any] = Field(
description="Obfuscated plugin-defined credential parameters from the datasource provider."
)
type: str
name: str
avatar_url: str | None
id: str
is_default: bool
class DatasourceCredentialListResponse(ResponseModel):
result: list[DatasourceCredentialResponse]
class DatasourceOAuthSchemaResponse(ResponseModel):
client_schema: list[ProviderConfig]
credentials_schema: list[ProviderConfig]
oauth_custom_client_params: dict[str, Any] | None = Field(
description="Masked plugin-defined OAuth client parameters, when configured for the tenant."
)
is_oauth_custom_client_enabled: bool
is_system_oauth_params_exists: bool
redirect_uri: str
class DatasourceProviderAuthResponse(ResponseModel):
author: str
provider: str
plugin_id: str
plugin_unique_identifier: str
icon: str
name: str
label: I18nObject
description: I18nObject
credential_schema: list[ProviderConfig]
oauth_schema: DatasourceOAuthSchemaResponse | None
credentials_list: list[DatasourceCredentialResponse]
class DatasourceProviderAuthListResponse(ResponseModel):
result: list[DatasourceProviderAuthResponse]
register_schema_models( register_schema_models(
@ -88,9 +138,9 @@ register_schema_models(
) )
register_response_schema_models( register_response_schema_models(
console_ns, console_ns,
DatasourceCredentialsResponse, DatasourceCredentialListResponse,
DatasourceProviderAuthListResponse,
PluginOAuthAuthorizationUrlResponse, PluginOAuthAuthorizationUrlResponse,
RedirectResponse,
SimpleResultResponse, SimpleResultResponse,
) )
@ -100,7 +150,7 @@ class DatasourcePluginOAuthAuthorizationUrl(Resource):
@console_ns.doc(params=query_params_from_model(DatasourceOAuthAuthorizationQuery)) @console_ns.doc(params=query_params_from_model(DatasourceOAuthAuthorizationQuery))
@console_ns.response( @console_ns.response(
200, 200,
"Authorization URL retrieved successfully", "Datasource OAuth authorization URL generated successfully",
console_ns.models[PluginOAuthAuthorizationUrlResponse.__name__], console_ns.models[PluginOAuthAuthorizationUrlResponse.__name__],
) )
@setup_required @setup_required
@ -140,7 +190,8 @@ class DatasourcePluginOAuthAuthorizationUrl(Resource):
redirect_uri=redirect_uri, redirect_uri=redirect_uri,
system_credentials=oauth_config, system_credentials=oauth_config,
) )
response = make_response(jsonable_encoder(authorization_url_response)) # response-contract:ignore cookie-bearing Flask response
response = make_response(dump_response(PluginOAuthAuthorizationUrlResponse, authorization_url_response))
response.set_cookie( response.set_cookie(
"context_id", "context_id",
context_id, context_id,
@ -154,11 +205,8 @@ class DatasourcePluginOAuthAuthorizationUrl(Resource):
@console_ns.route("/oauth/plugin/<path:provider_id>/datasource/callback") @console_ns.route("/oauth/plugin/<path:provider_id>/datasource/callback")
class DatasourceOAuthCallback(Resource): class DatasourceOAuthCallback(Resource):
@console_ns.doc(params=query_params_from_model(DatasourceOAuthCallbackQuery)) @console_ns.doc(params=query_params_from_model(DatasourceOAuthCallbackQuery))
@console_ns.response( # response-contract:ignore redirect response
302, @console_ns.response(302, "Redirect to OAuth callback page")
"Redirect to console OAuth callback page",
console_ns.models[RedirectResponse.__name__],
)
@setup_required @setup_required
def get(self, provider_id: str): def get(self, provider_id: str):
context_id = request.cookies.get("context_id") or request.args.get("context_id") context_id = request.cookies.get("context_id") or request.args.get("context_id")
@ -217,7 +265,9 @@ class DatasourceOAuthCallback(Resource):
@console_ns.route("/auth/plugin/datasource/<path:provider_id>") @console_ns.route("/auth/plugin/datasource/<path:provider_id>")
class DatasourceAuth(Resource): class DatasourceAuth(Resource):
@console_ns.expect(console_ns.models[DatasourceCredentialPayload.__name__]) @console_ns.expect(console_ns.models[DatasourceCredentialPayload.__name__])
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__]) @console_ns.response(
200, "Datasource credential created successfully", console_ns.models[SimpleResultResponse.__name__]
)
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
@ -238,12 +288,16 @@ class DatasourceAuth(Resource):
) )
except CredentialsValidateFailedError as ex: except CredentialsValidateFailedError as ex:
raise ValueError(str(ex)) raise ValueError(str(ex))
return {"result": "success"}, 200 return SimpleResultResponse(result="success").model_dump(mode="json"), 200
@console_ns.response(
200,
"Datasource credentials retrieved successfully",
console_ns.models[DatasourceCredentialListResponse.__name__],
)
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
@console_ns.response(200, "Success", console_ns.models[DatasourceCredentialsResponse.__name__])
@with_current_user @with_current_user
@with_current_tenant_id @with_current_tenant_id
def get(self, current_tenant_id: str, user: Account, provider_id: str): def get(self, current_tenant_id: str, user: Account, provider_id: str):
@ -256,7 +310,7 @@ class DatasourceAuth(Resource):
plugin_id=datasource_provider_id.plugin_id, plugin_id=datasource_provider_id.plugin_id,
user=user, user=user,
) )
return {"result": datasources}, 200 return dump_response(DatasourceCredentialListResponse, {"result": datasources}), 200
@console_ns.route("/auth/plugin/datasource/<path:provider_id>/delete") @console_ns.route("/auth/plugin/datasource/<path:provider_id>/delete")
@ -282,13 +336,15 @@ class DatasourceAuthDeleteApi(Resource):
provider=provider_name, provider=provider_name,
plugin_id=plugin_id, plugin_id=plugin_id,
) )
return {"result": "success"}, 200 return SimpleResultResponse(result="success").model_dump(mode="json"), 200
@console_ns.route("/auth/plugin/datasource/<path:provider_id>/update") @console_ns.route("/auth/plugin/datasource/<path:provider_id>/update")
class DatasourceAuthUpdateApi(Resource): class DatasourceAuthUpdateApi(Resource):
@console_ns.expect(console_ns.models[DatasourceCredentialUpdatePayload.__name__]) @console_ns.expect(console_ns.models[DatasourceCredentialUpdatePayload.__name__])
@console_ns.response(201, "Success", console_ns.models[SimpleResultResponse.__name__]) @console_ns.response(
201, "Datasource credential updated successfully", console_ns.models[SimpleResultResponse.__name__]
)
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
@ -308,12 +364,16 @@ class DatasourceAuthUpdateApi(Resource):
credentials=payload.credentials or {}, credentials=payload.credentials or {},
name=payload.name, name=payload.name,
) )
return {"result": "success"}, 201 return SimpleResultResponse(result="success").model_dump(mode="json"), 201
@console_ns.route("/auth/plugin/datasource/list") @console_ns.route("/auth/plugin/datasource/list")
class DatasourceAuthListApi(Resource): class DatasourceAuthListApi(Resource):
@console_ns.response(200, "Success", console_ns.models[DatasourceCredentialsResponse.__name__]) @console_ns.response(
200,
"Datasource credentials retrieved successfully",
console_ns.models[DatasourceProviderAuthListResponse.__name__],
)
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
@ -321,12 +381,16 @@ class DatasourceAuthListApi(Resource):
def get(self, current_tenant_id: str): def get(self, current_tenant_id: str):
datasource_provider_service = DatasourceProviderService() datasource_provider_service = DatasourceProviderService()
datasources = datasource_provider_service.get_all_datasource_credentials(tenant_id=current_tenant_id) datasources = datasource_provider_service.get_all_datasource_credentials(tenant_id=current_tenant_id)
return {"result": jsonable_encoder(datasources)}, 200 return dump_response(DatasourceProviderAuthListResponse, {"result": datasources}), 200
@console_ns.route("/auth/plugin/datasource/default-list") @console_ns.route("/auth/plugin/datasource/default-list")
class DatasourceHardCodeAuthListApi(Resource): class DatasourceHardCodeAuthListApi(Resource):
@console_ns.response(200, "Success", console_ns.models[DatasourceCredentialsResponse.__name__]) @console_ns.response(
200,
"Default datasource credentials retrieved successfully",
console_ns.models[DatasourceProviderAuthListResponse.__name__],
)
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
@ -334,13 +398,15 @@ class DatasourceHardCodeAuthListApi(Resource):
def get(self, current_tenant_id: str): def get(self, current_tenant_id: str):
datasource_provider_service = DatasourceProviderService() datasource_provider_service = DatasourceProviderService()
datasources = datasource_provider_service.get_hard_code_datasource_credentials(tenant_id=current_tenant_id) datasources = datasource_provider_service.get_hard_code_datasource_credentials(tenant_id=current_tenant_id)
return {"result": jsonable_encoder(datasources)}, 200 return dump_response(DatasourceProviderAuthListResponse, {"result": datasources}), 200
@console_ns.route("/auth/plugin/datasource/<path:provider_id>/custom-client") @console_ns.route("/auth/plugin/datasource/<path:provider_id>/custom-client")
class DatasourceAuthOauthCustomClient(Resource): class DatasourceAuthOauthCustomClient(Resource):
@console_ns.expect(console_ns.models[DatasourceCustomClientPayload.__name__]) @console_ns.expect(console_ns.models[DatasourceCustomClientPayload.__name__])
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__]) @console_ns.response(
200, "Datasource OAuth custom client saved successfully", console_ns.models[SimpleResultResponse.__name__]
)
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required
@ -357,7 +423,7 @@ class DatasourceAuthOauthCustomClient(Resource):
client_params=payload.client_params or {}, client_params=payload.client_params or {},
enabled=payload.enable_oauth_custom_client or False, enabled=payload.enable_oauth_custom_client or False,
) )
return {"result": "success"}, 200 return SimpleResultResponse(result="success").model_dump(mode="json"), 200
@setup_required @setup_required
@login_required @login_required
@ -371,7 +437,7 @@ class DatasourceAuthOauthCustomClient(Resource):
tenant_id=current_tenant_id, tenant_id=current_tenant_id,
datasource_provider_id=datasource_provider_id, datasource_provider_id=datasource_provider_id,
) )
return {"result": "success"}, 200 return SimpleResultResponse(result="success").model_dump(mode="json"), 200
@console_ns.route("/auth/plugin/datasource/<path:provider_id>/default") @console_ns.route("/auth/plugin/datasource/<path:provider_id>/default")
@ -393,7 +459,7 @@ class DatasourceAuthDefaultApi(Resource):
datasource_provider_id=datasource_provider_id, datasource_provider_id=datasource_provider_id,
credential_id=payload.id, credential_id=payload.id,
) )
return {"result": "success"}, 200 return SimpleResultResponse(result="success").model_dump(mode="json"), 200
@console_ns.route("/auth/plugin/datasource/<path:provider_id>/update-name") @console_ns.route("/auth/plugin/datasource/<path:provider_id>/update-name")
@ -416,4 +482,4 @@ class DatasourceUpdateProviderNameApi(Resource):
name=payload.name, name=payload.name,
credential_id=payload.credential_id, credential_id=payload.credential_id,
) )
return {"result": "success"}, 200 return SimpleResultResponse(result="success").model_dump(mode="json"), 200

View File

@ -3,9 +3,9 @@ from typing import Any
from flask_restx import ( # type: ignore from flask_restx import ( # type: ignore
Resource, # type: ignore Resource, # type: ignore
) )
from pydantic import BaseModel, RootModel from pydantic import BaseModel
from controllers.common.schema import register_response_schema_models, register_schema_models from controllers.common.schema import register_schema_models
from controllers.console import console_ns from controllers.console import console_ns
from controllers.console.datasets.wraps import get_rag_pipeline from controllers.console.datasets.wraps import get_rag_pipeline
from controllers.console.wraps import account_initialization_required, setup_required, with_current_user from controllers.console.wraps import account_initialization_required, setup_required, with_current_user
@ -21,18 +21,13 @@ class Parser(BaseModel):
credential_id: str | None = None credential_id: str | None = None
class DataSourceContentPreviewResponse(RootModel[Any]):
root: Any
register_schema_models(console_ns, Parser) register_schema_models(console_ns, Parser)
register_response_schema_models(console_ns, DataSourceContentPreviewResponse)
@console_ns.route("/rag/pipelines/<uuid:pipeline_id>/workflows/published/datasource/nodes/<string:node_id>/preview") @console_ns.route("/rag/pipelines/<uuid:pipeline_id>/workflows/published/datasource/nodes/<string:node_id>/preview")
class DataSourceContentPreviewApi(Resource): class DataSourceContentPreviewApi(Resource):
@console_ns.expect(console_ns.models[Parser.__name__]) @console_ns.expect(console_ns.models[Parser.__name__])
@console_ns.response(200, "Success", console_ns.models[DataSourceContentPreviewResponse.__name__]) @console_ns.response(200, "Success")
@setup_required @setup_required
@login_required @login_required
@account_initialization_required @account_initialization_required

View File

@ -1,9 +1,10 @@
from collections.abc import Generator from collections.abc import Generator
from datetime import datetime
from typing import Any from typing import Any
from uuid import UUID from uuid import UUID
from flask import request from flask import request
from pydantic import BaseModel, Field, RootModel from pydantic import BaseModel, Field, RootModel, field_validator
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from werkzeug.exceptions import Forbidden, NotFound from werkzeug.exceptions import Forbidden, NotFound
@ -13,24 +14,20 @@ from controllers.common.errors import FilenameNotExistsError, NoFileUploadedErro
from controllers.common.fields import GeneratedAppResponse from controllers.common.fields import GeneratedAppResponse
from controllers.common.schema import ( from controllers.common.schema import (
query_params_from_model, query_params_from_model,
query_params_from_request,
register_response_schema_models, register_response_schema_models,
register_schema_model, register_schema_model,
register_schema_models,
) )
from controllers.console.app.wraps import with_session from controllers.console.app.wraps import with_session
from controllers.service_api import service_api_ns from controllers.service_api import service_api_ns
from controllers.service_api.dataset.error import PipelineRunError from controllers.service_api.dataset.error import PipelineRunError
from controllers.service_api.dataset.rag_pipeline.serializers import serialize_upload_file from controllers.service_api.schema import event_stream_response, json_or_event_stream_response, multipart_file_params
from controllers.service_api.schema import (
event_stream_response,
json_or_event_stream_response,
multipart_file_params,
)
from controllers.service_api.wraps import DatasetApiResource from controllers.service_api.wraps import DatasetApiResource
from core.app.apps.pipeline.pipeline_generator import PipelineGenerator from core.app.apps.pipeline.pipeline_generator import PipelineGenerator
from core.app.entities.app_invoke_entities import InvokeFrom from core.app.entities.app_invoke_entities import InvokeFrom
from fields.base import ResponseModel from fields.base import ResponseModel
from libs import helper from libs import helper
from libs.helper import dump_response
from libs.login import current_user from libs.login import current_user
from models import Account from models import Account
from models.dataset import Dataset, Pipeline from models.dataset import Dataset, Pipeline
@ -84,7 +81,7 @@ class DatasourcePluginResponse(ResponseModel):
datasource_type: str | None = None datasource_type: str | None = None
title: str | None = None title: str | None = None
user_input_variables: list[dict[str, Any]] = Field(default_factory=list) user_input_variables: list[dict[str, Any]] = Field(default_factory=list)
credentials: list[DatasourceCredentialInfoResponse] credentials: list[DatasourceCredentialInfoResponse] = Field(default_factory=list)
class DatasourcePluginListResponse(RootModel[list[DatasourcePluginResponse]]): class DatasourcePluginListResponse(RootModel[list[DatasourcePluginResponse]]):
@ -100,14 +97,22 @@ class PipelineUploadFileResponse(ResponseModel):
created_by: str created_by: str
created_at: str | None = None created_at: str | None = None
@field_validator("created_at", mode="before")
@classmethod
def _normalize_created_at(cls, value: datetime | str | None) -> str | None:
if isinstance(value, datetime):
return value.isoformat()
return value
register_schema_model(service_api_ns, DatasourceNodeRunPayload) register_schema_model(service_api_ns, DatasourceNodeRunPayload)
register_schema_model(service_api_ns, DatasourcePluginsQuery)
register_schema_model(service_api_ns, PipelineRunApiEntity) register_schema_model(service_api_ns, PipelineRunApiEntity)
register_schema_models(service_api_ns, DatasourcePluginsQuery)
register_response_schema_models( register_response_schema_models(
service_api_ns, service_api_ns,
DatasourceCredentialInfoResponse,
DatasourcePluginResponse,
DatasourcePluginListResponse, DatasourcePluginListResponse,
GeneratedAppResponse,
PipelineUploadFileResponse, PipelineUploadFileResponse,
) )
@ -119,8 +124,8 @@ class DatasourcePluginsApi(DatasetApiResource):
@service_api_ns.doc( @service_api_ns.doc(
summary="List Datasource Plugins", summary="List Datasource Plugins",
description=( description=(
"List the datasource nodes configured in the knowledge pipeline. Each node includes the " "List the datasource nodes configured in the knowledge pipeline. Each node includes the plugin it uses "
"plugin it uses plus the metadata needed to run it." "plus the metadata needed to run it."
), ),
tags=["Knowledge Pipeline"], tags=["Knowledge Pipeline"],
responses={ responses={
@ -152,14 +157,13 @@ class DatasourcePluginsApi(DatasetApiResource):
if not dataset: if not dataset:
raise NotFound("Dataset not found.") raise NotFound("Dataset not found.")
# Get query parameter to determine published or draft query = query_params_from_request(DatasourcePluginsQuery)
is_published: bool = request.args.get("is_published", default=True, type=bool)
rag_pipeline_service: RagPipelineService = RagPipelineService() rag_pipeline_service: RagPipelineService = RagPipelineService()
datasource_plugins: list[dict[Any, Any]] = rag_pipeline_service.get_datasource_plugins( datasource_plugins: list[dict[Any, Any]] = rag_pipeline_service.get_datasource_plugins(
tenant_id=tenant_id, dataset_id=dataset_id_str, is_published=is_published tenant_id=tenant_id, dataset_id=dataset_id_str, is_published=query.is_published
) )
return datasource_plugins, 200 return dump_response(DatasourcePluginListResponse, datasource_plugins), 200
@service_api_ns.route("/datasets/<uuid:dataset_id>/pipeline/datasource/nodes/<string:node_id>/run") @service_api_ns.route("/datasets/<uuid:dataset_id>/pipeline/datasource/nodes/<string:node_id>/run")
@ -169,8 +173,8 @@ class DatasourceNodeRunApi(DatasetApiResource):
@service_api_ns.doc( @service_api_ns.doc(
summary="Run Datasource Node", summary="Run Datasource Node",
description=( description=(
"Execute a single datasource node within the knowledge pipeline. Returns a streaming " "Execute a single datasource node within the knowledge pipeline. Returns a streaming response with the "
"response with the node execution results." "node execution results."
), ),
tags=["Knowledge Pipeline"], tags=["Knowledge Pipeline"],
responses={ responses={
@ -189,11 +193,6 @@ class DatasourceNodeRunApi(DatasetApiResource):
} }
) )
@service_api_ns.expect(service_api_ns.models[DatasourceNodeRunPayload.__name__]) @service_api_ns.expect(service_api_ns.models[DatasourceNodeRunPayload.__name__])
@service_api_ns.response(
200,
"Datasource node run successfully",
service_api_ns.models[GeneratedAppResponse.__name__],
)
def post(self, tenant_id: str, dataset_id: UUID, node_id: str): def post(self, tenant_id: str, dataset_id: UUID, node_id: str):
"""Resource for getting datasource plugins.""" """Resource for getting datasource plugins."""
dataset_id_str = str(dataset_id) dataset_id_str = str(dataset_id)
@ -210,10 +209,11 @@ class DatasourceNodeRunApi(DatasetApiResource):
datasource_node_run_api_entity = DatasourceNodeRunApiEntity.model_validate( datasource_node_run_api_entity = DatasourceNodeRunApiEntity.model_validate(
{ {
**payload.model_dump(exclude_none=True), **payload.model_dump(exclude_none=True),
"pipeline_id": str(pipeline.id), "pipeline_id": pipeline.id,
"node_id": node_id, "node_id": node_id,
} }
) )
# response-contract:ignore compact_generate_response
return helper.compact_generate_response( return helper.compact_generate_response(
PipelineGenerator.convert_to_event_stream( PipelineGenerator.convert_to_event_stream(
rag_pipeline_service.run_datasource_workflow_node( rag_pipeline_service.run_datasource_workflow_node(
@ -236,8 +236,8 @@ class PipelineRunApi(DatasetApiResource):
@service_api_ns.doc( @service_api_ns.doc(
summary="Run Pipeline", summary="Run Pipeline",
description=( description=(
"Execute the full knowledge pipeline for a knowledge base. Supports both streaming and " "Execute the full knowledge pipeline for a knowledge base. Supports both streaming and blocking response "
"blocking response modes." "modes."
), ),
tags=["Knowledge Pipeline"], tags=["Knowledge Pipeline"],
responses={ responses={
@ -293,6 +293,7 @@ class PipelineRunApi(DatasetApiResource):
streaming=payload.response_mode == "streaming", streaming=payload.response_mode == "streaming",
) )
# response-contract:ignore compact_generate_response
return helper.compact_generate_response(response) return helper.compact_generate_response(response)
except Exception as ex: except Exception as ex:
raise PipelineRunError(description=str(ex)) raise PipelineRunError(description=str(ex))
@ -368,4 +369,4 @@ class KnowledgebasePipelineFileUploadApi(DatasetApiResource):
except services.errors.file.UnsupportedFileTypeError: except services.errors.file.UnsupportedFileTypeError:
raise UnsupportedFileTypeError() raise UnsupportedFileTypeError()
return serialize_upload_file(upload_file), 201 return dump_response(PipelineUploadFileResponse, upload_file), 201

View File

@ -1,32 +0,0 @@
"""
Serialization helpers for Service API knowledge pipeline endpoints.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, TypedDict
if TYPE_CHECKING:
from models.model import UploadFile
class UploadFileDict(TypedDict):
id: str
name: str
size: int
extension: str
mime_type: str | None
created_by: str
created_at: str | None
def serialize_upload_file(upload_file: UploadFile) -> UploadFileDict:
return {
"id": upload_file.id,
"name": upload_file.name,
"size": upload_file.size,
"extension": upload_file.extension,
"mime_type": upload_file.mime_type,
"created_by": upload_file.created_by,
"created_at": upload_file.created_at.isoformat() if upload_file.created_at else None,
}

View File

@ -5106,14 +5106,14 @@ Refresh MCP server configuration and regenerate server code
| Code | Description | Schema | | Code | Description | Schema |
| ---- | ----------- | ------ | | ---- | ----------- | ------ |
| 200 | Success | **application/json**: [DatasourceCredentialsResponse](#datasourcecredentialsresponse)<br> | | 200 | Default datasource credentials retrieved successfully | **application/json**: [DatasourceProviderAuthListResponse](#datasourceproviderauthlistresponse)<br> |
### [GET] /auth/plugin/datasource/list ### [GET] /auth/plugin/datasource/list
#### Responses #### Responses
| Code | Description | Schema | | Code | Description | Schema |
| ---- | ----------- | ------ | | ---- | ----------- | ------ |
| 200 | Success | **application/json**: [DatasourceCredentialsResponse](#datasourcecredentialsresponse)<br> | | 200 | Datasource credentials retrieved successfully | **application/json**: [DatasourceProviderAuthListResponse](#datasourceproviderauthlistresponse)<br> |
### [GET] /auth/plugin/datasource/{provider_id} ### [GET] /auth/plugin/datasource/{provider_id}
#### Parameters #### Parameters
@ -5126,7 +5126,7 @@ Refresh MCP server configuration and regenerate server code
| Code | Description | Schema | | Code | Description | Schema |
| ---- | ----------- | ------ | | ---- | ----------- | ------ |
| 200 | Success | **application/json**: [DatasourceCredentialsResponse](#datasourcecredentialsresponse)<br> | | 200 | Datasource credentials retrieved successfully | **application/json**: [DatasourceCredentialListResponse](#datasourcecredentiallistresponse)<br> |
### [POST] /auth/plugin/datasource/{provider_id} ### [POST] /auth/plugin/datasource/{provider_id}
#### Parameters #### Parameters
@ -5145,7 +5145,7 @@ Refresh MCP server configuration and regenerate server code
| Code | Description | Schema | | Code | Description | Schema |
| ---- | ----------- | ------ | | ---- | ----------- | ------ |
| 200 | Success | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> | | 200 | Datasource credential created successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [DELETE] /auth/plugin/datasource/{provider_id}/custom-client ### [DELETE] /auth/plugin/datasource/{provider_id}/custom-client
#### Parameters #### Parameters
@ -5177,7 +5177,7 @@ Refresh MCP server configuration and regenerate server code
| Code | Description | Schema | | Code | Description | Schema |
| ---- | ----------- | ------ | | ---- | ----------- | ------ |
| 200 | Success | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> | | 200 | Datasource OAuth custom client saved successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [POST] /auth/plugin/datasource/{provider_id}/default ### [POST] /auth/plugin/datasource/{provider_id}/default
#### Parameters #### Parameters
@ -5234,7 +5234,7 @@ Refresh MCP server configuration and regenerate server code
| Code | Description | Schema | | Code | Description | Schema |
| ---- | ----------- | ------ | | ---- | ----------- | ------ |
| 201 | Success | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> | | 201 | Datasource credential updated successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [POST] /auth/plugin/datasource/{provider_id}/update-name ### [POST] /auth/plugin/datasource/{provider_id}/update-name
#### Parameters #### Parameters
@ -7638,9 +7638,9 @@ Initiate OAuth login process
#### Responses #### Responses
| Code | Description | Schema | | Code | Description |
| ---- | ----------- | ------ | | ---- | ----------- |
| 302 | Redirect to console OAuth callback page | **application/json**: [RedirectResponse](#redirectresponse)<br> | | 302 | Redirect to OAuth callback page |
### [GET] /oauth/plugin/{provider_id}/datasource/get-authorization-url ### [GET] /oauth/plugin/{provider_id}/datasource/get-authorization-url
#### Parameters #### Parameters
@ -7654,7 +7654,7 @@ Initiate OAuth login process
| Code | Description | Schema | | Code | Description | Schema |
| ---- | ----------- | ------ | | ---- | ----------- | ------ |
| 200 | Authorization URL retrieved successfully | **application/json**: [PluginOAuthAuthorizationUrlResponse](#pluginoauthauthorizationurlresponse)<br> | | 200 | Datasource OAuth authorization URL generated successfully | **application/json**: [PluginOAuthAuthorizationUrlResponse](#pluginoauthauthorizationurlresponse)<br> |
### [GET] /oauth/plugin/{provider}/tool/authorization-url ### [GET] /oauth/plugin/{provider}/tool/authorization-url
#### Parameters #### Parameters
@ -8477,9 +8477,9 @@ Initiate OAuth login process
#### Responses #### Responses
| Code | Description | Schema | | Code | Description |
| ---- | ----------- | ------ | | ---- | ----------- |
| 200 | Success | **application/json**: [DataSourceContentPreviewResponse](#datasourcecontentpreviewresponse)<br> | | 200 | Success |
### [POST] /rag/pipelines/{pipeline_id}/workflows/published/datasource/nodes/{node_id}/run ### [POST] /rag/pipelines/{pipeline_id}/workflows/published/datasource/nodes/{node_id}/run
**Run rag pipeline datasource** **Run rag pipeline datasource**
@ -16109,12 +16109,6 @@ Model class for provider custom model configuration.
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| info_list | [InfoList](#infolist) | | Yes | | info_list | [InfoList](#infolist) | | Yes |
#### DataSourceContentPreviewResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| DataSourceContentPreviewResponse | | | |
#### DataSourceIntegrateIconResponse #### DataSourceIntegrateIconResponse
| Name | Type | Description | Required | | Name | Type | Description | Required |
@ -16635,32 +16629,43 @@ Model class for provider custom model configuration.
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| credential_id | string | | Yes | | credential_id | string | | Yes |
#### DatasourceCredentialListResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| result | [ [DatasourceCredentialResponse](#datasourcecredentialresponse) ] | | Yes |
#### DatasourceCredentialPayload #### DatasourceCredentialPayload
| Name | Type | Description | Required | | Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| credentials | object | | Yes | | credentials | object | Plugin-defined credential parameters. The schema is declared by the datasource provider. | Yes |
| name | string | | No | | name | string | | No |
#### DatasourceCredentialResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| avatar_url | string | | Yes |
| credential | object | Obfuscated plugin-defined credential parameters from the datasource provider. | Yes |
| id | string | | Yes |
| is_default | boolean | | Yes |
| name | string | | Yes |
| type | string | | Yes |
#### DatasourceCredentialUpdatePayload #### DatasourceCredentialUpdatePayload
| Name | Type | Description | Required | | Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| credential_id | string | | Yes | | credential_id | string | | Yes |
| credentials | object | | No | | credentials | object | Plugin-defined credential parameters. The schema is declared by the datasource provider. | No |
| name | string | | No | | name | string | | No |
#### DatasourceCredentialsResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| result | | | Yes |
#### DatasourceCustomClientPayload #### DatasourceCustomClientPayload
| Name | Type | Description | Required | | Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| client_params | object | | No | | client_params | object | Plugin-defined OAuth client parameters. The schema is declared by the datasource provider. | No |
| enable_oauth_custom_client | boolean | | No | | enable_oauth_custom_client | boolean | | No |
#### DatasourceDefaultPayload #### DatasourceDefaultPayload
@ -16692,6 +16697,39 @@ Model class for provider custom model configuration.
| error | string | Error message from OAuth provider | No | | error | string | Error message from OAuth provider | No |
| state | string | OAuth state parameter | No | | state | string | OAuth state parameter | No |
#### DatasourceOAuthSchemaResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| client_schema | [ [ProviderConfig](#providerconfig) ] | | Yes |
| credentials_schema | [ [ProviderConfig](#providerconfig) ] | | Yes |
| is_oauth_custom_client_enabled | boolean | | Yes |
| is_system_oauth_params_exists | boolean | | Yes |
| oauth_custom_client_params | object | Masked plugin-defined OAuth client parameters, when configured for the tenant. | Yes |
| redirect_uri | string | | Yes |
#### DatasourceProviderAuthListResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| result | [ [DatasourceProviderAuthResponse](#datasourceproviderauthresponse) ] | | Yes |
#### DatasourceProviderAuthResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| author | string | | Yes |
| credential_schema | [ [ProviderConfig](#providerconfig) ] | | Yes |
| credentials_list | [ [DatasourceCredentialResponse](#datasourcecredentialresponse) ] | | Yes |
| description | [I18nObject](#i18nobject) | | Yes |
| icon | string | | Yes |
| label | [I18nObject](#i18nobject) | | Yes |
| name | string | | Yes |
| oauth_schema | [DatasourceOAuthSchemaResponse](#datasourceoauthschemaresponse) | | Yes |
| plugin_id | string | | Yes |
| plugin_unique_identifier | string | | Yes |
| provider | string | | Yes |
#### DatasourceUpdateNamePayload #### DatasourceUpdateNamePayload
| Name | Type | Description | Required | | Name | Type | Description | Required |

View File

@ -1046,12 +1046,12 @@ Execute a single datasource node within the knowledge pipeline. Returns a stream
#### Responses #### Responses
| Code | Description | Schema | | Code | Description |
| ---- | ----------- | ------ | | ---- | ----------- |
| 200 | Streaming response with node execution events. | **text/event-stream**: [GeneratedAppResponse](#generatedappresponse)<br> | | 200 | Streaming response with node execution events. |
| 401 | Unauthorized - invalid API token | | | 401 | Unauthorized - invalid API token |
| 403 | Forbidden - dataset API access or workspace access denied | | | 403 | Forbidden - dataset API access or workspace access denied |
| 404 | `not_found` : Dataset not found. | | | 404 | `not_found` : Dataset not found. |
### [POST] /datasets/{dataset_id}/pipeline/run ### [POST] /datasets/{dataset_id}/pipeline/run
**Run Pipeline** **Run Pipeline**
@ -2960,7 +2960,7 @@ Enum class for custom configuration status.
| Name | Type | Description | Required | | Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| credentials | [ [DatasourceCredentialInfoResponse](#datasourcecredentialinforesponse) ] | | Yes | | credentials | [ [DatasourceCredentialInfoResponse](#datasourcecredentialinforesponse) ] | | No |
| datasource_type | string | | No | | datasource_type | string | | No |
| node_id | string | | No | | node_id | string | | No |
| plugin_id | string | | No | | plugin_id | string | | No |

View File

@ -607,7 +607,11 @@ class TestMiscApis:
method = unwrap(api.get) method = unwrap(api.get)
service = MagicMock() service = MagicMock()
service.get_recommended_plugins.return_value = [{"id": "p1"}] recommended_plugins = {
"installed_recommended_plugins": [{"id": "p1"}],
"uninstalled_recommended_plugins": [{"id": "p2"}],
}
service.get_recommended_plugins.return_value = recommended_plugins
user = make_account() user = make_account()
tenant_id = "tenant-1" tenant_id = "tenant-1"
@ -619,7 +623,7 @@ class TestMiscApis:
), ),
): ):
result = method(api, tenant_id, user) result = method(api, tenant_id, user)
assert result == [{"id": "p1"}] assert result == recommended_plugins
service.get_recommended_plugins.assert_called_once_with("all", user, tenant_id) service.get_recommended_plugins.assert_called_once_with("all", user, tenant_id)

View File

@ -1,4 +1,5 @@
import inspect import inspect
from datetime import UTC, datetime
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import pytest import pytest
@ -23,6 +24,76 @@ from graphon.model_runtime.errors.validate import CredentialsValidateFailedError
from services.datasource_provider_service import DatasourceProviderService from services.datasource_provider_service import DatasourceProviderService
from services.plugin.oauth_service import OAuthProxyService from services.plugin.oauth_service import OAuthProxyService
_PROVIDER_ID = "langgenius/notion_datasource/notion"
def _i18n(text: str) -> dict[str, str]:
return {"en_US": text, "zh_Hans": text, "pt_BR": text, "ja_JP": text}
def _provider_config(name: str, type_: str, label: str, *, required: bool = True) -> dict:
return {
"type": type_,
"name": name,
"scope": None,
"required": required,
"default": None,
"options": None,
"multiple": False,
"label": _i18n(label),
"help": None,
"url": None,
"placeholder": None,
}
def _datasource_credential(credential_id: str = "cred-1", *, is_default: bool = True) -> dict:
return {
"credential": {
"api_key": "******",
"workspace": "engineering",
"database_id": "db-123",
},
"type": "api-key",
"name": "API Key",
"avatar_url": "https://cdn.example.com/notion.png",
"id": credential_id,
"is_default": is_default,
}
def _datasource_auth() -> dict:
return {
"author": "Dify",
"provider": "notion",
"plugin_id": "langgenius/notion_datasource",
"plugin_unique_identifier": "langgenius/notion_datasource:0.0.1",
"icon": "icon.svg",
"name": "notion",
"label": _i18n("Notion"),
"description": _i18n("Notion datasource"),
"credential_schema": [
_provider_config("api_key", "secret-input", "API key"),
],
"oauth_schema": {
"client_schema": [
_provider_config("client_id", "text-input", "Client ID"),
],
"credentials_schema": [
_provider_config("access_token", "secret-input", "Access token"),
],
"oauth_custom_client_params": {"client_id": "masked-client", "client_secret": "********"},
"is_oauth_custom_client_enabled": True,
"is_system_oauth_params_exists": True,
"redirect_uri": "https://api.example.com/oauth/callback",
},
"credentials_list": [_datasource_credential(), _datasource_credential("cred-2", is_default=False)],
}
def _success_response() -> dict[str, str]:
return {"result": "success"}
class TestDatasourcePluginOAuthAuthorizationUrl: class TestDatasourcePluginOAuthAuthorizationUrl:
def test_get_success(self, app: Flask): def test_get_success(self, app: Flask):
@ -30,28 +101,50 @@ class TestDatasourcePluginOAuthAuthorizationUrl:
method = inspect.unwrap(api.get) method = inspect.unwrap(api.get)
user = MagicMock(id="user-1") user = MagicMock(id="user-1")
oauth_client = {"client_id": "abc", "client_secret": "shh", "scopes": ["read", "write"]}
auth_url_payload = {
"authorization_url": "https://auth.example.com/oauth?client_id=abc&state=xyz",
}
with ( with (
app.test_request_context("/?credential_id=cred-1"), app.test_request_context("/?credential_id=cred-1"),
patch.object( patch.object(
DatasourceProviderService, DatasourceProviderService,
"get_oauth_client", "get_oauth_client",
return_value={"client_id": "abc"}, return_value=oauth_client,
), ) as get_oauth_client,
patch.object( patch.object(
OAuthProxyService, OAuthProxyService,
"create_proxy_context", "create_proxy_context",
return_value="ctx-1", return_value="ctx-1",
), ) as create_proxy_context,
patch.object( patch.object(
OAuthHandler, OAuthHandler,
"get_authorization_url", "get_authorization_url",
return_value={"url": "http://auth"}, return_value=auth_url_payload,
), ) as get_authorization_url,
): ):
response = method(api, "tenant-1", user, "notion") response = method(api, "tenant-1", user, _PROVIDER_ID)
assert response.status_code == 200 assert response.status_code == 200
assert response.get_json() == auth_url_payload
assert "context_id=ctx-1" in response.headers.get("Set-Cookie")
provider_id = get_oauth_client.call_args.kwargs["datasource_provider_id"]
assert str(provider_id) == _PROVIDER_ID
get_oauth_client.assert_called_once()
create_proxy_context.assert_called_once_with(
user_id="user-1",
tenant_id="tenant-1",
plugin_id="langgenius/notion_datasource",
provider="notion",
credential_id="cred-1",
)
get_authorization_url.assert_called_once()
assert get_authorization_url.call_args.kwargs["tenant_id"] == "tenant-1"
assert get_authorization_url.call_args.kwargs["user_id"] == "user-1"
assert get_authorization_url.call_args.kwargs["plugin_id"] == "langgenius/notion_datasource"
assert get_authorization_url.call_args.kwargs["provider"] == "notion"
assert get_authorization_url.call_args.kwargs["system_credentials"] == oauth_client
def test_get_no_oauth_config(self, app: Flask): def test_get_no_oauth_config(self, app: Flask):
api = DatasourcePluginOAuthAuthorizationUrl() api = DatasourcePluginOAuthAuthorizationUrl()
@ -90,10 +183,10 @@ class TestDatasourcePluginOAuthAuthorizationUrl:
patch.object( patch.object(
OAuthHandler, OAuthHandler,
"get_authorization_url", "get_authorization_url",
return_value={"url": "http://auth"}, return_value={"authorization_url": "http://auth"},
), ),
): ):
response = method(api, "tenant-1", user, "notion") response = method(api, "tenant-1", user, _PROVIDER_ID)
assert response.status_code == 200 assert response.status_code == 200
assert "context_id" in response.headers.get("Set-Cookie") assert "context_id" in response.headers.get("Set-Cookie")
@ -106,8 +199,9 @@ class TestDatasourceOAuthCallback:
oauth_response = MagicMock() oauth_response = MagicMock()
oauth_response.credentials = {"token": "abc"} oauth_response.credentials = {"token": "abc"}
oauth_response.expires_at = None expires_at = datetime(2024, 1, 2, 3, 4, 5, tzinfo=UTC)
oauth_response.metadata = {"name": "test"} oauth_response.expires_at = expires_at
oauth_response.metadata = {"name": "Workspace Bot", "avatar_url": "https://avatar.example.com/bot.png"}
context = { context = {
"user_id": "user-1", "user_id": "user-1",
@ -125,7 +219,7 @@ class TestDatasourceOAuthCallback:
patch.object( patch.object(
DatasourceProviderService, DatasourceProviderService,
"get_oauth_client", "get_oauth_client",
return_value={"client_id": "abc"}, return_value={"client_id": "abc", "client_secret": "secret"},
), ),
patch.object( patch.object(
OAuthHandler, OAuthHandler,
@ -136,11 +230,22 @@ class TestDatasourceOAuthCallback:
DatasourceProviderService, DatasourceProviderService,
"add_datasource_oauth_provider", "add_datasource_oauth_provider",
return_value=None, return_value=None,
), ) as add_oauth_provider,
): ):
response = method(api, "notion") response = method(api, _PROVIDER_ID)
assert response.status_code == 302 assert response.status_code == 302
assert "/oauth-callback" in response.location
add_oauth_provider.assert_called_once()
assert add_oauth_provider.call_args.kwargs == {
"tenant_id": "tenant-1",
"provider_id": add_oauth_provider.call_args.kwargs["provider_id"],
"avatar_url": "https://avatar.example.com/bot.png",
"name": "Workspace Bot",
"expire_at": expires_at,
"credentials": {"token": "abc"},
}
assert str(add_oauth_provider.call_args.kwargs["provider_id"]) == _PROVIDER_ID
def test_callback_missing_context(self, app: Flask): def test_callback_missing_context(self, app: Flask):
api = DatasourceOAuthCallback() api = DatasourceOAuthCallback()
@ -223,12 +328,16 @@ class TestDatasourceOAuthCallback:
DatasourceProviderService, DatasourceProviderService,
"reauthorize_datasource_oauth_provider", "reauthorize_datasource_oauth_provider",
return_value=None, return_value=None,
), ) as reauthorize_provider,
): ):
response = method(api, "notion") response = method(api, _PROVIDER_ID)
assert response.status_code == 302 assert response.status_code == 302
assert "/oauth-callback" in response.location assert "/oauth-callback" in response.location
reauthorize_provider.assert_called_once()
assert str(reauthorize_provider.call_args.kwargs["provider_id"]) == _PROVIDER_ID
assert reauthorize_provider.call_args.kwargs["credential_id"] == "cred-1"
assert reauthorize_provider.call_args.kwargs["credentials"] == {"token": "abc"}
def test_callback_context_id_from_cookie(self, app: Flask): def test_callback_context_id_from_cookie(self, app: Flask):
api = DatasourceOAuthCallback() api = DatasourceOAuthCallback()
@ -278,7 +387,14 @@ class TestDatasourceAuth:
api = DatasourceAuth() api = DatasourceAuth()
method = inspect.unwrap(api.post) method = inspect.unwrap(api.post)
payload = {"credentials": {"key": "val"}} payload = {
"name": "Engineering Notion",
"credentials": {
"api_key": "secret-token",
"workspace": "engineering",
"database_id": "db-123",
},
}
with ( with (
app.test_request_context("/", json=payload), app.test_request_context("/", json=payload),
@ -287,11 +403,17 @@ class TestDatasourceAuth:
DatasourceProviderService, DatasourceProviderService,
"add_datasource_api_key_provider", "add_datasource_api_key_provider",
return_value=None, return_value=None,
), ) as add_api_key_provider,
): ):
response, status = method(api, "tenant-1", "notion") response, status = method(api, "tenant-1", _PROVIDER_ID)
assert response == _success_response()
assert status == 200 assert status == 200
add_api_key_provider.assert_called_once()
assert add_api_key_provider.call_args.kwargs["tenant_id"] == "tenant-1"
assert str(add_api_key_provider.call_args.kwargs["provider_id"]) == _PROVIDER_ID
assert add_api_key_provider.call_args.kwargs["credentials"] == payload["credentials"]
assert add_api_key_provider.call_args.kwargs["name"] == "Engineering Notion"
def test_post_invalid_credentials(self, app: Flask): def test_post_invalid_credentials(self, app: Flask):
api = DatasourceAuth() api = DatasourceAuth()
@ -321,19 +443,19 @@ class TestDatasourceAuth:
patch.object( patch.object(
DatasourceProviderService, DatasourceProviderService,
"list_datasource_credentials", "list_datasource_credentials",
return_value=[{"id": "1"}], return_value=[_datasource_credential()],
), ),
): ):
response, status = method(api, "tenant-1", user, "notion") response, status = method(api, "tenant-1", user, _PROVIDER_ID)
assert status == 200 assert status == 200
assert response["result"] assert response == {"result": [_datasource_credential()]}
def test_post_missing_credentials(self, app: Flask): def test_post_missing_credentials(self, app: Flask):
api = DatasourceAuth() api = DatasourceAuth()
method = inspect.unwrap(api.post) method = inspect.unwrap(api.post)
payload = {} payload: dict[str, object] = {}
with ( with (
app.test_request_context("/", json=payload), app.test_request_context("/", json=payload),
@ -375,17 +497,24 @@ class TestDatasourceAuthDeleteApi:
DatasourceProviderService, DatasourceProviderService,
"remove_datasource_credentials", "remove_datasource_credentials",
return_value=None, return_value=None,
), ) as remove_datasource_credentials,
): ):
response, status = method(api, "tenant-1", "notion") response, status = method(api, "tenant-1", _PROVIDER_ID)
assert response == _success_response()
assert status == 200 assert status == 200
remove_datasource_credentials.assert_called_once_with(
tenant_id="tenant-1",
auth_id="cred-1",
provider="notion",
plugin_id="langgenius/notion_datasource",
)
def test_delete_missing_credential_id(self, app: Flask): def test_delete_missing_credential_id(self, app: Flask):
api = DatasourceAuthDeleteApi() api = DatasourceAuthDeleteApi()
method = inspect.unwrap(api.post) method = inspect.unwrap(api.post)
payload = {} payload: dict[str, object] = {}
with ( with (
app.test_request_context("/", json=payload), app.test_request_context("/", json=payload),
@ -400,7 +529,11 @@ class TestDatasourceAuthUpdateApi:
api = DatasourceAuthUpdateApi() api = DatasourceAuthUpdateApi()
method = inspect.unwrap(api.post) method = inspect.unwrap(api.post)
payload = {"credential_id": "id", "credentials": {"k": "v"}} payload = {
"credential_id": "cred-1",
"name": "Updated Notion",
"credentials": {"api_key": "new-secret", "database_id": "db-456"},
}
with ( with (
app.test_request_context("/", json=payload), app.test_request_context("/", json=payload),
@ -409,11 +542,20 @@ class TestDatasourceAuthUpdateApi:
DatasourceProviderService, DatasourceProviderService,
"update_datasource_credentials", "update_datasource_credentials",
return_value=None, return_value=None,
), ) as update_datasource_credentials,
): ):
response, status = method(api, "tenant-1", "notion") response, status = method(api, "tenant-1", _PROVIDER_ID)
assert response == _success_response()
assert status == 201 assert status == 201
update_datasource_credentials.assert_called_once_with(
tenant_id="tenant-1",
auth_id="cred-1",
provider="notion",
plugin_id="langgenius/notion_datasource",
credentials=payload["credentials"],
name="Updated Notion",
)
def test_update_with_credentials_none(self, app: Flask): def test_update_with_credentials_none(self, app: Flask):
api = DatasourceAuthUpdateApi() api = DatasourceAuthUpdateApi()
@ -432,7 +574,9 @@ class TestDatasourceAuthUpdateApi:
): ):
response, status = method(api, "tenant-1", "notion") response, status = method(api, "tenant-1", "notion")
assert response == _success_response()
update_mock.assert_called_once() update_mock.assert_called_once()
assert update_mock.call_args.kwargs["credentials"] == {}
assert status == 201 assert status == 201
def test_update_name_only(self, app: Flask): def test_update_name_only(self, app: Flask):
@ -450,8 +594,9 @@ class TestDatasourceAuthUpdateApi:
return_value=None, return_value=None,
), ),
): ):
_, status = method(api, "tenant-1", "notion") response, status = method(api, "tenant-1", "notion")
assert response == _success_response()
assert status == 201 assert status == 201
def test_update_with_empty_credentials_dict(self, app: Flask): def test_update_with_empty_credentials_dict(self, app: Flask):
@ -469,8 +614,9 @@ class TestDatasourceAuthUpdateApi:
return_value=None, return_value=None,
) as update_mock, ) as update_mock,
): ):
_, status = method(api, "tenant-1", "notion") response, status = method(api, "tenant-1", "notion")
assert response == _success_response()
update_mock.assert_called_once() update_mock.assert_called_once()
assert status == 201 assert status == 201
@ -485,12 +631,13 @@ class TestDatasourceAuthListApi:
patch.object( patch.object(
DatasourceProviderService, DatasourceProviderService,
"get_all_datasource_credentials", "get_all_datasource_credentials",
return_value=[{"id": "1"}], return_value=[_datasource_auth()],
), ),
): ):
response, status = method(api, "tenant-1") response, status = method(api, "tenant-1")
assert status == 200 assert status == 200
assert response == {"result": [_datasource_auth()]}
def test_auth_list_empty(self, app: Flask): def test_auth_list_empty(self, app: Flask):
api = DatasourceAuthListApi() api = DatasourceAuthListApi()
@ -537,7 +684,7 @@ class TestDatasourceHardCodeAuthListApi:
patch.object( patch.object(
DatasourceProviderService, DatasourceProviderService,
"get_hard_code_datasource_credentials", "get_hard_code_datasource_credentials",
return_value=[{"id": "1"}], return_value=[_datasource_auth()],
), ),
): ):
response, status = method(api, "tenant-1") response, status = method(api, "tenant-1")
@ -550,7 +697,14 @@ class TestDatasourceAuthOauthCustomClient:
api = DatasourceAuthOauthCustomClient() api = DatasourceAuthOauthCustomClient()
method = inspect.unwrap(api.post) method = inspect.unwrap(api.post)
payload = {"client_params": {}, "enable_oauth_custom_client": True} payload = {
"client_params": {
"client_id": "custom-client",
"client_secret": "custom-secret",
"authorize_url": "https://auth.example.com/authorize",
},
"enable_oauth_custom_client": True,
}
with ( with (
app.test_request_context("/", json=payload), app.test_request_context("/", json=payload),
@ -559,11 +713,17 @@ class TestDatasourceAuthOauthCustomClient:
DatasourceProviderService, DatasourceProviderService,
"setup_oauth_custom_client_params", "setup_oauth_custom_client_params",
return_value=None, return_value=None,
), ) as setup_custom_client,
): ):
response, status = method(api, "tenant-1", "notion") response, status = method(api, "tenant-1", _PROVIDER_ID)
assert response == _success_response()
assert status == 200 assert status == 200
setup_custom_client.assert_called_once()
assert setup_custom_client.call_args.kwargs["tenant_id"] == "tenant-1"
assert str(setup_custom_client.call_args.kwargs["datasource_provider_id"]) == _PROVIDER_ID
assert setup_custom_client.call_args.kwargs["client_params"] == payload["client_params"]
assert setup_custom_client.call_args.kwargs["enabled"] is True
def test_delete_success(self, app: Flask): def test_delete_success(self, app: Flask):
api = DatasourceAuthOauthCustomClient() api = DatasourceAuthOauthCustomClient()
@ -575,17 +735,20 @@ class TestDatasourceAuthOauthCustomClient:
DatasourceProviderService, DatasourceProviderService,
"remove_oauth_custom_client_params", "remove_oauth_custom_client_params",
return_value=None, return_value=None,
), ) as remove_custom_client,
): ):
response, status = method(api, "tenant-1", "notion") response, status = method(api, "tenant-1", _PROVIDER_ID)
assert response == _success_response()
assert status == 200 assert status == 200
remove_custom_client.assert_called_once()
assert str(remove_custom_client.call_args.kwargs["datasource_provider_id"]) == _PROVIDER_ID
def test_post_empty_payload(self, app: Flask): def test_post_empty_payload(self, app: Flask):
api = DatasourceAuthOauthCustomClient() api = DatasourceAuthOauthCustomClient()
method = inspect.unwrap(api.post) method = inspect.unwrap(api.post)
payload = {} payload: dict[str, object] = {}
with ( with (
app.test_request_context("/", json=payload), app.test_request_context("/", json=payload),
@ -596,8 +759,9 @@ class TestDatasourceAuthOauthCustomClient:
return_value=None, return_value=None,
), ),
): ):
_, status = method(api, "tenant-1", "notion") response, status = method(api, "tenant-1", "notion")
assert response == _success_response()
assert status == 200 assert status == 200
def test_post_disabled_flag(self, app: Flask): def test_post_disabled_flag(self, app: Flask):
@ -618,9 +782,12 @@ class TestDatasourceAuthOauthCustomClient:
return_value=None, return_value=None,
) as setup_mock, ) as setup_mock,
): ):
_, status = method(api, "tenant-1", "notion") response, status = method(api, "tenant-1", "notion")
assert response == _success_response()
setup_mock.assert_called_once() setup_mock.assert_called_once()
assert setup_mock.call_args.kwargs["client_params"] == {"a": 1}
assert setup_mock.call_args.kwargs["enabled"] is False
assert status == 200 assert status == 200
@ -638,17 +805,22 @@ class TestDatasourceAuthDefaultApi:
DatasourceProviderService, DatasourceProviderService,
"set_default_datasource_provider", "set_default_datasource_provider",
return_value=None, return_value=None,
), ) as set_default_datasource_provider,
): ):
response, status = method(api, "tenant-1", "notion") response, status = method(api, "tenant-1", _PROVIDER_ID)
assert response == _success_response()
assert status == 200 assert status == 200
set_default_datasource_provider.assert_called_once()
assert set_default_datasource_provider.call_args.kwargs["tenant_id"] == "tenant-1"
assert str(set_default_datasource_provider.call_args.kwargs["datasource_provider_id"]) == _PROVIDER_ID
assert set_default_datasource_provider.call_args.kwargs["credential_id"] == "cred-1"
def test_default_missing_id(self, app: Flask): def test_default_missing_id(self, app: Flask):
api = DatasourceAuthDefaultApi() api = DatasourceAuthDefaultApi()
method = inspect.unwrap(api.post) method = inspect.unwrap(api.post)
payload = {} payload: dict[str, object] = {}
with ( with (
app.test_request_context("/", json=payload), app.test_request_context("/", json=payload),
@ -663,7 +835,7 @@ class TestDatasourceUpdateProviderNameApi:
api = DatasourceUpdateProviderNameApi() api = DatasourceUpdateProviderNameApi()
method = inspect.unwrap(api.post) method = inspect.unwrap(api.post)
payload = {"credential_id": "id", "name": "New Name"} payload = {"credential_id": "cred-1", "name": "New Name"}
with ( with (
app.test_request_context("/", json=payload), app.test_request_context("/", json=payload),
@ -672,11 +844,17 @@ class TestDatasourceUpdateProviderNameApi:
DatasourceProviderService, DatasourceProviderService,
"update_datasource_provider_name", "update_datasource_provider_name",
return_value=None, return_value=None,
), ) as update_datasource_provider_name,
): ):
response, status = method(api, "tenant-1", "notion") response, status = method(api, "tenant-1", _PROVIDER_ID)
assert response == _success_response()
assert status == 200 assert status == 200
update_datasource_provider_name.assert_called_once()
assert update_datasource_provider_name.call_args.kwargs["tenant_id"] == "tenant-1"
assert str(update_datasource_provider_name.call_args.kwargs["datasource_provider_id"]) == _PROVIDER_ID
assert update_datasource_provider_name.call_args.kwargs["name"] == "New Name"
assert update_datasource_provider_name.call_args.kwargs["credential_id"] == "cred-1"
def test_update_name_too_long(self, app: Flask): def test_update_name_too_long(self, app: Flask):
api = DatasourceUpdateProviderNameApi() api = DatasourceUpdateProviderNameApi()

View File

@ -158,3 +158,65 @@ def test_rag_pipeline_workflow_patch_serializes_response_model(app: Flask, monke
assert response["id"] == "workflow-1" assert response["id"] == "workflow-1"
assert response["marked_name"] == "Updated release" assert response["marked_name"] == "Updated release"
assert response["hash"] == "hash-1" assert response["hash"] == "hash-1"
def test_default_rag_pipeline_block_configs_serializes_root_response(monkeypatch: pytest.MonkeyPatch) -> None:
block_configs = [{"type": "start", "config": {"title": "Start"}}]
monkeypatch.setattr(
module,
"RagPipelineService",
lambda: SimpleNamespace(get_default_block_configs=lambda: block_configs),
)
api = module.DefaultRagPipelineBlockConfigsApi()
handler = unwrap_all(api.get)
response = handler(api, _pipeline())
assert response == block_configs
def test_draft_rag_pipeline_second_step_parameters_serializes_variables(app, monkeypatch: pytest.MonkeyPatch) -> None:
variables = [
{
"belong_to_node_id": "shared",
"type": "number",
"label": "Chunk size",
"variable": "chunk_size",
"default_value": 1024,
"required": True,
}
]
monkeypatch.setattr(
module,
"RagPipelineService",
lambda: SimpleNamespace(get_second_step_parameters=lambda **_kwargs: variables),
)
api = module.DraftRagPipelineSecondStepApi()
handler = unwrap_all(api.get)
with app.test_request_context("/?node_id=node-1"):
response = handler(api, _pipeline())
assert response["variables"] == variables
def test_rag_pipeline_recommended_plugins_serializes_known_envelope(app, monkeypatch: pytest.MonkeyPatch) -> None:
recommended_plugins = {
"installed_recommended_plugins": [{"name": "Dify Extractor", "meta": {"version": "1.0.0"}}],
"uninstalled_recommended_plugins": [{"plugin_id": "langgenius/notion_datasource"}],
}
monkeypatch.setattr(
module,
"RagPipelineService",
lambda: SimpleNamespace(get_recommended_plugins=lambda *_args: recommended_plugins),
)
api = module.RagPipelineRecommendedPluginApi()
handler = unwrap_all(api.get)
with app.test_request_context("/?type=tool"):
response = handler(api, "tenant-1", _account())
assert response == recommended_plugins

View File

@ -325,10 +325,12 @@ class TestPipelineRunApiEntity:
def test_entity_missing_required_field(self): def test_entity_missing_required_field(self):
"""Test entity raises on missing required field.""" """Test entity raises on missing required field."""
with pytest.raises(ValueError): with pytest.raises(ValueError):
PipelineRunApiEntity( PipelineRunApiEntity.model_validate(
inputs={}, {
datasource_type="online_document", "inputs": {},
# missing datasource_info_list, start_node_id, etc. "datasource_type": "online_document",
# missing datasource_info_list, start_node_id, etc.
}
) )
@ -382,8 +384,19 @@ class TestDatasourcePluginsApiGet:
mock_dataset = Mock() mock_dataset = Mock()
mock_db.session.scalar.return_value = mock_dataset mock_db.session.scalar.return_value = mock_dataset
datasource_plugins = [
{
"node_id": "node-datasource-1",
"plugin_id": "plugin-a",
"provider_name": "provider-a",
"datasource_type": "online_document",
"title": "Online Docs",
"user_input_variables": [{"variable": "url", "label": "URL", "type": "text-input", "required": True}],
"credentials": [{"id": "cred-1", "name": "Default credential", "type": "oauth2", "is_default": True}],
}
]
mock_svc_instance = Mock() mock_svc_instance = Mock()
mock_svc_instance.get_datasource_plugins.return_value = [{"name": "plugin_a"}] mock_svc_instance.get_datasource_plugins.return_value = datasource_plugins
mock_svc_cls.return_value = mock_svc_instance mock_svc_cls.return_value = mock_svc_instance
with app.test_request_context("/datasets/test/pipeline/datasource-plugins?is_published=true"): with app.test_request_context("/datasets/test/pipeline/datasource-plugins?is_published=true"):
@ -391,11 +404,33 @@ class TestDatasourcePluginsApiGet:
response, status = api.get(tenant_id=tenant_id, dataset_id=dataset_id) response, status = api.get(tenant_id=tenant_id, dataset_id=dataset_id)
assert status == 200 assert status == 200
assert response == [{"name": "plugin_a"}] assert response == datasource_plugins
mock_svc_instance.get_datasource_plugins.assert_called_once_with( mock_svc_instance.get_datasource_plugins.assert_called_once_with(
tenant_id=tenant_id, dataset_id=dataset_id, is_published=True tenant_id=tenant_id, dataset_id=dataset_id, is_published=True
) )
@patch("controllers.service_api.dataset.rag_pipeline.rag_pipeline_workflow.db")
@patch("controllers.service_api.dataset.rag_pipeline.rag_pipeline_workflow.RagPipelineService")
def test_get_plugins_parses_false_is_published_query(self, mock_svc_cls, mock_db, app: Flask):
"""Test false query string is parsed as boolean False."""
tenant_id = str(uuid.uuid4())
dataset_id = str(uuid.uuid4())
mock_db.session.scalar.return_value = Mock()
mock_svc_instance = Mock()
mock_svc_instance.get_datasource_plugins.return_value = []
mock_svc_cls.return_value = mock_svc_instance
with app.test_request_context("/datasets/test/pipeline/datasource-plugins?is_published=false"):
api = DatasourcePluginsApi()
response, status = api.get(tenant_id=tenant_id, dataset_id=dataset_id)
assert status == 200
assert response == []
mock_svc_instance.get_datasource_plugins.assert_called_once_with(
tenant_id=tenant_id, dataset_id=dataset_id, is_published=False
)
@patch("controllers.service_api.dataset.rag_pipeline.rag_pipeline_workflow.db") @patch("controllers.service_api.dataset.rag_pipeline.rag_pipeline_workflow.db")
def test_get_plugins_not_found(self, mock_db, app: Flask): def test_get_plugins_not_found(self, mock_db, app: Flask):
"""Test NotFound when dataset check fails.""" """Test NotFound when dataset check fails."""

View File

@ -2,9 +2,10 @@
Unit tests for Service API knowledge pipeline file-upload serialization. Unit tests for Service API knowledge pipeline file-upload serialization.
""" """
import importlib.util
from datetime import UTC, datetime from datetime import UTC, datetime
from pathlib import Path
from controllers.service_api.dataset.rag_pipeline.rag_pipeline_workflow import PipelineUploadFileResponse
from libs.helper import dump_response
class FakeUploadFile: class FakeUploadFile:
@ -17,21 +18,7 @@ class FakeUploadFile:
created_at: datetime | None created_at: datetime | None
def _load_serialize_upload_file():
api_dir = Path(__file__).resolve().parents[5]
serializers_path = api_dir / "controllers" / "service_api" / "dataset" / "rag_pipeline" / "serializers.py"
spec = importlib.util.spec_from_file_location("rag_pipeline_serializers", serializers_path)
assert spec
assert spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) # type: ignore[attr-defined]
return module.serialize_upload_file
def test_file_upload_created_at_is_isoformat_string(): def test_file_upload_created_at_is_isoformat_string():
serialize_upload_file = _load_serialize_upload_file()
created_at = datetime(2026, 2, 8, 12, 0, 0, tzinfo=UTC) created_at = datetime(2026, 2, 8, 12, 0, 0, tzinfo=UTC)
upload_file = FakeUploadFile() upload_file = FakeUploadFile()
upload_file.id = "file-1" upload_file.id = "file-1"
@ -42,13 +29,11 @@ def test_file_upload_created_at_is_isoformat_string():
upload_file.created_by = "account-1" upload_file.created_by = "account-1"
upload_file.created_at = created_at upload_file.created_at = created_at
result = serialize_upload_file(upload_file) result = dump_response(PipelineUploadFileResponse, upload_file)
assert result["created_at"] == created_at.isoformat() assert result["created_at"] == created_at.isoformat()
def test_file_upload_created_at_none_serializes_to_null(): def test_file_upload_created_at_none_serializes_to_null():
serialize_upload_file = _load_serialize_upload_file()
upload_file = FakeUploadFile() upload_file = FakeUploadFile()
upload_file.id = "file-1" upload_file.id = "file-1"
upload_file.name = "test.pdf" upload_file.name = "test.pdf"
@ -58,5 +43,5 @@ def test_file_upload_created_at_none_serializes_to_null():
upload_file.created_by = "account-1" upload_file.created_by = "account-1"
upload_file.created_at = None upload_file.created_at = None
result = serialize_upload_file(upload_file) result = dump_response(PipelineUploadFileResponse, upload_file)
assert result["created_at"] is None assert result["created_at"] is None

View File

@ -4,8 +4,12 @@ export type ClientOptions = {
baseUrl: `${string}://${string}/console/api` | (string & {}) baseUrl: `${string}://${string}/console/api` | (string & {})
} }
export type DatasourceCredentialsResponse = { export type DatasourceProviderAuthListResponse = {
result: unknown result: Array<DatasourceProviderAuthResponse>
}
export type DatasourceCredentialListResponse = {
result: Array<DatasourceCredentialResponse>
} }
export type DatasourceCredentialPayload = { export type DatasourceCredentialPayload = {
@ -47,6 +51,90 @@ export type DatasourceUpdateNamePayload = {
name: string name: string
} }
export type DatasourceProviderAuthResponse = {
author: string
credential_schema: Array<ProviderConfig>
credentials_list: Array<DatasourceCredentialResponse>
description: I18nObject
icon: string
label: I18nObject
name: string
oauth_schema: DatasourceOAuthSchemaResponse | null
plugin_id: string
plugin_unique_identifier: string
provider: string
}
export type DatasourceCredentialResponse = {
avatar_url: string | null
credential: {
[key: string]: unknown
}
id: string
is_default: boolean
name: string
type: string
}
export type ProviderConfig = {
default?: number | string | number | boolean | null
help?: I18nObject | null
label?: I18nObject | null
multiple?: boolean
name: string
options?: Array<Option> | null
placeholder?: I18nObject | null
required?: boolean
scope?: AppSelectorScope | ModelSelectorScope | ToolSelectorScope | null
type: CoreEntitiesProviderEntitiesBasicProviderConfigType
url?: string | null
}
export type I18nObject = {
en_US: string
ja_JP?: string | null
pt_BR?: string | null
zh_Hans?: string | null
}
export type DatasourceOAuthSchemaResponse = {
client_schema: Array<ProviderConfig>
credentials_schema: Array<ProviderConfig>
is_oauth_custom_client_enabled: boolean
is_system_oauth_params_exists: boolean
oauth_custom_client_params: {
[key: string]: unknown
} | null
redirect_uri: string
}
export type Option = {
label: I18nObject
value: string
}
export type AppSelectorScope = 'all' | 'chat' | 'completion' | 'workflow'
export type ModelSelectorScope
= | 'llm'
| 'moderation'
| 'rerank'
| 'speech2text'
| 'text-embedding'
| 'tts'
| 'vision'
export type ToolSelectorScope = 'all' | 'builtin' | 'custom' | 'workflow'
export type CoreEntitiesProviderEntitiesBasicProviderConfigType
= | 'app-selector'
| 'array[tools]'
| 'boolean'
| 'model-selector'
| 'secret-input'
| 'select'
| 'text-input'
export type GetAuthPluginDatasourceDefaultListData = { export type GetAuthPluginDatasourceDefaultListData = {
body?: never body?: never
path?: never path?: never
@ -55,7 +143,7 @@ export type GetAuthPluginDatasourceDefaultListData = {
} }
export type GetAuthPluginDatasourceDefaultListResponses = { export type GetAuthPluginDatasourceDefaultListResponses = {
200: DatasourceCredentialsResponse 200: DatasourceProviderAuthListResponse
} }
export type GetAuthPluginDatasourceDefaultListResponse export type GetAuthPluginDatasourceDefaultListResponse
@ -69,7 +157,7 @@ export type GetAuthPluginDatasourceListData = {
} }
export type GetAuthPluginDatasourceListResponses = { export type GetAuthPluginDatasourceListResponses = {
200: DatasourceCredentialsResponse 200: DatasourceProviderAuthListResponse
} }
export type GetAuthPluginDatasourceListResponse export type GetAuthPluginDatasourceListResponse
@ -85,7 +173,7 @@ export type GetAuthPluginDatasourceByProviderIdData = {
} }
export type GetAuthPluginDatasourceByProviderIdResponses = { export type GetAuthPluginDatasourceByProviderIdResponses = {
200: DatasourceCredentialsResponse 200: DatasourceCredentialListResponse
} }
export type GetAuthPluginDatasourceByProviderIdResponse export type GetAuthPluginDatasourceByProviderIdResponse

View File

@ -2,13 +2,6 @@
import * as z from 'zod' import * as z from 'zod'
/**
* DatasourceCredentialsResponse
*/
export const zDatasourceCredentialsResponse = z.object({
result: z.unknown(),
})
/** /**
* DatasourceCredentialPayload * DatasourceCredentialPayload
*/ */
@ -64,23 +57,153 @@ export const zDatasourceUpdateNamePayload = z.object({
}) })
/** /**
* Success * DatasourceCredentialResponse
*/ */
export const zGetAuthPluginDatasourceDefaultListResponse = zDatasourceCredentialsResponse export const zDatasourceCredentialResponse = z.object({
avatar_url: z.string().nullable(),
credential: z.record(z.string(), z.unknown()),
id: z.string(),
is_default: z.boolean(),
name: z.string(),
type: z.string(),
})
/** /**
* Success * DatasourceCredentialListResponse
*/ */
export const zGetAuthPluginDatasourceListResponse = zDatasourceCredentialsResponse export const zDatasourceCredentialListResponse = z.object({
result: z.array(zDatasourceCredentialResponse),
})
/**
* I18nObject
*
* Model class for i18n object.
*/
export const zI18nObject = z.object({
en_US: z.string(),
ja_JP: z.string().nullish(),
pt_BR: z.string().nullish(),
zh_Hans: z.string().nullish(),
})
/**
* Option
*/
export const zOption = z.object({
label: zI18nObject,
value: z.string(),
})
/**
* AppSelectorScope
*/
export const zAppSelectorScope = z.enum(['all', 'chat', 'completion', 'workflow'])
/**
* ModelSelectorScope
*/
export const zModelSelectorScope = z.enum([
'llm',
'moderation',
'rerank',
'speech2text',
'text-embedding',
'tts',
'vision',
])
/**
* ToolSelectorScope
*/
export const zToolSelectorScope = z.enum(['all', 'builtin', 'custom', 'workflow'])
/**
* Type
*/
export const zCoreEntitiesProviderEntitiesBasicProviderConfigType = z.enum([
'app-selector',
'array[tools]',
'boolean',
'model-selector',
'secret-input',
'select',
'text-input',
])
/**
* ProviderConfig
*
* Model class for common provider settings like credentials
*/
export const zProviderConfig = z.object({
default: z.union([z.int(), z.string(), z.number(), z.boolean()]).nullish(),
help: zI18nObject.nullish(),
label: zI18nObject.nullish(),
multiple: z.boolean().optional().default(false),
name: z.string(),
options: z.array(zOption).nullish(),
placeholder: zI18nObject.nullish(),
required: z.boolean().optional().default(false),
scope: z.union([zAppSelectorScope, zModelSelectorScope, zToolSelectorScope]).nullish(),
type: zCoreEntitiesProviderEntitiesBasicProviderConfigType,
url: z.string().nullish(),
})
/**
* DatasourceOAuthSchemaResponse
*/
export const zDatasourceOAuthSchemaResponse = z.object({
client_schema: z.array(zProviderConfig),
credentials_schema: z.array(zProviderConfig),
is_oauth_custom_client_enabled: z.boolean(),
is_system_oauth_params_exists: z.boolean(),
oauth_custom_client_params: z.record(z.string(), z.unknown()).nullable(),
redirect_uri: z.string(),
})
/**
* DatasourceProviderAuthResponse
*/
export const zDatasourceProviderAuthResponse = z.object({
author: z.string(),
credential_schema: z.array(zProviderConfig),
credentials_list: z.array(zDatasourceCredentialResponse),
description: zI18nObject,
icon: z.string(),
label: zI18nObject,
name: z.string(),
oauth_schema: zDatasourceOAuthSchemaResponse.nullable(),
plugin_id: z.string(),
plugin_unique_identifier: z.string(),
provider: z.string(),
})
/**
* DatasourceProviderAuthListResponse
*/
export const zDatasourceProviderAuthListResponse = z.object({
result: z.array(zDatasourceProviderAuthResponse),
})
/**
* Default datasource credentials retrieved successfully
*/
export const zGetAuthPluginDatasourceDefaultListResponse = zDatasourceProviderAuthListResponse
/**
* Datasource credentials retrieved successfully
*/
export const zGetAuthPluginDatasourceListResponse = zDatasourceProviderAuthListResponse
export const zGetAuthPluginDatasourceByProviderIdPath = z.object({ export const zGetAuthPluginDatasourceByProviderIdPath = z.object({
provider_id: z.string(), provider_id: z.string(),
}) })
/** /**
* Success * Datasource credentials retrieved successfully
*/ */
export const zGetAuthPluginDatasourceByProviderIdResponse = zDatasourceCredentialsResponse export const zGetAuthPluginDatasourceByProviderIdResponse = zDatasourceCredentialListResponse
export const zPostAuthPluginDatasourceByProviderIdBody = zDatasourceCredentialPayload export const zPostAuthPluginDatasourceByProviderIdBody = zDatasourceCredentialPayload
@ -89,7 +212,7 @@ export const zPostAuthPluginDatasourceByProviderIdPath = z.object({
}) })
/** /**
* Success * Datasource credential created successfully
*/ */
export const zPostAuthPluginDatasourceByProviderIdResponse = zSimpleResultResponse export const zPostAuthPluginDatasourceByProviderIdResponse = zSimpleResultResponse
@ -109,7 +232,7 @@ export const zPostAuthPluginDatasourceByProviderIdCustomClientPath = z.object({
}) })
/** /**
* Success * Datasource OAuth custom client saved successfully
*/ */
export const zPostAuthPluginDatasourceByProviderIdCustomClientResponse = zSimpleResultResponse export const zPostAuthPluginDatasourceByProviderIdCustomClientResponse = zSimpleResultResponse
@ -142,7 +265,7 @@ export const zPostAuthPluginDatasourceByProviderIdUpdatePath = z.object({
}) })
/** /**
* Success * Datasource credential updated successfully
*/ */
export const zPostAuthPluginDatasourceByProviderIdUpdateResponse = zSimpleResultResponse export const zPostAuthPluginDatasourceByProviderIdUpdateResponse = zSimpleResultResponse

View File

@ -135,7 +135,7 @@ export const zGetOauthPluginByProviderIdDatasourceGetAuthorizationUrlQuery = z.o
}) })
/** /**
* Authorization URL retrieved successfully * Datasource OAuth authorization URL generated successfully
*/ */
export const zGetOauthPluginByProviderIdDatasourceGetAuthorizationUrlResponse export const zGetOauthPluginByProviderIdDatasourceGetAuthorizationUrlResponse
= zPluginOAuthAuthorizationUrlResponse = zPluginOAuthAuthorizationUrlResponse

View File

@ -327,8 +327,6 @@ export type Parser = {
} }
} }
export type DataSourceContentPreviewResponse = unknown
export type PublishedWorkflowRunPayload = { export type PublishedWorkflowRunPayload = {
datasource_info_list: Array<{ datasource_info_list: Array<{
[key: string]: unknown [key: string]: unknown
@ -1329,7 +1327,9 @@ export type PostRagPipelinesByPipelineIdWorkflowsPublishedDatasourceNodesByNodeI
export type PostRagPipelinesByPipelineIdWorkflowsPublishedDatasourceNodesByNodeIdPreviewResponses export type PostRagPipelinesByPipelineIdWorkflowsPublishedDatasourceNodesByNodeIdPreviewResponses
= { = {
200: DataSourceContentPreviewResponse 200: {
[key: string]: unknown
}
} }
export type PostRagPipelinesByPipelineIdWorkflowsPublishedDatasourceNodesByNodeIdPreviewResponse export type PostRagPipelinesByPipelineIdWorkflowsPublishedDatasourceNodesByNodeIdPreviewResponse

View File

@ -199,11 +199,6 @@ export const zParser = z.object({
inputs: z.record(z.string(), z.unknown()), inputs: z.record(z.string(), z.unknown()),
}) })
/**
* DataSourceContentPreviewResponse
*/
export const zDataSourceContentPreviewResponse = z.unknown()
/** /**
* PublishedWorkflowRunPayload * PublishedWorkflowRunPayload
*/ */
@ -1179,7 +1174,7 @@ export const zPostRagPipelinesByPipelineIdWorkflowsPublishedDatasourceNodesByNod
* Success * Success
*/ */
export const zPostRagPipelinesByPipelineIdWorkflowsPublishedDatasourceNodesByNodeIdPreviewResponse export const zPostRagPipelinesByPipelineIdWorkflowsPublishedDatasourceNodesByNodeIdPreviewResponse
= zDataSourceContentPreviewResponse = z.record(z.string(), z.unknown())
export const zPostRagPipelinesByPipelineIdWorkflowsPublishedDatasourceNodesByNodeIdRunBody export const zPostRagPipelinesByPipelineIdWorkflowsPublishedDatasourceNodesByNodeIdRunBody
= zDatasourceNodeRunPayload = zDatasourceNodeRunPayload

View File

@ -567,7 +567,7 @@ export type DatasourceNodeRunPayload = {
export type DatasourcePluginListResponse = Array<DatasourcePluginResponse> export type DatasourcePluginListResponse = Array<DatasourcePluginResponse>
export type DatasourcePluginResponse = { export type DatasourcePluginResponse = {
credentials: Array<DatasourceCredentialInfoResponse> credentials?: Array<DatasourceCredentialInfoResponse>
datasource_type?: string | null datasource_type?: string | null
node_id?: string | null node_id?: string | null
plugin_id?: string | null plugin_id?: string | null
@ -3196,7 +3196,9 @@ export type PostDatasetsByDatasetIdPipelineDatasourceNodesByNodeIdRunErrors = {
} }
export type PostDatasetsByDatasetIdPipelineDatasourceNodesByNodeIdRunResponses = { export type PostDatasetsByDatasetIdPipelineDatasourceNodesByNodeIdRunResponses = {
200: GeneratedAppResponse 200: {
[key: string]: unknown
}
} }
export type PostDatasetsByDatasetIdPipelineDatasourceNodesByNodeIdRunResponse export type PostDatasetsByDatasetIdPipelineDatasourceNodesByNodeIdRunResponse

View File

@ -698,7 +698,7 @@ export const zDatasourceNodeRunPayload = z.object({
* DatasourcePluginResponse * DatasourcePluginResponse
*/ */
export const zDatasourcePluginResponse = z.object({ export const zDatasourcePluginResponse = z.object({
credentials: z.array(zDatasourceCredentialInfoResponse), credentials: z.array(zDatasourceCredentialInfoResponse).optional(),
datasource_type: z.string().nullish(), datasource_type: z.string().nullish(),
node_id: z.string().nullish(), node_id: z.string().nullish(),
plugin_id: z.string().nullish(), plugin_id: z.string().nullish(),
@ -3063,8 +3063,10 @@ export const zPostDatasetsByDatasetIdPipelineDatasourceNodesByNodeIdRunPath = z.
/** /**
* Streaming response with node execution events. * Streaming response with node execution events.
*/ */
export const zPostDatasetsByDatasetIdPipelineDatasourceNodesByNodeIdRunResponse export const zPostDatasetsByDatasetIdPipelineDatasourceNodesByNodeIdRunResponse = z.record(
= zGeneratedAppResponse z.string(),
z.unknown(),
)
export const zPostDatasetsByDatasetIdPipelineRunBody = zPipelineRunApiEntity export const zPostDatasetsByDatasetIdPipelineRunBody = zPipelineRunApiEntity