Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import CopyToClipboard from "@powerpipe/components/CopyToClipboard";
import ErrorMessage from "@powerpipe/components/ErrorMessage";
import Icon from "@powerpipe/components/Icon";
import LoadingIndicator from "@powerpipe/components/dashboards/LoadingIndicator";
import { DashboardRunState } from "@powerpipe/types";
import { DashboardRunState, PanelDefinition } from "@powerpipe/types";
import {
EdgeStatus,
GraphStatuses,
NodeStatus,
WithStatus,
} from "@powerpipe/components/dashboards/graphs/types";
import { Node } from "reactflow";

type NodeAndEdgePanelInformationProps = {
panel: PanelDefinition;
nodes: Node[];
status: DashboardRunState;
statuses: GraphStatuses;
Expand All @@ -21,6 +24,94 @@ const nodeOrEdgeTitle = (nodeOrEdge: NodeStatus | EdgeStatus) =>
nodeOrEdge?.category?.name ||
nodeOrEdge.id;

const formatNodeAndEdgeErrorsForCopy = (
panel: PanelDefinition,
statuses: GraphStatuses,
): string => {
const {
error: errorStatuses,
running: runningStatuses,
blocked: blockedStatuses,
complete: completeStatuses,
} = statuses;

// Build markdown format
let output = `# Graph Panel Error Report\n\n`;
output += `**Panel:** ${panel.name}\n`;
output += `**Type:** ${panel.panel_type || "graph"}\n`;
output += `**Status:** ${panel.status || "unknown"}\n`;
output += `**Timestamp:** ${new Date().toISOString()}\n\n`;

// Summary section
output += `## Summary\n`;
output += `- Complete: ${completeStatuses.nodes.length} nodes, ${completeStatuses.edges.length} edges, ${completeStatuses.withs.length} withs\n`;
output += `- Running: ${runningStatuses.nodes.length} nodes, ${runningStatuses.edges.length} edges, ${runningStatuses.withs.length} withs\n`;
output += `- Waiting: ${blockedStatuses.nodes.length} nodes, ${blockedStatuses.edges.length} edges, ${blockedStatuses.withs.length} withs\n`;
output += `- **Errors: ${errorStatuses.nodes.length} nodes, ${errorStatuses.edges.length} edges, ${errorStatuses.withs.length} withs**\n\n`;

// Only include error details if there are errors
if (errorStatuses.total === 0) {
return output;
}

output += `## Error Details\n\n`;

// With errors
if (errorStatuses.withs.length > 0) {
errorStatuses.withs.forEach((withBlock: WithStatus) => {
output += `### With: ${withBlock.id}\n`;
if (withBlock.title) {
output += `**Title:** ${withBlock.title}\n`;
}
if (withBlock.error) {
output += `**Error:**\n\`\`\`\n${withBlock.error}\n\`\`\`\n\n`;
} else {
output += `**Error:** (no error message provided)\n\n`;
}
});
}

// Node errors
if (errorStatuses.nodes.length > 0) {
errorStatuses.nodes.forEach((node: NodeStatus) => {
output += `### Node: ${node.id}\n`;
const title = nodeOrEdgeTitle(node);
if (title && title !== node.id) {
output += `**Title:** ${title}\n`;
}
if (node.category?.title) {
output += `**Category:** ${node.category.title}\n`;
}
if (node.error) {
output += `**Error:**\n\`\`\`\n${node.error}\n\`\`\`\n\n`;
} else {
output += `**Error:** (no error message provided)\n\n`;
}
});
}

// Edge errors
if (errorStatuses.edges.length > 0) {
errorStatuses.edges.forEach((edge: EdgeStatus) => {
output += `### Edge: ${edge.id}\n`;
const title = nodeOrEdgeTitle(edge);
if (title && title !== edge.id) {
output += `**Title:** ${title}\n`;
}
if (edge.category?.title) {
output += `**Category:** ${edge.category.title}\n`;
}
if (edge.error) {
output += `**Error:**\n\`\`\`\n${edge.error}\n\`\`\`\n\n`;
} else {
output += `**Error:** (no error message provided)\n\n`;
}
});
}

return output;
};

const WaitingRow = ({ title }) => (
<div className="flex items-center space-x-1">
<Icon
Expand Down Expand Up @@ -56,17 +147,29 @@ const ErrorRow = ({ title, error }: { title: string; error?: string }) => (
);

const NodeAndEdgePanelInformation = ({
panel,
nodes,
status,
statuses,
}: NodeAndEdgePanelInformationProps) => (
<div className="space-y-2 overflow-y-scroll">
<div className="space-y-1">
<div>
{statuses.complete.total} complete, {statuses.running.total} running,{" "}
{statuses.blocked.total} waiting, {statuses.error.total}{" "}
{statuses.error.total === 1 ? "error" : "errors"}.
}: NodeAndEdgePanelInformationProps) => {
const copyData = formatNodeAndEdgeErrorsForCopy(panel, statuses);

return (
<div className="space-y-2 overflow-y-scroll">
{/* Copy button - minimal, just the icon */}
<div className="flex justify-end px-4 pt-2" title="Copy to clipboard">
<CopyToClipboard
data={copyData}
className="text-foreground-light hover:text-foreground transition-colors"
/>
</div>

<div className="space-y-1 px-4">
<div>
{statuses.complete.total} complete, {statuses.running.total} running,{" "}
{statuses.blocked.total} waiting, {statuses.error.total}{" "}
{statuses.error.total === 1 ? "error" : "errors"}.
</div>
{statuses.initialized.total === 0 &&
statuses.blocked.total === 0 &&
statuses.running.total === 0 &&
Expand Down Expand Up @@ -134,8 +237,9 @@ const NodeAndEdgePanelInformation = ({
error={edge.error}
/>
))}
</div>
</div>
</div>
);
);
};

