AI

Copilot feature engagement入門——AI活用の定着度をFirestoreで追う

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

2026年9月18日
GitHub CopilotAIFirestoreNext.jsGitHub Actions
Copilot feature engagement入門——AI活用の定着度をFirestoreで追う

はじめに

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機能が継続的に使われているか」を見やすくなったことです。

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の利用メトリクスは、そのための地図として使うと実務に効きます。