Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: logs not showing for test runs #4102

Merged
merged 25 commits into from
Mar 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e0596f5
fix: remove outdated 'react-loading'
Kiryous Mar 17, 2025
c00302f
Merge branch 'main' into fix/4078-test-run-react-error
shahargl Mar 17, 2025
1eb96ad
tests: add e2e test for test run
Kiryous Mar 17, 2025
ab914ac
fix: workflow sync status
Kiryous Mar 17, 2025
f626303
Merge branch 'fix/4078-test-run-react-error' of github.com:keephq/kee…
Kiryous Mar 17, 2025
966726a
fix: copilotkit error if openai key is not set
Kiryous Mar 18, 2025
ba64e5d
wip: test workflows like runs
Kiryous Mar 18, 2025
8c96aca
chore: gitignore
Kiryous Mar 18, 2025
21ba4fe
Merge branch 'main' into fix/4093-test-run
Kiryous Mar 18, 2025
e251bd7
fix: rm unused imports
Kiryous Mar 18, 2025
bf173d7
fix: tests
Kiryous Mar 18, 2025
fe82a73
fix: ruff issues
Kiryous Mar 18, 2025
07b49d4
fix: assertion
Kiryous Mar 18, 2025
ea32029
Merge branch 'main' into fix/4093-test-run
Kiryous Mar 18, 2025
17d25d8
Merge branch 'main' into fix/4093-test-run
Kiryous Mar 19, 2025
eee346f
refactor: use `handle_manual_event_workflow` for test runs
Kiryous Mar 19, 2025
6ada9a2
fix: better ux for loading execution logs state
Kiryous Mar 19, 2025
5a13bef
fix: ruff and tests
Kiryous Mar 19, 2025
354f84a
tests: replace tests for deleted method
Kiryous Mar 19, 2025
74f38b2
fix: handle error inside WorkflowExecutionResultsInternal
Kiryous Mar 20, 2025
dcd615f
fix: downgrade migration
Kiryous Mar 20, 2025
c18022f
fix: tweak a test a bit
Kiryous Mar 20, 2025
2bca259
fix: types
Kiryous Mar 20, 2025
23c5637
Merge branch 'main' into fix/4093-test-run
Kiryous Mar 20, 2025
088a16a
fix: change the migration head
Kiryous Mar 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ scripts/automatic_extraction_rules.py

playwright_dump_*.html
playwright_dump_*.png
playwright_dump_*.txt

