Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 22 additions & 13 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Button, ConfigProvider, notification, Typography, theme } from "antd";
import { Button, ConfigProvider, notification, theme } from "antd";
import axios from "axios";
import { useEffect } from "react";
import { HelmetProvider } from "react-helmet-async";
import { BrowserRouter } from "react-router-dom";
import { GenericLoader } from "./components/generic-loader/GenericLoader";
import CustomMarkdown from "./components/helpers/custom-markdown/CustomMarkdown.jsx";
import { NotificationIdLine } from "./components/notification/NotificationIdLine.jsx";
import { PageTitle } from "./components/widgets/page-title/PageTitle.jsx";
import { THEME } from "./helpers/GetStaticData.js";
import { attachRequestIdInterceptor } from "./helpers/requestId.js";
Expand Down Expand Up @@ -63,20 +64,22 @@ function App() {

const showRequestId =
alertDetails?.type === "error" && alertDetails?.requestId;
const showExecutionId = Boolean(alertDetails?.executionId);
const description = (
<>
<CustomMarkdown text={alertDetails?.content} />
{showExecutionId && (
<NotificationIdLine
label="Execution ID"
value={alertDetails?.executionId}
stacked
/>
)}
{showRequestId && (
<div className="notification-request-id">
<Typography.Text type="secondary">Request ID:</Typography.Text>{" "}
<Typography.Text
code
copyable={{ text: alertDetails?.requestId }}
className="notification-request-id__value"
>
{alertDetails?.requestId}
</Typography.Text>
</div>
<NotificationIdLine
label="Request ID"
value={alertDetails?.requestId}
/>
)}
</>
);
Expand All @@ -90,8 +93,14 @@ function App() {
key: alertDetails?.key,
});

const logMessage = showRequestId
? `${alertDetails.content}\nRequest ID: \`${alertDetails.requestId}\``
const logSuffix = [
showExecutionId && `Execution ID: \`${alertDetails.executionId}\``,
showRequestId && `Request ID: \`${alertDetails.requestId}\``,
]
.filter(Boolean)
.join("\n");
const logMessage = logSuffix
? `${alertDetails.content}\n${logSuffix}`
: alertDetails.content;

