From 8540ca92429f93c48bdae0822aa30be0591115d4 Mon Sep 17 00:00:00 2001 From: Stephen Zhou Date: Thu, 2 Jul 2026 00:31:39 +0800 Subject: [PATCH] refactor(web): migrate snippet console contracts (#38258) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- api/controllers/console/workspace/snippets.py | 122 +++++- api/openapi/markdown/console-openapi.md | 150 ++++--- .../console/workspace/test_snippets.py | 87 +++- eslint-suppressions.json | 5 - .../api/console/workspaces/types.gen.ts | 142 ++++--- .../api/console/workspaces/zod.gen.ts | 226 ++++++----- .../snippet-info/__tests__/dropdown.spec.tsx | 2 + .../app-sidebar/snippet-info/dropdown.tsx | 4 +- .../__tests__/snippet-card.spec.tsx | 3 +- .../__tests__/snippet-create-button.spec.tsx | 10 +- .../snippet-list/components/snippet-card.tsx | 10 +- .../__tests__/snippet-sidebar.spec.tsx | 2 + .../components/hooks/use-snippet-publish.ts | 5 +- .../__tests__/use-create-snippet.spec.tsx | 10 +- .../__tests__/use-nodes-sync-draft.spec.ts | 18 +- .../snippets/hooks/use-create-snippet.ts | 6 +- .../snippets/hooks/use-nodes-sync-draft.ts | 4 +- .../__tests__/snippet-detail-card.spec.tsx | 1 + .../__tests__/snippet-list-item.spec.tsx | 1 + .../snippets/use-insert-snippet.ts | 4 +- web/contract/console/snippets.ts | 377 +----------------- web/contract/router.ts | 2 + web/models/snippet.ts | 4 +- web/service/use-snippet-workflows.ts | 49 ++- web/service/use-snippets.ts | 116 ++++-- web/types/snippet.ts | 109 ++--- 26 files changed, 674 insertions(+), 795 deletions(-) diff --git a/api/controllers/console/workspace/snippets.py b/api/controllers/console/workspace/snippets.py index e0987a3cae1..137bae66358 100644 --- a/api/controllers/console/workspace/snippets.py +++ b/api/controllers/console/workspace/snippets.py @@ -1,11 +1,13 @@ import logging import re +from datetime import datetime from typing import Any from urllib.parse import quote from flask import Response, request from flask_restx import Resource, marshal -from pydantic import RootModel +from pydantic import Field as PydanticField +from pydantic import field_validator from sqlalchemy.orm import Session, sessionmaker from werkzeug.datastructures import MultiDict from werkzeug.exceptions import NotFound @@ -30,27 +32,34 @@ from controllers.console.wraps import ( with_current_tenant_id, with_current_user, ) +from core.plugin.entities.plugin import PluginDependency from extensions.ext_database import db from fields.base import ResponseModel -from fields.snippet_fields import snippet_fields, snippet_list_fields, snippet_pagination_fields +from fields.snippet_fields import snippet_fields, snippet_list_fields +from libs.helper import to_timestamp from libs.login import login_required from models import Account from models.snippet import SnippetType -from services.app_dsl_service import ImportStatus -from services.snippet_dsl_service import SnippetDslService +from services.snippet_dsl_service import ImportStatus, SnippetDslService from services.snippet_service import SnippetService logger = logging.getLogger(__name__) _TAG_IDS_BRACKET_PATTERN = re.compile(r"^tag_ids\[(\d+)\]$") _CREATOR_IDS_BRACKET_PATTERN = re.compile(r"^creator_ids\[(\d+)\]$") +_CREATORS_BRACKET_PATTERN = re.compile(r"^creators\[(\d+)\]$") -class SnippetImportResponse(RootModel[dict[str, Any]]): - root: dict[str, Any] +class SnippetImportResponse(ResponseModel): + id: str + status: ImportStatus + snippet_id: str | None + current_dsl_version: str + imported_dsl_version: str + error: str -class SnippetDependencyCheckResponse(RootModel[dict[str, Any]]): - root: dict[str, Any] +class SnippetDependencyCheckResponse(ResponseModel): + leaked_dependencies: list[PluginDependency] class SnippetUseCountResponse(ResponseModel): @@ -58,6 +67,77 @@ class SnippetUseCountResponse(ResponseModel): use_count: int +class SnippetTagResponse(ResponseModel): + id: str + name: str + type: str + + +class SnippetAccountResponse(ResponseModel): + id: str + name: str + email: str + + +class SnippetListItemResponse(ResponseModel): + id: str + name: str + description: str | None + type: SnippetType + version: int + use_count: int + is_published: bool + icon_info: dict[str, Any] | None + tags: list[SnippetTagResponse] + created_by: str | None + author_name: str | None + created_at: int + updated_by: str | None + updated_at: int + + @field_validator("created_at", "updated_at", mode="before") + @classmethod + def _normalize_timestamp(cls, value: datetime | int | None) -> int: + timestamp = to_timestamp(value) + if timestamp is None: + raise ValueError("timestamp is required") + return timestamp + + +class SnippetResponse(ResponseModel): + id: str + name: str + description: str | None + type: SnippetType + version: int + use_count: int + is_published: bool + icon_info: dict[str, Any] | None + graph: dict[str, Any] = PydanticField(validation_alias="graph_dict") + input_fields: list[dict[str, Any]] = PydanticField(validation_alias="input_fields_list") + tags: list[SnippetTagResponse] + created_by: SnippetAccountResponse | None = PydanticField(validation_alias="created_by_account") + created_at: int + updated_by: SnippetAccountResponse | None = PydanticField(validation_alias="updated_by_account") + updated_at: int + + @field_validator("created_at", "updated_at", mode="before") + @classmethod + def _normalize_timestamp(cls, value: datetime | int | None) -> int: + timestamp = to_timestamp(value) + if timestamp is None: + raise ValueError("timestamp is required") + return timestamp + + +class SnippetPaginationResponse(ResponseModel): + data: list[SnippetListItemResponse] + page: int + limit: int + total: int + has_more: bool + + def _snippet_service() -> SnippetService: return SnippetService(sessionmaker(bind=db.engine, expire_on_commit=False)) @@ -73,11 +153,19 @@ def _normalize_snippet_list_query_args(query_args: MultiDict[str, str]) -> dict[ indexed_tag_ids.extend((int(match.group(1)), value) for value in query_args.getlist(key)) continue - match = _CREATOR_IDS_BRACKET_PATTERN.fullmatch(key) + match = _CREATOR_IDS_BRACKET_PATTERN.fullmatch(key) or _CREATORS_BRACKET_PATTERN.fullmatch(key) if match: indexed_creator_ids.extend((int(match.group(1)), value) for value in query_args.getlist(key)) continue + if key in {"tag_ids", "creators", "creator_ids"}: + values = query_args.getlist(key) + if values: + normalized["creators" if key in {"creators", "creator_ids"} else key] = ( + values if len(values) > 1 else values[0] + ) + continue + value = query_args.get(key) if value is not None: normalized[key] = value @@ -105,19 +193,17 @@ register_response_schema_models( SnippetImportResponse, SnippetDependencyCheckResponse, SnippetUseCountResponse, + SnippetListItemResponse, + SnippetResponse, + SnippetPaginationResponse, ) -# Create namespace models for marshaling -snippet_model = console_ns.model("Snippet", snippet_fields) -snippet_list_model = console_ns.model("SnippetList", snippet_list_fields) -snippet_pagination_model = console_ns.model("SnippetPagination", snippet_pagination_fields) - @console_ns.route("/workspaces/current/customized-snippets") class CustomizedSnippetsApi(Resource): @console_ns.doc("list_customized_snippets") @console_ns.doc(params=query_params_from_model(SnippetListQuery)) - @console_ns.response(200, "Snippets retrieved successfully", snippet_pagination_model) + @console_ns.response(200, "Snippets retrieved successfully", console_ns.models[SnippetPaginationResponse.__name__]) @setup_required @login_required @account_initialization_required @@ -148,7 +234,7 @@ class CustomizedSnippetsApi(Resource): @console_ns.doc("create_customized_snippet") @console_ns.expect(console_ns.models.get(CreateSnippetPayload.__name__)) - @console_ns.response(201, "Snippet created successfully", snippet_model) + @console_ns.response(201, "Snippet created successfully", console_ns.models[SnippetResponse.__name__]) @console_ns.response(400, "Invalid request") @setup_required @login_required @@ -191,7 +277,7 @@ class CustomizedSnippetsApi(Resource): @console_ns.route("/workspaces/current/customized-snippets/") class CustomizedSnippetDetailApi(Resource): @console_ns.doc("get_customized_snippet") - @console_ns.response(200, "Snippet retrieved successfully", snippet_model) + @console_ns.response(200, "Snippet retrieved successfully", console_ns.models[SnippetResponse.__name__]) @console_ns.response(404, "Snippet not found") @setup_required @login_required @@ -212,7 +298,7 @@ class CustomizedSnippetDetailApi(Resource): @console_ns.doc("update_customized_snippet") @console_ns.expect(console_ns.models.get(UpdateSnippetPayload.__name__)) - @console_ns.response(200, "Snippet updated successfully", snippet_model) + @console_ns.response(200, "Snippet updated successfully", console_ns.models[SnippetResponse.__name__]) @console_ns.response(400, "Invalid request") @console_ns.response(404, "Snippet not found") @setup_required diff --git a/api/openapi/markdown/console-openapi.md b/api/openapi/markdown/console-openapi.md index 3e9b93b086e..43451095ab7 100644 --- a/api/openapi/markdown/console-openapi.md +++ b/api/openapi/markdown/console-openapi.md @@ -9761,7 +9761,7 @@ Get list of available agent providers | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Snippets retrieved successfully | **application/json**: [SnippetPagination](#snippetpagination)
| +| 200 | Snippets retrieved successfully | **application/json**: [SnippetPaginationResponse](#snippetpaginationresponse)
| ### [POST] /workspaces/current/customized-snippets **Create a new customized snippet** @@ -9776,7 +9776,7 @@ Get list of available agent providers | Code | Description | Schema | | ---- | ----------- | ------ | -| 201 | Snippet created successfully | **application/json**: [Snippet](#snippet)
| +| 201 | Snippet created successfully | **application/json**: [SnippetResponse](#snippetresponse)
| | 400 | Invalid request | | ### [POST] /workspaces/current/customized-snippets/imports @@ -9841,7 +9841,7 @@ Get list of available agent providers | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Snippet retrieved successfully | **application/json**: [Snippet](#snippet)
| +| 200 | Snippet retrieved successfully | **application/json**: [SnippetResponse](#snippetresponse)
| | 404 | Snippet not found | | ### [PATCH] /workspaces/current/customized-snippets/{snippet_id} @@ -9863,7 +9863,7 @@ Get list of available agent providers | Code | Description | Schema | | ---- | ----------- | ------ | -| 200 | Snippet updated successfully | **application/json**: [Snippet](#snippet)
| +| 200 | Snippet updated successfully | **application/json**: [SnippetResponse](#snippetresponse)
| | 400 | Invalid request | | | 404 | Snippet not found | | @@ -20667,31 +20667,19 @@ Validated metadata extracted from a Skill package. | inferable | boolean | | Yes | | reason | string | | No | -#### Snippet +#### SnippetAccountResponse | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| created_at | long | | No | -| created_by | [_AnonymousInlineModel_b0fd3f86d9d5](#_anonymousinlinemodel_b0fd3f86d9d5) | | No | -| description | string | | No | -| graph | object | | No | -| icon_info | object | | No | -| id | string | | No | -| input_fields | object | | No | -| is_published | boolean | | No | -| name | string | | No | -| tags | [ [_AnonymousInlineModel_7b8b49ca164e](#_anonymousinlinemodel_7b8b49ca164e) ] | | No | -| type | string | | No | -| updated_at | long | | No | -| updated_by | [_AnonymousInlineModel_b0fd3f86d9d5](#_anonymousinlinemodel_b0fd3f86d9d5) | | No | -| use_count | integer | | No | -| version | integer | | No | +| email | string | | Yes | +| id | string | | Yes | +| name | string | | Yes | #### SnippetDependencyCheckResponse | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| SnippetDependencyCheckResponse | object | | | +| leaked_dependencies | [ [PluginDependency](#plugindependency) ] | | Yes | #### SnippetDraftConfigResponse @@ -20746,7 +20734,12 @@ Payload for importing snippet from DSL. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| SnippetImportResponse | object | | | +| current_dsl_version | string | | Yes | +| error | string | | Yes | +| id | string | | Yes | +| imported_dsl_version | string | | Yes | +| snippet_id | string | | Yes | +| status | [ImportStatus](#importstatus) | | Yes | #### SnippetIterationNodeRunPayload @@ -20756,24 +20749,24 @@ Payload for running an iteration node in snippet draft workflow. | ---- | ---- | ----------- | -------- | | inputs | object | | No | -#### SnippetList +#### SnippetListItemResponse | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| author_name | string | | No | -| created_at | long | | No | -| created_by | string | | No | -| description | string | | No | -| icon_info | object | | No | -| id | string | | No | -| is_published | boolean | | No | -| name | string | | No | -| tags | [ [_AnonymousInlineModel_7b8b49ca164e](#_anonymousinlinemodel_7b8b49ca164e) ] | | No | -| type | string | | No | -| updated_at | long | | No | -| updated_by | string | | No | -| use_count | integer | | No | -| version | integer | | No | +| author_name | string | | Yes | +| created_at | integer | | Yes | +| created_by | string | | Yes | +| description | string | | Yes | +| icon_info | object | | Yes | +| id | string | | Yes | +| is_published | boolean | | Yes | +| name | string | | Yes | +| tags | [ [SnippetTagResponse](#snippettagresponse) ] | | Yes | +| type | [SnippetType](#snippettype) | | Yes | +| updated_at | integer | | Yes | +| updated_by | string | | Yes | +| use_count | integer | | Yes | +| version | integer | | Yes | #### SnippetListQuery @@ -20796,15 +20789,51 @@ Payload for running a loop node in snippet draft workflow. | ---- | ---- | ----------- | -------- | | inputs | object | | No | -#### SnippetPagination +#### SnippetPaginationResponse | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| data | [ [_AnonymousInlineModel_744ff9cc03e6](#_anonymousinlinemodel_744ff9cc03e6) ] | | No | -| has_more | boolean | | No | -| limit | integer | | No | -| page | integer | | No | -| total | integer | | No | +| data | [ [SnippetListItemResponse](#snippetlistitemresponse) ] | | Yes | +| has_more | boolean | | Yes | +| limit | integer | | Yes | +| page | integer | | Yes | +| total | integer | | Yes | + +#### SnippetResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| created_at | integer | | Yes | +| created_by | [SnippetAccountResponse](#snippetaccountresponse) | | Yes | +| description | string | | Yes | +| graph | object | | Yes | +| icon_info | object | | Yes | +| id | string | | Yes | +| input_fields | [ object ] | | Yes | +| is_published | boolean | | Yes | +| name | string | | Yes | +| tags | [ [SnippetTagResponse](#snippettagresponse) ] | | Yes | +| type | [SnippetType](#snippettype) | | Yes | +| updated_at | integer | | Yes | +| updated_by | [SnippetAccountResponse](#snippetaccountresponse) | | Yes | +| use_count | integer | | Yes | +| version | integer | | Yes | + +#### SnippetTagResponse + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| id | string | | Yes | +| name | string | | Yes | +| type | string | | Yes | + +#### SnippetType + +Snippet Type Enum + +| Name | Type | Description | Required | +| ---- | ---- | ----------- | -------- | +| SnippetType | string | Snippet Type Enum | | #### SnippetUseCountResponse @@ -22862,41 +22891,6 @@ Workflow tool configuration | data | [ [AccessPolicy](#accesspolicy) ] | | No | | pagination | [Pagination](#pagination) | | No | -#### _AnonymousInlineModel_744ff9cc03e6 - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| author_name | string | | No | -| created_at | long | | No | -| created_by | string | | No | -| description | string | | No | -| icon_info | object | | No | -| id | string | | No | -| is_published | boolean | | No | -| name | string | | No | -| tags | [ [_AnonymousInlineModel_7b8b49ca164e](#_anonymousinlinemodel_7b8b49ca164e) ] | | No | -| type | string | | No | -| updated_at | long | | No | -| updated_by | string | | No | -| use_count | integer | | No | -| version | integer | | No | - -#### _AnonymousInlineModel_7b8b49ca164e - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| id | string | | No | -| name | string | | No | -| type | string | | No | - -#### _AnonymousInlineModel_b0fd3f86d9d5 - -| Name | Type | Description | Required | -| ---- | ---- | ----------- | -------- | -| email | string | | No | -| id | string | | No | -| name | string | | No | - #### _AnonymousInlineModel_b1954337d565 | Name | Type | Description | Required | diff --git a/api/tests/unit_tests/controllers/console/workspace/test_snippets.py b/api/tests/unit_tests/controllers/console/workspace/test_snippets.py index e8e005a1b83..248e526e78f 100644 --- a/api/tests/unit_tests/controllers/console/workspace/test_snippets.py +++ b/api/tests/unit_tests/controllers/console/workspace/test_snippets.py @@ -1,3 +1,4 @@ +from datetime import UTC, datetime from inspect import unwrap from types import SimpleNamespace from unittest.mock import ANY, Mock @@ -8,7 +9,6 @@ from werkzeug.exceptions import NotFound from controllers.console.workspace import snippets as snippets_module from models.account import Account, TenantAccountRole -from models.snippet import CustomizedSnippet from services.snippet_dsl_service import ImportStatus, SnippetImportInfo @@ -39,17 +39,30 @@ def _account(account_id: str = "account-1") -> Account: return account -def _snippet(**overrides) -> CustomizedSnippet: +def _snippet(**overrides) -> SimpleNamespace: data = { "id": "snippet-1", "tenant_id": "tenant-1", "name": "Snippet", "description": "Description", "type": snippets_module.SnippetType.NODE, - "created_by": "account-1", + "version": 1, + "use_count": 0, + "is_published": False, + "icon_info": None, + "graph_dict": {}, + "input_fields_list": [], + "tags": [], + "created_by": None, + "author_name": None, + "created_by_account": None, + "created_at": datetime.fromtimestamp(1_704_067_200, UTC), + "updated_by": None, + "updated_by_account": None, + "updated_at": datetime.fromtimestamp(1_704_153_600, UTC), } data.update(overrides) - return CustomizedSnippet(**data) + return SimpleNamespace(**data) def test_normalize_snippet_list_query_args_sorts_indexed_values(): @@ -70,24 +83,66 @@ def test_normalize_snippet_list_query_args_sorts_indexed_values(): } +def test_normalize_snippet_list_query_args_accepts_generated_creator_keys(): + query_args = snippets_module.MultiDict( + [ + ("creators[1]", "account-b"), + ("creators[0]", "account-a"), + ] + ) + + assert snippets_module._normalize_snippet_list_query_args(query_args) == { + "creators": ["account-a", "account-b"], + } + + +def test_normalize_snippet_list_query_args_accepts_repeated_creator_keys(): + query_args = snippets_module.MultiDict( + [ + ("creators", "account-a"), + ("creators", "account-b"), + ] + ) + + assert snippets_module._normalize_snippet_list_query_args(query_args) == { + "creators": ["account-a", "account-b"], + } + + def test_list_snippets_returns_pagination(app: Flask, monkeypatch: pytest.MonkeyPatch): snippets = [_snippet()] tag_id = "11111111-1111-1111-1111-111111111111" get_snippets = Mock(return_value=(snippets, 1, False)) monkeypatch.setattr(snippets_module.SnippetService, "get_snippets", get_snippets) - monkeypatch.setattr(snippets_module, "marshal", Mock(return_value=[{"id": "snippet-1"}])) api = snippets_module.CustomizedSnippetsApi() handler = unwrap(api.get) with app.test_request_context( - f"/workspaces/current/customized-snippets?page=2&limit=10&tag_ids[0]={tag_id}&creator_ids[0]=account-2" + f"/workspaces/current/customized-snippets?page=2&limit=10&tag_ids[0]={tag_id}&creators[0]=account-2" ): response, status_code = handler(api, "tenant-1") assert status_code == 200 assert response == { - "data": [{"id": "snippet-1"}], + "data": [ + { + "id": "snippet-1", + "name": "Snippet", + "description": "Description", + "type": snippets_module.SnippetType.NODE.value, + "version": 1, + "use_count": 0, + "is_published": False, + "icon_info": None, + "tags": [], + "created_by": None, + "author_name": None, + "created_at": 1_704_067_200, + "updated_by": None, + "updated_at": 1_704_153_600, + } + ], "page": 2, "limit": 10, "total": 1, @@ -124,7 +179,6 @@ def test_create_snippet_defaults_unknown_type_and_returns_created(app: Flask, mo ) ), ) - monkeypatch.setattr(snippets_module, "marshal", Mock(return_value={"id": "snippet-1"})) api = snippets_module.CustomizedSnippetsApi() handler = unwrap(api.post) @@ -137,7 +191,8 @@ def test_create_snippet_defaults_unknown_type_and_returns_created(app: Flask, mo response, status_code = handler(api, "tenant-1", user) assert status_code == 201 - assert response == {"id": "snippet-1"} + assert response["id"] == "snippet-1" + assert response["type"] == snippets_module.SnippetType.NODE.value assert create_snippet.call_args.kwargs["snippet_type"] == snippets_module.SnippetType.NODE @@ -184,7 +239,6 @@ def test_get_snippet_detail_raises_when_missing(app: Flask, monkeypatch: pytest. def test_get_snippet_detail_returns_snippet(app: Flask, monkeypatch: pytest.MonkeyPatch): snippet = _snippet() monkeypatch.setattr(snippets_module.SnippetService, "get_snippet_by_id", Mock(return_value=snippet)) - monkeypatch.setattr(snippets_module, "marshal", Mock(return_value={"id": "snippet-1"})) api = snippets_module.CustomizedSnippetDetailApi() handler = unwrap(api.get) @@ -193,7 +247,8 @@ def test_get_snippet_detail_returns_snippet(app: Flask, monkeypatch: pytest.Monk response, status_code = handler(api, "tenant-1", snippet_id="snippet-1") assert status_code == 200 - assert response == {"id": "snippet-1"} + assert response["id"] == "snippet-1" + assert response["name"] == "Snippet" def test_patch_snippet_returns_400_for_empty_payload(app: Flask, monkeypatch: pytest.MonkeyPatch): @@ -230,7 +285,6 @@ def test_patch_snippet_updates_and_commits(app: Flask, monkeypatch: pytest.Monke monkeypatch.setattr(snippets_module.SnippetService, "update_snippet", update_snippet) monkeypatch.setattr(snippets_module, "Session", SessionContext) monkeypatch.setattr(snippets_module, "db", SimpleNamespace(engine=object())) - monkeypatch.setattr(snippets_module, "marshal", Mock(return_value={"id": "snippet-1", "name": "New"})) api = snippets_module.CustomizedSnippetDetailApi() handler = unwrap(api.patch) @@ -243,7 +297,8 @@ def test_patch_snippet_updates_and_commits(app: Flask, monkeypatch: pytest.Monke response, status_code = handler(api, "tenant-1", user, snippet_id="snippet-1") assert status_code == 200 - assert response == {"id": "snippet-1", "name": "New"} + assert response["id"] == "snippet-1" + assert response["name"] == "New" update_snippet.assert_called_once() assert update_snippet.call_args.kwargs["data"] == { "name": "New", @@ -428,9 +483,7 @@ def test_check_dependencies_raises_when_snippet_missing(app: Flask, monkeypatch: def test_check_dependencies_returns_dependency_result(app: Flask, monkeypatch: pytest.MonkeyPatch): snippet = _snippet() - check_dependencies = Mock( - return_value=SimpleNamespace(model_dump=Mock(return_value={"dependencies": [], "missing_dependencies": []})) - ) + check_dependencies = Mock(return_value=SimpleNamespace(model_dump=Mock(return_value={"leaked_dependencies": []}))) session = SimpleNamespace() class SessionContext(_SessionContext): @@ -453,7 +506,7 @@ def test_check_dependencies_returns_dependency_result(app: Flask, monkeypatch: p response, status_code = handler(api, "tenant-1", snippet_id="snippet-1") assert status_code == 200 - assert response == {"dependencies": [], "missing_dependencies": []} + assert response == {"leaked_dependencies": []} check_dependencies.assert_called_once_with(snippet=snippet) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 3ec09274bbf..3b59147205e 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -7357,11 +7357,6 @@ "count": 4 } }, - "web/service/use-snippet-workflows.ts": { - "no-restricted-imports": { - "count": 1 - } - }, "web/service/use-tools.ts": { "no-restricted-imports": { "count": 1 diff --git a/packages/contracts/generated/api/console/workspaces/types.gen.ts b/packages/contracts/generated/api/console/workspaces/types.gen.ts index 5fd7557b763..f829280b984 100644 --- a/packages/contracts/generated/api/console/workspaces/types.gen.ts +++ b/packages/contracts/generated/api/console/workspaces/types.gen.ts @@ -31,12 +31,12 @@ export type AgentProviderListResponse = Array<{ [key: string]: unknown }> -export type SnippetPagination = { - data?: Array - has_more?: boolean - limit?: number - page?: number - total?: number +export type SnippetPaginationResponse = { + data: Array + has_more: boolean + limit: number + page: number + total: number } export type CreateSnippetPayload = { @@ -50,28 +50,28 @@ export type CreateSnippetPayload = { type?: 'group' | 'node' } -export type Snippet = { - created_at?: number - created_by?: AnonymousInlineModelB0Fd3F86D9D5 - description?: string - graph?: { +export type SnippetResponse = { + created_at: number + created_by: SnippetAccountResponse | null + description: string | null + graph: { [key: string]: unknown } - icon_info?: { + icon_info: { [key: string]: unknown - } - id?: string - input_fields?: { + } | null + id: string + input_fields: Array<{ [key: string]: unknown - } - is_published?: boolean - name?: string - tags?: Array - type?: string - updated_at?: number - updated_by?: AnonymousInlineModelB0Fd3F86D9D5 - use_count?: number - version?: number + }> + is_published: boolean + name: string + tags: Array + type: SnippetType + updated_at: number + updated_by: SnippetAccountResponse | null + use_count: number + version: number } export type SnippetImportPayload = { @@ -84,7 +84,12 @@ export type SnippetImportPayload = { } export type SnippetImportResponse = { - [key: string]: unknown + current_dsl_version: string + error: string + id: string + imported_dsl_version: string + snippet_id: string | null + status: ImportStatus } export type UpdateSnippetPayload = { @@ -94,7 +99,7 @@ export type UpdateSnippetPayload = { } export type SnippetDependencyCheckResponse = { - [key: string]: unknown + leaked_dependencies: Array } export type TextFileResponse = string @@ -944,23 +949,23 @@ export type WorkspaceCustomConfigResponse = { replace_webapp_logo?: string | null } -export type AnonymousInlineModel744Ff9Cc03E6 = { - author_name?: string - created_at?: number - created_by?: string - description?: string - icon_info?: { +export type SnippetListItemResponse = { + author_name: string | null + created_at: number + created_by: string | null + description: string | null + icon_info: { [key: string]: unknown - } - id?: string - is_published?: boolean - name?: string - tags?: Array - type?: string - updated_at?: number - updated_by?: string - use_count?: number - version?: number + } | null + id: string + is_published: boolean + name: string + tags: Array + type: SnippetType + updated_at: number + updated_by: string | null + use_count: number + version: number } export type IconInfo = { @@ -981,16 +986,26 @@ export type InputFieldDefinition = { type?: string | null } -export type AnonymousInlineModelB0Fd3F86D9D5 = { - email?: string - id?: string - name?: string +export type SnippetAccountResponse = { + email: string + id: string + name: string } -export type AnonymousInlineModel7B8B49Ca164e = { - id?: string - name?: string - type?: string +export type SnippetTagResponse = { + id: string + name: string + type: string +} + +export type SnippetType = 'group' | 'node' + +export type ImportStatus = 'completed' | 'completed-with-warnings' | 'failed' | 'pending' + +export type PluginDependency = { + current_identifier?: string | null + type: Type + value: Github | Marketplace | Package } export type AccountWithRole = { @@ -1391,6 +1406,25 @@ export type TriggerProviderSubscriptionApiEntity = { workflows_in_use: number } +export type Type = 'github' | 'marketplace' | 'package' + +export type Github = { + github_plugin_unique_identifier: string + package: string + repo: string + version: string +} + +export type Marketplace = { + marketplace_plugin_unique_identifier: string + version?: string | null +} + +export type Package = { + plugin_unique_identifier: string + version?: string | null +} + export type SimpleProviderEntityResponse = { icon_small?: I18nObject | null icon_small_dark?: I18nObject | null @@ -1879,7 +1913,7 @@ export type GetWorkspacesCurrentCustomizedSnippetsData = { } export type GetWorkspacesCurrentCustomizedSnippetsResponses = { - 200: SnippetPagination + 200: SnippetPaginationResponse } export type GetWorkspacesCurrentCustomizedSnippetsResponse @@ -1897,7 +1931,7 @@ export type PostWorkspacesCurrentCustomizedSnippetsErrors = { } export type PostWorkspacesCurrentCustomizedSnippetsResponses = { - 201: Snippet + 201: SnippetResponse } export type PostWorkspacesCurrentCustomizedSnippetsResponse @@ -1976,7 +2010,7 @@ export type GetWorkspacesCurrentCustomizedSnippetsBySnippetIdErrors = { } export type GetWorkspacesCurrentCustomizedSnippetsBySnippetIdResponses = { - 200: Snippet + 200: SnippetResponse } export type GetWorkspacesCurrentCustomizedSnippetsBySnippetIdResponse @@ -1997,7 +2031,7 @@ export type PatchWorkspacesCurrentCustomizedSnippetsBySnippetIdErrors = { } export type PatchWorkspacesCurrentCustomizedSnippetsBySnippetIdResponses = { - 200: Snippet + 200: SnippetResponse } export type PatchWorkspacesCurrentCustomizedSnippetsBySnippetIdResponse diff --git a/packages/contracts/generated/api/console/workspaces/zod.gen.ts b/packages/contracts/generated/api/console/workspaces/zod.gen.ts index e4f28cfacaa..6b1017f3afa 100644 --- a/packages/contracts/generated/api/console/workspaces/zod.gen.ts +++ b/packages/contracts/generated/api/console/workspaces/zod.gen.ts @@ -26,16 +26,6 @@ export const zSnippetImportPayload = z.object({ yaml_url: z.string().nullish(), }) -/** - * SnippetImportResponse - */ -export const zSnippetImportResponse = z.record(z.string(), z.unknown()) - -/** - * SnippetDependencyCheckResponse - */ -export const zSnippetDependencyCheckResponse = z.record(z.string(), z.unknown()) - /** * TextFileResponse */ @@ -801,91 +791,98 @@ export const zCreateSnippetPayload = z.object({ type: z.enum(['group', 'node']).optional().default('node'), }) -export const zAnonymousInlineModelB0Fd3F86D9D5 = z.object({ - email: z.string().optional(), - id: z.string().optional(), - name: z.string().optional(), +/** + * SnippetAccountResponse + */ +export const zSnippetAccountResponse = z.object({ + email: z.string(), + id: z.string(), + name: z.string(), }) -export const zAnonymousInlineModel7B8B49Ca164e = z.object({ - id: z.string().optional(), - name: z.string().optional(), - type: z.string().optional(), +/** + * SnippetTagResponse + */ +export const zSnippetTagResponse = z.object({ + id: z.string(), + name: z.string(), + type: z.string(), }) -export const zSnippet = z.object({ - created_at: z.coerce - .bigint() - .min(BigInt('-9223372036854775808'), { - error: 'Invalid value: Expected int64 to be >= -9223372036854775808', - }) - .max(BigInt('9223372036854775807'), { - error: 'Invalid value: Expected int64 to be <= 9223372036854775807', - }) - .optional(), - created_by: zAnonymousInlineModelB0Fd3F86D9D5.optional(), - description: z.string().optional(), - graph: z.record(z.string(), z.unknown()).optional(), - icon_info: z.record(z.string(), z.unknown()).optional(), - id: z.string().optional(), - input_fields: z.record(z.string(), z.unknown()).optional(), - is_published: z.boolean().optional(), - name: z.string().optional(), - tags: z.array(zAnonymousInlineModel7B8B49Ca164e).optional(), - type: z.string().optional(), - updated_at: z.coerce - .bigint() - .min(BigInt('-9223372036854775808'), { - error: 'Invalid value: Expected int64 to be >= -9223372036854775808', - }) - .max(BigInt('9223372036854775807'), { - error: 'Invalid value: Expected int64 to be <= 9223372036854775807', - }) - .optional(), - updated_by: zAnonymousInlineModelB0Fd3F86D9D5.optional(), - use_count: z.int().optional(), - version: z.int().optional(), +/** + * SnippetType + * + * Snippet Type Enum + */ +export const zSnippetType = z.enum(['group', 'node']) + +/** + * SnippetResponse + */ +export const zSnippetResponse = z.object({ + created_at: z.int(), + created_by: zSnippetAccountResponse.nullable(), + description: z.string().nullable(), + graph: z.record(z.string(), z.unknown()), + icon_info: z.record(z.string(), z.unknown()).nullable(), + id: z.string(), + input_fields: z.array(z.record(z.string(), z.unknown())), + is_published: z.boolean(), + name: z.string(), + tags: z.array(zSnippetTagResponse), + type: zSnippetType, + updated_at: z.int(), + updated_by: zSnippetAccountResponse.nullable(), + use_count: z.int(), + version: z.int(), }) -export const zAnonymousInlineModel744Ff9Cc03E6 = z.object({ - author_name: z.string().optional(), - created_at: z.coerce - .bigint() - .min(BigInt('-9223372036854775808'), { - error: 'Invalid value: Expected int64 to be >= -9223372036854775808', - }) - .max(BigInt('9223372036854775807'), { - error: 'Invalid value: Expected int64 to be <= 9223372036854775807', - }) - .optional(), - created_by: z.string().optional(), - description: z.string().optional(), - icon_info: z.record(z.string(), z.unknown()).optional(), - id: z.string().optional(), - is_published: z.boolean().optional(), - name: z.string().optional(), - tags: z.array(zAnonymousInlineModel7B8B49Ca164e).optional(), - type: z.string().optional(), - updated_at: z.coerce - .bigint() - .min(BigInt('-9223372036854775808'), { - error: 'Invalid value: Expected int64 to be >= -9223372036854775808', - }) - .max(BigInt('9223372036854775807'), { - error: 'Invalid value: Expected int64 to be <= 9223372036854775807', - }) - .optional(), - updated_by: z.string().optional(), - use_count: z.int().optional(), - version: z.int().optional(), +/** + * SnippetListItemResponse + */ +export const zSnippetListItemResponse = z.object({ + author_name: z.string().nullable(), + created_at: z.int(), + created_by: z.string().nullable(), + description: z.string().nullable(), + icon_info: z.record(z.string(), z.unknown()).nullable(), + id: z.string(), + is_published: z.boolean(), + name: z.string(), + tags: z.array(zSnippetTagResponse), + type: zSnippetType, + updated_at: z.int(), + updated_by: z.string().nullable(), + use_count: z.int(), + version: z.int(), }) -export const zSnippetPagination = z.object({ - data: z.array(zAnonymousInlineModel744Ff9Cc03E6).optional(), - has_more: z.boolean().optional(), - limit: z.int().optional(), - page: z.int().optional(), - total: z.int().optional(), +/** + * SnippetPaginationResponse + */ +export const zSnippetPaginationResponse = z.object({ + data: z.array(zSnippetListItemResponse), + has_more: z.boolean(), + limit: z.int(), + page: z.int(), + total: z.int(), +}) + +/** + * ImportStatus + */ +export const zImportStatus = z.enum(['completed', 'completed-with-warnings', 'failed', 'pending']) + +/** + * SnippetImportResponse + */ +export const zSnippetImportResponse = z.object({ + current_dsl_version: z.string(), + error: z.string(), + id: z.string(), + imported_dsl_version: z.string(), + snippet_id: z.string().nullable(), + status: zImportStatus, }) /** @@ -1493,6 +1490,53 @@ export const zTriggerProviderSubscriptionApiEntity = z.object({ */ export const zTriggerSubscriptionListResponse = z.array(zTriggerProviderSubscriptionApiEntity) +/** + * Type + */ +export const zType = z.enum(['github', 'marketplace', 'package']) + +/** + * Github + */ +export const zGithub = z.object({ + github_plugin_unique_identifier: z.string(), + package: z.string(), + repo: z.string(), + version: z.string(), +}) + +/** + * Marketplace + */ +export const zMarketplace = z.object({ + marketplace_plugin_unique_identifier: z.string(), + version: z.string().nullish(), +}) + +/** + * Package + */ +export const zPackage = z.object({ + plugin_unique_identifier: z.string(), + version: z.string().nullish(), +}) + +/** + * PluginDependency + */ +export const zPluginDependency = z.object({ + current_identifier: z.string().nullish(), + type: zType, + value: z.union([zGithub, zMarketplace, zPackage]), +}) + +/** + * SnippetDependencyCheckResponse + */ +export const zSnippetDependencyCheckResponse = z.object({ + leaked_dependencies: z.array(zPluginDependency), +}) + /** * ConfigurateMethod * @@ -2589,14 +2633,14 @@ export const zGetWorkspacesCurrentCustomizedSnippetsQuery = z.object({ /** * Snippets retrieved successfully */ -export const zGetWorkspacesCurrentCustomizedSnippetsResponse = zSnippetPagination +export const zGetWorkspacesCurrentCustomizedSnippetsResponse = zSnippetPaginationResponse export const zPostWorkspacesCurrentCustomizedSnippetsBody = zCreateSnippetPayload /** * Snippet created successfully */ -export const zPostWorkspacesCurrentCustomizedSnippetsResponse = zSnippet +export const zPostWorkspacesCurrentCustomizedSnippetsResponse = zSnippetResponse export const zPostWorkspacesCurrentCustomizedSnippetsImportsBody = zSnippetImportPayload @@ -2631,7 +2675,7 @@ export const zGetWorkspacesCurrentCustomizedSnippetsBySnippetIdPath = z.object({ /** * Snippet retrieved successfully */ -export const zGetWorkspacesCurrentCustomizedSnippetsBySnippetIdResponse = zSnippet +export const zGetWorkspacesCurrentCustomizedSnippetsBySnippetIdResponse = zSnippetResponse export const zPatchWorkspacesCurrentCustomizedSnippetsBySnippetIdBody = zUpdateSnippetPayload @@ -2642,7 +2686,7 @@ export const zPatchWorkspacesCurrentCustomizedSnippetsBySnippetIdPath = z.object /** * Snippet updated successfully */ -export const zPatchWorkspacesCurrentCustomizedSnippetsBySnippetIdResponse = zSnippet +export const zPatchWorkspacesCurrentCustomizedSnippetsBySnippetIdResponse = zSnippetResponse export const zGetWorkspacesCurrentCustomizedSnippetsBySnippetIdCheckDependenciesPath = z.object({ snippet_id: z.uuid(), diff --git a/web/app/components/app-sidebar/snippet-info/__tests__/dropdown.spec.tsx b/web/app/components/app-sidebar/snippet-info/__tests__/dropdown.spec.tsx index 4b3849cc8bb..e9a0c258159 100644 --- a/web/app/components/app-sidebar/snippet-info/__tests__/dropdown.spec.tsx +++ b/web/app/components/app-sidebar/snippet-info/__tests__/dropdown.spec.tsx @@ -222,6 +222,8 @@ describe('SnippetInfoDropdown', () => { expect(screen.getByText('snippet.editDialogTitle')).toBeInTheDocument() expect(screen.getByText('common.operation.save')).toBeInTheDocument() expect(screen.getByText(mockSnippet.name)).toBeInTheDocument() + if (!mockSnippet.description) + throw new Error('mockSnippet.description is required for this test') expect(screen.getByText(mockSnippet.description)).toBeInTheDocument() await user.click(screen.getByRole('button', { name: 'submit-edit' })) diff --git a/web/app/components/app-sidebar/snippet-info/dropdown.tsx b/web/app/components/app-sidebar/snippet-info/dropdown.tsx index a830ab09ac5..5c8f7a143f7 100644 --- a/web/app/components/app-sidebar/snippet-info/dropdown.tsx +++ b/web/app/components/app-sidebar/snippet-info/dropdown.tsx @@ -49,7 +49,7 @@ const SnippetInfoDropdown = ({ snippet }: SnippetInfoDropdownProps) => { const initialValue = React.useMemo(() => ({ name: snippet.name, - description: snippet.description, + description: snippet.description ?? undefined, }), [snippet.description, snippet.name]) const handleOpenEditDialog = React.useCallback(() => { @@ -80,7 +80,7 @@ const SnippetInfoDropdown = ({ snippet }: SnippetInfoDropdownProps) => { params: { snippetId: snippet.id }, body: { name, - description: description || undefined, + description, }, }, { onSuccess: () => { diff --git a/web/app/components/snippet-list/components/__tests__/snippet-card.spec.tsx b/web/app/components/snippet-list/components/__tests__/snippet-card.spec.tsx index 6ca7a307433..61e28d96bf7 100644 --- a/web/app/components/snippet-list/components/__tests__/snippet-card.spec.tsx +++ b/web/app/components/snippet-list/components/__tests__/snippet-card.spec.tsx @@ -113,6 +113,7 @@ const createSnippet = (overrides: Partial = {}): SnippetListIte updated_at: 1_704_153_600, updated_by: 'updater-id', ...overrides, + version: overrides.version ?? 1, }) describe('SnippetCard', () => { @@ -321,7 +322,7 @@ describe('SnippetCard', () => { expect(mockUpdateMutate).toHaveBeenCalledWith(expect.objectContaining({ body: { name: 'Updated Snippet', - description: undefined, + description: '', }, }), expect.any(Object)) expect(mockToastError).toHaveBeenCalledWith('Update failed') diff --git a/web/app/components/snippet-list/components/__tests__/snippet-create-button.spec.tsx b/web/app/components/snippet-list/components/__tests__/snippet-create-button.spec.tsx index 025654bcfb5..8ab329a9b17 100644 --- a/web/app/components/snippet-list/components/__tests__/snippet-create-button.spec.tsx +++ b/web/app/components/snippet-list/components/__tests__/snippet-create-button.spec.tsx @@ -52,7 +52,13 @@ vi.mock('@/service/use-snippets', () => ({ vi.mock('@/service/client', () => ({ consoleClient: { snippets: { - syncDraftWorkflow: mockSyncDraftWorkflow, + bySnippetId: { + workflows: { + draft: { + post: mockSyncDraftWorkflow, + }, + }, + }, }, }, })) @@ -104,7 +110,7 @@ describe('SnippetCreateButton', () => { }) }) expect(mockSyncDraftWorkflow).toHaveBeenCalledWith({ - params: { snippetId: 'snippet-123' }, + params: { snippet_id: 'snippet-123' }, body: { graph: { nodes: [], diff --git a/web/app/components/snippet-list/components/snippet-card.tsx b/web/app/components/snippet-list/components/snippet-card.tsx index 3f350920019..463b99b42e2 100644 --- a/web/app/components/snippet-list/components/snippet-card.tsx +++ b/web/app/components/snippet-list/components/snippet-card.tsx @@ -62,8 +62,8 @@ const SnippetCard = ({ return new Map((membersData?.accounts ?? []).map(member => [member.id, member.name])) }, [membersData?.accounts]) - const updatedByName = memberNameById.get(snippet.updated_by) - || memberNameById.get(snippet.created_by) + const updatedByName = (snippet.updated_by ? memberNameById.get(snippet.updated_by) : undefined) + || (snippet.created_by ? memberNameById.get(snippet.created_by) : undefined) || t('unknownUser') const updatedAt = snippet.updated_at || snippet.created_at @@ -73,7 +73,7 @@ const SnippetCard = ({ }) const initialValue = useMemo(() => ({ name: snippet.name, - description: snippet.description, + description: snippet.description ?? undefined, }), [snippet.description, snippet.name]) const handleOpenEditDialog = () => { @@ -119,7 +119,7 @@ const SnippetCard = ({ params: { snippetId: snippet.id }, body: { name, - description: description || undefined, + description, }, }, { onSuccess: () => { @@ -148,7 +148,7 @@ const SnippetCard = ({
-
+
{snippet.description}
diff --git a/web/app/components/snippets/components/__tests__/snippet-sidebar.spec.tsx b/web/app/components/snippets/components/__tests__/snippet-sidebar.spec.tsx index 422dfedb89b..4b2a697278d 100644 --- a/web/app/components/snippets/components/__tests__/snippet-sidebar.spec.tsx +++ b/web/app/components/snippets/components/__tests__/snippet-sidebar.spec.tsx @@ -144,6 +144,8 @@ describe('SnippetSidebarContent', () => { expect(screen.queryByRole('link', { name: /snippet\.management/i })).not.toBeInTheDocument() expect(screen.getByText(snippet.name)).toHaveAttribute('title', snippet.name) expect(screen.getByText(snippet.name)).toHaveClass('truncate') + if (!snippet.description) + throw new Error('snippet.description is required for this test') expect(screen.getByText(snippet.description)).toHaveAttribute('title', snippet.description) expect(screen.getByText(snippet.description)).toHaveClass('truncate') expect(screen.queryByRole('button', { name: /common\.operation\.add/i })).not.toBeInTheDocument() diff --git a/web/app/components/snippets/components/hooks/use-snippet-publish.ts b/web/app/components/snippets/components/hooks/use-snippet-publish.ts index 8c7f6a40468..febcdbb3411 100644 --- a/web/app/components/snippets/components/hooks/use-snippet-publish.ts +++ b/web/app/components/snippets/components/hooks/use-snippet-publish.ts @@ -33,9 +33,10 @@ export const useSnippetPublish = ({ params: { snippetId }, }) queryClient.setQueryData( - consoleQuery.snippets.detail.queryKey({ + consoleQuery.workspaces.current.customizedSnippets.bySnippetId.get.key({ + type: 'query', input: { - params: { snippetId }, + params: { snippet_id: snippetId }, }, }), old => old ? { ...old, is_published: true } : old, diff --git a/web/app/components/snippets/hooks/__tests__/use-create-snippet.spec.tsx b/web/app/components/snippets/hooks/__tests__/use-create-snippet.spec.tsx index 73b0f81ab35..a6cf99bcba0 100644 --- a/web/app/components/snippets/hooks/__tests__/use-create-snippet.spec.tsx +++ b/web/app/components/snippets/hooks/__tests__/use-create-snippet.spec.tsx @@ -32,7 +32,13 @@ vi.mock('@/service/use-snippets', () => ({ vi.mock('@/service/client', () => ({ consoleClient: { snippets: { - syncDraftWorkflow: mockSyncDraftWorkflow, + bySnippetId: { + workflows: { + draft: { + post: mockSyncDraftWorkflow, + }, + }, + }, }, }, })) @@ -123,7 +129,7 @@ describe('useCreateSnippet', () => { }, }) expect(mockSyncDraftWorkflow).toHaveBeenCalledWith({ - params: { snippetId: 'snippet-123' }, + params: { snippet_id: 'snippet-123' }, body: { graph, input_fields: [ diff --git a/web/app/components/snippets/hooks/__tests__/use-nodes-sync-draft.spec.ts b/web/app/components/snippets/hooks/__tests__/use-nodes-sync-draft.spec.ts index dc955e7f6cb..4fce06922b5 100644 --- a/web/app/components/snippets/hooks/__tests__/use-nodes-sync-draft.spec.ts +++ b/web/app/components/snippets/hooks/__tests__/use-nodes-sync-draft.spec.ts @@ -61,7 +61,13 @@ vi.mock('@/app/components/workflow/store', () => ({ vi.mock('@/service/client', () => ({ consoleClient: { snippets: { - syncDraftWorkflow: (...args: unknown[]) => mockSyncDraftWorkflow(...args), + bySnippetId: { + workflows: { + draft: { + post: (...args: unknown[]) => mockSyncDraftWorkflow(...args), + }, + }, + }, }, }, })) @@ -123,7 +129,7 @@ describe('snippet/use-nodes-sync-draft', () => { }) expect(mockSyncDraftWorkflow).toHaveBeenCalledWith({ - params: { snippetId: 'snippet-1' }, + params: { snippet_id: 'snippet-1' }, body: { graph: { nodes: [{ id: 'node-1', position: { x: 0, y: 0 }, data: { title: 'Start' } }], @@ -168,7 +174,7 @@ describe('snippet/use-nodes-sync-draft', () => { }) expect(mockSyncDraftWorkflow).toHaveBeenCalledWith({ - params: { snippetId: 'snippet-1' }, + params: { snippet_id: 'snippet-1' }, body: { graph: { nodes: [{ id: 'node-1', position: { x: 0, y: 0 }, data: { title: 'Start' } }], @@ -190,7 +196,7 @@ describe('snippet/use-nodes-sync-draft', () => { }) expect(mockSyncDraftWorkflow).toHaveBeenCalledWith({ - params: { snippetId: 'snippet-1' }, + params: { snippet_id: 'snippet-1' }, body: { graph: { nodes: [{ id: 'node-1', position: { x: 0, y: 0 }, data: { title: 'Start' } }], @@ -225,13 +231,13 @@ describe('snippet/use-nodes-sync-draft', () => { }) expect(mockSyncDraftWorkflow).toHaveBeenNthCalledWith(1, { - params: { snippetId: 'snippet-1' }, + params: { snippet_id: 'snippet-1' }, body: expect.objectContaining({ hash: 'draft-hash', }), }) expect(mockSyncDraftWorkflow).toHaveBeenNthCalledWith(2, { - params: { snippetId: 'snippet-1' }, + params: { snippet_id: 'snippet-1' }, body: expect.objectContaining({ hash: 'hash-after-first-sync', }), diff --git a/web/app/components/snippets/hooks/use-create-snippet.ts b/web/app/components/snippets/hooks/use-create-snippet.ts index 7ace87cbafd..69af925cee5 100644 --- a/web/app/components/snippets/hooks/use-create-snippet.ts +++ b/web/app/components/snippets/hooks/use-create-snippet.ts @@ -42,15 +42,15 @@ export const useCreateSnippet = () => { try { const createPayload = { name, - description: description || undefined, + description, graph, input_fields, } const snippet = await createSnippetMutation.mutateAsync({ body: createPayload, }) - await consoleClient.snippets.syncDraftWorkflow({ - params: { snippetId: snippet.id }, + await consoleClient.snippets.bySnippetId.workflows.draft.post({ + params: { snippet_id: snippet.id }, body: { graph: createPayload.graph, input_fields: createPayload.input_fields, diff --git a/web/app/components/snippets/hooks/use-nodes-sync-draft.ts b/web/app/components/snippets/hooks/use-nodes-sync-draft.ts index 3cdab0254b2..6638b985ef8 100644 --- a/web/app/components/snippets/hooks/use-nodes-sync-draft.ts +++ b/web/app/components/snippets/hooks/use-nodes-sync-draft.ts @@ -113,8 +113,8 @@ export const useNodesSyncDraft = (snippetId: string) => { } = workflowStore.getState() try { - const response = await consoleClient.snippets.syncDraftWorkflow({ - params: { snippetId }, + const response = await consoleClient.snippets.bySnippetId.workflows.draft.post({ + params: { snippet_id: snippetId }, body: { ...payload, hash: syncWorkflowDraftHash || undefined, diff --git a/web/app/components/workflow/block-selector/snippets/__tests__/snippet-detail-card.spec.tsx b/web/app/components/workflow/block-selector/snippets/__tests__/snippet-detail-card.spec.tsx index d2e510e5a55..dc5ab256337 100644 --- a/web/app/components/workflow/block-selector/snippets/__tests__/snippet-detail-card.spec.tsx +++ b/web/app/components/workflow/block-selector/snippets/__tests__/snippet-detail-card.spec.tsx @@ -31,6 +31,7 @@ const createSnippet = (overrides: Partial = {}): Publi updated_at: 2, updated_by: 'user-1', ...overrides, + version: overrides.version ?? 1, }) describe('SnippetDetailCard', () => { diff --git a/web/app/components/workflow/block-selector/snippets/__tests__/snippet-list-item.spec.tsx b/web/app/components/workflow/block-selector/snippets/__tests__/snippet-list-item.spec.tsx index aa31c131cb9..306382cffb6 100644 --- a/web/app/components/workflow/block-selector/snippets/__tests__/snippet-list-item.spec.tsx +++ b/web/app/components/workflow/block-selector/snippets/__tests__/snippet-list-item.spec.tsx @@ -15,6 +15,7 @@ const createSnippet = (overrides: Partial = {}): Publi updated_at: 2, updated_by: 'user-1', ...overrides, + version: overrides.version ?? 1, }) describe('SnippetListItem', () => { diff --git a/web/app/components/workflow/block-selector/snippets/use-insert-snippet.ts b/web/app/components/workflow/block-selector/snippets/use-insert-snippet.ts index 65dca39fbaf..82e6f019eb6 100644 --- a/web/app/components/workflow/block-selector/snippets/use-insert-snippet.ts +++ b/web/app/components/workflow/block-selector/snippets/use-insert-snippet.ts @@ -283,9 +283,9 @@ export const useInsertSnippet = () => { const handleInsertSnippet = useCallback(async (snippetId: string, insertPayload?: SnippetInsertPayload) => { try { - const workflow = await queryClient.fetchQuery(consoleQuery.snippets.publishedWorkflow.queryOptions({ + const workflow = await queryClient.fetchQuery(consoleQuery.snippets.bySnippetId.workflows.publish.get.queryOptions({ input: { - params: { snippetId }, + params: { snippet_id: snippetId }, }, })) const { nodes: snippetNodes, edges: snippetEdges } = getSnippetGraph(workflow.graph) diff --git a/web/contract/console/snippets.ts b/web/contract/console/snippets.ts index 768510dd240..5973314066a 100644 --- a/web/contract/console/snippets.ts +++ b/web/contract/console/snippets.ts @@ -1,376 +1 @@ -import type { - CreateSnippetPayload, - IncrementSnippetUseCountResponse, - PublishSnippetWorkflowResponse, - Snippet, - SnippetDraftConfig, - SnippetDraftNodeRunPayload, - SnippetDraftRunPayload, - SnippetDraftSyncPayload, - SnippetDraftSyncResponse, - SnippetImportPayload, - SnippetIterationNodeRunPayload, - SnippetListResponse, - SnippetLoopNodeRunPayload, - SnippetWorkflow, - UpdateSnippetPayload, - WorkflowNodeExecution, - WorkflowNodeExecutionListResponse, - WorkflowRunDetail, - WorkflowRunPagination, -} from '@/types/snippet' -import { type } from '@orpc/contract' -import { base } from '../base' - -export const listCustomizedSnippetsContract = base - .route({ - path: '/workspaces/current/customized-snippets', - method: 'GET', - }) - .input(type<{ - query: { - page: number - limit: number - keyword?: string - tag_ids?: string[] - creator_ids?: string[] - is_published?: boolean - } - }>()) - .output(type()) - -export const createCustomizedSnippetContract = base - .route({ - path: '/workspaces/current/customized-snippets', - method: 'POST', - }) - .input(type<{ - body: CreateSnippetPayload - }>()) - .output(type()) - -export const getCustomizedSnippetContract = base - .route({ - path: '/workspaces/current/customized-snippets/{snippetId}', - method: 'GET', - }) - .input(type<{ - params: { - snippetId: string - } - }>()) - .output(type()) - -export const updateCustomizedSnippetContract = base - .route({ - path: '/workspaces/current/customized-snippets/{snippetId}', - method: 'PATCH', - }) - .input(type<{ - params: { - snippetId: string - } - body: UpdateSnippetPayload - }>()) - .output(type()) - -export const deleteCustomizedSnippetContract = base - .route({ - path: '/workspaces/current/customized-snippets/{snippetId}', - method: 'DELETE', - }) - .input(type<{ - params: { - snippetId: string - } - }>()) - .output(type()) - -export const exportCustomizedSnippetContract = base - .route({ - path: '/workspaces/current/customized-snippets/{snippetId}/export', - method: 'GET', - }) - .input(type<{ - params: { - snippetId: string - } - query: { - include_secret?: 'true' | 'false' - } - }>()) - .output(type()) - -export const importCustomizedSnippetContract = base - .route({ - path: '/workspaces/current/customized-snippets/imports', - method: 'POST', - }) - .input(type<{ - body: SnippetImportPayload - }>()) - .output(type()) - -export const confirmSnippetImportContract = base - .route({ - path: '/workspaces/current/customized-snippets/imports/{importId}/confirm', - method: 'POST', - }) - .input(type<{ - params: { - importId: string - } - }>()) - .output(type()) - -export const checkSnippetDependenciesContract = base - .route({ - path: '/workspaces/current/customized-snippets/{snippetId}/check-dependencies', - method: 'GET', - }) - .input(type<{ - params: { - snippetId: string - } - }>()) - .output(type()) - -export const incrementSnippetUseCountContract = base - .route({ - path: '/workspaces/current/customized-snippets/{snippetId}/use-count/increment', - method: 'POST', - }) - .input(type<{ - params: { - snippetId: string - } - }>()) - .output(type()) - -export const getSnippetDraftWorkflowContract = base - .route({ - path: '/snippets/{snippetId}/workflows/draft', - method: 'GET', - }) - .input(type<{ - params: { - snippetId: string - } - }>()) - .output(type()) - -export const syncSnippetDraftWorkflowContract = base - .route({ - path: '/snippets/{snippetId}/workflows/draft', - method: 'POST', - }) - .input(type<{ - params: { - snippetId: string - } - body: SnippetDraftSyncPayload - }>()) - .output(type()) - -export const getSnippetDraftConfigContract = base - .route({ - path: '/snippets/{snippetId}/workflows/draft/config', - method: 'GET', - }) - .input(type<{ - params: { - snippetId: string - } - }>()) - .output(type()) - -export const getSnippetPublishedWorkflowContract = base - .route({ - path: '/snippets/{snippetId}/workflows/publish', - method: 'GET', - }) - .input(type<{ - params: { - snippetId: string - } - }>()) - .output(type()) - -export const publishSnippetWorkflowContract = base - .route({ - path: '/snippets/{snippetId}/workflows/publish', - method: 'POST', - }) - .input(type<{ - params: { - snippetId: string - } - }>()) - .output(type()) - -export const getSnippetDefaultBlockConfigsContract = base - .route({ - path: '/snippets/{snippetId}/workflows/default-workflow-block-configs', - method: 'GET', - }) - .input(type<{ - params: { - snippetId: string - } - }>()) - .output(type()) - -export const listSnippetWorkflowRunsContract = base - .route({ - path: '/snippets/{snippetId}/workflow-runs', - method: 'GET', - }) - .input(type<{ - params: { - snippetId: string - } - query: { - last_id?: string - limit?: number - } - }>()) - .output(type()) - -export const getSnippetWorkflowRunDetailContract = base - .route({ - path: '/snippets/{snippetId}/workflow-runs/{runId}', - method: 'GET', - }) - .input(type<{ - params: { - snippetId: string - runId: string - } - }>()) - .output(type()) - -export const listSnippetWorkflowRunNodeExecutionsContract = base - .route({ - path: '/snippets/{snippetId}/workflow-runs/{runId}/node-executions', - method: 'GET', - }) - .input(type<{ - params: { - snippetId: string - runId: string - } - }>()) - .output(type()) - -export const runSnippetDraftNodeContract = base - .route({ - path: '/snippets/{snippetId}/workflows/draft/nodes/{nodeId}/run', - method: 'POST', - }) - .input(type<{ - params: { - snippetId: string - nodeId: string - } - body: SnippetDraftNodeRunPayload - }>()) - .output(type()) - -export const getSnippetDraftNodeLastRunContract = base - .route({ - path: '/snippets/{snippetId}/workflows/draft/nodes/{nodeId}/last-run', - method: 'GET', - }) - .input(type<{ - params: { - snippetId: string - nodeId: string - } - }>()) - .output(type()) - -export const runSnippetDraftIterationNodeContract = base - .route({ - path: '/snippets/{snippetId}/workflows/draft/iteration/nodes/{nodeId}/run', - method: 'POST', - }) - .input(type<{ - params: { - snippetId: string - nodeId: string - } - body: SnippetIterationNodeRunPayload - }>()) - .output(type()) - -export const runSnippetDraftLoopNodeContract = base - .route({ - path: '/snippets/{snippetId}/workflows/draft/loop/nodes/{nodeId}/run', - method: 'POST', - }) - .input(type<{ - params: { - snippetId: string - nodeId: string - } - body: SnippetLoopNodeRunPayload - }>()) - .output(type()) - -export const runSnippetDraftWorkflowContract = base - .route({ - path: '/snippets/{snippetId}/workflows/draft/run', - method: 'POST', - }) - .input(type<{ - params: { - snippetId: string - } - body: SnippetDraftRunPayload - }>()) - .output(type()) - -export const stopSnippetWorkflowTaskContract = base - .route({ - path: '/snippets/{snippetId}/workflow-runs/tasks/{taskId}/stop', - method: 'POST', - }) - .input(type<{ - params: { - snippetId: string - taskId: string - } - }>()) - .output(type()) - -export const snippetsRouterContract = { - list: listCustomizedSnippetsContract, - create: createCustomizedSnippetContract, - detail: getCustomizedSnippetContract, - update: updateCustomizedSnippetContract, - delete: deleteCustomizedSnippetContract, - export: exportCustomizedSnippetContract, - import: importCustomizedSnippetContract, - confirmImport: confirmSnippetImportContract, - checkDependencies: checkSnippetDependenciesContract, - incrementUseCount: incrementSnippetUseCountContract, - draftWorkflow: getSnippetDraftWorkflowContract, - syncDraftWorkflow: syncSnippetDraftWorkflowContract, - draftConfig: getSnippetDraftConfigContract, - publishedWorkflow: getSnippetPublishedWorkflowContract, - publishWorkflow: publishSnippetWorkflowContract, - defaultBlockConfigs: getSnippetDefaultBlockConfigsContract, - workflowRuns: listSnippetWorkflowRunsContract, - workflowRunDetail: getSnippetWorkflowRunDetailContract, - workflowRunNodeExecutions: listSnippetWorkflowRunNodeExecutionsContract, - runDraftNode: runSnippetDraftNodeContract, - lastDraftNodeRun: getSnippetDraftNodeLastRunContract, - runDraftIterationNode: runSnippetDraftIterationNodeContract, - runDraftLoopNode: runSnippetDraftLoopNodeContract, - runDraftWorkflow: runSnippetDraftWorkflowContract, - stopWorkflowTask: stopSnippetWorkflowTaskContract, -} - -export const snippetsConsoleRouterContract = { - snippets: snippetsRouterContract, -} +export const snippetsConsoleRouterContract = {} diff --git a/web/contract/router.ts b/web/contract/router.ts index 48ff9a2579b..efb24c3057c 100644 --- a/web/contract/router.ts +++ b/web/contract/router.ts @@ -34,6 +34,7 @@ import { resetPassword } from '@dify/contracts/api/console/reset-password/orpc.g import { ruleCodeGenerate } from '@dify/contracts/api/console/rule-code-generate/orpc.gen' import { ruleGenerate } from '@dify/contracts/api/console/rule-generate/orpc.gen' import { ruleStructuredOutputGenerate } from '@dify/contracts/api/console/rule-structured-output-generate/orpc.gen' +import { snippets } from '@dify/contracts/api/console/snippets/orpc.gen' import { spec } from '@dify/contracts/api/console/spec/orpc.gen' import { systemFeatures } from '@dify/contracts/api/console/system-features/orpc.gen' import { tagBindings } from '@dify/contracts/api/console/tag-bindings/orpc.gen' @@ -87,6 +88,7 @@ const communityContract = { ruleCodeGenerate, ruleGenerate, ruleStructuredOutputGenerate, + snippets, spec, systemFeatures, tagBindings, diff --git a/web/models/snippet.ts b/web/models/snippet.ts index ccfefd7b306..ab5006e4b34 100644 --- a/web/models/snippet.ts +++ b/web/models/snippet.ts @@ -8,7 +8,7 @@ export type SnippetSection = 'orchestrate' export type SnippetListItem = { id: string name: string - description: string + description: string | null updatedAt: string usage: string tags: Tag[] @@ -19,7 +19,7 @@ export type SnippetListItem = { export type SnippetDetail = { id: string name: string - description: string + description: string | null updatedAt: string usage: string tags: Tag[] diff --git a/web/service/use-snippet-workflows.ts b/web/service/use-snippet-workflows.ts index 7ab9761fbc3..3a93ae0d0ca 100644 --- a/web/service/use-snippet-workflows.ts +++ b/web/service/use-snippet-workflows.ts @@ -1,7 +1,6 @@ -import type { SnippetWorkflow } from '@/types/snippet' +import type { PublishSnippetWorkflowResponse, SnippetWorkflow } from '@/types/snippet' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { consoleQuery } from '@/service/client' -import { get } from './base' +import { consoleClient, consoleQuery } from '@/service/client' const isNotFoundError = (error: unknown) => { return !!error && typeof error === 'object' && 'status' in error && error.status === 404 @@ -9,7 +8,11 @@ const isNotFoundError = (error: unknown) => { export const fetchSnippetDraftWorkflow = async (snippetId: string) => { try { - return await get(`/snippets/${snippetId}/workflows/draft`, {}, { silent: true }) + return await consoleClient.snippets.bySnippetId.workflows.draft.get({ + params: { snippet_id: snippetId }, + }, { + context: { silent: true }, + }) } catch (error) { if (isNotFoundError(error)) @@ -19,30 +22,35 @@ export const fetchSnippetDraftWorkflow = async (snippetId: string) => { } } +const snippetWorkflowContract = consoleQuery.snippets.bySnippetId +const snippetWorkflowClient = consoleClient.snippets.bySnippetId + const invalidateSnippetWorkflowQueries = async ( queryClient: ReturnType, snippetId: string, ) => { await Promise.all([ queryClient.invalidateQueries({ - queryKey: consoleQuery.snippets.draftWorkflow.queryKey({ + queryKey: snippetWorkflowContract.workflows.draft.get.key({ + type: 'query', input: { - params: { snippetId }, + params: { snippet_id: snippetId }, }, }), }), queryClient.invalidateQueries({ - queryKey: consoleQuery.snippets.publishedWorkflow.queryKey({ + queryKey: snippetWorkflowContract.workflows.publish.get.key({ + type: 'query', input: { - params: { snippetId }, + params: { snippet_id: snippetId }, }, }), }), queryClient.invalidateQueries({ - queryKey: consoleQuery.snippets.workflowRuns.key(), + queryKey: snippetWorkflowContract.workflowRuns.get.key({ type: 'query' }), }), queryClient.invalidateQueries({ - queryKey: consoleQuery.snippets.lastDraftNodeRun.key(), + queryKey: snippetWorkflowContract.workflows.draft.nodes.byNodeId.lastRun.get.key({ type: 'query' }), }), ]) } @@ -50,9 +58,9 @@ export const useSnippetPublishedWorkflow = ( snippetId: string, onSuccess?: (publishedWorkflow: SnippetWorkflow) => void, ) => { - const queryOptions = consoleQuery.snippets.publishedWorkflow.queryOptions({ + const queryOptions = snippetWorkflowContract.workflows.publish.get.queryOptions({ input: { - params: { snippetId }, + params: { snippet_id: snippetId }, }, enabled: !!snippetId, }) @@ -80,9 +88,9 @@ export const useSnippetDefaultBlockConfigs = ( snippetId: string, onSuccess?: (nodesDefaultConfigs: unknown) => void, ) => { - const queryOptions = consoleQuery.snippets.defaultBlockConfigs.queryOptions({ + const queryOptions = snippetWorkflowContract.workflows.defaultWorkflowBlockConfigs.get.queryOptions({ input: { - params: { snippetId }, + params: { snippet_id: snippetId }, }, enabled: !!snippetId, }) @@ -100,11 +108,16 @@ export const useSnippetDefaultBlockConfigs = ( export const usePublishSnippetWorkflowMutation = (snippetId: string) => { const queryClient = useQueryClient() - return useMutation({ - ...consoleQuery.snippets.publishWorkflow.mutationOptions({ - onSuccess: async () => { - await invalidateSnippetWorkflowQueries(queryClient, snippetId) + return useMutation({ + mutationKey: snippetWorkflowContract.workflows.publish.post.mutationKey(), + mutationFn: ({ params }) => snippetWorkflowClient.workflows.publish.post({ + params: { + snippet_id: params.snippetId, }, + body: {}, }), + onSuccess: async () => { + await invalidateSnippetWorkflowQueries(queryClient, snippetId) + }, }) } diff --git a/web/service/use-snippets.ts b/web/service/use-snippets.ts index a58499840f5..9aa04620b50 100644 --- a/web/service/use-snippets.ts +++ b/web/service/use-snippets.ts @@ -6,10 +6,14 @@ import type { SnippetListItem as SnippetListItemUIModel, } from '@/models/snippet' import type { + CreateSnippetPayload, + IncrementSnippetUseCountResponse, Snippet as SnippetContract, SnippetDSLImportResponse, + SnippetImportPayload, SnippetListResponse, SnippetWorkflow, + UpdateSnippetPayload, } from '@/types/snippet' import { keepPreviousData, @@ -31,6 +35,17 @@ type SnippetListParams = { } type SnippetSummary = Pick +type SnippetIdInput = { + params: { + snippetId: string + } +} +type CreateSnippetInput = { + body: CreateSnippetPayload +} +type UpdateSnippetInput = SnippetIdInput & { + body: UpdateSnippetPayload +} const DEFAULT_SNIPPET_LIST_PARAMS = { page: 1, @@ -124,8 +139,13 @@ const normalizeSnippetListParams = (params: SnippetListParams) => { const snippetListRootKey = ['snippets', 'list'] as const const snippetListKey = (params: SnippetListParams) => [...snippetListRootKey, params] +const customizedSnippetsContract = consoleQuery.workspaces.current.customizedSnippets +const customizedSnippetsClient = consoleClient.workspaces.current.customizedSnippets const invalidateSnippetQueries = (queryClient: ReturnType) => { + queryClient.invalidateQueries({ + queryKey: customizedSnippetsContract.key(), + }) queryClient.invalidateQueries({ queryKey: consoleQuery.snippets.key(), }) @@ -134,15 +154,26 @@ const invalidateSnippetQueries = (queryClient: ReturnType }) } +const toGeneratedSnippetListQuery = (params: SnippetListParams) => { + return { + page: params.page, + limit: params.limit, + ...(params.keyword ? { keyword: params.keyword } : {}), + ...(params.tag_ids?.length ? { tag_ids: params.tag_ids } : {}), + ...(params.creator_ids?.length ? { creators: params.creator_ids } : {}), + ...(typeof params.is_published === 'boolean' ? { is_published: params.is_published } : {}), + } +} + export const useInfiniteSnippetList = (params: SnippetListParams = {}, options?: { enabled?: boolean }) => { const normalizedParams = normalizeSnippetListParams(params) return useInfiniteQuery({ queryKey: snippetListKey(normalizedParams), queryFn: ({ pageParam = normalizedParams.page }) => { - return consoleClient.snippets.list({ + return customizedSnippetsClient.get({ query: { - ...normalizedParams, + ...toGeneratedSnippetListQuery(normalizedParams), page: pageParam as number, }, }) @@ -155,65 +186,82 @@ export const useInfiniteSnippetList = (params: SnippetListParams = {}, options?: } export const useSnippetApiDetail = (snippetId: string) => { - return useQuery(consoleQuery.snippets.detail.queryOptions({ - input: { - params: { snippetId }, - }, + return useQuery({ + ...customizedSnippetsContract.bySnippetId.get.queryOptions({ + input: { + params: { snippet_id: snippetId }, + }, + }), enabled: !!snippetId, - })) + }) } export const useCreateSnippetMutation = () => { const queryClient = useQueryClient() - return useMutation(consoleQuery.snippets.create.mutationOptions({ + return useMutation({ + mutationKey: customizedSnippetsContract.post.mutationKey(), + mutationFn: input => customizedSnippetsClient.post(input), onSuccess: () => { invalidateSnippetQueries(queryClient) }, - })) + }) } export const useUpdateSnippetMutation = () => { const queryClient = useQueryClient() - return useMutation({ - ...consoleQuery.snippets.update.mutationOptions({ - onSuccess: () => { - invalidateSnippetQueries(queryClient) + return useMutation({ + mutationKey: customizedSnippetsContract.bySnippetId.patch.mutationKey(), + mutationFn: ({ params, body }) => customizedSnippetsClient.bySnippetId.patch({ + params: { + snippet_id: params.snippetId, }, + body, }), + onSuccess: () => { + invalidateSnippetQueries(queryClient) + }, }) } export const useDeleteSnippetMutation = () => { const queryClient = useQueryClient() - return useMutation({ - ...consoleQuery.snippets.delete.mutationOptions({ - onSuccess: () => { - invalidateSnippetQueries(queryClient) + return useMutation({ + mutationKey: customizedSnippetsContract.bySnippetId.delete.mutationKey(), + mutationFn: ({ params }) => customizedSnippetsClient.bySnippetId.delete({ + params: { + snippet_id: params.snippetId, }, }), + onSuccess: () => { + invalidateSnippetQueries(queryClient) + }, }) } export const useIncrementSnippetUseCountMutation = () => { const queryClient = useQueryClient() - return useMutation({ - ...consoleQuery.snippets.incrementUseCount.mutationOptions({ - onSuccess: () => { - invalidateSnippetQueries(queryClient) + return useMutation({ + mutationKey: customizedSnippetsContract.bySnippetId.useCount.increment.post.mutationKey(), + mutationFn: ({ params }) => customizedSnippetsClient.bySnippetId.useCount.increment.post({ + params: { + snippet_id: params.snippetId, }, }), + onSuccess: () => { + invalidateSnippetQueries(queryClient) + }, }) } export const useExportSnippetMutation = () => { return useMutation({ mutationFn: ({ snippetId, include = false }) => { - return consoleClient.snippets.export({ - params: { snippetId }, + return customizedSnippetsClient.bySnippetId.export.get({ + params: { snippet_id: snippetId }, query: { include_secret: include ? 'true' : 'false' }, }) }, @@ -225,13 +273,15 @@ export const useImportSnippetDSLMutation = () => { return useMutation({ mutationFn: ({ mode, yamlContent, yamlUrl }) => { - return consoleClient.snippets.import({ - body: { - mode, - yaml_content: yamlContent, - yaml_url: yamlUrl, - }, - }) as Promise + const body: SnippetImportPayload = { + mode, + yaml_content: yamlContent, + yaml_url: yamlUrl, + } + + return customizedSnippetsClient.imports.post({ + body, + }) }, onSuccess: () => { invalidateSnippetQueries(queryClient) @@ -244,11 +294,11 @@ export const useConfirmSnippetImportMutation = () => { return useMutation({ mutationFn: ({ importId }) => { - return consoleClient.snippets.confirmImport({ + return customizedSnippetsClient.imports.byImportId.confirm.post({ params: { - importId, + import_id: importId, }, - }) as Promise + }) }, onSuccess: () => { invalidateSnippetQueries(queryClient) diff --git a/web/types/snippet.ts b/web/types/snippet.ts index 27301bfff5a..e511614bcc0 100644 --- a/web/types/snippet.ts +++ b/web/types/snippet.ts @@ -4,23 +4,37 @@ type SnippetType = 'node' | 'group' type SnippetInputField = Record -export type Snippet = { +type SnippetTag = Tag + +type SnippetAccount = { id: string name: string - description: string - type: SnippetType - is_published: boolean - version: string - use_count: number - tags: Tag[] - input_fields: SnippetInputField[] - created_at: number - created_by: string - updated_at: number - updated_by: string + email: string } -export type SnippetListItem = Omit +export type SnippetListItem = { + id: string + name: string + description: string | null + type: SnippetType + is_published: boolean + version: number + use_count: number + icon_info?: Record | null + tags: SnippetTag[] + created_at: number + created_by: string | null + author_name?: string | null + updated_at: number + updated_by: string | null +} + +export type Snippet = Omit & { + graph: Record + input_fields: SnippetInputField[] + created_by: SnippetAccount | null + updated_by: SnippetAccount | null +} export type SnippetListResponse = { data: SnippetListItem[] @@ -44,7 +58,7 @@ export type UpdateSnippetPayload = { } export type SnippetImportPayload = { - mode?: string + mode: string yaml_content?: string yaml_url?: string snippet_id?: string @@ -55,9 +69,9 @@ export type SnippetImportPayload = { export type SnippetDSLImportResponse = { id: string status: string - snippet_id?: string - current_dsl_version?: string - imported_dsl_version?: string + snippet_id: string | null + current_dsl_version: string + imported_dsl_version: string error: string } @@ -77,9 +91,8 @@ export type SnippetWorkflow = { } export type SnippetDraftSyncPayload = { - graph?: Record + graph: Record hash?: string - environment_variables?: Record[] conversation_variables?: Record[] input_fields?: SnippetInputField[] } @@ -90,68 +103,12 @@ export type SnippetDraftSyncResponse = { updated_at: number } -export type SnippetDraftConfig = { - parallel_depth_limit: number -} - export type PublishSnippetWorkflowResponse = { result: string created_at: number } -export type WorkflowRunDetail = { - id: string - version: string - status: 'running' | 'succeeded' | 'failed' | 'stopped' | 'partial-succeeded' - elapsed_time: number - total_tokens: number - total_steps: number - created_at: number - finished_at: number - exceptions_count: number -} - -export type WorkflowRunPagination = { - limit: number - has_more: boolean - data: WorkflowRunDetail[] -} - -export type WorkflowNodeExecution = { - id: string - index: number - node_id: string - node_type: string - title: string - inputs: Record - process_data: Record - outputs: Record - status: string - error: string - elapsed_time: number - created_at: number - finished_at: number -} - -export type WorkflowNodeExecutionListResponse = { - data: WorkflowNodeExecution[] -} - -export type SnippetDraftNodeRunPayload = { - inputs?: Record - query?: string - files?: Record[] -} - export type SnippetDraftRunPayload = { - inputs?: Record + inputs: Record files?: Record[] } - -export type SnippetIterationNodeRunPayload = { - inputs?: Record -} - -export type SnippetLoopNodeRunPayload = { - inputs?: Record -}