UIPackage
Menu

Framework

Change language

Boilerplate repo

Spark Job Stage Visualizer

blockdevops

Databricks and Apache Spark distributed job execution DAG visualizer with stage performance metrics, task skew distribution quantiles, and executor JVM GC telemetry.

Also available for Vue ->

Installation

$npx shadcn@latest add https://uipkge.dev/r/react/spark-job-stage-visualizer.json
Named registry:npx shadcn@latest add @uipkge-react/spark-job-stage-visualizerInstalls to:components/blocks/

Variants

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
initialStageIdnumberoptional
stagesSparkStage[]optional
classNamestringoptional

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

TaskQuantiles
interface TaskQuantiles {
  min: number
  p25: number
  median: number
  p75: number
  p95: number
  max: number
}
TaskBreakdownMetrics
interface TaskBreakdownMetrics {
  executorRunTime: string
  jvmGcTime: string
  shuffleFetchWait: string
  serializationTime: string
  deserializationTime: string
  peakMemory: string
}
SparkStage
interface SparkStage {
  id: number
  name: string
  callSite: string
  operator: string
  status: StageStatus
  tasksTotal: number
  tasksSucceeded: number
  tasksFailed: number
  duration: string
  durationSec: number
  inputBytes: string
  inputRecords: string
  outputBytes: string
  outputRecords: string
  shuffleReadBytes: string
  shuffleWriteBytes: string
  spillMemory: string
  spillDisk: string
  skewRatio: number
  quantiles: TaskQuantiles
  taskBreakdown: TaskBreakdownMetrics
  details: string
  upstreamStageIds: number[]
  downstreamStageIds: number[]
  branch: 'left' | 'right' | 'join'
}
SparkExecutor
interface SparkExecutor {
  id: string
  host: string
  role: 'driver' | 'executor'
  status: ExecutorStatus
  cores: number
  memoryUsedGB: number
  memoryTotalGB: number
  memoryPercent: number
  gcTimeSec: number
  gcPercent: number
  tasksCompleted: number
  tasksTotal: number
  tasksFailed: number
  shuffleReadGB: number
  shuffleWriteGB: number
  spillGB: number
}
AqeRule
interface AqeRule {
  id: string
  name: string
  status: 'applied' | 'skipped'
  description: string
  impact: string
}