pushLogMessages([
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/components/agency/agency/Agency.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,16 @@ function Agency() {
const execIdValue = initialRes?.data?.execution_id;

setExecutionId(execIdValue);
if (execIdValue && !isStepExecution) {
// Live progress on this page is stale; point users at the logs page.
setAlertDetails({
type: "info",
title: "Workflow run started",
content: `[View logs](/logs/WF/${execIdValue}) to track progress`,
Comment thread
chandrasekharan-zipstack marked this conversation as resolved.
executionId: execIdValue,
duration: 0,
});
}
body["execution_id"] = execIdValue;
if (isStepExecution) {
body["execution_action"] = wfExecutionTypes[executionAction];
Expand Down
20 changes: 1 addition & 19 deletions frontend/src/components/logging/detailed-logs/DetailedLogs.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
ArrowLeftOutlined,
CalendarOutlined,
ClockCircleOutlined,
CloseCircleFilled,
Expand All @@ -24,12 +23,11 @@ import {
} from "antd";
import PropTypes from "prop-types";
import { useEffect, useRef, useState } from "react";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import { useParams } from "react-router-dom";

import { useAxiosPrivate } from "../../../hooks/useAxiosPrivate";
import { useExceptionHandler } from "../../../hooks/useExceptionHandler";
import { useAlertStore } from "../../../store/alert-store";
import { useSessionStore } from "../../../store/session-store";
import "./DetailedLogs.css";
import {
formattedDateTime,
Expand Down Expand Up @@ -93,12 +91,8 @@ const DetailedLogs = () => {
const axiosPrivate = useAxiosPrivate();
const { setAlertDetails } = useAlertStore();
const handleException = useExceptionHandler();
const navigate = useNavigate();
const location = useLocation();
const { sessionDetails } = useSessionStore();
const { getUrl } = useRequestUrl();
const copyToClipboard = useCopyToClipboard();
const cameFromDashboard = location.state?.from === "dashboard";

const [executionDetails, setExecutionDetails] = useState();
const [executionFiles, setExecutionFiles] = useState();
Expand Down Expand Up @@ -460,18 +454,6 @@ const DetailedLogs = () => {
<div className="detailed-logs-container">
<div className="detailed-logs-header">
<Typography.Title className="logs-title" level={4}>
<Button
type="text"
shape="circle"
icon={<ArrowLeftOutlined />}
onClick={() =>
navigate(
cameFromDashboard
? `/${sessionDetails?.orgName}/dashboard`
: `/${sessionDetails?.orgName}/logs`,
)
}
/>
{type} Execution ID {id}
<Button
className="copy-btn-outlined"
Expand Down
20 changes: 5 additions & 15 deletions frontend/src/components/logging/execution-logs/ExecutionLogs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
formattedDateTimeWithSeconds,
} from "../../../helpers/GetStaticData";
import { useAxiosPrivate } from "../../../hooks/useAxiosPrivate";
import { useBackNavigation } from "../../../hooks/useBackNavigation";
import { useExceptionHandler } from "../../../hooks/useExceptionHandler";
import useRequestUrl from "../../../hooks/useRequestUrl";
import { useAlertStore } from "../../../store/alert-store";
Expand Down Expand Up @@ -49,19 +50,9 @@ function ExecutionLogs() {
const autoRefreshIntervalRef = useRef(null);
const currentPath = location.pathname !== `/${sessionDetails?.orgName}/logs`;

// Compute back route - use location state if available, otherwise default to logs listing
const backRoute = id
? location.state?.from || `/${sessionDetails?.orgName}/logs`
: null;

// Scroll-restoration wins; otherwise preserve caller's upstream UI state.
const backRouteState =
id && location.state?.scrollToCardId
? {
scrollToCardId: location.state.scrollToCardId,
cardExpanded: location.state.cardExpanded,
}
: location.state?.backRouteState || null;
const handleNavigateBack = useBackNavigation(
`/${sessionDetails?.orgName}/logs`,
);

const items = [
{
Expand Down Expand Up @@ -220,8 +211,7 @@ function ExecutionLogs() {
title={"Execution Logs"}
customButtons={logTabs}
enableSearch={false}
previousRoute={backRoute}
previousRouteState={backRouteState}
onNavigateBack={id ? handleNavigateBack : undefined}
/>
<div className="file-log-layout">
{id ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ function RecentActivity({ data, loading }) {
}
const typeConfig = TYPE_CONFIG[item.type] || TYPE_CONFIG.workflow;
navigate(`/${orgName}/logs/${typeConfig.logType}/${item.execution_id}`, {
state: { from: "dashboard" },
state: { from: `/${orgName}/dashboard` },
});
};

Expand Down
31 changes: 31 additions & 0 deletions frontend/src/components/notification/NotificationIdLine.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Typography } from "antd";
import PropTypes from "prop-types";

function NotificationIdLine({ label, value, stacked = false }) {
if (!value) {
return null;
}
const className = stacked
? "notification-id-line notification-id-line--stacked"
: "notification-id-line";
return (
<div className={className}>
<Typography.Text type="secondary">{label}:</Typography.Text>
<Typography.Text
code
copyable={{ text: value }}
className="notification-id-line__value"
>
{value}
</Typography.Text>
</div>
);
}

NotificationIdLine.propTypes = {
label: PropTypes.string.isRequired,
value: PropTypes.string,
stacked: PropTypes.bool,
};

export { NotificationIdLine };
41 changes: 41 additions & 0 deletions frontend/src/hooks/useBackNavigation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useCallback } from "react";
import { useLocation, useNavigate } from "react-router-dom";

/*
Generic back-navigation for pages reached from multiple callsites.

Priority:
1. location.state.from — explicit hint from the linking page (also
reinjects scrollToCardId / backRouteState so list pages can restore
scroll position on return).
2. navigate(-1) — SPA history exists; pop one entry. Covers
callsites that didn't set state (e.g. markdown links in alerts).
3. fallbackPath — deep link / refresh with no history.
*/
function useBackNavigation(fallbackPath, fallbackState = null) {
const navigate = useNavigate();
const location = useLocation();

return useCallback(() => {
const explicitFrom = location.state?.from;
if (explicitFrom) {
const restoredState = location.state?.scrollToCardId
? {
scrollToCardId: location.state.scrollToCardId,
cardExpanded: location.state.cardExpanded,
}
: location.state?.backRouteState || null;
navigate(explicitFrom, { state: restoredState });
return;
}
if (location.key !== "default") {
navigate(-1);
return;
}
if (fallbackPath) {
navigate(fallbackPath, { state: fallbackState });
}
}, [location.key, location.state, navigate, fallbackPath, fallbackState]);
}

export { useBackNavigation };
14 changes: 10 additions & 4 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -252,19 +252,25 @@ body {
z-index: 2000;
}

.notification-request-id,
.notification-request-id__value {
.notification-id-line,
.notification-id-line__value {
font-size: 12px;
}

.notification-request-id {
.notification-id-line {
margin-top: 8px;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 4px;
}

.notification-request-id__value {
.notification-id-line--stacked {
flex-direction: column;
align-items: flex-start;
gap: 2px;
}

.notification-id-line__value {
word-break: break-all;
}
1 change: 1 addition & 0 deletions frontend/src/store/alert-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const STORE_VARIABLES = {
duration: DEFAULT_DURATION,
key: null,
requestId: null,
executionId: null,
},
};

Expand Down
6 changes: 5 additions & 1 deletion workers/log_consumer/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
This module contains Celery tasks for processing execution logs.
"""

import json
import os
from typing import Any
from urllib.parse import quote
Expand Down Expand Up @@ -107,7 +108,10 @@ def logs_consumer(**kwargs: Any) -> None:

# Emit WebSocket event directly through Socket.IO (via Redis broker)
try:
payload = {"data": log_message}
# Coerce UUID/datetime/etc. to JSON-safe primitives — Socket.IO's
# serializer is stdlib json and chokes on non-primitive types.
safe_message = json.loads(json.dumps(log_message, default=str))
payload = {"data": safe_message}
sio.emit(event, data=payload, room=room)
logger.debug(f"WebSocket event emitted successfully for room {room}")
except Exception as e:
Expand Down