dataflow-rs 2.1.5

A lightweight rules engine for building IFTTT-style automation and data processing pipelines in Rust. Define rules with JSONLogic conditions, execute actions, and chain workflows.
Documentation
import { useState } from 'react';
import { useDebugger } from '../context';
import type { Message, Change } from '../../../types';

type ViewTab = 'message' | 'changes';

interface MessageStatePanelProps {
  /** Additional CSS class */
  className?: string;
}

/**
 * Panel showing message state at current execution step
 */
export function MessageStatePanel({ className = '' }: MessageStatePanelProps) {
  const { currentStep, currentMessage, currentChanges } = useDebugger();
  const [activeTab, setActiveTab] = useState<ViewTab>('message');

  if (!currentStep || !currentMessage) {
    return (
      <div className={`df-debug-state-panel df-debug-state-empty ${className}`}>
        <p>No step selected. Run debug to see message state changes.</p>
      </div>
    );
  }

  const { workflow_id, task_id, result } = currentStep;

  const tabs: { key: ViewTab; label: string }[] = [
    { key: 'message', label: 'Message' },
    { key: 'changes', label: `Changes (${currentChanges.length})` },
  ];

  return (
    <div className={`df-debug-state-panel ${className}`}>
      <div className="df-debug-state-header">
        <div className="df-debug-state-tabs">
          {tabs.map(({ key, label }) => (
            <button
              key={key}
              className={`df-debug-state-tab ${activeTab === key ? 'df-debug-state-tab-active' : ''}`}
              onClick={() => setActiveTab(key)}
            >
              {label}
            </button>
          ))}
        </div>
      </div>

      <div className="df-debug-state-content">
        {activeTab === 'message' && (
          <MessageView message={currentMessage} />
        )}
        {activeTab === 'changes' && (
          <ChangesView changes={currentChanges} />
        )}
      </div>

      {/* Step info footer */}
      <div className="df-debug-state-footer">
        <div className="df-debug-state-step-type">
          <span className={`df-debug-state-badge df-debug-state-badge-${result}`}>
            {result}
          </span>
          <span className="df-debug-state-type-label">
            {workflow_id}{task_id ? ` / ${task_id}` : ''}
          </span>
        </div>
        {currentMessage.errors.length > 0 && (
          <div className="df-debug-state-error">
            Errors: {currentMessage.errors.length}
          </div>
        )}
      </div>
    </div>
  );
}

interface MessageViewProps {
  message: Message;
}

function MessageView({ message }: MessageViewProps) {
  const [expandedSections, setExpandedSections] = useState<Set<string>>(
    new Set(['context'])
  );

  const toggleSection = (section: string) => {
    setExpandedSections((prev) => {
      const next = new Set(prev);
      if (next.has(section)) {
        next.delete(section);
      } else {
        next.add(section);
      }
      return next;
    });
  };

  const sections = [
    { key: 'context', value: message.context },
    { key: 'payload', value: message.payload },
    { key: 'errors', value: message.errors },
    { key: 'audit_trail', value: message.audit_trail },
  ];

  return (
    <div className="df-debug-message-view">
      {sections.map(({ key, value }) => {
        const isExpanded = expandedSections.has(key);
        const isEmpty = Array.isArray(value)
          ? value.length === 0
          : typeof value === 'object' && value !== null
            ? Object.keys(value).length === 0
            : !value;

        return (
          <div key={key} className="df-debug-message-section">
            <div
              className="df-debug-message-section-header"
              onClick={() => toggleSection(key)}
            >
              <span className="df-debug-section-arrow">
                {isExpanded ? '▼' : '▶'}
              </span>
              <span className="df-debug-section-key">{key}</span>
              {isEmpty && <span className="df-debug-section-empty">(empty)</span>}
            </div>
            {isExpanded && (
              <pre className="df-debug-message-section-content">
                {JSON.stringify(value, null, 2)}
              </pre>
            )}
          </div>
        );
      })}
    </div>
  );
}

interface ChangesViewProps {
  changes: Change[];
}

function ChangesView({ changes }: ChangesViewProps) {
  if (changes.length === 0) {
    return (
      <div className="df-debug-changes-empty">
        No changes recorded in this step
      </div>
    );
  }

  return (
    <div className="df-debug-changes-view">
      {changes.map((change, index) => {
        const isModified = change.old_value !== undefined && change.new_value !== undefined;
        const isAdded = change.old_value === undefined && change.new_value !== undefined;
        const isRemoved = change.old_value !== undefined && change.new_value === undefined;
        const changeType = isAdded ? 'added' : isRemoved ? 'removed' : 'modified';

        return (
          <div key={index} className={`df-debug-change-item df-debug-change-${changeType}`}>
            <div className="df-debug-change-header">
              <span className={`df-debug-change-op df-debug-change-op-${changeType}`}>
                {changeType}
              </span>
              <span className="df-debug-change-path">{change.path}</span>
            </div>
            {isModified && (
              <div className="df-debug-change-values">
                <span className="df-debug-change-old">{formatValue(change.old_value)}</span>
                <span className="df-debug-change-arrow">→</span>
                <span className="df-debug-change-new">{formatValue(change.new_value)}</span>
              </div>
            )}
            {isAdded && (
              <div className="df-debug-change-values">
                <span className="df-debug-change-new">{formatValue(change.new_value)}</span>
              </div>
            )}
            {isRemoved && (
              <div className="df-debug-change-values">
                <span className="df-debug-change-old">{formatValue(change.old_value)}</span>
              </div>
            )}
          </div>
        );
      })}
    </div>
  );
}

function formatValue(value: unknown): string {
  if (value === undefined) return 'undefined';
  if (value === null) return 'null';
  if (typeof value === 'string') return `"${value}"`;
  if (typeof value === 'object') return JSON.stringify(value);
  return String(value);
}