Copilot feature engagement入門——AI活用の定着度をFirestoreで追う
GitHub Copilot impact dashboardのfeature engagement追加を題材に、Next.js + Firestore + GitHub ActionsでAI機能の定着度を追う実装を解説します。

はじめに
GitHub Copilotをチームへ導入したあと、こんな会話になったことはありませんか?
| 知りたいこと | 管理画面だけで困りやすいこと |
|---|---|
| 補完、CLI、Code Reviewのどれが定着しているか | 機能ごとの継続利用をチームの文脈で追いにくい |
| 研修後に利用が広がったか | 施策の日付、チーム、リポジトリと突き合わせたい |
| AI利用が偏っていないか | 日次でFirestoreに蓄積して、自社の管理画面で見たい |
2026年9月17日、GitHubはCopilot impact dashboardにfeature engagementを追加したと発表しました。アクティブユーザーが主要機能をどれくらい継続利用しているかを確認でき、EnterpriseとOrganizationの28日集計レポートAPIにも同じ観点のデータが追加されています。
本記事では、架空の案件管理SaaS「CaseBoard」を題材に、GitHub Copilotの機能別エンゲージメントをGitHub Actionsで取得し、Firestoreへ蓄積してNext.js App Routerの管理画面で見る流れを作ります。
参考にした一次情報は次の通りです。
- Copilot impact dashboard now shows feature engagement
- REST API endpoints for Copilot usage metrics
- GitHub Copilot usage metrics
- Reconciling Copilot usage metrics across dashboards, APIs, and reports
何が変わったのか
今回のポイントは、単に「何回使われたか」ではなく、「どのCopilot機能が継続的に使われているか」を見やすくなったことです。
GitHubの発表では、Copilot impact dashboardで主要機能を28日間のうち少なくとも2日利用したアクティブユーザー数を確認できるようになりました。さらに、EnterpriseとOrganizationの28日集計レポートAPIには copilot_feature_engagement、機能別内訳には totals_by_feature が追加・拡張されています。
対象として挙げられている機能は、code completion、agent edit、passive Copilot code review、active Copilot code review、Copilot cloud agent、Copilot CLI、Copilot appです。
| 観点 | 見たい問い | CaseBoardでの使い道 |
|---|---|---|
| 機能別エンゲージメント | どの機能が定着しているか | チームごとの研修テーマを決める |
| 28日ローリング | 一時的な利用ではなく継続利用か | 新機能導入後の変化を見る |
| adoption phase | 利用段階が進んでいるか | オンボーディング対象を見つける |
| API取得 | 自社データと結合できるか | GitHub IssuesやFirestore上の案件種別と突き合わせる |
事前準備
Organization単位で最新28日レポートを取得する公式エンドポイントは次です。
GET /orgs/{org}/copilot/metrics/reports/organization-28-day/latest
Enterprise単位では次を使います。
GET /enterprises/{enterprise}/copilot/metrics/reports/enterprise-28-day/latest
APIレスポンスはレポート本体を直接返すのではなく、期限付きの download_links を返します。リンク先のNDJSONをダウンロードして、1行ずつ処理します。
Organizationで使う場合は、GitHub Docsにある通り「Organization Copilot metrics」のread権限が必要です。APIを有効にするには、Copilot usage metrics policyも有効になっている必要があります。
なお、GitHub Docsでは、ダッシュボード、API、エクスポートは同じテレメトリを元にしつつ、集計方法や時間窓が異なると説明されています。最近の日付は処理が完了していないこともあるため、CaseBoardでは毎朝「最新28日」を同期し、表示側で report_end_day を明示します。
ハンズオン1: Firestoreの保存形を決める
新しいフィールドは便利ですが、発表直後に内部構造を決め打ちすると後で壊れやすくなります。そこで、確認済みの totals_by_feature は型を付け、copilot_feature_engagement はrawとして保存します。
// src/lib/copilotMetrics/types.ts
export type CopilotFeatureTotal = {
feature: string;
user_initiated_interaction_count?: number;
code_generation_activity_count?: number;
code_acceptance_activity_count?: number;
loc_added_sum?: number;
loc_deleted_sum?: number;
};
export type CopilotDayTotal = {
day: string;
daily_active_users?: number;
weekly_active_users?: number;
monthly_active_users?: number;
totals_by_feature?: CopilotFeatureTotal[];
copilot_feature_engagement?: unknown;
users_in_phase_28d?: unknown;
};
export type CopilotAggregateReport = {
report_start_day: string;
report_end_day: string;
day_totals?: CopilotDayTotal[];
};
Firestoreでは、1回の同期結果を copilotEngagementReports/{org}_{reportEndDay} に保存します。後から表示用集計を作り直せるよう、rawと正規化済み配列を両方残します。
import { FieldValue, type Firestore } from "firebase-admin/firestore";
import type { CopilotAggregateReport } from "./types";
export async function saveCopilotEngagementReport(
db: Firestore,
org: string,
report: CopilotAggregateReport,
) {
const id = `${org}_${report.report_end_day}`;
const latestDay = report.day_totals?.at(-1);
await db.collection("copilotEngagementReports").doc(id).set(
{
org,
reportStartDay: report.report_start_day,
reportEndDay: report.report_end_day,
latestDailyActiveUsers: latestDay?.daily_active_users ?? 0,
latestFeatureTotals: latestDay?.totals_by_feature ?? [],
featureEngagementRaw: latestDay?.copilot_feature_engagement ?? null,
phasePopulationRaw: latestDay?.users_in_phase_28d ?? null,
syncedAt: FieldValue.serverTimestamp(),
},
{ merge: true },
);
}
ここで大切なのは、「APIに新しい値が来たら全部画面に出す」のではなく、「プロダクトとして判断に使う値」と「後で検証するために残す値」を分けることです。
ハンズオン2: GitHub APIからNDJSONを取得する
次に、GitHub Actionsから呼び出す同期処理を作ります。ここではOrganizationの最新28日レポートに絞ります。
type ReportLinksResponse = {
download_links: string[];
report_start_day?: string;
report_end_day?: string;
};
export async function fetchCopilotReportLinks(org: string, token: string) {
const response = await fetch(
`https://api.github.com/orgs/${org}/copilot/metrics/reports/organization-28-day/latest`,
{
headers: {
Accept: "application/vnd.github+json",
Authorization: `Bearer ${token}`,
"X-GitHub-Api-Version": "2026-03-10",
},
},
);
if (!response.ok) {
throw new Error(`GitHub API failed: ${response.status}`);
}
const body = (await response.json()) as ReportLinksResponse;
return body.download_links;
}
export async function downloadNdjson<T>(url: string): Promise<T[]> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Report download failed: ${response.status}`);
}
const text = await response.text();
return text
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line) as T);
}
download_links は一時的なURLです。Firestoreには保存せず、同期処理の中で使い切ります。保存するのはレポートの中身と同期時刻だけにします。
ハンズオン3: GitHub Actionsで毎日同期する
CaseBoardでは、毎朝10時JSTに最新28日レポートを取り直します。GitHub ActionsのcronはUTCなので、0 1 * * * がJSTの10時です。
name: Sync Copilot Feature Engagement
on:
schedule:
- cron: "0 1 * * *"
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "yarn"
- run: yarn install --frozen-lockfile
- name: Sync Copilot engagement
env:
GITHUB_ORG: ${{ vars.GITHUB_ORG }}
GITHUB_COPILOT_METRICS_TOKEN: ${{ secrets.COPILOT_METRICS_TOKEN }}
FIREBASE_SERVICE_ACCOUNT_JSON: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_JSON }}
run: node scripts/sync-copilot-engagement.mjs
このworkflowに本番デプロイ権限は不要です。GitHub APIの読み取りトークンとFirestoreへ保存するための資格情報だけを渡します。AI利用状況のレポートは個人やチームの行動データを含むため、保存先のFirestore Security Rulesや管理画面の認可も合わせて確認します。
Next.js管理画面で見る
最後に、Firestoreから最新レポートを読み、機能別の利用を表示します。ここではServer Componentから呼び出す想定で、表示用の型へ整えます。
import { getFirestore } from "firebase-admin/firestore";
type EngagementViewModel = {
feature: string;
interactions: number;
generated: number;
accepted: number;
};
export async function getLatestCopilotEngagement(org: string) {
const db = getFirestore();
const snapshot = await db
.collection("copilotEngagementReports")
.where("org", "==", org)
.orderBy("reportEndDay", "desc")
.limit(1)
.get();
const doc = snapshot.docs[0];
if (!doc) {
return [];
}
const totals = doc.get("latestFeatureTotals") as unknown;
if (!Array.isArray(totals)) {
return [];
}
return totals.map((item): EngagementViewModel => {
const value = item as {
feature?: string;
user_initiated_interaction_count?: number;
code_generation_activity_count?: number;
code_acceptance_activity_count?: number;
};
return {
feature: value.feature ?? "unknown",
interactions: value.user_initiated_interaction_count ?? 0,
generated: value.code_generation_activity_count ?? 0,
accepted: value.code_acceptance_activity_count ?? 0,
};
});
}
画面では「よく使われている機能」をランキングにするだけでなく、チーム施策と一緒に見ます。たとえば、8月にCode Review研修を実施したなら、active Copilot code reviewの利用が伸びたかを確認します。CLI研修をしたなら、Copilot CLIの定着を見る、という具合です。
運用で気をつけたいこと
Feature engagementは、評価や監視のためだけに使うと現場から嫌われます。CaseBoardでは、個人の順位表ではなく、チームの学習テーマを決めるための指標として扱います。
| 注意点 | 実務での扱い |
|---|---|
| 直近データは遅れることがある | reportEndDay を画面に出す |
| ダッシュボードとAPIで集計窓が違う | 28日ローリングか日次かを明記する |
| 新フィールドの構造が変わる可能性 | raw保存し、表示用変換を薄くする |
| 個人利用データが含まれる | 管理者だけが見られる画面にする |
Copilotの導入効果は、行数や回数だけでは測れません。補完だけが増えているのか、レビューやCLIまで広がっているのかを見ることで、次に投資すべき支援が見えます。
まとめ
GitHub Copilot impact dashboardのfeature engagement追加により、AI機能がチームへどれだけ定着しているかを見やすくなりました。
Next.js + Firestore + GitHub Actionsで扱うなら、まずは公式APIから最新28日レポートを日次同期し、確認済みの totals_by_feature を表示用に整え、copilot_feature_engagement はrawで保存するのがおすすめです。
大事なのは、数字を詰問の材料にしないことです。どの機能が使われ、どこで止まっているのかを見て、研修、ドキュメント、開発ワークフローを少しずつ直していく。Copilotの利用メトリクスは、そのための地図として使うと実務に効きます。