ee/experimental/ai_temp/*
,e!ee/experimental/ai_temp/.gitkeep
Expand Down
25 changes: 24 additions & 1 deletion keep-ui/entities/workflows/lib/yaml-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { parseDocument, Document, YAMLMap, Pair, Scalar } from "yaml";
import {
parseDocument,
Document,
YAMLMap,
Pair,
Scalar,
stringify,
} from "yaml";
import { Definition } from "../model/types";
import { getYamlWorkflowDefinition } from "./parser";

const YAML_STRINGIFY_OPTIONS = {
indent: 2,
Expand Down Expand Up @@ -66,3 +75,17 @@ export function parseWorkflowYamlStringToJSON(yamlString: string) {
: yamlString;
return parseDocument(content).toJSON();
}

export function getBodyFromStringOrDefinitionOrObject(
definition: Definition | string | Record<string, unknown>
) {
if (typeof definition === "string") {
return definition;
}
if (typeof definition === "object" && "workflow" in definition) {
return stringify(definition);
}
return stringify({
workflow: getYamlWorkflowDefinition(definition as Definition),
});
}
15 changes: 1 addition & 14 deletions keep-ui/entities/workflows/model/useWorkflowActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,9 @@ import { showSuccessToast } from "@/shared/ui/utils/showSuccessToast";
import { useRevalidateMultiple } from "@/shared/lib/state-utils";
import { showErrorToast } from "@/shared/ui";
import { Definition } from "@/entities/workflows/model/types";
import { stringify } from "yaml";
import { useCallback } from "react";
import { getYamlWorkflowDefinition } from "@/entities/workflows/lib/parser";
import { KeepApiError } from "@/shared/api/KeepApiError";

function getBodyFromStringOrDefinitionOrObject(
definition: Definition | string | Record<string, unknown>
) {
if (typeof definition === "string") {
return definition;
}
if (typeof definition === "object" && "workflow" in definition) {
return stringify(definition);
}
return stringify(getYamlWorkflowDefinition(definition as Definition));
}
import { getBodyFromStringOrDefinitionOrObject } from "../lib/yaml-utils";

type UseWorkflowActionsReturn = {
uploadWorkflowFiles: (files: FileList) => Promise<string[]>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,13 +228,15 @@ export function WorkflowExecutionLogs({
checks,
hoveredStep,
selectedStep,
isLoading,
}: {
logs: LogEntry[] | null;
results: Record<string, any> | null;
status: WorkflowExecutionDetail["status"];
checks: number;
hoveredStep: string | null;
selectedStep: string | null;
isLoading: boolean;
}) {
const groupedLogs = useMemo(() => {
if (!logs) {
Expand Down Expand Up @@ -324,7 +326,7 @@ export function WorkflowExecutionLogs({
return (
<Card className="flex flex-col overflow-hidden p-2">
<div className="flex-1 overflow-auto">
{status === "in_progress" ? (
{isLoading ? (
<div>
{Array.from({ length: 6 }).map((_, index) => (
<div key={index} className="flex gap-2 h-10">
Expand All @@ -336,10 +338,12 @@ export function WorkflowExecutionLogs({
</div>
</div>
))}
<p>
The workflow is in progress, will check again in one second (times
checked: {checks})
</p>
{status === "in_progress" && (
<p>
The workflow is in progress, will check again in one second
(times checked: {checks})
</p>
)}
</div>
) : (
<div className="flex flex-col gap-1">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import React, { useEffect, useState, useMemo } from "react";
import { Card, Title, Callout } from "@tremor/react";
import { Card, Callout, Button } from "@tremor/react";
import Loading from "@/app/(keep)/loading";
import { ExclamationCircleIcon } from "@heroicons/react/24/outline";
import { TabGroup, Tab, TabList, TabPanel, TabPanels } from "@tremor/react";
Expand All @@ -16,7 +16,8 @@ import MonacoYAMLEditor from "@/shared/ui/YAMLCodeblock/ui/MonacoYAMLEditor";
import { WorkflowExecutionError } from "./WorkflowExecutionError";
import { WorkflowExecutionLogs } from "./WorkflowExecutionLogs";
import { setFavicon } from "@/shared/ui/utils/favicon";
import { ResizableColumns } from "@/shared/ui";
import { EmptyStateCard, ResizableColumns } from "@/shared/ui";
import { useRevalidateMultiple } from "@/shared/lib/state-utils";

const convertWorkflowStatusToFaviconStatus = (
status: WorkflowExecutionDetail["status"]
Expand All @@ -35,18 +36,19 @@ interface WorkflowResultsProps {
| WorkflowExecutionFailure
| null;
standalone?: boolean;
workflowYaml?: string;
}

export function WorkflowExecutionResults({
workflowId,
workflowExecutionId,
initialWorkflowExecution,
standalone = false,
workflowYaml,
}: WorkflowResultsProps) {
const api = useApi();
const [refreshInterval, setRefreshInterval] = useState(1000);
const [checks, setChecks] = useState(1);
const [error, setError] = useState<string | null>(null);
const [checks, setChecks] = useState(0);

const { data: executionData, error: executionError } = useSWR(
api.isReady() && workflowExecutionId
Expand All @@ -60,26 +62,29 @@ export function WorkflowExecutionResults({
return fetchedData;
},
{
dedupingInterval: 990,
refreshInterval: refreshInterval,
fallbackData: initialWorkflowExecution,
}
);

const { data: workflowData, error: workflowError } = useSWR(
api.isReady() ? `/workflows/${workflowId}` : null,
api.isReady() && !workflowYaml ? `/workflows/${workflowId}` : null,
(url) => api.get(url)
);

const finalYaml = workflowYaml ?? workflowData?.workflow_raw;

useEffect(() => {
if (!standalone || !workflowData) {
if (!standalone || !executionData) {
return;
}
const status = executionData?.error ? "failed" : executionData?.status;
document.title = `${workflowData.name} - Workflow Results - Keep`;
const status = executionData.error ? "failed" : executionData.status;
document.title = `${executionData.workflow_name} - Workflow Results - Keep`;
if (status) {
setFavicon(convertWorkflowStatusToFaviconStatus(status));
}
}, [standalone, executionData, workflowData]);
}, [standalone, executionData]);

useEffect(() => {
if (!executionData) return;
Expand All @@ -89,15 +94,13 @@ export function WorkflowExecutionResults({
setRefreshInterval(0);
}
if (executionData.error) {
setError(executionData?.error);
setRefreshInterval(0);
} else if (executionData?.status === "success") {
setError(executionData?.error);
setRefreshInterval(0);
}
}, [executionData]);

if (!executionData || !workflowData) {
if (!executionData || !finalYaml) {
return <Loading />;
}

Expand Down Expand Up @@ -126,8 +129,9 @@ export function WorkflowExecutionResults({
<WorkflowExecutionResultsInternal
workflowId={workflowId}
executionData={executionData}
workflowRaw={workflowData.workflow_raw}
workflowRaw={finalYaml}
checks={checks}
isLoading={refreshInterval > 0}
/>
);
}
Expand All @@ -137,11 +141,13 @@ export function WorkflowExecutionResultsInternal({
executionData,
workflowRaw,
checks,
isLoading,
}: {
executionData: WorkflowExecutionDetail | WorkflowExecutionFailure;
workflowId: string;
workflowRaw: string | undefined;
checks: number;
isLoading: boolean;
}) {
const [hoveredStep, setHoveredStep] = useState<string | null>(null);
const [selectedStep, setSelectedStep] = useState<string | null>(null);
Expand All @@ -151,7 +157,7 @@ export function WorkflowExecutionResultsInternal({
let results: WorkflowExecutionDetail["results"] | undefined;
let eventId: string | undefined;
let eventType: string | undefined;

const revalidateMultiple = useRevalidateMultiple();
if (isWorkflowExecution(executionData)) {
status = executionData.status;
logs = executionData.logs;
Expand Down Expand Up @@ -232,16 +238,35 @@ export function WorkflowExecutionResultsInternal({
)}
<ResizableColumns initialLeftWidth={50}>
<div className="pr-2">
<Card className="p-0 overflow-hidden">
<WorkflowExecutionLogs
logs={logs ?? null}
results={results ?? null}
status={status ?? ""}
checks={checks}
hoveredStep={hoveredStep}
selectedStep={selectedStep}
/>
</Card>
{logs ? (
<Card className="p-0 overflow-hidden">
<WorkflowExecutionLogs
logs={logs ?? null}
results={results ?? null}
status={status ?? ""}
checks={checks}
hoveredStep={hoveredStep}
selectedStep={selectedStep}
isLoading={isLoading}
/>
</Card>
) : (
<EmptyStateCard
title="No logs found"
description="The workflow is still running"
>
<Button
variant="primary"
color="orange"
size="sm"
onClick={() => {
revalidateMultiple([`/workflows/${workflowId}/runs`]);
}}
>
Refresh
</Button>
</EmptyStateCard>
)}
</div>
<div className="px-2">
<Card className="p-0 overflow-hidden">
Expand Down
8 changes: 5 additions & 3 deletions keep-ui/features/workflows/builder/ui/Editor/StepTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { SparklesIcon } from "@heroicons/react/24/outline";
import { useCopilotChat } from "@copilotkit/react-core";
import { Role } from "@copilotkit/runtime-client-gql";
import { TextMessage } from "@copilotkit/runtime-client-gql";
import { useConfig } from "@/utils/hooks/useConfig";

export function useTestStep() {
const api = useApi();
Expand Down Expand Up @@ -38,6 +39,7 @@ const WFDebugWithAI = ({
errors: { [key: string]: string };
description: string;
}) => {
// careful, useCopilotChat may not be available if user has not set an OpenAI API key
const { appendMessage } = useCopilotChat();
return (
<Button
Expand Down Expand Up @@ -68,11 +70,11 @@ const WFDebugWithAIButton = ({
errors: { [key: string]: string };
description: string;
}) => {
try {
return <WFDebugWithAI errors={errors} description={description} />;
} catch (e) {
const { data: config } = useConfig();
if (!config?.OPEN_AI_API_KEY_SET) {
return null;
}
return <WFDebugWithAI errors={errors} description={description} />;
};

const variablesRegex = /{{[\s]*.*?[\s]*}}/g;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
import { Title } from "@tremor/react";
import {
isWorkflowExecution,
WorkflowExecutionDetail,
WorkflowExecutionFailure,
} from "@/shared/api/workflow-executions";
import { WorkflowExecutionResults } from "@/features/workflow-execution-results";
import { IoClose } from "react-icons/io5";
import { KeepLoader } from "@/shared/ui";

interface Props {
closeModal: () => void;
workflowId: string;
workflowExecution: WorkflowExecutionDetail | WorkflowExecutionFailure | null;
workflowExecutionId: string;
workflowYamlSent: string | null;
}

export function BuilderWorkflowTestRunModalContent({
closeModal,
workflowId,
workflowExecution,
workflowExecutionId,
workflowYamlSent,
}: Props) {
return (
<div className="flex flex-col gap-4">
Expand All @@ -32,21 +28,11 @@ export function BuilderWorkflowTestRunModalContent({
</div>
</div>
<div className="flex flex-col">
{workflowExecution ? (
<WorkflowExecutionResults
workflowId={workflowId}
initialWorkflowExecution={workflowExecution}
workflowExecutionId={
isWorkflowExecution(workflowExecution)
? workflowExecution.id
: null
}
/>
) : (
<div className="flex justify-center">
<KeepLoader loadingText="Loading workflow execution results..." />
</div>
)}
<WorkflowExecutionResults
workflowId={workflowId}
workflowExecutionId={workflowExecutionId}
workflowYaml={workflowYamlSent ?? ""}
/>
</div>
</div>
);
Expand Down
Loading
Loading