diff --git a/.github/workflows/package-stats.yml b/.github/workflows/package-stats.yml index ba24b645..09f648b2 100644 --- a/.github/workflows/package-stats.yml +++ b/.github/workflows/package-stats.yml @@ -49,7 +49,7 @@ jobs: run: | set -euo pipefail files=( - docs/assets/package-downloads-monthly.svg + docs/assets/package-downloads-daily.svg docs/assets/package-downloads.md docs/assets/package-downloads-history.jsonl ) @@ -82,6 +82,10 @@ jobs: done git add "${files[@]}" + # Drop superseded monthly chart so the data branch does not keep a stale SVG. + if [ -f docs/assets/package-downloads-monthly.svg ]; then + git rm -f docs/assets/package-downloads-monthly.svg + fi if git diff --cached --quiet; then echo "Package stats already up to date." exit 0 diff --git a/README.md b/README.md index 3a8655bd..b04a5d48 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ separately from standalone release downloads. Charts refresh daily via [`package-stats.yml`](.github/workflows/package-stats.yml). Exact numbers: [package-downloads.md](https://github.com/bitdynamics-ab/canton-devkit/blob/release-stats-data/docs/assets/package-downloads.md). -![Monthly DPM package downloads](https://raw.githubusercontent.com/bitdynamics-ab/canton-devkit/release-stats-data/docs/assets/package-downloads-monthly.svg) +![Daily DPM package downloads](https://raw.githubusercontent.com/bitdynamics-ab/canton-devkit/release-stats-data/docs/assets/package-downloads-daily.svg) ## Contributing diff --git a/scripts/package-stats-daily.jq b/scripts/package-stats-daily.jq new file mode 100644 index 00000000..1bbdc390 --- /dev/null +++ b/scripts/package-stats-daily.jq @@ -0,0 +1,11 @@ +# Daily download counts from cumulative daily snapshots. +# +# Input: JSON array of { date, total, ... } (use jq -s on a JSONL history file). +# Output: [ { date: "YYYY-MM-DD", downloads: N }, ... ] oldest first. +# The first snapshot is baseline only and contributes no delta. Negative steps clamp to 0. + +sort_by(.date) +| . as $rows +| [ range(1; length) + | { date: $rows[.].date, + downloads: ([($rows[.].total - $rows[.-1].total), 0] | max) } ] diff --git a/scripts/package-stats-monthly.jq b/scripts/package-stats-monthly.jq deleted file mode 100644 index 663ca9a1..00000000 --- a/scripts/package-stats-monthly.jq +++ /dev/null @@ -1,37 +0,0 @@ -# Monthly download counts from cumulative daily snapshots. -# -# Input: JSON array of { date, total, ... } (use jq -s on a JSONL history file). -# Output: [ { month: "YYYY-MM", downloads: N }, ... ] oldest first. -# Missing calendar months between the first and last observed delta month are filled with 0. -# The first snapshot is baseline only and contributes no delta. Negative steps clamp to 0. - -def year_month: - .[0:7]; - -def month_index: - ((.[0:4] | tonumber) * 12) + (.[5:7] | tonumber); - -def from_month_index: - ((. - 1) / 12 | floor) as $y - | ((. - 1) % 12 + 1) as $m - | ($m | tostring | if length == 1 then "0\(.)" else . end) as $mm - | "\($y)-\($mm)"; - -sort_by(.date) -| . as $rows -| [ range(1; length) - | { month: ($rows[.].date | year_month), - idx: ($rows[.].date | year_month | month_index), - delta: ([($rows[.].total - $rows[.-1].total), 0] | max) } ] -| group_by(.month) -| map({ month: .[0].month, idx: .[0].idx, downloads: (map(.delta) | add) }) -| . as $observed -| if ($observed | length) == 0 then [] - else - ($observed | map(.idx) | min) as $first - | ($observed | map(.idx) | max) as $last - | ($observed | map({(.month): .downloads}) | add) as $by_month - | [ range($first; $last + 1) - | from_month_index as $m - | { month: $m, downloads: ($by_month[$m] // 0) } ] - end diff --git a/scripts/package-stats.sh b/scripts/package-stats.sh index 2a126ff0..6e2b3073 100755 --- a/scripts/package-stats.sh +++ b/scripts/package-stats.sh @@ -15,8 +15,8 @@ # https://github.com/bitdynamics-ab/canton-devkit/pkgs/container/canton-devkit # # Outputs (into docs/assets/): -# package-downloads-monthly.svg — new downloads per calendar month (line; last 6 months) -# package-downloads.md — total + monthly summary table +# package-downloads-daily.svg — new downloads per day (line; last 30 days) +# package-downloads.md — total + daily summary table (last 30 days) # package-downloads-history.jsonl — appended daily snapshot (total + last30Days) # # Environment: @@ -188,7 +188,7 @@ snapshot_date="${SNAPSHOT_DATE:-$(date -u +%Y-%m-%d)}" touch "${HISTORY_FILE}" history_len="$(jq -s 'length' "${HISTORY_FILE}" 2>/dev/null || echo 0)" -# Backfill when history has fewer than 2 rows so monthly deltas are possible +# Backfill when history has fewer than 2 rows so daily deltas are possible # immediately from the page sparkline. if [ "${history_len}" -lt 2 ]; then echo "seeding history from sparkline (history_len=${history_len})" >&2 @@ -221,19 +221,21 @@ today_row="$(jq -nc --arg d "${snapshot_date}" --argjson total "${total}" --argj mv "${HISTORY_FILE}.tmp" "${HISTORY_FILE}" echo "updated ${HISTORY_FILE} (snapshot ${snapshot_date})" -# --- Monthly chart from history deltas ------------------------------------ -monthly_rows="$(jq -s -f "${script_dir}/package-stats-monthly.jq" "${HISTORY_FILE}")" +# --- Daily chart from history deltas -------------------------------------- +# GitHub only exposes a cumulative total. Diff consecutive snapshots, clamp +# negatives, then chart the trailing window so x-axis labels stay readable. +daily_rows="$(jq -s -f "${script_dir}/package-stats-daily.jq" "${HISTORY_FILE}")" -by_month_data="$(printf '%s' "${monthly_rows}" | jq ' - .[-6:] as $months - | { labels: [ $months[].month ], +by_day_data="$(printf '%s' "${daily_rows}" | jq ' + .[-30:] as $days + | { labels: [ $days[].date[5:] ], series: [ { name: "Downloads", color: "#2563eb", - values: [ $months[].downloads ] } ] + values: [ $days[].downloads ] } ] } ')" -render_line_chart "${OUT_DIR}/package-downloads-monthly.svg" \ - "Monthly DPM package downloads (last 6 months)" "Month (oldest -> newest)" "${by_month_data}" +render_line_chart "${OUT_DIR}/package-downloads-daily.svg" \ + "Daily DPM package downloads (last 30 days)" "Day (oldest -> newest)" "${by_day_data}" # --- Markdown summary ----------------------------------------------------- { @@ -244,14 +246,14 @@ render_line_chart "${OUT_DIR}/package-downloads-monthly.svg" \ printf '**Last 30 days (page sparkline):** %s.\n\n' "${last30}" echo "Source: [GitHub Packages](${PACKAGE_URL})." echo - echo "### Monthly downloads" + echo "### Daily downloads (last 30 days)" echo - echo "| Month | Downloads |" + echo "| Date | Downloads |" echo "|---|---|" - if [ "$(printf '%s' "${monthly_rows}" | jq 'length')" -eq 0 ]; then + if [ "$(printf '%s' "${daily_rows}" | jq 'length')" -eq 0 ]; then echo "| — | 0 (need at least two daily snapshots) |" else - printf '%s' "${monthly_rows}" | jq -r 'reverse[] | "| \(.month) | \(.downloads) |"' + printf '%s' "${daily_rows}" | jq -r '.[-30:] | reverse[] | "| \(.date) | \(.downloads) |"' fi echo } > "${OUT_DIR}/package-downloads.md" diff --git a/scripts/package-stats_test.sh b/scripts/package-stats_test.sh index 4032f229..bdbf1a8a 100755 --- a/scripts/package-stats_test.sh +++ b/scripts/package-stats_test.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash -# Regression checks for scripts/package-stats.sh and package-stats-monthly.jq. +# Regression checks for scripts/package-stats.sh and package-stats-daily.jq. set -euo pipefail script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -jq_filter="${script_dir}/package-stats-monthly.jq" +jq_filter="${script_dir}/package-stats-daily.jq" fixture="${script_dir}/testdata/package-stats-page.html" tmp="$(mktemp -d)" trap 'rm -rf "${tmp}"' EXIT @@ -28,25 +28,16 @@ assert_file_contains() { fi } -# --- Monthly jq (same contract as release-stats-monthly.jq) ---------------- +# --- Daily jq ------------------------------------------------------------- cat > "${tmp}/history.jsonl" <<'EOF' {"date":"2026-07-06","total":10} -{"date":"2026-07-20","total":12} -{"date":"2026-07-28","total":15} -{"date":"2026-08-05","total":18} +{"date":"2026-07-07","total":12} +{"date":"2026-07-08","total":15} +{"date":"2026-07-09","total":18} EOF -assert_eq "monthly aggregation" \ +assert_eq "daily aggregation" \ "$(jq -s -f "${jq_filter}" "${tmp}/history.jsonl")" \ - '[{"month":"2026-07","downloads":5},{"month":"2026-08","downloads":3}]' - -cat > "${tmp}/history-gap.jsonl" <<'EOF' -{"date":"2026-07-06","total":10} -{"date":"2026-07-10","total":14} -{"date":"2026-09-02","total":20} -EOF -assert_eq "gap fill" \ - "$(jq -s -f "${jq_filter}" "${tmp}/history-gap.jsonl")" \ - '[{"month":"2026-07","downloads":4},{"month":"2026-08","downloads":0},{"month":"2026-09","downloads":6}]' + '[{"date":"2026-07-07","downloads":2},{"date":"2026-07-08","downloads":3},{"date":"2026-07-09","downloads":3}]' cat > "${tmp}/history-clamp.jsonl" <<'EOF' {"date":"2026-08-03","total":20} @@ -55,13 +46,19 @@ cat > "${tmp}/history-clamp.jsonl" <<'EOF' EOF assert_eq "negative delta clamp" \ "$(jq -s -f "${jq_filter}" "${tmp}/history-clamp.jsonl")" \ - '[{"month":"2026-08","downloads":2}]' + '[{"date":"2026-08-04","downloads":0},{"date":"2026-08-05","downloads":2}]' printf '%s\n' '{"date":"2026-08-03","total":20}' > "${tmp}/history-one.jsonl" assert_eq "single snapshot" \ "$(jq -s -f "${jq_filter}" "${tmp}/history-one.jsonl")" \ '[]' +# Chart window: last 30 days only (full series may be longer). +window="$(printf '%s' "$(jq -n '[range(1;41) | {date: ("2026-01-" + (if . < 10 then "0\(.)" else "\(.)" end)), downloads: .}]')" | jq -c '.[-30:] | { labels: [.[].date[5:]], values: [.[].downloads] }')" +assert_eq "chart last-30 window length" \ + "$(printf '%s' "${window}" | jq '.labels | length')" \ + '30' + # --- End-to-end against HTML fixture -------------------------------------- out="${tmp}/out" mkdir -p "${out}" @@ -72,7 +69,7 @@ OUT_DIR="${out}" \ history="${out}/package-downloads-history.jsonl" md="${out}/package-downloads.md" -svg="${out}/package-downloads-monthly.svg" +svg="${out}/package-downloads-daily.svg" [ -f "${history}" ] || { echo "FAIL: missing history" >&2; exit 1; } [ -f "${md}" ] || { echo "FAIL: missing markdown" >&2; exit 1; } @@ -92,8 +89,8 @@ assert_eq "first seeded row" \ '{"date":"2026-08-01","total":10,"last30Days":null}' assert_file_contains "md total" "${md}" "**Total downloads:** 100" -assert_file_contains "md month" "${md}" "| 2026-10 | 50 |" -assert_file_contains "svg title" "${svg}" "Monthly DPM package downloads" +assert_file_contains "md day" "${md}" "| 2026-10-20 | 20 |" +assert_file_contains "svg title" "${svg}" "Daily DPM package downloads" # Idempotent same-day re-run must not duplicate rows. OUT_DIR="${out}" \