export default NodeAndEdgePanelInformation;
4 changes: 4 additions & 0 deletions ui/dashboard/src/components/dashboards/graphs/Graph/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ const CustomControls = () => {
};

const useNodeAndEdgePanelInformation = (
panel: GraphProps,
nodeAndEdgeStatus: NodeAndEdgeStatus,
dataFormat: NodeAndEdgeDataFormat,
nodes: Node[],
Expand Down Expand Up @@ -566,13 +567,15 @@ const useNodeAndEdgePanelInformation = (
// @ts-ignore
setPanelInformation(() => (
<NodeAndEdgePanelInformation
panel={panel}
nodes={nodes}
status={status}
statuses={statuses}
/>
));
setShowPanelInformation(true);
}, [
panel,
dataFormat,
nodeAndEdgeStatus,
nodes,
Expand All @@ -587,6 +590,7 @@ const Graph = (props) => {
const { selectedPanel } = useDashboardPanelDetail();
const graphOptions = useGraphOptions(props);
useNodeAndEdgePanelInformation(
props,
props.nodeAndEdgeStatus,
props.dataFormat,
graphOptions.nodes,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import CopyToClipboard from "@powerpipe/components/CopyToClipboard";
import ErrorMessage from "../../../ErrorMessage";
import Icon from "../../../Icon";
import LoadingIndicator from "@powerpipe/components/dashboards/LoadingIndicator";
Expand All @@ -24,6 +25,22 @@ type BasePanelStatusProps = {
definition: PanelDefinition;
};

const formatPanelErrorForCopy = (panel: PanelDefinition): string => {
let output = `# Panel Error Report\n\n`;
output += `**Panel:** ${panel.name}\n`;
output += `**Type:** ${panel.panel_type || "unknown"}\n`;
output += `**Status:** ${panel.status || "error"}\n`;
output += `**Timestamp:** ${new Date().toISOString()}\n\n`;

if (panel.error) {
output += `## Error Message\n\`\`\`\n${panel.error}\n\`\`\`\n`;
} else {
output += `## Error Message\n\`\`\`\n(no error message provided)\n\`\`\`\n`;
}

return output;
};

const BasePanelStatus = ({
children,
className,
Expand Down Expand Up @@ -150,21 +167,36 @@ const PanelCancelled = ({ definition }) => {
};

const PanelError = ({ definition }) => {
const copyData = formatPanelErrorForCopy(definition);

return (
<BasePanelStatus
className="bg-alert-light border-alert text-foreground"
definition={definition}
>
<div className="flex items-center space-x-1">
<Icon
className="w-3.5 h-3.5 text-alert shrink-0"
icon="materialsymbols-solid:error"
/>
<span className="block truncate">Error</span>
<div className="space-y-2">
{/* Header with error icon and copy button */}
<div className="flex items-center justify-between">
<div className="flex items-center space-x-1">
<Icon
className="w-3.5 h-3.5 text-alert shrink-0"
icon="materialsymbols-solid:error"
/>
<span className="block truncate">Error</span>
</div>
<div title="Copy to clipboard">
<CopyToClipboard
data={copyData}
className="text-foreground-light hover:text-foreground transition-colors"
/>
</div>
</div>

{/* Error message */}
<span className="block">
<ErrorMessage error={definition.error} />
</span>
</div>
<span className="block">
<ErrorMessage error={definition.error} />
</span>
</BasePanelStatus>
);
};
Expand Down