diff --git a/tools/gemini-cli-bot/metrics/scripts/latency.ts b/tools/gemini-cli-bot/metrics/scripts/latency.ts index 7dd5dcd1f6..cac759686e 100644 --- a/tools/gemini-cli-bot/metrics/scripts/latency.ts +++ b/tools/gemini-cli-bot/metrics/scripts/latency.ts @@ -2,26 +2,30 @@ * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 - * - * @license */ import { GITHUB_OWNER, GITHUB_REPO } from '../types.js'; import { execSync } from 'node:child_process'; try { + const now = new Date(); + const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + const since = sevenDaysAgo.toISOString().split('T')[0]; + const query = ` - query($owner: String!, $repo: String!) { - repository(owner: $owner, name: $repo) { - pullRequests(last: 100, states: MERGED) { - nodes { + query($prQuery: String!, $issueQuery: String!) { + prSearch: search(query: $prQuery, type: ISSUE, first: 100) { + nodes { + ... on PullRequest { authorAssociation createdAt mergedAt } } - issues(last: 100, states: CLOSED) { - nodes { + } + issueSearch: search(query: $issueQuery, type: ISSUE, first: 100) { + nodes { + ... on Issue { authorAssociation createdAt closedAt @@ -30,36 +34,45 @@ try { } } `; + + const prQuery = `repo:${GITHUB_OWNER}/${GITHUB_REPO} is:pr is:merged merged:>=${since}`; + const issueQuery = `repo:${GITHUB_OWNER}/${GITHUB_REPO} is:issue is:closed closed:>=${since}`; + const output = execSync( - `gh api graphql -F owner=${GITHUB_OWNER} -F repo=${GITHUB_REPO} -f query='${query}'`, + `gh api graphql -F prQuery='${prQuery}' -F issueQuery='${issueQuery}' -f query='${query}'`, { encoding: 'utf-8' }, ); - const data = JSON.parse(output).data.repository; + const data = JSON.parse(output).data; - const prs = data.pullRequests.nodes.map( - (p: { - authorAssociation: string; - mergedAt: string; - createdAt: string; - }) => ({ - association: p.authorAssociation, - latencyHours: - (new Date(p.mergedAt).getTime() - new Date(p.createdAt).getTime()) / - (1000 * 60 * 60), - }), - ); - const issues = data.issues.nodes.map( - (i: { - authorAssociation: string; - closedAt: string; - createdAt: string; - }) => ({ - association: i.authorAssociation, - latencyHours: - (new Date(i.closedAt).getTime() - new Date(i.createdAt).getTime()) / - (1000 * 60 * 60), - }), - ); + const prs = (data?.prSearch?.nodes || []) + .filter((p: any) => p && p.mergedAt) + .map( + (p: { + authorAssociation: string; + mergedAt: string; + createdAt: string; + }) => ({ + association: p.authorAssociation, + latencyHours: + (new Date(p.mergedAt).getTime() - new Date(p.createdAt).getTime()) / + (1000 * 60 * 60), + }), + ); + + const issues = (data?.issueSearch?.nodes || []) + .filter((i: any) => i && i.closedAt) + .map( + (i: { + authorAssociation: string; + closedAt: string; + createdAt: string; + }) => ({ + association: i.authorAssociation, + latencyHours: + (new Date(i.closedAt).getTime() - new Date(i.createdAt).getTime()) / + (1000 * 60 * 60), + }), + ); const isMaintainer = (assoc: string) => ['MEMBER', 'OWNER', 'COLLABORATOR'].includes(assoc); diff --git a/tools/gemini-cli-bot/metrics/scripts/throughput.ts b/tools/gemini-cli-bot/metrics/scripts/throughput.ts index 3806dd407a..e26ee9e720 100644 --- a/tools/gemini-cli-bot/metrics/scripts/throughput.ts +++ b/tools/gemini-cli-bot/metrics/scripts/throughput.ts @@ -2,25 +2,29 @@ * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 - * - * @license */ import { GITHUB_OWNER, GITHUB_REPO } from '../types.js'; import { execSync } from 'node:child_process'; try { + const now = new Date(); + const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + const since = sevenDaysAgo.toISOString().split('T')[0]; + const query = ` - query($owner: String!, $repo: String!) { - repository(owner: $owner, name: $repo) { - pullRequests(last: 100, states: MERGED) { - nodes { + query($prQuery: String!, $issueQuery: String!) { + prSearch: search(query: $prQuery, type: ISSUE, first: 100) { + nodes { + ... on PullRequest { authorAssociation mergedAt } } - issues(last: 100, states: CLOSED) { - nodes { + } + issueSearch: search(query: $issueQuery, type: ISSUE, first: 100) { + nodes { + ... on Issue { authorAssociation closedAt } @@ -28,37 +32,38 @@ try { } } `; + + const prQuery = `repo:${GITHUB_OWNER}/${GITHUB_REPO} is:pr is:merged merged:>=${since}`; + const issueQuery = `repo:${GITHUB_OWNER}/${GITHUB_REPO} is:issue is:closed closed:>=${since}`; + const output = execSync( - `gh api graphql -F owner=${GITHUB_OWNER} -F repo=${GITHUB_REPO} -f query='${query}'`, + `gh api graphql -F prQuery='${prQuery}' -F issueQuery='${issueQuery}' -f query='${query}'`, { encoding: 'utf-8' }, ); - const data = JSON.parse(output).data.repository; + const data = JSON.parse(output).data; - const prs = data.pullRequests.nodes - .map((p: { authorAssociation: string; mergedAt: string }) => ({ + const prs = (data?.prSearch?.nodes || []) + .filter((p: any) => p && p.mergedAt) + .map((p: any) => ({ association: p.authorAssociation, date: new Date(p.mergedAt).getTime(), - })) - .sort((a: { date: number }, b: { date: number }) => a.date - b.date); + })); - const issues = data.issues.nodes - .map((i: { authorAssociation: string; closedAt: string }) => ({ + const issues = (data?.issueSearch?.nodes || []) + .filter((i: any) => i && i.closedAt) + .map((i: any) => ({ association: i.authorAssociation, date: new Date(i.closedAt).getTime(), - })) - .sort((a: { date: number }, b: { date: number }) => a.date - b.date); + })); const isMaintainer = (assoc: string) => ['MEMBER', 'OWNER', 'COLLABORATOR'].includes(assoc); + // We use a fixed 7-day denominator to prevent throughput spikes from small samples const calculateThroughput = ( items: { association: string; date: number }[], ) => { - if (items.length < 2) return 0; - const first = items[0].date; - const last = items[items.length - 1].date; - const days = (last - first) / (1000 * 60 * 60 * 24); - return days > 0 ? items.length / days : items.length; // items per day + return items.length / 7; // items per day over 7 days }; const prOverall = calculateThroughput(prs); diff --git a/tools/gemini-cli-bot/metrics/scripts/user_touches.ts b/tools/gemini-cli-bot/metrics/scripts/user_touches.ts index 5ccffa94fc..ffa26b3c9c 100644 --- a/tools/gemini-cli-bot/metrics/scripts/user_touches.ts +++ b/tools/gemini-cli-bot/metrics/scripts/user_touches.ts @@ -2,26 +2,30 @@ * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 - * - * @license */ import { GITHUB_OWNER, GITHUB_REPO } from '../types.js'; import { execSync } from 'node:child_process'; try { + const now = new Date(); + const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + const since = sevenDaysAgo.toISOString().split('T')[0]; + const query = ` - query($owner: String!, $repo: String!) { - repository(owner: $owner, name: $repo) { - pullRequests(last: 100, states: MERGED) { - nodes { + query($prQuery: String!, $issueQuery: String!) { + prSearch: search(query: $prQuery, type: ISSUE, first: 100) { + nodes { + ... on PullRequest { authorAssociation comments { totalCount } reviews { totalCount } } } - issues(last: 100, states: CLOSED) { - nodes { + } + issueSearch: search(query: $issueQuery, type: ISSUE, first: 100) { + nodes { + ... on Issue { authorAssociation comments { totalCount } } @@ -29,17 +33,21 @@ try { } } `; + + const prQuery = `repo:${GITHUB_OWNER}/${GITHUB_REPO} is:pr is:merged merged:>=${since}`; + const issueQuery = `repo:${GITHUB_OWNER}/${GITHUB_REPO} is:issue is:closed closed:>=${since}`; + const output = execSync( - `gh api graphql -F owner=${GITHUB_OWNER} -F repo=${GITHUB_REPO} -f query='${query}'`, + `gh api graphql -F prQuery='${prQuery}' -F issueQuery='${issueQuery}' -f query='${query}'`, { encoding: 'utf-8' }, ); - const data = JSON.parse(output).data.repository; + const data = JSON.parse(output).data; - const prs = data.pullRequests.nodes; - const issues = data.issues.nodes; + const prNodes = data?.prSearch?.nodes || []; + const issueNodes = data?.issueSearch?.nodes || []; const allItems = [ - ...prs.map( + ...prNodes.filter((p: any) => p).map( (p: { authorAssociation: string; comments: { totalCount: number }; @@ -49,7 +57,7 @@ try { touches: p.comments.totalCount + (p.reviews ? p.reviews.totalCount : 0), }), ), - ...issues.map( + ...issueNodes.filter((i: any) => i).map( (i: { authorAssociation: string; comments: { totalCount: number } }) => ({ association: i.authorAssociation, touches: i.comments.totalCount,