イベント管理者のためのChrome拡張機能を作った話

2023/08/21に公開

最近ダッシュボード - connpassでイベントに出たり、主催したりすることが多くなったので、イベント統計情報に追加表示をする Chrome 拡張機能を作ったので実装の内容を紹介します。

↓ のように CVR(PV)と CVR(UU)を表示しています。(この画像の統計情報はダミーです)
stats

  • 拡張機能はこちらから追加できます

https://chrome.google.com/webstore/detail/connpass-advanced-stats/pnmbnkjhpgbcdlbhnidjhllieimckgnh?hl=ja&authuser=0

  • リポジトリはこちら

https://github.com/tokku5552/connpass-advanced-stats

プロジェクト構成

今回は popup 表示(chrome の右上をクリックして使うタイプの拡張機能)と、dom をいじる script を両方組み込む想定だったので、生意気にyarn workspaceのモノレポ構成にしています。

tree . -I node_modules -I out
.
├── assets # アイコンなどのファイル
│   ├── connpass_stats_cover.png
│   ├── icon_128x128.png
│   └── icon_stats.png
├── extensions
│   ├── manifest.json # Chrome Extensionの設定ファイル。
│   └── scripts
│       ├── index.js
│       └── index.js.map
├── popup/ # Next.jsのプロジェクト。現状は何もない。
├── scripts/ # DOM操作用のプロジェクト
│   ├── jest.config.js
│   ├── package.json
│   ├── src
│   │   ├── index.ts
│   │   └── setupTests.ts
│   └── tsconfig.json
├── renovate.json
├── package.json # workspaceの定義
├── README.md
└── yarn.lock

root においてある package.json はこんな感じ。

package.json
{
  "name": "connpass-advanced-stats",
  "version": "1.0.0",
  "private": true,
  "license": "MIT",
  "workspaces": {
    "packages": [
      "popup",
      "scripts"
    ],
    "nohoist": [
      "**"
    ]
  },
  "scripts": {
    "dev": "concurrently -k \"yarn workspace popup dev\" ",
    "lint": "concurrently -k \"yarn workspace popup lint\" ",
    "test": "concurrently -k \"yarn workspace popup test\" ",
    "build": "yarn build:prebuild && yarn build:main",
    "build:main": "yarn workspace popup build && yarn workspace scripts build && cp -pr scripts/out/* extensions/scripts",
    "build:prebuild": "rimraf extensions/scripts && mkdir -p extensions/scripts"
  },
  "devDependencies": {
    "concurrently": "^7.6.0",
    "rimraf": "^3.0.2 ",
    "eslint": "^8.32.0",
    "prettier": "^2.8.4",
    "typescript": "^4.9.4"
  }
}

popupscriptsに分けていて、タスクの実行にはconcurrentlyを使っている。けどnpm-run-allに変えたい、、、
基本的には各 package のビルドを行って、その生成物をextensionsフォルダに放り込んで、zip で圧縮して Google Web Store にアップロードします。

実装

Chrome 拡張機能ではmanifest.jsonという設定ファイルで、どういう動作をさせるか、ユーザーにどういった権限を要求するかを指定します。
下記のようにcontent_scriptsmatchesを指定すると、マッチした URL のときに指定した js ファイルが実行されるようになります。

extensions/manifest.json
{
  "manifest_version": 3,
  "name": "connpass advanced stats",
  "description": "connpass advanced stats",
  "version": "1.0",
  "content_scripts": [
    {
      "matches": [
        "https://connpass.com/event/*/stats/"
      ],
      "js": [
        "./scripts/index.js"
      ]
    }
  ]
}

このcontent_scriptsで実行しているファイルは以下です。(直接 DOM 操作するの実は初めて書いた)
connpass のイベントの管理者になると、自分が管理者のイベントに対して、イベント統計ページが見れるようになりますが、その HTML からページビューや申込者数などの数値を取ってきて、割り算して並べて表示しているだけです。

