mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-06 05:35:46 +02:00
6ef78ab6c8
Crash clusters now grouped by error_class (not duplicated per version). Shows errors with skill, error class, count, failed step, example message, and unique session count — so you can tell if it's one user or widespread. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
168 lines
7.3 KiB
Bash
Executable File
168 lines
7.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# gstack-community-dashboard — community usage stats from Supabase
|
|
#
|
|
# Queries the Supabase REST API to show community-wide gstack usage:
|
|
# skill popularity, crash clusters, version distribution, retention.
|
|
#
|
|
# Env overrides (for testing):
|
|
# GSTACK_DIR — override auto-detected gstack root
|
|
# GSTACK_SUPABASE_URL — override Supabase project URL
|
|
# GSTACK_SUPABASE_ANON_KEY — override Supabase anon key
|
|
set -uo pipefail
|
|
|
|
GSTACK_DIR="${GSTACK_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
|
|
|
|
# Source Supabase config if not overridden by env
|
|
if [ -z "${GSTACK_SUPABASE_URL:-}" ] && [ -f "$GSTACK_DIR/supabase/config.sh" ]; then
|
|
. "$GSTACK_DIR/supabase/config.sh"
|
|
fi
|
|
SUPABASE_URL="${GSTACK_SUPABASE_URL:-}"
|
|
ANON_KEY="${GSTACK_SUPABASE_ANON_KEY:-}"
|
|
|
|
if [ -z "$SUPABASE_URL" ] || [ -z "$ANON_KEY" ]; then
|
|
echo "gstack community dashboard"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
echo "Supabase not configured yet. The community dashboard will be"
|
|
echo "available once the gstack Supabase project is set up."
|
|
echo ""
|
|
echo "For local analytics, run: gstack-analytics"
|
|
exit 0
|
|
fi
|
|
|
|
# ─── Helper: query Supabase REST API ─────────────────────────
|
|
query() {
|
|
local table="$1"
|
|
local params="${2:-}"
|
|
curl -sf --max-time 10 \
|
|
"${SUPABASE_URL}/rest/v1/${table}?${params}" \
|
|
-H "apikey: ${ANON_KEY}" \
|
|
-H "Authorization: Bearer ${ANON_KEY}" \
|
|
2>/dev/null || echo "[]"
|
|
}
|
|
|
|
echo "gstack community dashboard"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
|
|
# ─── Weekly active installs ──────────────────────────────────
|
|
WEEK_AGO="$(date -u -v-7d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "")"
|
|
if [ -n "$WEEK_AGO" ]; then
|
|
PULSE="$(curl -sf --max-time 10 \
|
|
"${SUPABASE_URL}/functions/v1/community-pulse" \
|
|
-H "Authorization: Bearer ${ANON_KEY}" \
|
|
2>/dev/null || echo '{"weekly_active":0}')"
|
|
|
|
WEEKLY="$(echo "$PULSE" | grep -o '"weekly_active":[0-9]*' | grep -o '[0-9]*' || echo "0")"
|
|
CHANGE="$(echo "$PULSE" | grep -o '"change_pct":[0-9-]*' | grep -o '[0-9-]*' || echo "0")"
|
|
|
|
echo "Weekly active installs: ${WEEKLY}"
|
|
if [ "$CHANGE" -gt 0 ] 2>/dev/null; then
|
|
echo " Change: +${CHANGE}%"
|
|
elif [ "$CHANGE" -lt 0 ] 2>/dev/null; then
|
|
echo " Change: ${CHANGE}%"
|
|
fi
|
|
echo ""
|
|
fi
|
|
|
|
# ─── Skill popularity (top 10) ───────────────────────────────
|
|
echo "Top skills (last 7 days)"
|
|
echo "────────────────────────"
|
|
|
|
# Query telemetry_events, group by skill
|
|
EVENTS="$(query "telemetry_events" "select=skill,gstack_version,session_id&event_type=eq.skill_run&event_timestamp=gte.${WEEK_AGO}&limit=1000" 2>/dev/null || echo "[]")"
|
|
|
|
if [ "$EVENTS" != "[]" ] && [ -n "$EVENTS" ]; then
|
|
echo "$EVENTS" | grep -o '"skill":"[^"]*"' | awk -F'"' '{print $4}' | sort | uniq -c | sort -rn | head -10 | while read -r COUNT SKILL; do
|
|
printf " /%-20s %d runs\n" "$SKILL" "$COUNT"
|
|
done
|
|
else
|
|
echo " No data yet"
|
|
fi
|
|
echo ""
|
|
|
|
# ─── Errors (last 7 days) ────────────────────────────────────
|
|
echo "Top errors (last 7 days)"
|
|
echo "────────────────────────"
|
|
|
|
ERRORS="$(query "telemetry_events" "select=skill,error_class,error_message,failed_step,duration_s,session_id&outcome=eq.error&event_timestamp=gte.${WEEK_AGO}&order=event_timestamp.desc&limit=200" 2>/dev/null || echo "[]")"
|
|
|
|
if [ "$ERRORS" != "[]" ] && [ -n "$ERRORS" ]; then
|
|
# Group by skill + error_class, show count and example message
|
|
echo "$ERRORS" | grep -o '"skill":"[^"]*"[^}]*"error_class":"[^"]*"' | \
|
|
sed 's/.*"skill":"//;s/".*"error_class":"/\t/' | sed 's/"$//' | \
|
|
sort | uniq -c | sort -rn | head -8 | while read -r COUNT COMBO; do
|
|
SKILL="$(echo "$COMBO" | cut -f1)"
|
|
ERR="$(echo "$COMBO" | cut -f2)"
|
|
# Find an example error_message for this combo
|
|
MSG="$(echo "$ERRORS" | grep -o "\"skill\":\"${SKILL}\"[^}]*\"error_message\":\"[^\"]*\"" | \
|
|
grep -o '"error_message":"[^"]*"' | head -1 | sed 's/"error_message":"//;s/"$//' || true)"
|
|
# Find an example failed_step
|
|
STEP="$(echo "$ERRORS" | grep -o "\"skill\":\"${SKILL}\"[^}]*\"failed_step\":\"[^\"]*\"" | \
|
|
grep -o '"failed_step":"[^"]*"' | head -1 | sed 's/"failed_step":"//;s/"$//' || true)"
|
|
|
|
printf " /%-12s %-18s %3d errors\n" "$SKILL" "${ERR:-unknown}" "$COUNT"
|
|
[ -n "$STEP" ] && printf " step: %s\n" "$STEP"
|
|
[ -n "$MSG" ] && printf " e.g.: %s\n" "$(echo "$MSG" | head -c 80)"
|
|
done
|
|
|
|
# Show how many unique sessions have errors
|
|
ERR_SESSIONS="$(echo "$ERRORS" | grep -o '"session_id":"[^"]*"' | sort -u | wc -l | tr -d ' ')"
|
|
echo ""
|
|
echo " ${ERR_SESSIONS} unique session(s) with errors"
|
|
else
|
|
echo " No errors reported"
|
|
fi
|
|
echo ""
|
|
|
|
# ─── Version distribution ────────────────────────────────────
|
|
echo "Version distribution (last 7 days)"
|
|
echo "───────────────────────────────────"
|
|
|
|
if [ "$EVENTS" != "[]" ] && [ -n "$EVENTS" ]; then
|
|
echo "$EVENTS" | grep -o '"gstack_version":"[^"]*"' | awk -F'"' '{print $4}' | sort | uniq -c | sort -rn | head -5 | while read -r COUNT VER; do
|
|
printf " v%-15s %d events\n" "$VER" "$COUNT"
|
|
done
|
|
else
|
|
echo " No data yet"
|
|
fi
|
|
|
|
# ─── Sessions (distinct session_id, works for all tiers) ────
|
|
echo "Sessions (last 7 days)"
|
|
echo "──────────────────────"
|
|
|
|
if [ "$EVENTS" != "[]" ] && [ -n "$EVENTS" ]; then
|
|
SESSION_COUNT="$(echo "$EVENTS" | grep -o '"session_id":"[^"]*"' | sort -u | wc -l | tr -d ' ')"
|
|
echo " ${SESSION_COUNT} unique sessions"
|
|
else
|
|
echo " No session data"
|
|
fi
|
|
echo ""
|
|
|
|
# ─── Skill recommendations ─────────────────────────────────
|
|
# Fetch top skills for recommendations
|
|
TOP_SKILLS="$(echo "$EVENTS" | grep -o '"skill":"[^"]*"' | awk -F'"' '{print $4}' | sort | uniq -c | sort -rn | head -3 | awk '{print $2}' | tr '\n' ',' | sed 's/,$//')"
|
|
|
|
if [ -n "$TOP_SKILLS" ]; then
|
|
RECS="$(curl -sf --max-time 10 \
|
|
"${SUPABASE_URL}/functions/v1/community-recommendations?skills=${TOP_SKILLS}" \
|
|
-H "Authorization: Bearer ${ANON_KEY}" \
|
|
2>/dev/null || echo '{"recommendations":[]}')"
|
|
|
|
REC_LIST="$(echo "$RECS" | grep -o '"skill":"[^"]*"' | awk -F'"' '{print $4}')"
|
|
REC_REASONS="$(echo "$RECS" | grep -o '"reason":"[^"]*"' | awk -F'"' '{print $4}')"
|
|
|
|
if [ -n "$REC_LIST" ]; then
|
|
echo "Skills you might like"
|
|
echo "─────────────────────"
|
|
paste <(echo "$REC_LIST") <(echo "$REC_REASONS") 2>/dev/null | while IFS=$'\t' read -r SKILL REASON; do
|
|
[ -z "$SKILL" ] && continue
|
|
printf " /%-20s %s\n" "$SKILL" "${REASON:-}"
|
|
done
|
|
echo ""
|
|
fi
|
|
fi
|
|
|
|
echo "For local analytics: gstack-analytics"
|
|
echo "For benchmarks: gstack-community-benchmarks"
|