@sentry/junior-github 0.109.0 → 0.111.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +219 -116
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1687,6 +1687,20 @@ var issueStatsSchema = z10.object({
1687
1687
  ...row,
1688
1688
  medianCloseTimeMs: row.medianCloseTimeMs ?? void 0
1689
1689
  }));
1690
+ var pullRequestDaySchema = z10.object({
1691
+ closed: z10.number().int().nonnegative(),
1692
+ created: z10.number().int().nonnegative(),
1693
+ date: z10.string().date(),
1694
+ merged: z10.number().int().nonnegative()
1695
+ }).strict();
1696
+ var issueDaySchema = z10.object({
1697
+ closedCompleted: z10.number().int().nonnegative(),
1698
+ closedDuplicate: z10.number().int().nonnegative(),
1699
+ closedNotPlanned: z10.number().int().nonnegative(),
1700
+ closedUnknown: z10.number().int().nonnegative(),
1701
+ created: z10.number().int().nonnegative(),
1702
+ date: z10.string().date()
1703
+ }).strict();
1690
1704
  var issueRepositoryStatsSchema = z10.object({
1691
1705
  closedCompleted: z10.number().int().nonnegative(),
1692
1706
  closedDuplicate: z10.number().int().nonnegative(),
@@ -1778,6 +1792,55 @@ async function aggregatePullRequestWindows(args) {
1778
1792
  `);
1779
1793
  return z10.array(pullRequestStatsSchema).parse(queryRows(result));
1780
1794
  }
1795
+ async function aggregatePullRequestDays(args) {
1796
+ const end = new Date(args.nowMs);
1797
+ const start = startOfUtcDay(args.nowMs - (WINDOWS.at(-1) - 1) * DAY_MS);
1798
+ const table = juniorGitHubPullRequests;
1799
+ const result = await args.db.execute(sql3`
1800
+ WITH days AS (
1801
+ SELECT generate_series(
1802
+ date_trunc('day', ${start}::timestamptz AT TIME ZONE 'UTC'),
1803
+ date_trunc('day', ${end}::timestamptz AT TIME ZONE 'UTC'),
1804
+ interval '1 day'
1805
+ ) AS day
1806
+ ), events AS (
1807
+ SELECT
1808
+ date_trunc('day', ${table.openedAt} AT TIME ZONE 'UTC') AS day,
1809
+ 'created' AS kind
1810
+ FROM ${table}
1811
+ WHERE ${table.openedAt} >= ${start}
1812
+ UNION ALL
1813
+ SELECT
1814
+ date_trunc('day', ${table.mergedAt} AT TIME ZONE 'UTC') AS day,
1815
+ 'merged' AS kind
1816
+ FROM ${table}
1817
+ WHERE ${table.state} = 'merged' AND ${table.mergedAt} >= ${start}
1818
+ UNION ALL
1819
+ SELECT
1820
+ date_trunc('day', ${table.closedAt} AT TIME ZONE 'UTC') AS day,
1821
+ 'closed' AS kind
1822
+ FROM ${table}
1823
+ WHERE ${table.state} = 'closed_unmerged' AND ${table.closedAt} >= ${start}
1824
+ ), daily AS (
1825
+ SELECT
1826
+ events.day,
1827
+ count(*) FILTER (WHERE events.kind = 'created')::integer AS created,
1828
+ count(*) FILTER (WHERE events.kind = 'merged')::integer AS merged,
1829
+ count(*) FILTER (WHERE events.kind = 'closed')::integer AS closed
1830
+ FROM events
1831
+ GROUP BY events.day
1832
+ )
1833
+ SELECT
1834
+ to_char(days.day, 'YYYY-MM-DD') AS "date",
1835
+ coalesce(daily.created, 0)::integer AS "created",
1836
+ coalesce(daily.merged, 0)::integer AS "merged",
1837
+ coalesce(daily.closed, 0)::integer AS "closed"
1838
+ FROM days
1839
+ LEFT JOIN daily ON daily.day = days.day
1840
+ ORDER BY days.day
1841
+ `);
1842
+ return z10.array(pullRequestDaySchema).parse(queryRows(result));
1843
+ }
1781
1844
  async function aggregatePullRequestRepositories(args) {
1782
1845
  const start = new Date(args.nowMs - 30 * DAY_MS);
1783
1846
  const table = juniorGitHubPullRequests;
@@ -1887,6 +1950,57 @@ async function aggregateIssueWindows(args) {
1887
1950
  `);
1888
1951
  return z10.array(issueStatsSchema).parse(queryRows(result));
1889
1952
  }
1953
+ async function aggregateIssueDays(args) {
1954
+ const end = new Date(args.nowMs);
1955
+ const start = startOfUtcDay(args.nowMs - (WINDOWS.at(-1) - 1) * DAY_MS);
1956
+ const table = juniorGitHubIssues;
1957
+ const result = await args.db.execute(sql3`
1958
+ WITH days AS (
1959
+ SELECT generate_series(
1960
+ date_trunc('day', ${start}::timestamptz AT TIME ZONE 'UTC'),
1961
+ date_trunc('day', ${end}::timestamptz AT TIME ZONE 'UTC'),
1962
+ interval '1 day'
1963
+ ) AS day
1964
+ ), events AS (
1965
+ SELECT
1966
+ date_trunc('day', ${table.openedAt} AT TIME ZONE 'UTC') AS day,
1967
+ 'created' AS kind
1968
+ FROM ${table}
1969
+ WHERE ${table.openedAt} >= ${start}
1970
+ UNION ALL
1971
+ SELECT
1972
+ date_trunc('day', ${table.closedAt} AT TIME ZONE 'UTC') AS day,
1973
+ coalesce(${table.stateReason}, 'unknown') AS kind
1974
+ FROM ${table}
1975
+ WHERE ${table.state} = 'closed' AND ${table.closedAt} >= ${start}
1976
+ ), daily AS (
1977
+ SELECT
1978
+ events.day,
1979
+ count(*) FILTER (WHERE events.kind = 'created')::integer AS created,
1980
+ count(*) FILTER (WHERE events.kind = 'completed')::integer
1981
+ AS closed_completed,
1982
+ count(*) FILTER (WHERE events.kind = 'duplicate')::integer
1983
+ AS closed_duplicate,
1984
+ count(*) FILTER (WHERE events.kind = 'not_planned')::integer
1985
+ AS closed_not_planned,
1986
+ count(*) FILTER (WHERE events.kind = 'unknown')::integer
1987
+ AS closed_unknown
1988
+ FROM events
1989
+ GROUP BY events.day
1990
+ )
1991
+ SELECT
1992
+ to_char(days.day, 'YYYY-MM-DD') AS "date",
1993
+ coalesce(daily.created, 0)::integer AS "created",
1994
+ coalesce(daily.closed_completed, 0)::integer AS "closedCompleted",
1995
+ coalesce(daily.closed_duplicate, 0)::integer AS "closedDuplicate",
1996
+ coalesce(daily.closed_not_planned, 0)::integer AS "closedNotPlanned",
1997
+ coalesce(daily.closed_unknown, 0)::integer AS "closedUnknown"
1998
+ FROM days
1999
+ LEFT JOIN daily ON daily.day = days.day
2000
+ ORDER BY days.day
2001
+ `);
2002
+ return z10.array(issueDaySchema).parse(queryRows(result));
2003
+ }
1890
2004
  async function aggregateIssueRepositories(args) {
1891
2005
  const start = new Date(args.nowMs - 30 * DAY_MS);
1892
2006
  const table = juniorGitHubIssues;
@@ -1937,11 +2051,25 @@ function formatDuration(value) {
1937
2051
  function formatCommitComposition(stats) {
1938
2052
  return `${stats.juniorOnly} Junior-only \xB7 ${stats.mixed} mixed \xB7 ${stats.compositionUnknown} unknown`;
1939
2053
  }
2054
+ function startOfUtcDay(timestampMs) {
2055
+ const date = new Date(timestampMs);
2056
+ date.setUTCHours(0, 0, 0, 0);
2057
+ return date;
2058
+ }
1940
2059
  async function buildGitHubOutcomeReport(args) {
1941
- const [windows, repositories, issueWindows, issueRepositories] = await Promise.all([
2060
+ const [
2061
+ windows,
2062
+ pullRequestDays,
2063
+ repositories,
2064
+ issueWindows,
2065
+ issueDays,
2066
+ issueRepositories
2067
+ ] = await Promise.all([
1942
2068
  aggregatePullRequestWindows(args),
2069
+ aggregatePullRequestDays(args),
1943
2070
  aggregatePullRequestRepositories(args),
1944
2071
  aggregateIssueWindows(args),
2072
+ aggregateIssueDays(args),
1945
2073
  aggregateIssueRepositories(args)
1946
2074
  ]);
1947
2075
  const thirtyDays = windows.find((window) => window.days === 30);
@@ -1950,20 +2078,10 @@ async function buildGitHubOutcomeReport(args) {
1950
2078
  generatedAt: new Date(args.nowMs).toISOString(),
1951
2079
  title: "GitHub work delivered",
1952
2080
  metrics: [
1953
- { label: "PRs created \xB7 30d", value: String(thirtyDays.created) },
1954
- {
1955
- label: "PRs merged \xB7 30d",
1956
- tone: thirtyDays.merged > 0 ? "good" : "neutral",
1957
- value: String(thirtyDays.merged)
1958
- },
1959
2081
  {
1960
2082
  label: "Junior-only merge rate \xB7 30d",
1961
2083
  value: formatPercent(thirtyDays.juniorOnlyRate)
1962
2084
  },
1963
- {
1964
- label: "PRs closed unmerged \xB7 30d",
1965
- value: String(thirtyDays.closed)
1966
- },
1967
2085
  {
1968
2086
  label: "PR merge rate \xB7 30d",
1969
2087
  value: formatPercent(thirtyDays.mergeRate)
@@ -1972,46 +2090,60 @@ async function buildGitHubOutcomeReport(args) {
1972
2090
  label: "median PR merge time \xB7 30d",
1973
2091
  value: formatDuration(thirtyDays.medianMergeTimeMs)
1974
2092
  },
1975
- { label: "issues created \xB7 30d", value: String(issueThirtyDays.created) },
1976
- {
1977
- label: "issues completed \xB7 30d",
1978
- tone: issueThirtyDays.closedCompleted > 0 ? "good" : "neutral",
1979
- value: String(issueThirtyDays.closedCompleted)
1980
- },
1981
- {
1982
- label: "issues closed as duplicate \xB7 30d",
1983
- value: String(issueThirtyDays.closedDuplicate)
1984
- },
1985
2093
  {
1986
- label: "issues closed as not planned \xB7 30d",
1987
- value: String(issueThirtyDays.closedNotPlanned)
2094
+ label: "median issue close time \xB7 30d",
2095
+ value: formatDuration(issueThirtyDays.medianCloseTimeMs)
1988
2096
  }
1989
2097
  ],
1990
- recordSets: [
2098
+ widgets: [
1991
2099
  {
1992
- title: "Pull request outcome windows",
1993
- fields: [
1994
- { key: "window", label: "Window" },
2100
+ id: "pull-request-outcomes",
2101
+ type: "bar_chart",
2102
+ title: "Pull request outcomes",
2103
+ description: "Daily outcomes",
2104
+ timeRangeDays: [...WINDOWS],
2105
+ series: [
1995
2106
  { key: "created", label: "Created" },
1996
- { key: "merged", label: "Merged" },
1997
- { key: "closed", label: "Closed unmerged" },
1998
- { key: "commitComposition", label: "Merged commit composition" },
1999
- { key: "mergeRate", label: "Merge rate" },
2000
- { key: "mergeTime", label: "Median merge time" }
2107
+ { key: "merged", label: "Merged", tone: "good" },
2108
+ { key: "closed", label: "Closed unmerged", tone: "danger" }
2001
2109
  ],
2002
- records: windows.map((stats) => ({
2003
- id: `${stats.days}d`,
2110
+ categories: pullRequestDays.map((stats) => ({
2111
+ id: stats.date,
2112
+ label: stats.date,
2004
2113
  values: {
2005
- window: `${stats.days} days`,
2006
- created: String(stats.created),
2007
- merged: String(stats.merged),
2008
- closed: String(stats.closed),
2009
- commitComposition: formatCommitComposition(stats),
2010
- mergeRate: formatPercent(stats.mergeRate),
2011
- mergeTime: formatDuration(stats.medianMergeTimeMs)
2114
+ created: stats.created,
2115
+ merged: stats.merged,
2116
+ closed: stats.closed
2012
2117
  }
2013
2118
  }))
2014
2119
  },
2120
+ {
2121
+ id: "issue-outcomes",
2122
+ type: "bar_chart",
2123
+ title: "Issue outcomes",
2124
+ description: "Daily outcomes",
2125
+ timeRangeDays: [...WINDOWS],
2126
+ series: [
2127
+ { key: "created", label: "Created" },
2128
+ { key: "completed", label: "Completed", tone: "good" },
2129
+ { key: "duplicate", label: "Duplicate", tone: "warning" },
2130
+ { key: "notPlanned", label: "Not planned", tone: "danger" },
2131
+ { key: "unknown", label: "Unknown" }
2132
+ ],
2133
+ categories: issueDays.map((stats) => ({
2134
+ id: stats.date,
2135
+ label: stats.date,
2136
+ values: {
2137
+ created: stats.created,
2138
+ completed: stats.closedCompleted,
2139
+ duplicate: stats.closedDuplicate,
2140
+ notPlanned: stats.closedNotPlanned,
2141
+ unknown: stats.closedUnknown
2142
+ }
2143
+ }))
2144
+ }
2145
+ ],
2146
+ recordSets: [
2015
2147
  {
2016
2148
  title: "Pull request repositories \xB7 30d",
2017
2149
  emptyText: "No Junior-owned pull request activity yet.",
@@ -2035,30 +2167,6 @@ async function buildGitHubOutcomeReport(args) {
2035
2167
  }
2036
2168
  }))
2037
2169
  },
2038
- {
2039
- title: "Issue outcome windows",
2040
- fields: [
2041
- { key: "window", label: "Window" },
2042
- { key: "created", label: "Created" },
2043
- { key: "completed", label: "Completed" },
2044
- { key: "duplicate", label: "Duplicate" },
2045
- { key: "notPlanned", label: "Not planned" },
2046
- { key: "unknown", label: "Unknown reason" },
2047
- { key: "closeTime", label: "Median close time" }
2048
- ],
2049
- records: issueWindows.map((stats) => ({
2050
- id: `${stats.days}d`,
2051
- values: {
2052
- window: `${stats.days} days`,
2053
- created: String(stats.created),
2054
- completed: String(stats.closedCompleted),
2055
- duplicate: String(stats.closedDuplicate),
2056
- notPlanned: String(stats.closedNotPlanned),
2057
- unknown: String(stats.closedUnknown),
2058
- closeTime: formatDuration(stats.medianCloseTimeMs)
2059
- }
2060
- }))
2061
- },
2062
2170
  {
2063
2171
  title: "Issue repositories \xB7 30d",
2064
2172
  emptyText: "No Junior-owned issue activity yet.",
@@ -2317,6 +2425,52 @@ if [ -n "\${JUNIOR_GIT_ACTOR_COAUTHOR_TRAILERS:-}" ]; then
2317
2425
  done <<< "$JUNIOR_GIT_ACTOR_COAUTHOR_TRAILERS"
2318
2426
  fi
2319
2427
 
2428
+ # The hook owns co-author attribution. Remove every model-supplied co-author
2429
+ # from the final Git trailer block before appending the host-derived actors.
2430
+ tmp_file=$(mktemp)
2431
+ trap 'rm -f "$tmp_file"' EXIT
2432
+ awk '
2433
+ { lines[NR] = $0 }
2434
+ END {
2435
+ line = NR
2436
+ while (line > 0 && lines[line] == "") {
2437
+ line--
2438
+ }
2439
+ start = line
2440
+ while (start > 0 && lines[start] != "") {
2441
+ start--
2442
+ }
2443
+ valid = line > 0
2444
+ for (i = start + 1; valid && i <= line; i++) {
2445
+ if (lines[i] !~ /^[[:alnum:]-]+: .+/) {
2446
+ valid = 0
2447
+ }
2448
+ }
2449
+ if (!valid) {
2450
+ for (i = 1; i <= NR; i++) {
2451
+ print lines[i]
2452
+ }
2453
+ exit 0
2454
+ }
2455
+ kept = 0
2456
+ for (i = start + 1; i <= line; i++) {
2457
+ if (tolower(lines[i]) !~ /^co-authored-by: /) {
2458
+ kept++
2459
+ }
2460
+ }
2461
+ before = kept > 0 ? start : start - 1
2462
+ for (i = 1; i <= before; i++) {
2463
+ print lines[i]
2464
+ }
2465
+ for (i = start + 1; i <= line; i++) {
2466
+ if (tolower(lines[i]) !~ /^co-authored-by: /) {
2467
+ print lines[i]
2468
+ }
2469
+ }
2470
+ }
2471
+ ' "$message_file" > "$tmp_file"
2472
+ cat "$tmp_file" > "$message_file"
2473
+
2320
2474
  final_trailer_block=$(awk '
2321
2475
  { lines[NR] = $0 }
2322
2476
  END {
@@ -2342,57 +2496,6 @@ final_trailer_block=$(awk '
2342
2496
  }
2343
2497
  ' "$message_file")
2344
2498
 
2345
- duplicate_desired_trailer=false
2346
- while IFS= read -r desired_trailer; do
2347
- if [ -n "$desired_trailer" ] && [ "$(printf '%s\\n' "$final_trailer_block" | grep -Fxc -- "$desired_trailer")" -gt 1 ]; then
2348
- duplicate_desired_trailer=true
2349
- break
2350
- fi
2351
- done <<< "$desired_trailers"
2352
-
2353
- if [ "$duplicate_desired_trailer" = true ]; then
2354
- tmp_file=$(mktemp)
2355
- desired_file=$(mktemp)
2356
- trap 'rm -f "$tmp_file" "$desired_file"' EXIT
2357
- printf '%s' "$desired_trailers" > "$desired_file"
2358
- awk -v desired_file="$desired_file" '
2359
- BEGIN {
2360
- while ((getline value < desired_file) > 0) {
2361
- if (value != "") {
2362
- wanted[value] = 1
2363
- }
2364
- }
2365
- close(desired_file)
2366
- }
2367
- { lines[NR] = $0 }
2368
- END {
2369
- line = NR
2370
- while (line > 0 && lines[line] == "") {
2371
- line--
2372
- }
2373
- start = line
2374
- while (start > 0 && lines[start] != "") {
2375
- start--
2376
- }
2377
- valid = line > 0
2378
- for (i = start + 1; valid && i <= line; i++) {
2379
- if (lines[i] !~ /^[[:alnum:]-]+: .+/) {
2380
- valid = 0
2381
- }
2382
- }
2383
- for (i = 1; i <= NR; i++) {
2384
- if (valid && i > start && i <= line && wanted[lines[i]]) {
2385
- if (seen[lines[i]]++) {
2386
- continue
2387
- }
2388
- }
2389
- print lines[i]
2390
- }
2391
- }
2392
- ' "$message_file" > "$tmp_file"
2393
- cat "$tmp_file" > "$message_file"
2394
- fi
2395
-
2396
2499
  missing_trailers=""
2397
2500
  collect_missing_trailer() {
2398
2501
  if ! printf '%s\\n' "$final_trailer_block" | grep -Fqx -- "$1"; then
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-github",
3
- "version": "0.109.0",
3
+ "version": "0.111.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -31,7 +31,7 @@
31
31
  "@sinclair/typebox": "^0.34.49",
32
32
  "drizzle-orm": "^0.45.2",
33
33
  "zod": "^4.4.3",
34
- "@sentry/junior-plugin-api": "0.109.0"
34
+ "@sentry/junior-plugin-api": "0.111.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "^25.9.1",