Files installed (7)

  • components/blocks/SparkJobStageVisualizer.tsx8.6 kB
    'use client'
    
    import * as React from 'react'
    import { Check, CheckCircle2, Clock, Copy, Download, Flame, Server, ShieldAlert, X } from 'lucide-react'
    import { cn } from '@/lib/utils'
    import { Badge } from '@/components/ui/badge'
    import { Button } from '@/components/ui/button'
    import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
    import { SparkJobKpis } from './SparkJobKpis'
    import { SparkStageDagTab } from './SparkStageDagTab'
    import { SparkStageSkewTab } from './SparkStageSkewTab'
    import { SparkExecutorsAqe } from './SparkExecutorsAqe'
    import type {
      AqeRule,
      ExecutorStatus,
      SparkExecutor,
      SparkStage,
      StageStatus,
      TaskBreakdownMetrics,
      TaskQuantiles,
    } from './spark-stage-types'
    import { defaultStages } from './spark-stage-data'
    
    export type { AqeRule, ExecutorStatus, SparkExecutor, SparkStage, StageStatus, TaskBreakdownMetrics, TaskQuantiles }
    
    export interface SparkJobStageVisualizerProps {
      initialStageId?: number
      stages?: SparkStage[]
      className?: string
    }
    
    export function SparkJobStageVisualizer({
      initialStageId = 2,
      stages = defaultStages,
      className,
    }: SparkJobStageVisualizerProps) {
      const [selectedStageId, setSelectedStageId] = React.useState<number>(initialStageId)
      const [activeTab, setActiveTab] = React.useState<string>('dag')
      const [copiedAppId, setCopiedAppId] = React.useState<boolean>(false)
      const [showKillConfirm, setShowKillConfirm] = React.useState<boolean>(false)
      const [downloadStarted, setDownloadStarted] = React.useState<boolean>(false)
    
      const selectedStage = React.useMemo(() => {
        return stages.find((s) => s.id === selectedStageId) ?? stages[2] ?? stages[0]
      }, [stages, selectedStageId])
    
      const copyAppId = React.useCallback(() => {
        navigator.clipboard?.writeText('app-20260821-142850-0042')
        setCopiedAppId(true)
        setTimeout(() => {
          setCopiedAppId(false)
        }, 2000)
      }, [])
    
      const triggerDownload = React.useCallback(() => {
        setDownloadStarted(true)
        setTimeout(() => {
          setDownloadStarted(false)
        }, 2500)
      }, [])
    
      return (
        <div className={cn('text-foreground w-full space-y-6 font-sans antialiased', className)}>
          {/* Header Section */}
          <div className="border-border bg-card rounded-xl border p-5 shadow-xs transition-colors">
            <div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
              {/* Title & Identifiers */}
              <div className="space-y-1.5">
                <div className="flex flex-wrap items-center gap-2.5">
                  <div className="flex size-9 items-center justify-center rounded-lg bg-orange-500/10 text-orange-600 dark:text-orange-400">
                    <Flame className="size-5" />
                  </div>
                  <div>
                    <div className="flex flex-wrap items-center gap-2">
                      <h1 className="text-base font-semibold tracking-tight sm:text-lg">AggregatedCustomerLTVJob</h1>
                      <Badge wrap variant="outline" className="text-muted-foreground font-mono text-xs">
                        app-20260821-142850-0042
                      </Badge>
                      <Button
                        variant="ghost"
                        size="xs"
                        className="text-muted-foreground hover:text-foreground h-6 px-1.5 text-xs"
                        aria-label="Copy Spark Application ID"
                        onClick={copyAppId}
                      >
                        {copiedAppId ? <Check className="text-success size-3" /> : <Copy className="size-3" />}
                        <span className="sr-only">Copy Application ID</span>
                      </Button>
                    </div>
                    <p className="text-muted-foreground text-xs">
                      Apache Spark 3.5.1 · Adaptive Query Execution (AQE) Enabled
                    </p>
                  </div>
                </div>
    
                {/* Metadata Tags */}
                <div className="text-muted-foreground flex flex-wrap items-center gap-x-4 gap-y-1 pt-1 text-xs">
                  <div className="flex items-center gap-1.5">
                    <span className="bg-success size-2 rounded-full" />
                    <span className="text-foreground font-medium">Job Succeeded</span>
                    <span>·</span>
                    <span className="font-mono tabular-nums">03m:42s</span>
                  </div>
                  <div className="flex items-center gap-1">
                    <Server className="size-3.5" />
                    <span>
                      Cluster: <strong className="text-foreground font-medium">c5d.4xlarge (4 nodes · 112 vCPUs)</strong>
                    </span>
                  </div>
                  <div className="flex items-center gap-1">
                    <Clock className="size-3.5" />
                    <span>
                      Started: <strong className="text-foreground font-medium">14:28:50 UTC</strong>
                    </span>
                  </div>
                </div>
              </div>
    
              {/* Action Buttons */}
              <div className="flex flex-wrap items-center gap-2">
                <Button
                  aria-label="Download attachment"
                  variant="outline"
                  size="sm"
                  className="h-8 gap-1.5 text-xs"
                  disabled={downloadStarted}
                  onClick={triggerDownload}
                >
                  {downloadStarted ? <CheckCircle2 className="text-success size-3.5" /> : <Download className="size-3.5" />}
                  <span>{downloadStarted ? 'Log Exported' : 'Download EventLog'}</span>
                </Button>
    
                <Button
                  variant="ghost"
                  size="sm"
                  className="text-destructive hover:bg-destructive/10 hover:text-destructive h-8 gap-1.5 text-xs"
                  onClick={() => setShowKillConfirm(!showKillConfirm)}
                >
                  <X className="size-3.5" />
                  <span>Kill Application</span>
                </Button>
              </div>
            </div>
    
            {/* Kill Confirmation Alert (Collapsible) */}
            {showKillConfirm && (
              <div className="border-destructive/30 bg-destructive/5 text-destructive mt-4 flex items-center justify-between rounded-lg border p-3 text-xs">
                <div className="flex flex-wrap items-center gap-2">
                  <ShieldAlert className="size-4 shrink-0" />
                  <span>
                    This application is in finished state (SUCCEEDED). Terminating an inactive application will release log
                    retention locks.
                  </span>
                </div>
                <div className="flex flex-wrap items-center gap-2">
                  <Button size="xs" variant="destructive" onClick={() => setShowKillConfirm(false)}>
                    Confirm Release
                  </Button>
                  <Button size="xs" variant="ghost" onClick={() => setShowKillConfirm(false)}>
                    Cancel
                  </Button>
                </div>
              </div>
            )}
          </div>
    
          {/* 4 Spark Job Execution KPI Cards */}
          <SparkJobKpis />
    
          {/* Main Content Tabs */}
          <Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-4">
            <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
              <TabsList className="grid w-full grid-cols-2 sm:w-auto sm:grid-cols-4">
                <TabsTrigger value="dag" className="text-xs">
                  Stages DAG Flow
                </TabsTrigger>
                <TabsTrigger value="skew" className="text-xs">
                  Task Skew & Quantiles
                </TabsTrigger>
                <TabsTrigger value="executors" className="text-xs">
                  Executors (4)
                </TabsTrigger>
                <TabsTrigger value="aqe" className="text-xs">
                  AQE Optimizer
                </TabsTrigger>
              </TabsList>
    
              <div className="text-muted-foreground flex flex-wrap items-center gap-2 text-xs">
                <span className="bg-primary inline-block size-2 rounded-full" />
                <span>Active Selection:</span>
                <span className="text-foreground font-medium">
                  Stage {selectedStage.id} ({selectedStage.name})
                </span>
              </div>
            </div>
    
            {/* TAB 1: INTERACTIVE SPARK STAGES DAG FLOW */}
            <SparkStageDagTab
              selectedStage={selectedStage}
              selectedStageId={selectedStageId}
              stages={stages}
              onSelectStage={setSelectedStageId}
            />
    
            {/* TAB 2: TASK SKEW & QUANTILES DISTRIBUTION */}
            <SparkStageSkewTab
              selectedStage={selectedStage}
              stages={stages}
              selectedStageId={selectedStageId}
              onSelectStage={setSelectedStageId}
            />
    
            {/* TAB 3 & 4: Executors + AQE tabs */}
            <SparkExecutorsAqe />
          </Tabs>
        </div>
      )
    }
    
    export default SparkJobStageVisualizer
    
  • components/blocks/SparkJobKpis.tsx4.5 kB
  • components/blocks/SparkStageDagTab.tsx22.1 kB
  • components/blocks/SparkStageSkewTab.tsx12.9 kB
  • components/blocks/SparkExecutorsAqe.tsx11.3 kB
  • components/blocks/spark-stage-data.ts7 kB
  • components/blocks/spark-stage-types.ts1.5 kB

Raw manifest:https://uipkge.dev/r/react/spark-job-stage-visualizer.json