Team Ai
Apppublic

harshapitla/github-actions-analytics

sourceHugging Faceupdated 10mo agoView on Hugging Face
0likes
KpiOverview.jsx242 linesDownload Raw Back to overview
1import React, { useMemo } from "react";2import "./overview.css";3 4/* ================= AUTHOR EXTRACTOR ================= */5function extractAuthor(row) {6  if (!row.author_profile_link) return "missing_author";7 8  try {9    const url = new URL(row.author_profile_link);10    const parts = url.pathname.split("/").filter(Boolean);11    return parts[0] || "missing_author";12  } catch {13    return "missing_author";14  }15}16 17/* ================= KPI CARD ================= */18function KpiCard({ icon, title, value, subtitle, accent }) {19  return (20    <div className={`kpi-card ${accent || ""}`}>21      <div className="kpi-header">22        <span className="kpi-icon">{icon}</span>23        <span className="kpi-title">{title}</span>24      </div>25      <div className="kpi-value">{value}</div>26      {subtitle && <div className="kpi-sub">{subtitle}</div>}27    </div>28  );29}30 31/* ================= PROGRESS ROW ================= */32function ProgressRow({ index, name, value, max, unit }) {33  const pct = Math.min((value / max) * 100, 100);34 35  return (36    <div className="progress-row">37      <div className="progress-top">38        <span className="progress-rank">{index}.</span>39        <span className="progress-name" title={name}>40          {name}41        </span>42        <span className="progress-value">43          {value.toLocaleString()} {unit}44        </span>45      </div>46 47      <div className="progress-bar">48        <div className="progress-fill" style={{ width: `${pct}%` }} />49      </div>50    </div>51  );52}53 54/* ================= RANK CARD ================= */55function RankedCard({ title, subtitle, items, unit }) {56  const max = Math.max(...items.map((i) => i.value), 1);57 58  return (59    <div className="rank-card">60      <div className="rank-header">61        <div>62          <h3>{title}</h3>63          <p>{subtitle}</p>64        </div>65      </div>66 67      {items.map((item, i) => (68        <ProgressRow69          key={i}70          index={i + 1}71          name={item.name}72          value={item.value}73          max={max}74          unit={unit}75        />76      ))}77    </div>78  );79}80 81/* ================= MAIN OVERVIEW ================= */82export default function KpiOverview({ rows = [] }) {83  const metrics = useMemo(() => {84    if (!rows.length) return null;85 86    let totalStars = 0;87    let missingAuthorCount = 0;88    let withVersionCount = 0;89 90    const authors = new Map();91    const tags = new Map();92 93    rows.forEach((r) => {94      const stars = Number(r.no_of_stars) || 0;95      totalStars += stars;96 97      // Author98      const author = extractAuthor(r);99      if (author === "missing_author") {100        missingAuthorCount += 1;101      } else {102        authors.set(author, (authors.get(author) || 0) + stars);103      }104 105      // Version presence106      if (r.latest_version && String(r.latest_version).trim() !== "") {107        withVersionCount += 1;108      }109 110      // Tags111      if (r.tags) {112        r.tags.split(",").forEach((t) => {113          const tag = t.trim().toLowerCase();114          if (tag) tags.set(tag, (tags.get(tag) || 0) + 1);115        });116      }117    });118 119    // Top 10% star share120    const sortedStars = rows121      .map((r) => Number(r.no_of_stars) || 0)122      .sort((a, b) => b - a);123 124    const top10Count = Math.ceil(sortedStars.length * 0.1);125    const top10Stars = sortedStars126      .slice(0, top10Count)127      .reduce((a, b) => a + b, 0);128 129    return {130      totalActions: rows.length,131      totalStars,132      avgStars: totalStars / rows.length,133      top10Share: (top10Stars / totalStars) * 100,134 135      uniqueAuthors: authors.size,136      missingAuthorPct: (missingAuthorCount / rows.length) * 100,137      versionCoveragePct: (withVersionCount / rows.length) * 100,138 139      topAuthors: [...authors.entries()]140        .sort((a, b) => b[1] - a[1])141        .slice(0, 3)142        .map(([name, value]) => ({ name, value })),143 144      topActions: [...rows]145        .filter((r) => r.no_of_stars > 0)146        .sort((a, b) => b.no_of_stars - a.no_of_stars)147        .slice(0, 3)148        .map((r) => ({149          name: r.action_title,150          value: r.no_of_stars,151        })),152 153      topTags: [...tags.entries()]154        .sort((a, b) => b[1] - a[1])155        .slice(0, 3)156        .map(([name, value]) => ({ name, value })),157    };158  }, [rows]);159 160  if (!metrics) return null;161 162  return (163    <>164      {/* ================= CONTEXT ================= */}165      <div className="snapshot-banner">166        <strong>Data snapshot</strong> โ€” based on latest available action167        metadata168      </div>169 170      {/* ================= KPI GRID ================= */}171      <div className="kpi-grid">172        <KpiCard icon="๐Ÿ“ฆ" title="Total Actions" value={metrics.totalActions} />173 174        <KpiCard175          icon="๐Ÿ‘ค"176          title="Unique Authors"177          value={metrics.uniqueAuthors}178          subtitle="Publishers with valid profiles"179        />180 181        <KpiCard182          icon="โญ"183          title="Total Stars"184          value={metrics.totalStars.toLocaleString()}185        />186 187        <KpiCard188          icon="๐Ÿ“Š"189          title="Avg Stars per Action"190          value={metrics.avgStars.toFixed(1)}191        />192 193        <KpiCard194          icon="๐Ÿ”ฅ"195          title="Top 10% Star Share"196          value={`${metrics.top10Share.toFixed(1)}%`}197          subtitle="Star concentration"198          accent="accent-warning"199        />200 201        <KpiCard202          icon="๐Ÿ“ฆ"203          title="Version Coverage"204          value={`${metrics.versionCoveragePct.toFixed(1)}%`}205          subtitle="Actions with a version listed"206        />207 208        <KpiCard209          icon="โš ๏ธ"210          title="Missing Author Info"211          value={`${metrics.missingAuthorPct.toFixed(1)}%`}212          subtitle="Actions without author profile"213        />214      </div>215 216      {/* ================= RANKED SECTIONS ================= */}217      <div className="rank-grid">218        <RankedCard219          title="Top Authors"220          subtitle="Publishers with highest total stars"221          items={metrics.topAuthors}222          unit="stars"223        />224 225        <RankedCard226          title="Top Actions"227          subtitle="Most starred actions"228          items={metrics.topActions}229          unit="stars"230        />231 232        <RankedCard233          title="Top Tags"234          subtitle="Most commonly used categories"235          items={metrics.topTags}236          unit="actions"237        />238      </div>239    </>240  );241}242