diff --git a/backend/workflow_manager/workflow_v2/workflow_helper.py b/backend/workflow_manager/workflow_v2/workflow_helper.py index 73aef3e9db..e739afb340 100644 --- a/backend/workflow_manager/workflow_v2/workflow_helper.py +++ b/backend/workflow_manager/workflow_v2/workflow_helper.py @@ -994,18 +994,48 @@ def make_async_result(obj: AsyncResult) -> dict[str, Any]: "info": obj.info, } + USAGE_DISPLAY_LIMIT = 5 + @staticmethod def can_update_workflow(workflow_id: str) -> dict[str, Any]: try: workflow: Workflow = Workflow.objects.get(pk=workflow_id) if not workflow or workflow is None: raise WorkflowDoesNotExistError() - used_count = Pipeline.objects.filter(workflow=workflow).count() - if used_count == 0: - used_count = APIDeployment.objects.filter(workflow=workflow).count() - return {"can_update": used_count == 0} + + pipeline_count = Pipeline.objects.filter(workflow=workflow).count() + api_count = APIDeployment.objects.filter(workflow=workflow).count() + + if (pipeline_count + api_count) == 0: + return { + "can_update": True, + "pipelines": [], + "api_names": [], + "pipeline_count": 0, + "api_count": 0, + } + + limit = WorkflowHelper.USAGE_DISPLAY_LIMIT + pipelines = list( + Pipeline.objects.filter(workflow=workflow).values( + "pipeline_name", "pipeline_type" + )[:limit] + ) + api_names = list( + APIDeployment.objects.filter(workflow=workflow).values_list( + "display_name", flat=True + )[:limit] + ) + + return { + "can_update": False, + "pipelines": pipelines, + "api_names": api_names, + "pipeline_count": pipeline_count, + "api_count": api_count, + } except Workflow.DoesNotExist: - logger.error(f"Error getting workflow: {id}") + logger.error(f"Error getting workflow: {workflow_id}") raise WorkflowDoesNotExistError() diff --git a/frontend/src/components/workflows/workflow/Workflows.jsx b/frontend/src/components/workflows/workflow/Workflows.jsx index 47872515b4..af17ce3dad 100644 --- a/frontend/src/components/workflows/workflow/Workflows.jsx +++ b/frontend/src/components/workflows/workflow/Workflows.jsx @@ -149,37 +149,85 @@ function Workflows() { }); } - const canDeleteProject = async (id) => { - let status = false; - await projectApiService.canUpdate(id).then((res) => { - status = res?.data?.can_update || false; - }); - return status; + const checkWorkflowUsage = async (id) => { + const res = await projectApiService.canUpdate(id); + const data = res?.data || {}; + return { + canUpdate: data.can_update || false, + pipelines: data.pipelines || [], + apiNames: data.api_names || [], + pipelineCount: data.pipeline_count || 0, + apiCount: data.api_count || 0, + }; + }; + + const getUsageMessage = (workflowName, usage) => { + const { pipelines, apiNames, pipelineCount, apiCount } = usage; + const totalCount = pipelineCount + apiCount; + if (totalCount === 0) { + return `Cannot delete \`${workflowName}\` as it is currently in use.`; + } + + const displayLimit = 3; + const lines = []; + + if (apiNames.length > 0) { + const shown = apiNames.slice(0, displayLimit); + shown.forEach((name) => { + lines.push(`- \`${name}\` (API Deployment)`); + }); + if (apiCount > shown.length) { + lines.push( + `- ...and ${apiCount - shown.length} more API deployment(s)`, + ); + } + } + + if (pipelines.length > 0) { + const shown = pipelines.slice(0, displayLimit); + shown.forEach((p) => { + const name = p.pipeline_name; + const type = p.pipeline_type; + lines.push(`- \`${name}\` (${type} Pipeline)`); + }); + const remaining = pipelineCount - shown.length; + if (remaining > 0) { + lines.push(`- ...and ${remaining} more pipeline(s)`); + } + } + + const details = lines.join("\n"); + return `Cannot delete \`${workflowName}\` as it is used in:\n${details}`; }; const deleteProject = async (_evt, project) => { - const canDelete = await canDeleteProject(project.id); - if (canDelete) { - projectApiService - .deleteProject(project.id) - .then(() => { - getProjectList(); - setAlertDetails({ - type: "success", - content: "Workflow deleted successfully", + try { + const usage = await checkWorkflowUsage(project.id); + if (usage.canUpdate) { + projectApiService + .deleteProject(project.id) + .then(() => { + getProjectList(); + setAlertDetails({ + type: "success", + content: "Workflow deleted successfully", + }); + }) + .catch((err) => { + setAlertDetails( + handleException(err, `Unable to delete workflow ${project.id}`), + ); }); - }) - .catch((err) => { - setAlertDetails( - handleException(err, `Unable to delete workflow ${project.id}`), - ); + } else { + setAlertDetails({ + type: "error", + content: getUsageMessage(project.workflow_name, usage), }); - } else { - setAlertDetails({ - type: "error", - content: - "Cannot delete this Workflow, since it is used in one or many of the API/ETL/Task pipelines", - }); + } + } catch (err) { + setAlertDetails( + handleException(err, `Unable to delete workflow ${project.id}`), + ); } }; @@ -249,7 +297,7 @@ function Workflows() { setPostHogCustomEvent("intent_new_wf_project", { info: "Clicked on '+ New Workflow' button", }); - } catch (err) { + } catch (_err) { // If an error occurs while setting custom posthog event, ignore it and continue } };