diff --git a/community-dashboard/.dockerignore b/community-dashboard/.dockerignore new file mode 100644 index 0000000..c08d1f7 --- /dev/null +++ b/community-dashboard/.dockerignore @@ -0,0 +1,3 @@ +cdk/cdk.out +cdk/node_modules +.git diff --git a/community-dashboard/.gitignore b/community-dashboard/.gitignore new file mode 100644 index 0000000..b501a16 --- /dev/null +++ b/community-dashboard/.gitignore @@ -0,0 +1,14 @@ +# SQLite database with metrics data (generated by strands-metrics) +metrics.db + +# Rust build artifacts +strands-metrics/target/ + +# CDK +cdk/node_modules/ +cdk/cdk.out/ +cdk/dist/ +cdk/.env + +# Docker local data +docker/data/ diff --git a/community-dashboard/README.md b/community-dashboard/README.md new file mode 100644 index 0000000..ce1493f --- /dev/null +++ b/community-dashboard/README.md @@ -0,0 +1,130 @@ +# community-dashboard + +GitHub metrics collection and Grafana dashboards for the [strands-agents](https://github.com/strands-agents) organization. + +A unified Docker container syncs GitHub data (issues, PRs, stars, commits, CI runs, reviews, comments) into a local SQLite database on a daily cron schedule, and serves pre-built Grafana dashboards for org-wide health and triage visibility. + + +## Directory Structure + +``` +community-dashboard/ +├── README.md ← you are here +├── docker/ +│ ├── Dockerfile ← unified Grafana + metrics-sync image +│ ├── entrypoint.sh ← initial backfill, cron, then Grafana +│ └── docker-compose.local.yaml ← local dev compose +├── provisioning/ ← Grafana auto-provisioning +│ ├── datasources/ +│ │ └── automatic.yaml ← SQLite datasource +│ └── dashboards/ +│ ├── dashboards.yaml ← dashboard provider config +│ ├── health.json ← org health dashboard +│ └── triage.json ← triage dashboard +├── strands-metrics/ ← Rust CLI (syncs GitHub → SQLite) +│ ├── Cargo.toml +│ └── src/ +│ ├── main.rs +│ ├── client.rs +│ ├── db.rs +│ └── aggregates.rs +└── cdk/ ← AWS CDK deployment + ├── bin/app.ts + ├── lib/community-dashboard-stack.ts + ├── package.json + ├── tsconfig.json + ├── cdk.json + └── .env.example +``` + +## Prerequisites + +| Tool | Purpose | +|------|---------| +| **Docker** + **Docker Compose** | Build & run the unified container | +| **GitHub PAT** | Token with read access to the `strands-agents` org (public repos) | +| **Node.js** ≥ 18 | CDK CLI (AWS deployment only) | +| **AWS CDK CLI** | `npm install -g aws-cdk` (AWS deployment only) | +| **Rust toolchain** | Only needed if building strands-metrics locally outside Docker | + +## Local Development + +Build and run the unified container locally: + +```bash +cd community-dashboard +GITHUB_TOKEN=ghp_your_token docker compose -f docker/docker-compose.local.yaml up --build +``` + +On first start the container will: +1. Run a full GitHub sync (this takes a few minutes) +2. Start a daily cron job (06:00 UTC) for incremental syncs +3. Launch Grafana + +Open [http://localhost:3000](http://localhost:3000) — no login required (anonymous read-only). + +The SQLite database is persisted in `docker/data/` on the host so subsequent restarts skip the initial backfill. + +### Running strands-metrics standalone + +If you prefer to run the Rust CLI directly (without Docker): + +```bash +cd strands-metrics +GITHUB_TOKEN=ghp_xxx cargo run --release -- sync # full/incremental sync +GITHUB_TOKEN=ghp_xxx cargo run --release -- sweep # reconcile stale open items +cargo run --release -- query "SELECT date, stars FROM daily_metrics WHERE repo='sdk-python' ORDER BY date DESC LIMIT 10" +``` + +By default the CLI writes to `../metrics.db` (the `community-dashboard/` root). + +## AWS Deployment + +The CDK stack deploys everything to AWS as a single Fargate service with EFS-backed persistent storage: + +``` +CloudFront (HTTPS) → ALB (HTTP:80) → ECS Fargate → unified Docker image → EFS (metrics.db) +``` + +### 1. Create the GitHub token secret + +```bash +aws secretsmanager create-secret \ + --name strands-grafana/github-token \ + --secret-string "ghp_your_token" \ + --region us-west-2 +``` + +### 2. Configure and deploy + +```bash +cd cdk +cp .env.example .env +# Edit .env — set GITHUB_SECRET_ARN to the ARN from step 1 + +npm install +npx cdk deploy +``` + +The stack creates: +- **VPC** (2 AZs, 1 NAT gateway) +- **EFS** file system with access point at `/grafana-data` (RETAIN policy) +- **ECS Fargate** service (0.5 vCPU, 1 GB RAM) +- **ALB** on port 80 with health check at `/api/health` +- **CloudFront** distribution for HTTPS access + +The Grafana URL (HTTPS) is printed in the stack outputs. + +### Tear down + +```bash +cd cdk +npx cdk destroy +``` + +> **Note:** The EFS file system has a RETAIN removal policy — delete it manually if you want to remove the data. + +## Dashboards + +- **Health** — org-wide metrics: stars, open issues & PRs, merge times (internal vs external), CI health, code churn, time-to-first-response +- **Triage** — focused view for issue/PR triage workflows diff --git a/community-dashboard/cdk/.env.example b/community-dashboard/cdk/.env.example new file mode 100644 index 0000000..ee01d65 --- /dev/null +++ b/community-dashboard/cdk/.env.example @@ -0,0 +1,3 @@ +# ARN of the Secrets Manager secret holding the GitHub personal access token. +# The secret value should be a plain-text token (not JSON). +GITHUB_SECRET_ARN=arn:aws:secretsmanager:us-west-2:ACCOUNT:secret:strands-grafana/github-token diff --git a/community-dashboard/cdk/bin/app.ts b/community-dashboard/cdk/bin/app.ts new file mode 100644 index 0000000..ef3cbcf --- /dev/null +++ b/community-dashboard/cdk/bin/app.ts @@ -0,0 +1,24 @@ +#!/usr/bin/env node +import "source-map-support/register"; +import * as dotenv from "dotenv"; +import * as cdk from "aws-cdk-lib"; +import { CommunityDashboardStack } from "../lib/community-dashboard-stack"; + +// Load environment variables from .env file (if present) +dotenv.config(); + +const app = new cdk.App(); + +const env = { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION ?? process.env.AWS_REGION ?? "us-west-2", +}; + +new CommunityDashboardStack(app, "CommunityDashboardStack", { + env, + description: + "Community Dashboard — GitHub metrics collection and dashboards for strands-agents org", + tags: { + Project: "community-dashboard", + }, +}); diff --git a/community-dashboard/cdk/cdk.json b/community-dashboard/cdk/cdk.json new file mode 100644 index 0000000..c820084 --- /dev/null +++ b/community-dashboard/cdk/cdk.json @@ -0,0 +1,26 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/app.ts", + "watch": { + "include": ["**"], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": ["aws"], + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false + } +} diff --git a/community-dashboard/cdk/lib/community-dashboard-stack.ts b/community-dashboard/cdk/lib/community-dashboard-stack.ts new file mode 100644 index 0000000..2d96771 --- /dev/null +++ b/community-dashboard/cdk/lib/community-dashboard-stack.ts @@ -0,0 +1,209 @@ +import * as cdk from "aws-cdk-lib"; +import * as ec2 from "aws-cdk-lib/aws-ec2"; +import * as ecs from "aws-cdk-lib/aws-ecs"; +import * as efs from "aws-cdk-lib/aws-efs"; +import * as elbv2 from "aws-cdk-lib/aws-elasticloadbalancingv2"; +import * as cloudfront from "aws-cdk-lib/aws-cloudfront"; +import * as origins from "aws-cdk-lib/aws-cloudfront-origins"; +import * as logs from "aws-cdk-lib/aws-logs"; +import * as secretsmanager from "aws-cdk-lib/aws-secretsmanager"; +import { Construct } from "constructs"; +import * as path from "path"; + +export class CommunityDashboardStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // ── Secrets Manager ────────────────────────────────────────────────── + // The GitHub PAT must already exist in Secrets Manager as a plain-text + // secret. Pass the ARN via the GITHUB_SECRET_ARN env var or CDK context. + const secretArn = + process.env.GITHUB_SECRET_ARN ?? + this.node.tryGetContext("githubSecretArn"); + + if (!secretArn) { + throw new Error( + "GITHUB_SECRET_ARN environment variable or 'githubSecretArn' CDK context must be set.\n" + + "Create the secret first:\n" + + ' aws secretsmanager create-secret --name strands-grafana/github-token --secret-string "ghp_xxx" --region us-west-2' + ); + } + + const githubSecret = secretsmanager.Secret.fromSecretCompleteArn( + this, + "GitHubTokenSecret", + secretArn + ); + + // ── VPC ────────────────────────────────────────────────────────────── + const vpc = new ec2.Vpc(this, "Vpc", { + maxAzs: 2, + natGateways: 1, + }); + + // ── EFS (persistent storage for metrics.db) ───────────────────────── + const fileSystem = new efs.FileSystem(this, "MetricsFs", { + vpc, + removalPolicy: cdk.RemovalPolicy.RETAIN, + performanceMode: efs.PerformanceMode.GENERAL_PURPOSE, + lifecyclePolicy: efs.LifecyclePolicy.AFTER_30_DAYS, + encrypted: true, + }); + + const accessPoint = fileSystem.addAccessPoint("GrafanaData", { + path: "/grafana-data", + createAcl: { + ownerUid: "0", + ownerGid: "0", + permissions: "755", + }, + posixUser: { + uid: "0", + gid: "0", + }, + }); + + // ── ECS Cluster ───────────────────────────────────────────────────── + const cluster = new ecs.Cluster(this, "Cluster", { + vpc, + containerInsights: true, + }); + + // ── Task Definition ───────────────────────────────────────────────── + const taskDef = new ecs.FargateTaskDefinition(this, "TaskDef", { + cpu: 512, + memoryLimitMiB: 1024, + }); + + // Mount EFS volume + taskDef.addVolume({ + name: "metrics-data", + efsVolumeConfiguration: { + fileSystemId: fileSystem.fileSystemId, + transitEncryption: "ENABLED", + authorizationConfig: { + accessPointId: accessPoint.accessPointId, + iam: "ENABLED", + }, + }, + }); + + // Grant EFS access to the task role + fileSystem.grant( + taskDef.taskRole, + "elasticfilesystem:ClientMount", + "elasticfilesystem:ClientWrite", + "elasticfilesystem:ClientRootAccess" + ); + + // Container definition — built from the unified Dockerfile + const container = taskDef.addContainer("grafana", { + image: ecs.ContainerImage.fromAsset( + path.join(__dirname, "../../"), + { + file: "docker/Dockerfile", + platform: cdk.aws_ecr_assets.Platform.LINUX_AMD64, + } + ), + logging: ecs.LogDrivers.awsLogs({ + streamPrefix: "community-dashboard", + logRetention: logs.RetentionDays.TWO_WEEKS, + }), + portMappings: [{ containerPort: 3000 }], + secrets: { + GITHUB_TOKEN: ecs.Secret.fromSecretsManager(githubSecret), + }, + healthCheck: { + command: [ + "CMD-SHELL", + "wget -qO- http://localhost:3000/api/health || exit 1", + ], + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + retries: 3, + startPeriod: cdk.Duration.seconds(120), + }, + }); + + container.addMountPoints({ + sourceVolume: "metrics-data", + containerPath: "/var/lib/grafana/data", + readOnly: false, + }); + + // ── Fargate Service + ALB ─────────────────────────────────────────── + const service = new ecs.FargateService(this, "Service", { + cluster, + taskDefinition: taskDef, + desiredCount: 1, + assignPublicIp: false, + platformVersion: ecs.FargatePlatformVersion.LATEST, + }); + + // Allow the service to reach EFS + service.connections.allowTo(fileSystem, ec2.Port.tcp(2049), "EFS access"); + + // Application Load Balancer + const alb = new elbv2.ApplicationLoadBalancer(this, "Alb", { + vpc, + internetFacing: true, + }); + + const listener = alb.addListener("HttpListener", { + port: 80, + }); + + listener.addTargets("GrafanaTarget", { + port: 3000, + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [service], + healthCheck: { + path: "/api/health", + interval: cdk.Duration.seconds(30), + healthyThresholdCount: 2, + unhealthyThresholdCount: 3, + }, + deregistrationDelay: cdk.Duration.seconds(30), + }); + + // ── Outputs ───────────────────────────────────────────────────────── + // CloudFront distribution — provides HTTPS on *.cloudfront.net + const distribution = new cloudfront.Distribution(this, "Distribution", { + defaultBehavior: { + origin: new origins.HttpOrigin(alb.loadBalancerDnsName, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + cachePolicy: cloudfront.CachePolicy.CACHING_DISABLED, + originRequestPolicy: cloudfront.OriginRequestPolicy.ALL_VIEWER, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + }, + }); + + new cdk.CfnOutput(this, "GrafanaUrl", { + value: `https://${distribution.distributionDomainName}`, + description: "Grafana dashboard URL (HTTPS via CloudFront)", + }); + + new cdk.CfnOutput(this, "AlbUrl", { + value: `http://${alb.loadBalancerDnsName}`, + description: "Grafana dashboard URL (ALB, HTTP only)", + }); + + new cdk.CfnOutput(this, "EfsFileSystemId", { + value: fileSystem.fileSystemId, + description: "EFS file system ID (persistent metrics.db storage)", + }); + + new cdk.CfnOutput(this, "ClusterArn", { + value: cluster.clusterArn, + description: "ECS cluster ARN", + }); + + new cdk.CfnOutput(this, "CreateSecretCommand", { + value: + 'aws secretsmanager create-secret --name strands-grafana/github-token --secret-string "ghp_xxx" --region us-west-2', + description: "Command to create the GitHub token secret (one-time)", + }); + } +} diff --git a/community-dashboard/cdk/package.json b/community-dashboard/cdk/package.json new file mode 100644 index 0000000..4f8d12e --- /dev/null +++ b/community-dashboard/cdk/package.json @@ -0,0 +1,26 @@ +{ + "name": "community-dashboard-cdk", + "version": "1.0.0", + "description": "AWS CDK infrastructure for community dashboard — GitHub metrics and Grafana", + "bin": { + "cdk": "bin/app.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "cdk": "cdk", + "synth": "cdk synth", + "deploy": "cdk deploy", + "destroy": "cdk destroy" + }, + "dependencies": { + "aws-cdk-lib": "^2.170.0", + "constructs": "^10.0.0", + "dotenv": "^16.0.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "aws-cdk": "^2.170.0", + "typescript": "^5.0.0" + } +} diff --git a/community-dashboard/cdk/tsconfig.json b/community-dashboard/cdk/tsconfig.json new file mode 100644 index 0000000..10368b0 --- /dev/null +++ b/community-dashboard/cdk/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": ["./node_modules/@types"], + "outDir": "dist", + "rootDir": "." + }, + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/community-dashboard/docker/Dockerfile b/community-dashboard/docker/Dockerfile new file mode 100644 index 0000000..a1adb4a --- /dev/null +++ b/community-dashboard/docker/Dockerfile @@ -0,0 +1,64 @@ +# ============================================================================= +# Stage 1: Build the strands-metrics Rust binary +# ============================================================================= +FROM rust:1.88-alpine AS builder + +RUN apk add --no-cache musl-dev pkgconfig openssl-dev openssl-libs-static + +WORKDIR /build + +# Copy the Rust project source +COPY strands-metrics/Cargo.toml strands-metrics/Cargo.lock* ./ +COPY strands-metrics/src/ ./src/ + +# Build in release mode (statically linked against musl) +RUN cargo build --release + +# ============================================================================= +# Stage 2: Unified Grafana + metrics sync image +# ============================================================================= +FROM grafana/grafana:latest + +USER root + +# Install ca-certificates and curl for downloading supercronic +RUN apk add --no-cache ca-certificates curl + +# Install supercronic (cron replacement designed for containers) +ARG SUPERCRONIC_VERSION=v0.2.43 +ARG SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/${SUPERCRONIC_VERSION}/supercronic-linux-amd64 +RUN curl -fsSL "${SUPERCRONIC_URL}" -o /usr/local/bin/supercronic \ + && chmod +x /usr/local/bin/supercronic + +# Copy the compiled strands-metrics binary +COPY --from=builder /build/target/release/strands-metrics /usr/local/bin/strands-metrics + +# Copy Grafana provisioning configs +COPY provisioning/datasources/ /etc/grafana/provisioning/datasources/ +COPY provisioning/dashboards/ /etc/grafana/provisioning/dashboards/ + +# Copy entrypoint script +COPY docker/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +# Create the data directory for EFS / local mount +RUN mkdir -p /var/lib/grafana/data + +# Bundle a pre-built metrics.db so the first boot doesn't need a long backfill. +# The entrypoint will copy this to the EFS volume if no DB exists yet. +# The wildcard pattern makes this a no-op if the file doesn't exist. +COPY docker/data/metrics.d[b] /seed/ + +# Grafana configuration via environment variables +ENV GF_INSTALL_PLUGINS=frser-sqlite-datasource +ENV GF_PLUGIN_FRSER_SQLITE_DATASOURCE_ALLOW_LOCAL_MODE=true +ENV GF_AUTH_ANONYMOUS_ENABLED=true +ENV GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer +ENV GF_AUTH_BASIC_ENABLED=false +ENV GF_AUTH_DISABLE_LOGIN_FORM=true +ENV GF_USERS_ALLOW_SIGN_UP=false +ENV GF_SECURITY_ALLOW_EMBEDDING=true + +EXPOSE 3000 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/community-dashboard/docker/docker-compose.local.yaml b/community-dashboard/docker/docker-compose.local.yaml new file mode 100644 index 0000000..0bc3f43 --- /dev/null +++ b/community-dashboard/docker/docker-compose.local.yaml @@ -0,0 +1,21 @@ +# Local development docker-compose +# Build and run the unified Grafana + metrics-sync container locally. +# +# Usage: +# GITHUB_TOKEN=ghp_xxx docker compose -f docker/docker-compose.local.yaml up --build +# +# Then open http://localhost:3000 + +services: + grafana: + build: + context: .. + dockerfile: docker/Dockerfile + container_name: community-dashboard + ports: + - "3000:3000" + volumes: + - ./data:/var/lib/grafana/data + environment: + - GITHUB_TOKEN=${GITHUB_TOKEN} + restart: unless-stopped diff --git a/community-dashboard/docker/entrypoint.sh b/community-dashboard/docker/entrypoint.sh new file mode 100755 index 0000000..dacd09c --- /dev/null +++ b/community-dashboard/docker/entrypoint.sh @@ -0,0 +1,49 @@ +#!/bin/sh +set -e + +DB_PATH="/var/lib/grafana/data/metrics.db" + +# ── Initial backfill ──────────────────────────────────────────────────────── +# If the database doesn't exist or is empty on the EFS volume, seed it from +# the pre-built copy baked into the image. The daily cron handles incremental +# updates from there — no hours-long backfill needed on first boot. +SEED_PATH="/seed/metrics.db" + +if [ ! -f "$DB_PATH" ] || [ ! -s "$DB_PATH" ]; then + if [ -f "$SEED_PATH" ]; then + echo "[entrypoint] Seeding metrics.db from bundled snapshot..." + cp "$SEED_PATH" "$DB_PATH" + echo "[entrypoint] Seed copy complete ($(du -h "$DB_PATH" | cut -f1))." + # Run a quick incremental sync to pick up anything newer than the snapshot + if [ -n "$GITHUB_TOKEN" ]; then + echo "[entrypoint] Running incremental sync to catch up..." + strands-metrics --db-path "$DB_PATH" sync || \ + echo "[entrypoint] WARNING: Incremental sync failed (will retry on next cron run)." + fi + else + echo "[entrypoint] No seed DB found — running full sync..." + if [ -z "$GITHUB_TOKEN" ]; then + echo "[entrypoint] WARNING: GITHUB_TOKEN is not set. Skipping sync." + else + strands-metrics --db-path "$DB_PATH" sync || \ + echo "[entrypoint] WARNING: Initial sync failed (will retry on next cron run)." + fi + fi +else + echo "[entrypoint] metrics.db already exists with data — skipping seed." +fi + +# ── Cron schedule ─────────────────────────────────────────────────────────── +# Sync daily at 06:00 UTC. Output is forwarded to container stdout/stderr +# via /proc/1/fd/1 so it shows up in docker logs / CloudWatch. +CRONTAB="/tmp/crontab" +cat > "$CRONTAB" <<'EOF' +0 6 * * * strands-metrics --db-path /var/lib/grafana/data/metrics.db sync >> /proc/1/fd/1 2>&1 +EOF + +echo "[entrypoint] Starting supercronic (daily sync at 06:00 UTC)..." +supercronic "$CRONTAB" & + +# ── Start Grafana ─────────────────────────────────────────────────────────── +echo "[entrypoint] Launching Grafana..." +exec /run.sh diff --git a/community-dashboard/provisioning/dashboards/dashboards.yaml b/community-dashboard/provisioning/dashboards/dashboards.yaml new file mode 100644 index 0000000..0a19b11 --- /dev/null +++ b/community-dashboard/provisioning/dashboards/dashboards.yaml @@ -0,0 +1,11 @@ +apiVersion: 1 + +providers: + - name: "DevEx Dashboards" + orgId: 1 + folder: "" + type: file + disableDeletion: false + updateIntervalSeconds: 60 + options: + path: /etc/grafana/provisioning/dashboards diff --git a/community-dashboard/provisioning/dashboards/health.json b/community-dashboard/provisioning/dashboards/health.json new file mode 100644 index 0000000..1508b03 --- /dev/null +++ b/community-dashboard/provisioning/dashboards/health.json @@ -0,0 +1,12577 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 4, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 100, + "panels": [], + "title": "Volume", + "type": "row" + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value sdk-python" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1 + }, + "id": 11, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "queryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time, \n repo as metric, \n open_prs_count as value \nFROM daily_metrics \nWHERE repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging') \nORDER BY date ASC", + "queryType": "table", + "rawQueryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time, \n repo as metric, \n open_prs_count as value \nFROM daily_metrics \nWHERE repo IN (${repo:singlequote}) \nORDER BY date ASC", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "Total Open PRs", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "multi" + } + } + ], + "type": "timeseries", + "description": "Total count of open pull requests." + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value sdk-python" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 1 + }, + "id": 20, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "queryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time, \n repo as metric, \n open_issues_count as value \nFROM daily_metrics \nWHERE repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging') \nORDER BY date ASC", + "queryType": "table", + "rawQueryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time, \n repo as metric, \n open_issues_count as value \nFROM daily_metrics \nWHERE repo IN (${repo:singlequote}) \nORDER BY date ASC", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "Total Open Issues", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "multi" + } + } + ], + "type": "timeseries", + "description": "Total count of open issues (excluding pull requests)." + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value sdk-python" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 23, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "never", + "stacking": "normal", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 100 + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "queryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time, \n repo as metric, \n prs_opened as value \nFROM daily_metrics \nWHERE repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging') \n AND CAST(strftime('%s', date) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date) as INTEGER) <= $__to / 1000\nORDER BY date ASC", + "queryType": "table", + "rawQueryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time, \n repo as metric, \n prs_opened as value \nFROM daily_metrics \nWHERE repo IN (${repo:singlequote}) \n AND CAST(strftime('%s', date) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date) as INTEGER) <= $__to / 1000\nORDER BY date ASC", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "PRs Opened (Daily)", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "multi" + } + } + ], + "type": "barchart", + "description": "Number of new pull requests opened per day." + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value sdk-python" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 2, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "never", + "stacking": "normal", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 100 + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "queryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time, \n repo as metric, \n issues_opened as value \nFROM daily_metrics \nWHERE repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging') \n AND CAST(strftime('%s', date) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date) as INTEGER) <= $__to / 1000\nORDER BY date ASC", + "queryType": "table", + "rawQueryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time, \n repo as metric, \n issues_opened as value \nFROM daily_metrics \nWHERE repo IN (${repo:singlequote}) \n AND CAST(strftime('%s', date) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date) as INTEGER) <= $__to / 1000\nORDER BY date ASC", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "Issues Opened (Daily)", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "multi" + } + } + ], + "type": "barchart", + "description": "Number of new issues opened per day." + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value sdk-python" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 1, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "never", + "stacking": "normal", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 100 + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "queryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time, \n repo as metric, \n prs_merged as value \nFROM daily_metrics \nWHERE repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging') \n AND prs_merged > 0\n AND CAST(strftime('%s', date) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date) as INTEGER) <= $__to / 1000\nORDER BY date ASC", + "queryType": "table", + "rawQueryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time, \n repo as metric, \n prs_merged as value \nFROM daily_metrics \nWHERE repo IN (${repo:singlequote}) \n AND prs_merged > 0\n AND CAST(strftime('%s', date) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date) as INTEGER) <= $__to / 1000\nORDER BY date ASC", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "PRs Merged (Daily)", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "multi" + } + } + ], + "type": "barchart", + "description": "Number of pull requests merged per day." + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value sdk-python" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 3, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "never", + "stacking": "normal", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 100 + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "queryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time, \n repo as metric, \n issues_closed as value \nFROM daily_metrics \nWHERE repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging') \n AND CAST(strftime('%s', date) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date) as INTEGER) <= $__to / 1000\nORDER BY date ASC", + "queryType": "table", + "rawQueryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time, \n repo as metric, \n issues_closed as value \nFROM daily_metrics \nWHERE repo IN (${repo:singlequote}) \n AND CAST(strftime('%s', date) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date) as INTEGER) <= $__to / 1000\nORDER BY date ASC", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "Issues Closed (Daily)", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "multi" + } + } + ], + "type": "barchart", + "description": "Number of issues closed per day." + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": true, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value sdk-python" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 24 + }, + "id": 16, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "queryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time,\n repo as metric,\n open_items_count - LAG(open_items_count, 7) OVER (PARTITION BY repo ORDER BY date) as value\nFROM daily_metrics\nWHERE repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging')\n AND CAST(strftime('%s', date) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date) as INTEGER) <= $__to / 1000\nORDER BY date ASC", + "queryType": "table", + "rawQueryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time,\n repo as metric,\n open_items_count - LAG(open_items_count, 7) OVER (PARTITION BY repo ORDER BY date) as value\nFROM daily_metrics\nWHERE repo IN (${repo:singlequote})\n AND CAST(strftime('%s', date) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date) as INTEGER) <= $__to / 1000\nORDER BY date ASC", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "Backlog Growth (Week-over-Week)", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "multi" + } + } + ], + "type": "timeseries", + "description": "Week-over-week change in total open items (issues + PRs). Positive = backlog growing, negative = backlog shrinking." + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 34 + }, + "id": 102, + "panels": [], + "title": "Quality", + "type": "row" + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value sdk-python" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 35 + }, + "id": 13, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "queryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time,\n repo as metric,\n AVG(ROUND(CAST(ci_failures AS FLOAT) / NULLIF(ci_runs, 0) * 100, 2)) \n OVER (PARTITION BY repo ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) as value\nFROM daily_metrics\nWHERE ci_runs > 0\n AND repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging')\n AND CAST(strftime('%s', date) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date) as INTEGER) <= $__to / 1000\nORDER BY date ASC", + "queryType": "table", + "rawQueryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time,\n repo as metric,\n AVG(ROUND(CAST(ci_failures AS FLOAT) / NULLIF(ci_runs, 0) * 100, 2)) \n OVER (PARTITION BY repo ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) as value\nFROM daily_metrics\nWHERE ci_runs > 0\n AND repo IN (${repo:singlequote})\n AND CAST(strftime('%s', date) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date) as INTEGER) <= $__to / 1000\nORDER BY date ASC", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "CI Failure Rate (7-Day Avg)", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "multi" + } + } + ], + "type": "timeseries", + "description": "7-day rolling average of CI failure rate: (failed CI runs / total CI runs) \u00d7 100%. Shows percentage of CI runs that failed." + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent", + "min": 0, + "max": 100 + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value sdk-python" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 35 + }, + "id": 15, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "queryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time,\n repo as metric,\n AVG(\n CAST(merged_count AS FLOAT) / NULLIF(total_count, 0) * 100\n ) OVER (PARTITION BY repo ORDER BY date ROWS BETWEEN 29 PRECEDING AND CURRENT ROW) as value\nFROM (\n SELECT \n date(COALESCE(merged_at, closed_at)) as date,\n repo,\n SUM(CASE WHEN merged_at IS NOT NULL THEN 1 ELSE 0 END) as merged_count,\n COUNT(*) as total_count\n FROM pull_requests\n WHERE (merged_at IS NOT NULL OR (state = 'closed' AND merged_at IS NULL))\n AND repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging')\n GROUP BY date(COALESCE(merged_at, closed_at)), repo\n) subquery\nWHERE CAST(strftime('%s', date) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date) as INTEGER) <= $__to / 1000\nORDER BY date ASC", + "queryType": "table", + "rawQueryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time,\n repo as metric,\n AVG(\n CAST(merged_count AS FLOAT) / NULLIF(total_count, 0) * 100\n ) OVER (PARTITION BY repo ORDER BY date ROWS BETWEEN 29 PRECEDING AND CURRENT ROW) as value\nFROM (\n SELECT \n date(COALESCE(merged_at, closed_at)) as date,\n repo,\n SUM(CASE WHEN merged_at IS NOT NULL THEN 1 ELSE 0 END) as merged_count,\n COUNT(*) as total_count\n FROM pull_requests\n WHERE (merged_at IS NOT NULL OR (state = 'closed' AND merged_at IS NULL))\n AND repo IN (${repo:singlequote})\n GROUP BY date(COALESCE(merged_at, closed_at)), repo\n) subquery\nWHERE CAST(strftime('%s', date) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date) as INTEGER) <= $__to / 1000\nORDER BY date ASC", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "PR Acceptance Rate (30-Day Avg)", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "multi" + } + } + ], + "type": "timeseries", + "description": "30-day rolling average of PR acceptance rate: (PRs merged / PRs opened) \u00d7 100%. Shows what percentage of opened PRs ultimately get merged." + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 41 + }, + "id": 101, + "panels": [], + "title": "Speed", + "type": "row" + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "h" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value sdk-python" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 42 + }, + "id": 21, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "queryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time, \n repo as metric, \n AVG(time_to_first_response) OVER (PARTITION BY repo ORDER BY date ROWS BETWEEN 30 PRECEDING AND CURRENT ROW) as value \nFROM daily_metrics WHERE repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging') AND time_to_first_response > 0\nORDER BY time ASC", + "queryType": "table", + "rawQueryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time, \n repo as metric, \n AVG(time_to_first_response) OVER (PARTITION BY repo ORDER BY date ROWS BETWEEN 30 PRECEDING AND CURRENT ROW) as value \nFROM daily_metrics WHERE repo IN (${repo:singlequote}) AND time_to_first_response > 0\nORDER BY time ASC", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "Time to First Response", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "multi" + } + } + ], + "type": "timeseries", + "description": "Average hours from issue/PR creation to first comment or review. 30-day rolling average." + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "h" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value sdk-python" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 42 + }, + "id": 22, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "queryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time, \n repo as metric, \n AVG(avg_issue_resolution_time) OVER (PARTITION BY repo ORDER BY date ROWS BETWEEN 30 PRECEDING AND CURRENT ROW) as value \nFROM daily_metrics WHERE repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging') AND avg_issue_resolution_time > 0\nORDER BY time ASC", + "queryType": "table", + "rawQueryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time, \n repo as metric, \n AVG(avg_issue_resolution_time) OVER (PARTITION BY repo ORDER BY date ROWS BETWEEN 30 PRECEDING AND CURRENT ROW) as value \nFROM daily_metrics WHERE repo IN (${repo:singlequote}) AND avg_issue_resolution_time > 0\nORDER BY time ASC", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "Issue Resolution Time", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "multi" + } + } + ], + "type": "timeseries", + "description": "Average hours from issue creation to closure. 30-day rolling average." + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value sdk-python" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 43 + }, + "id": 14, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "queryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time,\n repo as metric,\n AVG(churn_additions + churn_deletions) \n OVER (PARTITION BY repo ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) as value\nFROM daily_metrics\nWHERE (churn_additions > 0 OR churn_deletions > 0)\n AND (churn_additions + churn_deletions) <= 10000\n AND repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging')\n AND CAST(strftime('%s', date) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date) as INTEGER) <= $__to / 1000\nORDER BY date ASC", + "queryType": "table", + "rawQueryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time,\n repo as metric,\n AVG(churn_additions + churn_deletions) \n OVER (PARTITION BY repo ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) as value\nFROM daily_metrics\nWHERE (churn_additions > 0 OR churn_deletions > 0)\n AND (churn_additions + churn_deletions) <= 10000\n AND repo IN (${repo:singlequote})\n AND CAST(strftime('%s', date) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date) as INTEGER) <= $__to / 1000\nORDER BY date ASC", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "Code Churn (7-Day Avg, <10k)", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "multi" + } + } + ], + "type": "timeseries", + "description": "7-day rolling average of total lines changed per day (additions + deletions). Filters out days with >10,000 line changes to exclude anomalies like generated code, large imports, or bulk formatting changes." + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "h" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 50 + }, + "id": 17, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "queryText": "-- Internal Cycle Time\nSELECT \n CAST(strftime('%s', date(merged_at)) as INTEGER) as time,\n repo || ' (Internal)' as metric,\n AVG((julianday(merged_at) - julianday(created_at)) * 24) \n OVER (PARTITION BY repo ORDER BY date(merged_at) ROWS BETWEEN 13 PRECEDING AND CURRENT ROW) as value\nFROM pull_requests\nWHERE merged_at IS NOT NULL\n AND json_extract(data, '$.author_association') IN ('OWNER', 'MEMBER', 'COLLABORATOR')\n AND repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging')\n AND CAST(strftime('%s', date(merged_at)) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date(merged_at)) as INTEGER) <= $__to / 1000\n\nUNION ALL\n\n-- External Cycle Time\nSELECT \n CAST(strftime('%s', date(merged_at)) as INTEGER) as time,\n repo || ' (External)' as metric,\n AVG((julianday(merged_at) - julianday(created_at)) * 24) \n OVER (PARTITION BY repo ORDER BY date(merged_at) ROWS BETWEEN 13 PRECEDING AND CURRENT ROW) as value\nFROM pull_requests\nWHERE merged_at IS NOT NULL\n AND json_extract(data, '$.author_association') NOT IN ('OWNER', 'MEMBER', 'COLLABORATOR')\n AND repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging')\n AND CAST(strftime('%s', date(merged_at)) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date(merged_at)) as INTEGER) <= $__to / 1000\n\nORDER BY time ASC", + "queryType": "table", + "rawQueryText": "-- Internal Cycle Time\nSELECT \n CAST(strftime('%s', date(merged_at)) as INTEGER) as time,\n repo || ' (Internal)' as metric,\n AVG((julianday(merged_at) - julianday(created_at)) * 24) \n OVER (PARTITION BY repo ORDER BY date(merged_at) ROWS BETWEEN 13 PRECEDING AND CURRENT ROW) as value\nFROM pull_requests\nWHERE merged_at IS NOT NULL\n AND json_extract(data, '$.author_association') IN ('OWNER', 'MEMBER', 'COLLABORATOR')\n AND repo IN (${repo:singlequote})\n AND CAST(strftime('%s', date(merged_at)) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date(merged_at)) as INTEGER) <= $__to / 1000\n\nUNION ALL\n\n-- External Cycle Time\nSELECT \n CAST(strftime('%s', date(merged_at)) as INTEGER) as time,\n repo || ' (External)' as metric,\n AVG((julianday(merged_at) - julianday(created_at)) * 24) \n OVER (PARTITION BY repo ORDER BY date(merged_at) ROWS BETWEEN 13 PRECEDING AND CURRENT ROW) as value\nFROM pull_requests\nWHERE merged_at IS NOT NULL\n AND json_extract(data, '$.author_association') NOT IN ('OWNER', 'MEMBER', 'COLLABORATOR')\n AND repo IN (${repo:singlequote})\n AND CAST(strftime('%s', date(merged_at)) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date(merged_at)) as INTEGER) <= $__to / 1000\n\nORDER BY time ASC", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "Cycle Time (PR Open \u2192 Merge, 14-Day Avg)", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "multi" + } + } + ], + "type": "timeseries", + "description": "Average hours from PR creation to merge, split by internal vs external contributors. 14-day rolling average." + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "h" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 50 + }, + "id": 20, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "queryText": "-- Internal Time to First Review\nSELECT \n CAST(strftime('%s', date(first_response)) as INTEGER) as time,\n repo || ' (Internal)' as metric,\n AVG((julianday(first_response) - julianday(created_at)) * 24) \n OVER (PARTITION BY repo ORDER BY date(first_response) ROWS BETWEEN 13 PRECEDING AND CURRENT ROW) as value\nFROM (\n SELECT \n pr.repo,\n pr.number,\n pr.created_at,\n MIN(CASE \n WHEN reviews.submitted_at IS NOT NULL THEN reviews.submitted_at\n WHEN comments.created_at IS NOT NULL THEN comments.created_at\n END) as first_response\n FROM pull_requests pr\n LEFT JOIN pr_reviews reviews ON pr.repo = reviews.repo AND pr.number = reviews.pr_number\n LEFT JOIN pr_review_comments comments ON pr.repo = comments.repo AND pr.number = comments.pr_number\n WHERE json_extract(pr.data, '$.author_association') IN ('OWNER', 'MEMBER', 'COLLABORATOR')\n GROUP BY pr.repo, pr.number\n HAVING first_response IS NOT NULL\n) responses\nWHERE repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging')\n AND CAST(strftime('%s', date(first_response)) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date(first_response)) as INTEGER) <= $__to / 1000\n\nUNION ALL\n\n-- External Time to First Review\nSELECT \n CAST(strftime('%s', date(first_response)) as INTEGER) as time,\n repo || ' (External)' as metric,\n AVG((julianday(first_response) - julianday(created_at)) * 24) \n OVER (PARTITION BY repo ORDER BY date(first_response) ROWS BETWEEN 13 PRECEDING AND CURRENT ROW) as value\nFROM (\n SELECT \n pr.repo,\n pr.number,\n pr.created_at,\n MIN(CASE \n WHEN reviews.submitted_at IS NOT NULL THEN reviews.submitted_at\n WHEN comments.created_at IS NOT NULL THEN comments.created_at\n END) as first_response\n FROM pull_requests pr\n LEFT JOIN pr_reviews reviews ON pr.repo = reviews.repo AND pr.number = reviews.pr_number\n LEFT JOIN pr_review_comments comments ON pr.repo = comments.repo AND pr.number = comments.pr_number\n WHERE json_extract(pr.data, '$.author_association') NOT IN ('OWNER', 'MEMBER', 'COLLABORATOR')\n GROUP BY pr.repo, pr.number\n HAVING first_response IS NOT NULL\n) responses\nWHERE repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging')\n AND CAST(strftime('%s', date(first_response)) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date(first_response)) as INTEGER) <= $__to / 1000\n\nORDER BY time ASC", + "queryType": "table", + "rawQueryText": "-- Internal Time to First Review\nSELECT \n CAST(strftime('%s', date(first_response)) as INTEGER) as time,\n repo || ' (Internal)' as metric,\n AVG((julianday(first_response) - julianday(created_at)) * 24) \n OVER (PARTITION BY repo ORDER BY date(first_response) ROWS BETWEEN 13 PRECEDING AND CURRENT ROW) as value\nFROM (\n SELECT \n pr.repo,\n pr.number,\n pr.created_at,\n MIN(CASE \n WHEN reviews.submitted_at IS NOT NULL THEN reviews.submitted_at\n WHEN comments.created_at IS NOT NULL THEN comments.created_at\n END) as first_response\n FROM pull_requests pr\n LEFT JOIN pr_reviews reviews ON pr.repo = reviews.repo AND pr.number = reviews.pr_number\n LEFT JOIN pr_review_comments comments ON pr.repo = comments.repo AND pr.number = comments.pr_number\n WHERE json_extract(pr.data, '$.author_association') IN ('OWNER', 'MEMBER', 'COLLABORATOR')\n GROUP BY pr.repo, pr.number\n HAVING first_response IS NOT NULL\n) responses\nWHERE repo IN (${repo:singlequote})\n AND CAST(strftime('%s', date(first_response)) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date(first_response)) as INTEGER) <= $__to / 1000\n\nUNION ALL\n\n-- External Time to First Review\nSELECT \n CAST(strftime('%s', date(first_response)) as INTEGER) as time,\n repo || ' (External)' as metric,\n AVG((julianday(first_response) - julianday(created_at)) * 24) \n OVER (PARTITION BY repo ORDER BY date(first_response) ROWS BETWEEN 13 PRECEDING AND CURRENT ROW) as value\nFROM (\n SELECT \n pr.repo,\n pr.number,\n pr.created_at,\n MIN(CASE \n WHEN reviews.submitted_at IS NOT NULL THEN reviews.submitted_at\n WHEN comments.created_at IS NOT NULL THEN comments.created_at\n END) as first_response\n FROM pull_requests pr\n LEFT JOIN pr_reviews reviews ON pr.repo = reviews.repo AND pr.number = reviews.pr_number\n LEFT JOIN pr_review_comments comments ON pr.repo = comments.repo AND pr.number = comments.pr_number\n WHERE json_extract(pr.data, '$.author_association') NOT IN ('OWNER', 'MEMBER', 'COLLABORATOR')\n GROUP BY pr.repo, pr.number\n HAVING first_response IS NOT NULL\n) responses\nWHERE repo IN (${repo:singlequote})\n AND CAST(strftime('%s', date(first_response)) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date(first_response)) as INTEGER) <= $__to / 1000\n\nORDER BY time ASC", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "Time to First Review (14-Day Avg)", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "multi" + } + } + ], + "type": "timeseries", + "description": "Average hours from PR creation to first review. 14-day rolling average." + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 51 + }, + "id": 103, + "panels": [], + "title": "Community", + "type": "row" + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value sdk-python" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 52 + }, + "id": 12, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "queryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time, \n repo as metric, \n stars as value \nFROM daily_metrics \nWHERE repo IN (${repo:singlequote}) \nORDER BY date ASC", + "queryType": "table", + "rawQueryText": "SELECT \n CAST(strftime('%s', date) as INTEGER) as time, \n repo as metric, \n stars as value \nFROM daily_metrics \nWHERE repo IN (${repo:singlequote}) \nORDER BY date ASC", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "Total Stars", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "multi" + } + } + ], + "type": "timeseries", + "description": "Cumulative count of GitHub stars across selected repositories, tracked over time." + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value sdk-python" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 52 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "queryText": "WITH weekly_contributors AS (\n SELECT \n date(created_at) as pr_date,\n repo,\n author\n FROM pull_requests\n WHERE json_extract(data, '$.author_association') NOT IN ('OWNER', 'MEMBER', 'COLLABORATOR')\n AND repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging')\n)\nSELECT \n CAST(strftime('%s', d.date) as INTEGER) as time,\n d.repo as metric,\n (\n SELECT COUNT(DISTINCT wc.author)\n FROM weekly_contributors wc\n WHERE wc.repo = d.repo\n AND wc.pr_date > date(d.date, '-7 days')\n AND wc.pr_date <= d.date\n ) as value\nFROM daily_metrics d\nWHERE d.repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging')\n AND CAST(strftime('%s', d.date) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', d.date) as INTEGER) <= $__to / 1000\nORDER BY d.date ASC", + "queryType": "table", + "rawQueryText": "WITH weekly_contributors AS (\n SELECT \n date(created_at) as pr_date,\n repo,\n author\n FROM pull_requests\n WHERE json_extract(data, '$.author_association') NOT IN ('OWNER', 'MEMBER', 'COLLABORATOR')\n AND repo IN (${repo:singlequote})\n)\nSELECT \n CAST(strftime('%s', d.date) as INTEGER) as time,\n d.repo as metric,\n (\n SELECT COUNT(DISTINCT wc.author)\n FROM weekly_contributors wc\n WHERE wc.repo = d.repo\n AND wc.pr_date > date(d.date, '-7 days')\n AND wc.pr_date <= d.date\n ) as value\nFROM daily_metrics d\nWHERE d.repo IN (${repo:singlequote})\n AND CAST(strftime('%s', d.date) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', d.date) as INTEGER) <= $__to / 1000\nORDER BY d.date ASC", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "Weekly Active External Contributors", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "multi" + } + } + ], + "type": "timeseries", + "description": "Count of unique external contributors (non-team members: not OWNER/MEMBER/COLLABORATOR) who created PRs in the rolling 7-day window ending on each date." + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "percent", + "min": 0, + "max": 100 + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value sdk-python" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 60 + }, + "id": 18, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "queryText": "SELECT \n CAST(strftime('%s', date(merged_at)) as INTEGER) as time,\n repo as metric,\n AVG(\n CASE WHEN json_extract(data, '$.author_association') NOT IN ('OWNER', 'MEMBER', 'COLLABORATOR') \n THEN 100.0 ELSE 0.0 END\n ) OVER (PARTITION BY repo ORDER BY date(merged_at) ROWS BETWEEN 29 PRECEDING AND CURRENT ROW) as value\nFROM pull_requests\nWHERE merged_at IS NOT NULL\n AND repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging')\n AND CAST(strftime('%s', date(merged_at)) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date(merged_at)) as INTEGER) <= $__to / 1000\nORDER BY merged_at ASC", + "queryType": "table", + "rawQueryText": "SELECT \n CAST(strftime('%s', date(merged_at)) as INTEGER) as time,\n repo as metric,\n AVG(\n CASE WHEN json_extract(data, '$.author_association') NOT IN ('OWNER', 'MEMBER', 'COLLABORATOR') \n THEN 100.0 ELSE 0.0 END\n ) OVER (PARTITION BY repo ORDER BY date(merged_at) ROWS BETWEEN 29 PRECEDING AND CURRENT ROW) as value\nFROM pull_requests\nWHERE merged_at IS NOT NULL\n AND repo IN (${repo:singlequote})\n AND CAST(strftime('%s', date(merged_at)) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date(merged_at)) as INTEGER) <= $__to / 1000\nORDER BY merged_at ASC", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "External Contribution Rate (30-Day Avg)", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "multi" + } + } + ], + "type": "timeseries", + "description": "30-day rolling average percentage of merged PRs that were authored by external contributors (non-team members)." + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineWidth": 1, + "scaleDistribution": { + "type": "linear" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "value sdk-python" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-python (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value sdk-typescript (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value tools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value docs (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-builder (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value agent-sop (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value samples (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value mcp-server (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value evals (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value .github (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#808080", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Internal)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (External)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (First Response)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "value devtools (Resolution Time)" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 60 + }, + "id": 9, + "options": { + "barRadius": 0, + "barWidth": 0.97, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "never", + "stacking": "normal", + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 100 + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "queryText": "-- Internal Rejection\nSELECT \n CAST(strftime('%s', date(closed_at)) as INTEGER) as time,\n repo || ' (Internal)' as metric,\n COUNT(*) as value\nFROM pull_requests\nWHERE state = 'closed' \n AND merged_at IS NULL \n AND json_extract(data, '$.author_association') IN ('OWNER', 'MEMBER', 'COLLABORATOR')\n AND repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging')\n AND CAST(strftime('%s', date(closed_at)) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date(closed_at)) as INTEGER) <= $__to / 1000\nGROUP BY date(closed_at), repo\n\nUNION ALL\n\n-- External Rejection\nSELECT \n CAST(strftime('%s', date(closed_at)) as INTEGER) as time,\n repo || ' (External)' as metric,\n COUNT(*) as value\nFROM pull_requests\nWHERE state = 'closed' \n AND merged_at IS NULL \n AND json_extract(data, '$.author_association') NOT IN ('OWNER', 'MEMBER', 'COLLABORATOR')\n AND repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging')\n AND CAST(strftime('%s', date(closed_at)) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date(closed_at)) as INTEGER) <= $__to / 1000\nGROUP BY date(closed_at), repo\n\nORDER BY time ASC", + "queryType": "table", + "rawQueryText": "-- Internal Rejection\nSELECT \n CAST(strftime('%s', date(closed_at)) as INTEGER) as time,\n repo || ' (Internal)' as metric,\n COUNT(*) as value\nFROM pull_requests\nWHERE state = 'closed' \n AND merged_at IS NULL \n AND json_extract(data, '$.author_association') IN ('OWNER', 'MEMBER', 'COLLABORATOR')\n AND repo IN (${repo:singlequote})\n AND CAST(strftime('%s', date(closed_at)) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date(closed_at)) as INTEGER) <= $__to / 1000\nGROUP BY date(closed_at), repo\n\nUNION ALL\n\n-- External Rejection\nSELECT \n CAST(strftime('%s', date(closed_at)) as INTEGER) as time,\n repo || ' (External)' as metric,\n COUNT(*) as value\nFROM pull_requests\nWHERE state = 'closed' \n AND merged_at IS NULL \n AND json_extract(data, '$.author_association') NOT IN ('OWNER', 'MEMBER', 'COLLABORATOR')\n AND repo IN (${repo:singlequote})\n AND CAST(strftime('%s', date(closed_at)) as INTEGER) >= $__from / 1000\n AND CAST(strftime('%s', date(closed_at)) as INTEGER) <= $__to / 1000\nGROUP BY date(closed_at), repo\n\nORDER BY time ASC", + "refId": "A", + "timeColumns": [ + "time", + "ts" + ] + } + ], + "title": "PRs Closed Without Merging (Daily)", + "transformations": [ + { + "id": "prepareTimeSeries", + "options": { + "format": "multi" + } + } + ], + "type": "barchart", + "description": "Daily count of PRs closed without being merged (rejected PRs), split by Internal (team) vs External (community) contributors." + } + ], + "preload": false, + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "allowCustomValue": false, + "current": { + "text": [ + "agent-builder", + "agent-sop", + "docs", + "sdk-python", + "sdk-typescript", + "tools" + ], + "value": [ + "agent-builder", + "agent-sop", + "docs", + "sdk-python", + "sdk-typescript", + "tools" + ] + }, + "definition": "SELECT DISTINCT repo FROM daily_metrics", + "description": "", + "includeAll": true, + "multi": true, + "name": "repo", + "options": [], + "query": "SELECT DISTINCT repo FROM daily_metrics", + "refresh": 1, + "regex": "", + "sort": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-30d", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Strands Health", + "uid": "adj9pgt", + "version": 2 +} \ No newline at end of file diff --git a/community-dashboard/provisioning/dashboards/triage.json b/community-dashboard/provisioning/dashboards/triage.json new file mode 100644 index 0000000..24b8c76 --- /dev/null +++ b/community-dashboard/provisioning/dashboards/triage.json @@ -0,0 +1,676 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 0, + "links": [], + "panels": [ + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "footer": { + "reducers": [] + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Title" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "title": "Open on GitHub", + "url": "${__data.fields.URL}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "URL" + }, + "properties": [ + { + "id": "custom.hideFrom.viz", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Status" + }, + "properties": [ + { + "id": "custom.width", + "value": 118 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Days Open" + }, + "properties": [ + { + "id": "custom.width", + "value": 87 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Author" + }, + "properties": [ + { + "id": "custom.width", + "value": 148 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Repo" + }, + "properties": [ + { + "id": "custom.width", + "value": 161 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 22, + "x": 1, + "y": 0 + }, + "id": 1, + "options": { + "cellHeight": "sm", + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Days Open" + } + ] + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "queryText": "SELECT \n p.repo as \"Repo\", \n p.author as \"Author\", \n p.title as \"Title\", \n json_extract(p.data, '$.html_url') as \"URL\",\n CASE \n WHEN r.state = 'APPROVED' THEN 'Approved' \n WHEN r.state = 'CHANGES_REQUESTED' THEN 'Changes Requested' \n ELSE 'Needs Review' \n END as \"Status\", \n CAST((julianday('now') - julianday(p.created_at)) as INTEGER) as \"Days Open\" \nFROM pull_requests p \nLEFT JOIN (\n SELECT repo, pr_number, state FROM pr_reviews r1 \n WHERE submitted_at = (SELECT MAX(submitted_at) FROM pr_reviews r2 WHERE r2.pr_number = r1.pr_number AND r2.repo = r1.repo)\n) r ON p.repo = r.repo AND p.number = r.pr_number \nWHERE p.state = 'open' \n AND json_extract(p.data, '$.author_association') IN ('OWNER', 'MEMBER', 'COLLABORATOR') \n AND p.repo IN ('agent-builder','agent-sop','docs','evals','mcp-server','private-docs-staging','private-sdk-python-staging','private-sdk-typescript-staging','private-tools-staging','samples','sdk-python','sdk-typescript','strands-action','strandsagents.com','tools','.github','private-samples-staging','private-strands-action-staging') \nORDER BY \"Days Open\" DESC", + "queryType": "table", + "rawQueryText": "SELECT \n p.repo as \"Repo\", \n p.author as \"Author\", \n p.title as \"Title\", \n json_extract(p.data, '$.html_url') as \"URL\",\n CASE \n WHEN r.state = 'APPROVED' THEN 'Approved' \n WHEN r.state = 'CHANGES_REQUESTED' THEN 'Changes Requested' \n ELSE 'Needs Review' \n END as \"Status\", \n CAST((julianday('now') - julianday(p.created_at)) as INTEGER) as \"Days Open\" \nFROM pull_requests p \nLEFT JOIN (\n SELECT repo, pr_number, state FROM pr_reviews r1 \n WHERE submitted_at = (SELECT MAX(submitted_at) FROM pr_reviews r2 WHERE r2.pr_number = r1.pr_number AND r2.repo = r1.repo)\n) r ON p.repo = r.repo AND p.number = r.pr_number \nWHERE p.state = 'open' \n AND json_extract(p.data, '$.author_association') IN ('OWNER', 'MEMBER', 'COLLABORATOR') \n AND p.repo IN (${repo:singlequote}) \nORDER BY \"Days Open\" DESC", + "refId": "A", + "timeColumns": ["time", "ts"] + } + ], + "title": "Team PRs", + "type": "table" + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "footer": { + "reducers": [] + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Title" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "title": "Open on GitHub", + "url": "${__data.fields.URL}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "URL" + }, + "properties": [ + { + "id": "custom.hideFrom.viz", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Status" + }, + "properties": [ + { + "id": "custom.width", + "value": 118 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Days Open" + }, + "properties": [ + { + "id": "custom.width", + "value": 87 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Author" + }, + "properties": [ + { + "id": "custom.width", + "value": 148 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Repo" + }, + "properties": [ + { + "id": "custom.width", + "value": 161 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 22, + "x": 1, + "y": 8 + }, + "id": 2, + "options": { + "cellHeight": "sm", + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Days Open" + } + ] + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "queryText": "SELECT \n p.repo as \"Repo\", \n p.author as \"Author\", \n p.title as \"Title\", \n json_extract(p.data, '$.html_url') as \"URL\",\n CASE \n WHEN r.state = 'APPROVED' THEN 'Approved' \n WHEN r.state = 'CHANGES_REQUESTED' THEN 'Changes Requested' \n ELSE 'Needs Review' \n END as \"Status\", \n CAST((julianday('now') - julianday(p.created_at)) as INTEGER) as \"Days Open\" \nFROM pull_requests p \nLEFT JOIN (\n SELECT repo, pr_number, state FROM pr_reviews r1 \n WHERE submitted_at = (SELECT MAX(submitted_at) FROM pr_reviews r2 WHERE r2.pr_number = r1.pr_number AND r2.repo = r1.repo)\n) r ON p.repo = r.repo AND p.number = r.pr_number \nWHERE p.state = 'open' \n AND json_extract(p.data, '$.author_association') NOT IN ('OWNER', 'MEMBER', 'COLLABORATOR') \n AND p.repo IN (${repo:singlequote}) \nORDER BY \"Days Open\" DESC", + "queryType": "table", + "rawQueryText": "SELECT \n p.repo as \"Repo\", \n p.author as \"Author\", \n p.title as \"Title\", \n json_extract(p.data, '$.html_url') as \"URL\",\n CASE \n WHEN r.state = 'APPROVED' THEN 'Approved' \n WHEN r.state = 'CHANGES_REQUESTED' THEN 'Changes Requested' \n ELSE 'Needs Review' \n END as \"Status\", \n CAST((julianday('now') - julianday(p.created_at)) as INTEGER) as \"Days Open\" \nFROM pull_requests p \nLEFT JOIN (\n SELECT repo, pr_number, state FROM pr_reviews r1 \n WHERE submitted_at = (SELECT MAX(submitted_at) FROM pr_reviews r2 WHERE r2.pr_number = r1.pr_number AND r2.repo = r1.repo)\n) r ON p.repo = r.repo AND p.number = r.pr_number \nWHERE p.state = 'open' \n AND json_extract(p.data, '$.author_association') NOT IN ('OWNER', 'MEMBER', 'COLLABORATOR') \n AND p.repo IN (${repo:singlequote}) \nORDER BY \"Days Open\" DESC", + "refId": "A" + } + ], + "title": "Community PRs (External)", + "type": "table" + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "footer": { + "reducers": [] + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Title" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "title": "Open on GitHub", + "url": "${__data.fields.URL}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "URL" + }, + "properties": [ + { + "id": "custom.hideFrom.viz", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Comments" + }, + "properties": [ + { + "id": "custom.width", + "value": 90 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Repo" + }, + "properties": [ + { + "id": "custom.width", + "value": 160 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 22, + "x": 1, + "y": 16 + }, + "id": 3, + "options": { + "cellHeight": "sm", + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Comments" + } + ] + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "queryText": "SELECT \n repo as \"Repo\", \n title as \"Title\", \n json_extract(data, '$.html_url') as \"URL\",\n json_extract(data, '$.comments') as \"Comments\" \nFROM issues \nWHERE state = 'open' AND json_extract(data, '$.milestone') IS NULL AND repo IN (${repo:singlequote}) \nORDER BY created_at ASC", + "queryType": "table", + "rawQueryText": "SELECT \n repo as \"Repo\", \n title as \"Title\", \n json_extract(data, '$.html_url') as \"URL\",\n json_extract(data, '$.comments') as \"Comments\" \nFROM issues \nWHERE state = 'open' AND json_extract(data, '$.milestone') IS NULL AND repo IN (${repo:singlequote}) \nORDER BY created_at ASC", + "refId": "A" + } + ], + "title": "Issues Needing Triage", + "type": "table" + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "footer": { + "reducers": [] + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Title" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "title": "Open on GitHub", + "url": "${__data.fields.URL}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "URL" + }, + "properties": [ + { + "id": "custom.hideFrom.viz", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Upvotes" + }, + "properties": [ + { + "id": "custom.width", + "value": 80 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 1, + "y": 24 + }, + "id": 4, + "options": { + "cellHeight": "sm", + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Upvotes" + } + ] + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "queryText": "SELECT \n title as \"Title\", \n json_extract(data, '$.html_url') as \"URL\",\n json_extract(data, '$.reactions.\"+1\"') as \"Upvotes\" \nFROM issues WHERE state = 'open' AND repo IN (${repo:singlequote}) \nORDER BY \"Upvotes\" DESC LIMIT 20", + "queryType": "table", + "rawQueryText": "SELECT \n title as \"Title\", \n json_extract(data, '$.html_url') as \"URL\",\n json_extract(data, '$.reactions.\"+1\"') as \"Upvotes\" \nFROM issues WHERE state = 'open' AND repo IN (${repo:singlequote}) \nORDER BY \"Upvotes\" DESC LIMIT 20", + "refId": "A", + "timeColumns": ["time", "ts"] + } + ], + "title": "Top Upvoted Issues", + "type": "table" + }, + { + "datasource": { + "type": "frser-sqlite-datasource", + "uid": "P2289B60D79A89B0C" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "footer": { + "reducers": [] + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Title" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "title": "Open on GitHub", + "url": "${__data.fields.URL}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "URL" + }, + "properties": [ + { + "id": "custom.hideFrom.viz", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Comments" + }, + "properties": [ + { + "id": "custom.width", + "value": 80 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 10, + "x": 13, + "y": 24 + }, + "id": 5, + "options": { + "cellHeight": "sm", + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Comments" + } + ] + }, + "pluginVersion": "12.3.1", + "targets": [ + { + "queryText": "SELECT \n title as \"Title\", \n json_extract(data, '$.html_url') as \"URL\",\n json_extract(data, '$.comments') as \"Comments\" \nFROM issues WHERE state = 'open' AND repo IN (${repo:singlequote}) \nORDER BY \"Comments\" DESC LIMIT 20", + "queryType": "table", + "rawQueryText": "SELECT \n title as \"Title\", \n json_extract(data, '$.html_url') as \"URL\",\n json_extract(data, '$.comments') as \"Comments\" \nFROM issues WHERE state = 'open' AND repo IN (${repo:singlequote}) \nORDER BY \"Comments\" DESC LIMIT 20", + "refId": "A" + } + ], + "title": "Most Discussed Issues", + "type": "table" + } + ], + "preload": false, + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "allowCustomValue": false, + "current": { + "text": ["All"], + "value": ["$__all"] + }, + "definition": "SELECT DISTINCT repo FROM daily_metrics", + "includeAll": true, + "multi": true, + "name": "repo", + "options": [], + "query": "SELECT DISTINCT repo FROM daily_metrics", + "refresh": 1, + "regex": "", + "sort": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-30d", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Strands Triage", + "uid": "advqxqk", + "version": 4 +} diff --git a/community-dashboard/provisioning/datasources/automatic.yaml b/community-dashboard/provisioning/datasources/automatic.yaml new file mode 100644 index 0000000..c3c6be5 --- /dev/null +++ b/community-dashboard/provisioning/datasources/automatic.yaml @@ -0,0 +1,10 @@ +apiVersion: 1 + +datasources: + - name: GitHub Metrics (SQLite) + type: frser-sqlite-datasource + access: proxy + isDefault: true + jsonData: + path: /var/lib/grafana/data/metrics.db + mode: ro diff --git a/community-dashboard/strands-metrics/Cargo.toml b/community-dashboard/strands-metrics/Cargo.toml new file mode 100644 index 0000000..fec7d1c --- /dev/null +++ b/community-dashboard/strands-metrics/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "strands-metrics" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0" +chrono = { version = "0.4", features = ["serde"] } +clap = { version = "4.5", features = ["derive"] } +http = "1.4.0" +indicatif = "0.18.3" +octocrab = "0.49" +rusqlite = { version = "0.38", features = ["bundled"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tokio = { version = "1", features = ["full"] } +tracing = "0.1" +tracing-subscriber = "0.3" diff --git a/community-dashboard/strands-metrics/src/aggregates.rs b/community-dashboard/strands-metrics/src/aggregates.rs new file mode 100644 index 0000000..edcb5cb --- /dev/null +++ b/community-dashboard/strands-metrics/src/aggregates.rs @@ -0,0 +1,215 @@ +use anyhow::Result; +use chrono::{DateTime, Duration, Utc}; +use rusqlite::{params, Connection}; + +pub fn compute_metrics(conn: &Connection) -> Result<()> { + // Smart detect of dirty window + let last_metric_date: Option = conn + .query_row("SELECT max(date) FROM daily_metrics", [], |row| row.get(0)) + .ok(); + + let start_date = match last_metric_date { + Some(d) => DateTime::parse_from_str(&d, "%Y-%m-%d") + .map(|dt| dt.with_timezone(&Utc) - Duration::days(3)) + .unwrap_or_else(|_| Utc::now()), + None => DateTime::parse_from_rfc3339("2010-01-01T00:00:00Z") + .unwrap() + .with_timezone(&Utc), + }; + + let start_date_str = start_date.format("%Y-%m-%d").to_string(); + + // Clear out the dirty window so we can recompute + conn.execute( + "DELETE FROM daily_metrics WHERE date >= ?1", + params![start_date_str], + )?; + + // PERFORMANCE OPTIMIZATION: Calculate response times ONCE in a temp table + // Calculating this inside the daily loop was O(N^2) and incredibly slow. + conn.execute( + "CREATE TEMP TABLE IF NOT EXISTS temp_response_times AS + SELECT + parent.repo, + date(parent.created_at) as created_date, + (julianday(MIN(activity.activity_at)) - julianday(parent.created_at)) * 24 as hours_to_response + FROM ( + SELECT id, repo, number, author, created_at FROM issues + UNION ALL + SELECT id, repo, number, author, created_at FROM pull_requests + ) as parent + JOIN ( + SELECT repo, issue_number as ref_number, author, created_at as activity_at FROM issue_comments + UNION ALL + SELECT repo, pr_number as ref_number, author, submitted_at as activity_at FROM pr_reviews + UNION ALL + SELECT repo, pr_number as ref_number, author, created_at as activity_at FROM pr_review_comments + ) as activity + ON parent.repo = activity.repo + AND parent.number = activity.ref_number + AND activity.activity_at > parent.created_at + AND activity.author != parent.author + GROUP BY parent.repo, parent.number", + [], + )?; + + let now = Utc::now(); + let num_days = (now - start_date).num_days(); + + for i in 0..=num_days { + let date = start_date + Duration::days(i); + let date_str = date.format("%Y-%m-%d").to_string(); + + conn.execute( + "INSERT OR IGNORE INTO daily_metrics (date, repo) + SELECT DISTINCT ?1, repo FROM ( + SELECT repo FROM pull_requests + UNION SELECT repo FROM issues + UNION SELECT repo FROM stargazers + UNION SELECT repo FROM commits + )", + params![date_str], + )?; + + conn.execute( + "UPDATE daily_metrics + SET prs_opened = (SELECT count(*) FROM pull_requests WHERE repo = daily_metrics.repo AND date(created_at) = date(daily_metrics.date)), + prs_merged = (SELECT count(*) FROM pull_requests WHERE repo = daily_metrics.repo AND merged_at IS NOT NULL AND date(merged_at) = date(daily_metrics.date)), + issues_opened = (SELECT count(*) FROM issues WHERE repo = daily_metrics.repo AND date(created_at) = date(daily_metrics.date)), + issues_closed = (SELECT count(*) FROM issues WHERE repo = daily_metrics.repo AND closed_at IS NOT NULL AND date(closed_at) = date(daily_metrics.date)) + WHERE date = ?1", + params![date_str], + )?; + + conn.execute( + "UPDATE daily_metrics + SET churn_additions = (SELECT COALESCE(SUM(additions), 0) FROM commits WHERE repo = daily_metrics.repo AND date(date) = date(daily_metrics.date)), + churn_deletions = (SELECT COALESCE(SUM(deletions), 0) FROM commits WHERE repo = daily_metrics.repo AND date(date) = date(daily_metrics.date)) + WHERE date = ?1", + params![date_str], + )?; + + conn.execute( + "UPDATE daily_metrics + SET ci_failures = (SELECT count(*) FROM workflow_runs WHERE repo = daily_metrics.repo AND conclusion = 'failure' AND date(created_at) = date(daily_metrics.date)), + ci_runs = (SELECT count(*) FROM workflow_runs WHERE repo = daily_metrics.repo AND date(created_at) = date(daily_metrics.date)) + WHERE date = ?1", + params![date_str], + )?; + + conn.execute( + "UPDATE daily_metrics + SET stars = ( + SELECT count(*) FROM stargazers + WHERE repo = daily_metrics.repo AND date(starred_at) <= date(daily_metrics.date) + ) + WHERE date = ?1", + params![date_str], + )?; + + // Open items snapshot (combined issues + PRs for backward compatibility) + conn.execute( + "UPDATE daily_metrics + SET open_items_count = ( + (SELECT count(*) FROM issues WHERE repo = daily_metrics.repo AND date(created_at) <= date(daily_metrics.date) AND (closed_at IS NULL OR date(closed_at) > date(daily_metrics.date))) + + + (SELECT count(*) FROM pull_requests WHERE repo = daily_metrics.repo AND date(created_at) <= date(daily_metrics.date) AND (closed_at IS NULL OR date(closed_at) > date(daily_metrics.date))) + ) + WHERE date = ?1", + params![date_str] + )?; + + // Open issues count (just issues, no PRs) + conn.execute( + "UPDATE daily_metrics + SET open_issues_count = ( + SELECT count(*) FROM issues WHERE repo = daily_metrics.repo AND date(created_at) <= date(daily_metrics.date) AND (closed_at IS NULL OR date(closed_at) > date(daily_metrics.date)) + ) + WHERE date = ?1", + params![date_str] + )?; + + // Open PRs count + conn.execute( + "UPDATE daily_metrics + SET open_prs_count = ( + SELECT count(*) FROM pull_requests WHERE repo = daily_metrics.repo AND date(created_at) <= date(daily_metrics.date) AND (closed_at IS NULL OR date(closed_at) > date(daily_metrics.date)) + ) + WHERE date = ?1", + params![date_str] + )?; + + // Response time stats - Optimized to use Temp Table + conn.execute( + "UPDATE daily_metrics + SET time_to_first_response = ( + SELECT AVG(hours_to_response) + FROM temp_response_times + WHERE repo = daily_metrics.repo + AND created_date = date(daily_metrics.date) + ) + WHERE date = ?1", + params![date_str], + )?; + + conn.execute( + "UPDATE daily_metrics + SET avg_issue_resolution_time = ( + SELECT AVG((julianday(closed_at) - julianday(created_at)) * 24) + FROM issues + WHERE repo = daily_metrics.repo + AND closed_at IS NOT NULL + AND date(closed_at) = date(daily_metrics.date) + ) + WHERE date = ?1", + params![date_str], + )?; + + conn.execute( + "UPDATE daily_metrics + SET avg_pr_resolution_time = ( + SELECT AVG((julianday(COALESCE(merged_at, closed_at)) - julianday(created_at)) * 24) + FROM pull_requests + WHERE repo = daily_metrics.repo + AND (merged_at IS NOT NULL OR closed_at IS NOT NULL) + AND date(COALESCE(merged_at, closed_at)) = date(daily_metrics.date) + ) + WHERE date = ?1", + params![date_str], + )?; + + // Internal vs external merge times + conn.execute( + "UPDATE daily_metrics + SET time_to_merge_internal = ( + SELECT AVG((julianday(merged_at) - julianday(created_at)) * 24) + FROM pull_requests + WHERE repo = daily_metrics.repo + AND merged_at IS NOT NULL + AND date(merged_at) = date(daily_metrics.date) + AND json_extract(data, '$.author_association') IN ('OWNER', 'MEMBER', 'COLLABORATOR') + ) + WHERE date = ?1", + params![date_str], + )?; + + conn.execute( + "UPDATE daily_metrics + SET time_to_merge_external = ( + SELECT AVG((julianday(merged_at) - julianday(created_at)) * 24) + FROM pull_requests + WHERE repo = daily_metrics.repo + AND merged_at IS NOT NULL + AND date(merged_at) = date(daily_metrics.date) + AND json_extract(data, '$.author_association') NOT IN ('OWNER', 'MEMBER', 'COLLABORATOR') + ) + WHERE date = ?1", + params![date_str], + )?; + } + + // Cleanup temp table + conn.execute("DROP TABLE IF EXISTS temp_response_times", [])?; + + Ok(()) +} diff --git a/community-dashboard/strands-metrics/src/client.rs b/community-dashboard/strands-metrics/src/client.rs new file mode 100644 index 0000000..fe39d58 --- /dev/null +++ b/community-dashboard/strands-metrics/src/client.rs @@ -0,0 +1,730 @@ +use anyhow::Result; +use chrono::{DateTime, Datelike, Utc}; +use http::header::ACCEPT; +use http::StatusCode; +use indicatif::ProgressBar; +use octocrab::{models, Octocrab, OctocrabBuilder}; +use rusqlite::{params, Connection}; +use serde::Deserialize; +use serde_json::Value; +use std::collections::HashSet; + +#[derive(Deserialize, Debug)] +struct SimpleUser { + login: String, +} + +#[derive(Deserialize, Debug)] +struct StarEntry { + starred_at: Option>, + user: Option, +} + +pub struct GitHubClient<'a> { + pub gh: Octocrab, + db: &'a mut Connection, + pb: ProgressBar, +} + +impl<'a> GitHubClient<'a> { + pub fn new(gh: Octocrab, db: &'a mut Connection, pb: ProgressBar) -> Self { + Self { gh, db, pb } + } + + pub async fn check_limits(&self) -> Result<()> { + let rate = self.gh.ratelimit().get().await?; + let core = rate.resources.core; + + if core.remaining < 50 { + let reset = core.reset; + let now = Utc::now().timestamp() as u64; + let wait_secs = reset.saturating_sub(now) + 10; + self.pb + .set_message(format!("Rate limit low. Sleeping {}s...", wait_secs)); + tokio::time::sleep(tokio::time::Duration::from_secs(wait_secs)).await; + } + Ok(()) + } + + pub async fn sync_org(&mut self, org: &str) -> Result<()> { + self.check_limits().await?; + let repos = self.fetch_repos(org).await?; + for repo in repos { + self.pb.set_message(format!("Syncing {}", repo.name)); + self.sync_repo(org, &repo).await?; + } + Ok(()) + } + + pub async fn sweep_org(&mut self, org: &str) -> Result<()> { + self.check_limits().await?; + let repos = self.fetch_repos(org).await?; + for repo in repos { + self.pb.set_message(format!("Sweeping {}", repo.name)); + self.sweep_repo(org, &repo).await?; + } + Ok(()) + } + + async fn fetch_repos(&self, org: &str) -> Result> { + let mut repos = Vec::new(); + let mut page = self.gh.orgs(org).list_repos().per_page(100).send().await?; + repos.extend(page.items); + while let Some(next) = page.next { + self.check_limits().await?; + page = self.gh.get_page(&Some(next)).await?.unwrap(); + repos.extend(page.items); + } + + repos.retain(|r| { + !r.archived.unwrap_or(false) + && !r.private.unwrap_or(false) + && !r.name.starts_with("private_") + }); + + Ok(repos) + } + + async fn sweep_repo(&self, org: &str, repo: &models::Repository) -> Result<()> { + let mut remote_open_numbers = HashSet::new(); + let route = format!("/repos/{}/{}/issues", org, repo.name); + let mut page: octocrab::Page = self + .gh + .get( + &route, + Some(&serde_json::json!({ + "state": "open", "per_page": 100 + })), + ) + .await?; + + loop { + let next_page = page.next.clone(); + for item in page.items { + if let Some(num) = item.get("number").and_then(|n| n.as_i64()) { + remote_open_numbers.insert(num); + } + } + if let Some(next) = next_page { + self.check_limits().await?; + page = self.gh.get_page(&Some(next)).await?.unwrap(); + } else { + break; + } + } + + let mut stmt = self.db.prepare( + "SELECT number FROM issues WHERE repo = ?1 AND state = 'open' AND closed_at IS NULL AND deleted_at IS NULL" + )?; + let local_open_nums: Vec = stmt + .query_map(params![repo.name], |row| row.get(0))? + .collect::, _>>()?; + + let now = Utc::now().to_rfc3339(); + + for local_num in local_open_nums { + if !remote_open_numbers.contains(&local_num) { + self.check_limits().await?; + let issue_route = format!("/repos/{}/{}/issues/{}", org, repo.name, local_num); + + let result: Result = self.gh.get(&issue_route, None::<&()>).await; + + match result { + Ok(json) => { + let state = json + .get("state") + .and_then(|s| s.as_str()) + .unwrap_or("closed"); + let closed_at = json.get("closed_at").and_then(|s| s.as_str()); + self.db.execute( + "UPDATE issues SET state = ?1, closed_at = ?2 WHERE repo = ?3 AND number = ?4", + params![state, closed_at, repo.name, local_num] + )?; + } + Err(e) => { + if Self::is_missing_resource(&e) { + // Explicit 404/410 means deleted/missing + self.db.execute( + "UPDATE issues SET state = 'deleted', deleted_at = ?1 WHERE repo = ?2 AND number = ?3", + params![now, repo.name, local_num] + )?; + } else { + // Any other error (500, 502, timeout) is a crash. + return Err(e.into()); + } + } + } + } + } + Ok(()) + } + + async fn sync_repo(&mut self, org: &str, repo: &models::Repository) -> Result<()> { + let repo_name = &repo.name; + let last_sync_key = format!("last_sync_{}_{}", org, repo_name); + + let since: DateTime = self + .db + .query_row( + "SELECT value FROM app_state WHERE key = ?1", + params![last_sync_key], + |row| { + let s: String = row.get(0)?; + Ok(DateTime::parse_from_rfc3339(&s) + .map(|dt| dt.with_timezone(&Utc)) + .unwrap_or(Utc::now())) + }, + ) + .unwrap_or_else(|_| { + DateTime::parse_from_rfc3339("1970-01-01T00:00:00Z") + .unwrap() + .with_timezone(&Utc) + }); + + self.sync_pull_requests(org, repo_name, since).await?; + self.sync_issues(org, repo_name, since).await?; + self.sync_issue_comments(org, repo_name, since).await?; + self.sync_pr_comments(org, repo_name, since).await?; + self.sync_stars(org, repo).await?; + self.sync_commits(org, repo_name, since).await?; + self.sync_workflows(org, repo_name, since).await?; + + let now_str = Utc::now().to_rfc3339(); + self.db.execute( + "INSERT OR REPLACE INTO app_state (key, value) VALUES (?1, ?2)", + params![last_sync_key, now_str], + )?; + + Ok(()) + } + + async fn sync_commits(&self, org: &str, repo: &str, since: DateTime) -> Result<()> { + self.check_limits().await?; + + let route = format!("/repos/{}/{}/commits", org, repo); + let mut page: octocrab::Page = self + .gh + .get( + &route, + Some(&serde_json::json!({ + "since": since.to_rfc3339(), "per_page": 100 + })), + ) + .await?; + + loop { + let next_page = page.next.clone(); + + // Optimization: Collect SHAs and check in batch locally to avoid DB thrashing + let mut shas = HashSet::new(); + for item in &page.items { + if let Some(sha) = item.get("sha").and_then(|s| s.as_str()) { + shas.insert(sha.to_string()); + } + } + + for sha in shas { + // Check if exists + let exists: bool = self + .db + .query_row("SELECT 1 FROM commits WHERE sha = ?1", params![sha], |_| { + Ok(true) + }) + .unwrap_or(false); + + if !exists { + // We must fetch details to get stats (additions/deletions) + // Check limits BEFORE the heavy call + self.check_limits().await?; + + let detail_route = format!("/repos/{}/{}/commits/{}", org, repo, sha); + let detail: Value = self.gh.get(&detail_route, None::<&()>).await?; + + let author = detail + .get("commit") + .and_then(|c| c.get("author")) + .and_then(|a| a.get("name")) + .and_then(|n| n.as_str()) + .unwrap_or("unknown"); + + let date_str = detail + .get("commit") + .and_then(|c| c.get("author")) + .and_then(|a| a.get("date")) + .and_then(|d| d.as_str()) + .unwrap_or(""); + + let stats = detail.get("stats"); + let adds = stats + .and_then(|s| s.get("additions")) + .and_then(|v| v.as_i64()) + .unwrap_or(0); + let dels = stats + .and_then(|s| s.get("deletions")) + .and_then(|v| v.as_i64()) + .unwrap_or(0); + let msg = detail + .get("commit") + .and_then(|c| c.get("message")) + .and_then(|m| m.as_str()) + .unwrap_or(""); + + self.db.execute( + "INSERT OR REPLACE INTO commits (sha, repo, author, date, additions, deletions, message) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", + params![sha, repo, author, date_str, adds, dels, msg] + )?; + } + } + + if let Some(next) = next_page { + self.check_limits().await?; + page = self.gh.get_page(&Some(next)).await?.unwrap(); + } else { + break; + } + } + Ok(()) + } + + async fn sync_workflows(&self, org: &str, repo: &str, since: DateTime) -> Result<()> { + self.check_limits().await?; + let route = format!("/repos/{}/{}/actions/runs", org, repo); + let created_filter = format!(">{}", since.format("%Y-%m-%d")); + + let mut page: octocrab::Page = self + .gh + .get( + &route, + Some(&serde_json::json!({ + "created": created_filter, "per_page": 100 + })), + ) + .await?; + + loop { + let next_page = page.next.clone(); + for run in page.items { + let id = run.get("id").and_then(|v| v.as_i64()).unwrap_or(0); + let name = run.get("name").and_then(|v| v.as_str()).unwrap_or(""); + let head = run + .get("head_branch") + .and_then(|v| v.as_str()) + .unwrap_or(""); + let conclusion = run + .get("conclusion") + .and_then(|v| v.as_str()) + .unwrap_or("in_progress"); + let created_at = run.get("created_at").and_then(|v| v.as_str()).unwrap_or(""); + let updated_at = run.get("updated_at").and_then(|v| v.as_str()).unwrap_or(""); + + let duration = if let (Some(start), Some(end)) = ( + run.get("created_at").and_then(|v| v.as_str()), + run.get("updated_at").and_then(|v| v.as_str()), + ) { + let s = DateTime::parse_from_rfc3339(start).unwrap_or(Utc::now().into()); + let e = DateTime::parse_from_rfc3339(end).unwrap_or(Utc::now().into()); + (e - s).num_milliseconds() + } else { + 0 + }; + + self.db.execute( + "INSERT OR REPLACE INTO workflow_runs (id, repo, name, head_branch, conclusion, created_at, updated_at, duration_ms) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)", + params![id, repo, name, head, conclusion, created_at, updated_at, duration] + )?; + } + + if let Some(next) = next_page { + self.check_limits().await?; + page = self.gh.get_page(&Some(next)).await?.unwrap(); + } else { + break; + } + } + Ok(()) + } + + async fn sync_stars(&mut self, org: &str, repo: &models::Repository) -> Result<()> { + self.check_limits().await?; + let token = std::env::var("GITHUB_TOKEN").unwrap_or_default(); + let star_gh = OctocrabBuilder::new() + .personal_token(token) + .add_header(ACCEPT, "application/vnd.github.star+json".to_string()) + .build()?; + + let mut remote_users = HashSet::new(); + + let route = format!("/repos/{}/{}/stargazers", org, repo.name); + let mut page: octocrab::Page = star_gh + .get(&route, Some(&serde_json::json!({ "per_page": 100 }))) + .await?; + + loop { + let next_page = page.next.clone(); + for entry in page.items { + if let (Some(starred_at), Some(user)) = (entry.starred_at, entry.user) { + remote_users.insert(user.login.clone()); + self.db.execute( + "INSERT OR REPLACE INTO stargazers (repo, user, starred_at) VALUES (?1, ?2, ?3)", + params![repo.name, user.login, starred_at.to_rfc3339()], + )?; + } + } + if let Some(next) = next_page { + self.check_limits().await?; + page = star_gh.get_page(&Some(next)).await?.unwrap(); + } else { + break; + } + } + + let mut stmt = self + .db + .prepare("SELECT user FROM stargazers WHERE repo = ?1")?; + let rows = stmt.query_map(params![repo.name], |row| row.get::<_, String>(0))?; + + let mut to_delete = Vec::new(); + for local_user in rows { + let u = local_user?; + if !remote_users.contains(&u) { + to_delete.push(u); + } + } + + for u in to_delete { + self.db.execute( + "DELETE FROM stargazers WHERE repo = ?1 AND user = ?2", + params![repo.name, u], + )?; + } + + Ok(()) + } + + async fn sync_pull_requests(&self, org: &str, repo: &str, since: DateTime) -> Result<()> { + self.check_limits().await?; + let mut page = self + .gh + .pulls(org, repo) + .list() + .state(octocrab::params::State::All) + .sort(octocrab::params::pulls::Sort::Updated) + .direction(octocrab::params::Direction::Descending) + .per_page(100) + .send() + .await?; + + let mut keep_fetching = true; + loop { + let next_page = page.next; + for pr in page.items { + if let Some(updated) = pr.updated_at { + if updated < since { + keep_fetching = false; + break; + } + } + + let json = serde_json::to_string(&pr)?; + let pr_id = pr.id.0 as i64; + let pr_number = pr.number as i64; + let state_str = match pr.state { + Some(models::IssueState::Open) => "open", + Some(models::IssueState::Closed) => "closed", + _ => "unknown", + }; + + self.db.execute( + "INSERT OR REPLACE INTO pull_requests + (id, repo, number, state, author, title, created_at, updated_at, merged_at, closed_at, data) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)", + params![ + pr_id, repo, pr_number, state_str, + pr.user.as_ref().map(|u| u.login.clone()).unwrap_or_default(), + pr.title.unwrap_or_default(), + pr.created_at.map(|d| d.to_rfc3339()).unwrap_or_default(), + pr.updated_at.map(|d| d.to_rfc3339()).unwrap_or_default(), + pr.merged_at.map(|t| t.to_rfc3339()), + pr.closed_at.map(|t| t.to_rfc3339()), + json + ], + )?; + + if pr.updated_at.map(|t| t >= since).unwrap_or(false) { + self.sync_reviews(org, repo, pr.number).await?; + } + } + + if !keep_fetching { + break; + } + if let Some(next) = next_page { + self.check_limits().await?; + page = self.gh.get_page(&Some(next)).await?.unwrap(); + } else { + break; + } + } + Ok(()) + } + + async fn sync_reviews(&self, org: &str, repo: &str, pr_number: u64) -> Result<()> { + let mut page = self + .gh + .pulls(org, repo) + .list_reviews(pr_number) + .per_page(100) + .send() + .await?; + loop { + let next_page = page.next; + for review in page.items { + let json = serde_json::to_string(&review)?; + let review_id = review.id.0 as i64; + let pr_num = pr_number as i64; + let state_str = review + .state + .map(|s| format!("{:?}", s).to_uppercase()) + .unwrap_or_else(|| "UNKNOWN".to_string()); + + self.db.execute( + "INSERT OR REPLACE INTO pr_reviews (id, repo, pr_number, state, author, submitted_at, data) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", + params![ + review_id, repo, pr_num, state_str, + review.user.as_ref().map(|u| u.login.clone()).unwrap_or_default(), + review.submitted_at.map(|t| t.to_rfc3339()).unwrap_or_default(), + json + ], + )?; + } + if let Some(next) = next_page { + self.check_limits().await?; + page = self.gh.get_page(&Some(next)).await?.unwrap(); + } else { + break; + } + } + Ok(()) + } + + async fn sync_issues(&self, org: &str, repo: &str, since: DateTime) -> Result<()> { + self.check_limits().await?; + let route = format!("/repos/{}/{}/issues", org, repo); + + // GitHub's /issues endpoint rejects very old "since" dates (returns 0 items). + // This appears to work for our use case. + let use_since_filter = since.year() >= 2010; + + let mut page: octocrab::Page = if use_since_filter { + self.gh.get(&route, Some(&serde_json::json!({ + "state": "all", "sort": "updated", "direction": "desc", "since": since.to_rfc3339(), "per_page": 100 + }))).await? + } else { + // First sync: don't pass since parameter to avoid GitHub API bug + self.gh.get(&route, Some(&serde_json::json!({ + "state": "all", "sort": "updated", "direction": "desc", "per_page": 100 + }))).await? + }; + + let mut keep_fetching = true; + loop { + let next_page = page.next.clone(); + for issue in page.items { + let updated_at_str = issue + .get("updated_at") + .and_then(|v| v.as_str()) + .unwrap_or(""); + let updated_at = DateTime::parse_from_rfc3339(updated_at_str) + .map(|dt| dt.with_timezone(&Utc)) + .unwrap_or_else(|_| Utc::now()); + + if updated_at < since { + keep_fetching = false; + break; + } + if issue.get("pull_request").is_some() { + continue; + } + + let json = serde_json::to_string(&issue)?; + let id = issue.get("id").and_then(|v| v.as_i64()).unwrap_or(0); + let number = issue.get("number").and_then(|v| v.as_i64()).unwrap_or(0); + let state = issue + .get("state") + .and_then(|v| v.as_str()) + .unwrap_or("unknown"); + let author = issue + .get("user") + .and_then(|u| u.get("login")) + .and_then(|l| l.as_str()) + .unwrap_or("unknown"); + let title = issue.get("title").and_then(|v| v.as_str()).unwrap_or(""); + let created = issue + .get("created_at") + .and_then(|v| v.as_str()) + .unwrap_or(""); + let closed = issue.get("closed_at").and_then(|v| v.as_str()); + + self.db.execute( + "INSERT OR REPLACE INTO issues + (id, repo, number, state, author, title, created_at, updated_at, closed_at, data) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)", + params![id, repo, number, state, author, title, created, updated_at_str, closed, json], + )?; + } + if !keep_fetching { + break; + } + if let Some(next) = next_page { + self.check_limits().await?; + page = self.gh.get_page(&Some(next)).await?.unwrap(); + } else { + break; + } + } + Ok(()) + } + + async fn sync_issue_comments(&self, org: &str, repo: &str, since: DateTime) -> Result<()> { + self.check_limits().await?; + let route = format!("/repos/{}/{}/issues/comments", org, repo); + let mut page: octocrab::Page = self.gh.get(&route, Some(&serde_json::json!({ + "sort": "updated", "direction": "desc", "since": since.to_rfc3339(), "per_page": 100 + }))).await?; + + let mut keep_fetching = true; + loop { + let next_page = page.next.clone(); + for comment in page.items { + let updated_at_str = comment + .get("updated_at") + .and_then(|v| v.as_str()) + .unwrap_or(""); + let updated_at = DateTime::parse_from_rfc3339(updated_at_str) + .map(|dt| dt.with_timezone(&Utc)) + .unwrap_or_else(|_| Utc::now()); + + if updated_at < since { + keep_fetching = false; + break; + } + let issue_url = comment + .get("issue_url") + .and_then(|v| v.as_str()) + .unwrap_or(""); + let issue_number: i64 = issue_url + .split('/') + .next_back() + .unwrap_or("0") + .parse() + .unwrap_or(0); + let id = comment.get("id").and_then(|v| v.as_i64()).unwrap_or(0); + let author = comment + .get("user") + .and_then(|u| u.get("login")) + .and_then(|l| l.as_str()) + .unwrap_or("unknown"); + let created = comment + .get("created_at") + .and_then(|v| v.as_str()) + .unwrap_or(""); + let json = serde_json::to_string(&comment)?; + + self.db.execute( + "INSERT OR REPLACE INTO issue_comments (id, repo, issue_number, author, created_at, updated_at, data) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", + params![id, repo, issue_number, author, created, updated_at_str, json], + )?; + } + if !keep_fetching { + break; + } + if let Some(next) = next_page { + self.check_limits().await?; + page = self.gh.get_page(&Some(next)).await?.unwrap(); + } else { + break; + } + } + Ok(()) + } + + async fn sync_pr_comments(&self, org: &str, repo: &str, since: DateTime) -> Result<()> { + self.check_limits().await?; + let route = format!("/repos/{}/{}/pulls/comments", org, repo); + let mut page: octocrab::Page = self.gh.get(&route, Some(&serde_json::json!({ + "sort": "updated", "direction": "desc", "since": since.to_rfc3339(), "per_page": 100 + }))).await?; + + let mut keep_fetching = true; + loop { + let next_page = page.next.clone(); + for comment in page.items { + let updated_at_str = comment + .get("updated_at") + .and_then(|v| v.as_str()) + .unwrap_or(""); + let updated_at = DateTime::parse_from_rfc3339(updated_at_str) + .map(|dt| dt.with_timezone(&Utc)) + .unwrap_or_else(|_| Utc::now()); + + if updated_at < since { + keep_fetching = false; + break; + } + let pull_url = comment + .get("pull_request_url") + .and_then(|v| v.as_str()) + .unwrap_or(""); + let pr_number: i64 = pull_url + .split('/') + .next_back() + .unwrap_or("0") + .parse() + .unwrap_or(0); + let id = comment.get("id").and_then(|v| v.as_i64()).unwrap_or(0); + let author = comment + .get("user") + .and_then(|u| u.get("login")) + .and_then(|l| l.as_str()) + .unwrap_or("unknown"); + let created = comment + .get("created_at") + .and_then(|v| v.as_str()) + .unwrap_or(""); + let json = serde_json::to_string(&comment)?; + + self.db.execute( + "INSERT OR REPLACE INTO pr_review_comments (id, repo, pr_number, author, created_at, updated_at, data) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", + params![id, repo, pr_number, author, created, updated_at_str, json], + )?; + } + if !keep_fetching { + break; + } + if let Some(next) = next_page { + self.check_limits().await?; + page = self.gh.get_page(&Some(next)).await?.unwrap(); + } else { + break; + } + } + Ok(()) + } + + fn is_missing_resource(err: &octocrab::Error) -> bool { + match err { + octocrab::Error::GitHub { source, .. } => { + source.status_code == StatusCode::NOT_FOUND + || source.status_code == StatusCode::GONE + || source.message.eq_ignore_ascii_case("Not Found") + || source.message.eq_ignore_ascii_case("Not Found.") + } + _ => false, + } + } +} diff --git a/community-dashboard/strands-metrics/src/db.rs b/community-dashboard/strands-metrics/src/db.rs new file mode 100644 index 0000000..294743b --- /dev/null +++ b/community-dashboard/strands-metrics/src/db.rs @@ -0,0 +1,192 @@ +use anyhow::Result; +use rusqlite::Connection; +use std::path::Path; + +pub fn init_db>(path: P) -> Result { + let conn = Connection::open(path)?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS app_state ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + )", + [], + )?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS pull_requests ( + id INTEGER PRIMARY KEY, + repo TEXT NOT NULL, + number INTEGER NOT NULL, + state TEXT NOT NULL, + author TEXT NOT NULL, + title TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + merged_at TEXT, + closed_at TEXT, + deleted_at TEXT, + data TEXT NOT NULL + )", + [], + )?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS issues ( + id INTEGER PRIMARY KEY, + repo TEXT NOT NULL, + number INTEGER NOT NULL, + state TEXT NOT NULL, + author TEXT NOT NULL, + title TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + closed_at TEXT, + deleted_at TEXT, + data TEXT NOT NULL + )", + [], + )?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS issue_comments ( + id INTEGER PRIMARY KEY, + repo TEXT NOT NULL, + issue_number INTEGER NOT NULL, + author TEXT NOT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + data TEXT NOT NULL + )", + [], + )?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS pr_reviews ( + id INTEGER PRIMARY KEY, + repo TEXT NOT NULL, + pr_number INTEGER NOT NULL, + state TEXT NOT NULL, + author TEXT NOT NULL, + submitted_at TEXT NOT NULL, + data TEXT NOT NULL + )", + [], + )?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS pr_review_comments ( + id INTEGER PRIMARY KEY, + repo TEXT NOT NULL, + pr_number INTEGER NOT NULL, + author TEXT NOT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + data TEXT NOT NULL + )", + [], + )?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS stargazers ( + repo TEXT NOT NULL, + user TEXT NOT NULL, + starred_at TEXT NOT NULL, + PRIMARY KEY (repo, user) + )", + [], + )?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS commits ( + sha TEXT PRIMARY KEY, + repo TEXT NOT NULL, + author TEXT NOT NULL, + date TEXT NOT NULL, + additions INTEGER DEFAULT 0, + deletions INTEGER DEFAULT 0, + message TEXT + )", + [], + )?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS workflow_runs ( + id INTEGER PRIMARY KEY, + repo TEXT NOT NULL, + name TEXT, + head_branch TEXT, + conclusion TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + duration_ms INTEGER DEFAULT 0 + )", + [], + )?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS daily_metrics ( + date TEXT NOT NULL, + repo TEXT NOT NULL, + + prs_opened INTEGER DEFAULT 0, + prs_merged INTEGER DEFAULT 0, + issues_opened INTEGER DEFAULT 0, + issues_closed INTEGER DEFAULT 0, + + churn_additions INTEGER DEFAULT 0, + churn_deletions INTEGER DEFAULT 0, + + ci_failures INTEGER DEFAULT 0, + ci_runs INTEGER DEFAULT 0, + + stars INTEGER DEFAULT 0, + + open_items_count INTEGER DEFAULT 0, + open_issues_count INTEGER DEFAULT 0, + open_prs_count INTEGER DEFAULT 0, + + time_to_first_response REAL DEFAULT 0, + avg_issue_resolution_time REAL DEFAULT 0, + avg_pr_resolution_time REAL DEFAULT 0, + + time_to_merge_internal REAL DEFAULT 0, + time_to_merge_external REAL DEFAULT 0, + + PRIMARY KEY (date, repo) + )", + [], + )?; + + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_pr_repo_updated ON pull_requests(repo, updated_at)", + [], + )?; + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_issues_repo_updated ON issues(repo, updated_at)", + [], + )?; + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_comments_repo_issue ON issue_comments(repo, issue_number)", + [], + )?; + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_reviews_repo_pr ON pr_reviews(repo, pr_number)", + [], + )?; + conn.execute("CREATE INDEX IF NOT EXISTS idx_review_comments_repo_pr ON pr_review_comments(repo, pr_number)", [])?; + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_stars_repo_date ON stargazers(repo, starred_at)", + [], + )?; + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_commits_repo_date ON commits(repo, date)", + [], + )?; + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_workflows_repo_date ON workflow_runs(repo, created_at)", + [], + )?; + + Ok(conn) +} diff --git a/community-dashboard/strands-metrics/src/main.rs b/community-dashboard/strands-metrics/src/main.rs new file mode 100644 index 0000000..479079a --- /dev/null +++ b/community-dashboard/strands-metrics/src/main.rs @@ -0,0 +1,119 @@ +mod aggregates; +mod client; +mod db; + +use anyhow::Result; +use clap::{Parser, Subcommand}; +use client::GitHubClient; +use db::init_db; +use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use octocrab::OctocrabBuilder; +use std::path::PathBuf; +use std::sync::Arc; +use tracing::level_filters::LevelFilter; + +const ORG: &str = "strands-agents"; + +#[derive(Parser)] +#[clap(author, version, about)] +struct Cli { + #[clap(long, short, default_value = "../metrics.db")] + db_path: PathBuf, + #[clap(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Smart sync. Grabs only what is new. + Sync, + /// Garbage collection. Checks open items against reality and marks missing ones as deleted. + Sweep, + /// Run raw SQL. + Query { sql: String }, +} + +#[tokio::main] +async fn main() -> Result<()> { + tracing_subscriber::fmt() + .with_writer(std::io::stderr) + .with_max_level(LevelFilter::WARN) + .init(); + + let args = Cli::parse(); + let mut conn = init_db(&args.db_path)?; + + match args.command { + Commands::Sync => { + let gh_token = std::env::var("GITHUB_TOKEN").expect("GITHUB_TOKEN must be set"); + let octocrab = OctocrabBuilder::new().personal_token(gh_token).build()?; + + let m = Arc::new(MultiProgress::new()); + let sty = ProgressStyle::with_template("{spinner:.green} {msg}") + .unwrap() + .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ "); + + let pb = m.add(ProgressBar::new_spinner()); + pb.set_style(sty.clone()); + pb.enable_steady_tick(std::time::Duration::from_millis(120)); + pb.set_message("Initializing Sync..."); + + let mut client = GitHubClient::new(octocrab, &mut conn, pb.clone()); + + client.sync_org(ORG).await?; + + pb.set_message("Calculating metrics..."); + aggregates::compute_metrics(&conn)?; + + pb.finish_with_message("Done!"); + } + Commands::Sweep => { + let gh_token = std::env::var("GITHUB_TOKEN").expect("GITHUB_TOKEN must be set"); + let octocrab = OctocrabBuilder::new().personal_token(gh_token).build()?; + + let m = Arc::new(MultiProgress::new()); + let sty = ProgressStyle::with_template("{spinner:.green} {msg}") + .unwrap() + .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ "); + + let pb = m.add(ProgressBar::new_spinner()); + pb.set_style(sty); + pb.enable_steady_tick(std::time::Duration::from_millis(120)); + pb.set_message("Starting Sweep..."); + + let mut client = GitHubClient::new(octocrab, &mut conn, pb.clone()); + client.sweep_org(ORG).await?; + + pb.finish_with_message("Sweep complete."); + } + Commands::Query { sql } => { + let mut stmt = conn.prepare(&sql)?; + let column_count = stmt.column_count(); + let names: Vec = stmt.column_names().into_iter().map(String::from).collect(); + + println!("{}", names.join(" | ")); + println!("{}", "-".repeat(names.len() * 15)); + + let mut rows = stmt.query([])?; + while let Some(row) = rows.next()? { + let mut row_values = Vec::new(); + for i in 0..column_count { + let val = row.get_ref(i)?; + let text = match val { + rusqlite::types::ValueRef::Null => "NULL".to_string(), + rusqlite::types::ValueRef::Integer(i) => i.to_string(), + rusqlite::types::ValueRef::Real(f) => f.to_string(), + rusqlite::types::ValueRef::Text(t) => { + String::from_utf8_lossy(t).to_string() + } + rusqlite::types::ValueRef::Blob(_) => "".to_string(), + }; + row_values.push(text); + } + println!("{}", row_values.join(" | ")); + } + } + } + + Ok(()) +}