scripts/src/index.ts
// CVR書き込み
window.onload = function () {
  const eventStatsElements = document.getElementsByClassName(
    'EventStatsHero stats_hero_area flex-row'
  );
  const pageview =
    +document.getElementsByClassName('PageviewsHero num')[0].innerHTML;
  const visitor =
    +document.getElementsByClassName('VisitorsHero num')[0].innerHTML;
  const participation = +document.getElementsByClassName(
    'ParticipationsHero num'
  )[0].innerHTML;

  const conversionRatePV = Math.floor((participation / pageview) * 1000) / 10;
  const conversionRatePVElement = createConversionPVElement(conversionRatePV);

  const conversionRateUU = Math.floor((participation / visitor) * 1000) / 10;
  const conversionRateUUElement = createConversionUUElement(conversionRateUU);

  eventStatsElements[0].appendChild(conversionRatePVElement);
  eventStatsElements[0].appendChild(conversionRateUUElement);
};

const createConversionPVElement = (value: number) => {
  const element = document.createElement('div');
  element.innerHTML = `
    <div class="list conversions">
      <p>CVR(PV)</p>
      <p class="ConversionHero num">${value}%</p>
    </div>
    `;
  element.style.color = '#ff9900';
  return element;
};

const createConversionUUElement = (value: number) => {
  const element = document.createElement('div');
  element.innerHTML = `
    <div class="list conversions">
      <p>CVR(UU)</p>
      <p class="ConversionHero num">${value}%</p>
    </div>
    `;
  element.style.color = '#0090c9';
  return element;
};

リリース

renovateで npm package のバージョンを自動更新したりしているので、こういった記事を参考にリリースを自動化しています。

release.yml
name: release
on:
  pull_request:
    types:
      - closed
    branches:
      - main
jobs:
  prepare:
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18
      - name: Cache node modules
        id: cache
        uses: actions/cache@v3
        with:
          path: "**/node_modules"
          key: cache-node-modules-${{ hashFiles('yarn.lock') }}
      - name: yarn install
        run: |
          yarn install
  release:
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    needs: prepare
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18
      - name: Cache node modules
        id: cache
        uses: actions/cache@v3
        with:
          path: "**/node_modules"
          key: cache-node-modules-${{ hashFiles('yarn.lock') }}
      - name: yarn install
        run: |
          yarn install
      - uses: fregante/daily-version-action@v2
        id: daily-version
      - name: build # NOTE: temporarily disable popup
        run: |
          yarn build
          rm -rf ./extensions/popup
      - name: Update manifest.json
        run: |
          npx dot-json@1 extensions/manifest.json version ${{ steps.daily-version.outputs.version }}
      - name: Archive
        run: |
          zip -r extension.zip ./extensions
      - name: Submit
        run: |
          npx chrome-webstore-upload-cli@2 upload --source extension.zip --auto-publish
        env:
          EXTENSION_ID: ${{ secrets.EXTENSION_ID }}
          CLIENT_ID: ${{ secrets.CLIENT_ID }}
          CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}
          REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }}
      - name: Create release draft
        uses: softprops/action-gh-release@v1
        with:
          tag_name: ${{ steps.daily-version.outputs.version }}
          draft: false
          generate_release_notes: true
          files: extension.zip

version はfregante/daily-version-action@v2を使って現在日付をもとに自動生成しています。
その version を元に tag を付与して、GitHub の release にも反映させるようにしています。

今後やりたいこと

  • CVR のグラフ追加する
  • イベント管理のページとかでも情報集計したりできそうなので、機能追加する
  • connpass が公開している API 使うともうちょっと発展的な数値を取れそうなので、追加する
  • scripts のビルドを高速化する(esbuild か vite か swc あたり?)

コントリビュートもお待ちしています 🙏

https://github.com/tokku5552/connpass-advanced-stats

他の拡張機能もあります。

Discussion