mirror of
https://github.com/langgenius/dify.git
synced 2026-10-16 15:35:32 +08:00
Server: - describe, check and fill human-input nodes with Dify's own model, delivery methods included; human-input action ids and __timeout are valid branches - refuse End nodes that share an output name, as the console's publish does - warn on a branch with no edge - the release check counts a run as tested when its graph runs the same as the draft, so editor-only fields no longer reset it CLI: - login needs --server; there is no default server - not_logged_in names the config folder it checked - on Linux without a DBus session, keep the token in tokens.yml, since the keyring there is lost when the session ends Skill: - log in with --no-wait only, then --resume after the user approves - report problems instead of substituting, and get a yes before building any change from the spec or plan - trim every file under 500 words
123 lines
4.6 KiB
Python
123 lines
4.6 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import asdict
|
|
from http import HTTPStatus
|
|
from typing import Final
|
|
|
|
from flask_restx import Resource
|
|
|
|
from controllers.openapi import openapi_ns
|
|
from controllers.openapi._contract import Example, Kind, endpoint, op_of
|
|
from controllers.openapi._models import (
|
|
DraftChanges,
|
|
Hint,
|
|
ReleaseCheckName,
|
|
ReleaseCheckResponse,
|
|
ReleaseCheckRow,
|
|
ReleaseState,
|
|
)
|
|
from controllers.openapi.app_dsl import AppDslExportApi, issue_row, node_credential_check
|
|
from controllers.openapi.app_run import _RUN_ROUTES, require_mode
|
|
from controllers.openapi.app_workflow import GRAPH_MODES, VERSION_READ_GUARDS, require_draft
|
|
from controllers.openapi.auth.context import Context
|
|
from core.app.entities.app_invoke_entities import InvokeFrom
|
|
from extensions.ext_application_services import application_services
|
|
from graphon.enums import WorkflowExecutionStatus
|
|
from models import AppMode, WorkflowRunTriggeredFrom
|
|
from models.workflow import Workflow
|
|
from services.workflow.graph_check import check_graph
|
|
from services.workflow.graph_diff import WorkflowSnapshot, diff_workflows, same_graph
|
|
from services.workflow_service import WorkflowService
|
|
|
|
_DRAFT_TEST_OPS: Final = {
|
|
mode: route.op for route in _RUN_ROUTES if route.invoke_from is InvokeFrom.DEBUGGER for mode in route.modes
|
|
}
|
|
|
|
|
|
def _snapshot(workflow: Workflow) -> WorkflowSnapshot:
|
|
return WorkflowSnapshot(
|
|
graph=workflow.graph_dict,
|
|
features=workflow.features_dict,
|
|
environment_variable_names=frozenset(v.name for v in workflow.environment_variables),
|
|
)
|
|
|
|
|
|
@openapi_ns.route("/apps/<string:app_id>/release:check")
|
|
class ReleaseCheckApi(Resource):
|
|
@endpoint(
|
|
op="check.console_app.release",
|
|
kind=Kind.OBJECT,
|
|
summary="Is the draft ready to publish: draft valid, last test passed on this draft, and what changed",
|
|
examples=(Example(title="Before publishing", input={"app_id": "<app_id>"}),),
|
|
requirements=VERSION_READ_GUARDS,
|
|
returns=(HTTPStatus.OK, ReleaseCheckResponse, "Release check"),
|
|
)
|
|
def get(self, ctx: Context, app_id: str):
|
|
require_mode(ctx.app, *GRAPH_MODES)
|
|
draft = require_draft(ctx)
|
|
mode = AppMode(ctx.app.mode)
|
|
service = WorkflowService()
|
|
environment = {v.name: v for v in draft.environment_variables}
|
|
issues = check_graph(
|
|
draft.graph_dict,
|
|
mode=mode,
|
|
resources=node_credential_check(ctx.workspace.id, environment, ctx.session),
|
|
)
|
|
runs = (
|
|
application_services()
|
|
.workflow_runs.get_paginate_workflow_runs(
|
|
ctx.request_context,
|
|
app_id=ctx.app.id,
|
|
args={"limit": 1},
|
|
triggered_from=WorkflowRunTriggeredFrom.DEBUGGING,
|
|
)
|
|
.data
|
|
)
|
|
last = runs[0] if runs else None
|
|
tested = (
|
|
last is not None
|
|
and last.status == WorkflowExecutionStatus.SUCCEEDED
|
|
and same_graph(last.graph_dict, draft.graph_dict)
|
|
)
|
|
published = service.get_published_workflow(ctx.app, session=ctx.session)
|
|
diff = diff_workflows(_snapshot(published) if published else None, _snapshot(draft))
|
|
checks = [
|
|
ReleaseCheckRow(
|
|
name=ReleaseCheckName.DRAFT_VALID,
|
|
passed=not issues,
|
|
detail=f"{len(issues)} issue(s)" if issues else "No issues",
|
|
),
|
|
ReleaseCheckRow(
|
|
name=ReleaseCheckName.TESTED,
|
|
passed=tested,
|
|
detail="The last draft test passed on this draft"
|
|
if tested
|
|
else "No passing draft test on the current draft",
|
|
),
|
|
]
|
|
hints = []
|
|
if issues:
|
|
hints.append(
|
|
Hint(
|
|
summary="Export the draft, fix each issue by its loc, then check and import",
|
|
op=op_of(AppDslExportApi.get),
|
|
input={"app_id": ctx.app.id},
|
|
)
|
|
)
|
|
if not tested:
|
|
hints.append(
|
|
Hint(
|
|
summary="Run the draft on the acceptance cases",
|
|
op=_DRAFT_TEST_OPS[mode],
|
|
input={"app_id": ctx.app.id},
|
|
)
|
|
)
|
|
return ReleaseCheckResponse(
|
|
ready=all(c.passed for c in checks),
|
|
checks=checks,
|
|
issues=[issue_row(i) for i in issues],
|
|
state=ReleaseState(service_api_enabled=ctx.app.enable_api, webapp_enabled=ctx.app.enable_site),
|
|
changes=DraftChanges.model_validate(asdict(diff)),
|
|
hints=hints,
|
|
)
|