Backlogのステータス変更に疲れた僕が、拡張機能を作ったら世界が変わったんだが

# はじめに

こんにちは！Gakken LEAPでバックエンドエンジニアをしているmatsuuraです。

ついにやってきました、Qiitaアドベントカレンダー2024！
Gakken LEAPとしては初参加のこのイベント、記念すべき一番手を務めるのはなんと私です。
ぜひ、Gakken LEAPの他の記事も見てもらえたらと思います！

https://qiita.com/advent-calendar/2024/gleap

さて、みなさん、Backlogは使っていますか？
私のプロジェクトでも利用しているタスク管理ツールですが……最近、こんなことを思ったんです。

「**課題のステータス変更、面倒すぎない？**」

## ステータス変更4クリックの壁

Backlogでタスク対応を開始する時のフロー、みなさんご存じですよね？

1. 一覧やカンバンから対応する課題を選ぶ
2. 課題詳細画面で内容を確認する
3. ステータスを「未対応(Open)」から「対応中(In progress)」に変更する

普通にやればこれだけ。でも、この **3番目のステップがとにかく煩わしい** のです。

ステータスを変更するには、以下の4クリックが必要です。

- **1クリック目**
  ステータス変更用のオブジェクトを開く
  ![1click.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323069/16be1ab3-c425-d047-555d-dced159467a7.png)

- **2クリック目**
  ステータス変更のセレクトボックスを開く
  ![2click.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323069/5faee7f5-6c31-3aee-eede-58cccb942691.png)

- **3クリック目**
  セレクトボックスから「対応中(In progress)」を選択する
  ![3click.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323069/8e336f66-d681-cf8b-cc36-4408a3e23c81.png)

- **4クリック目**
  ステータス変更を確定する
  ![4click.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323069/b75a6d83-0fca-f169-f83e-912f4cd3d66c.png)

**「え、4クリックも？」**
これが日常的に積み重なると、エンジニアの集中力ゲージがみるみる減少していきます。

もちろん、カンバン表示でドラッグ＆ドロップすれば楽ですが、詳細画面で内容を確認した流れで「ポチッ」と変更したいですよね。

## 世界を変えるためにChrome拡張を作ってみた

そんなわけで、「クリック地獄から抜け出したい！」と奮起した私は、**Backlog API**を使って、 **1クリックでステータス変更ができるGoogle Chrome拡張機能** を作ることにしました。

どうやって1クリックの世界を実現したかをご紹介します！

# やったこと

上述の通り、Backlog APIを使用してBacklogのステータスを1クリックで変更するボタンを画面に表示するChrome拡張機能を作成しました。

それだけです。

## 実装もろもろ

### Chrome拡張機能の作成

まず、Chromeの拡張機能を作成する際、必要になるファイルが2種類あります。

1. manifest.json
    - 作成するChrome拡張機能のブループリント
    - 含まれる情報
        - 拡張機能のバージョン番号
        - 拡張機能のタイトル
        - 拡張機能を実行するために必要なアクセス許可
    - [参考](https://learn.microsoft.com/ja-jp/microsoft-edge/extensions-chromium/getting-started/manifest-format?tabs=v3)
2. 実行するJavaScriptソース
    - 実行したい処理

#### manifest.json

```json
{
  "manifest_version": 3,
  "name": "Backlog Task Status Changer",
  "version": "1.0",
  "description": "Add buttons to change task status with a single click.",
  "permissions": ["activeTab", "scripting"],
  "content_scripts": [
    {
      "matches": ["https://hogehoge.backlog.jp/*"],
      "js": ["content.js"]
    }
  ]
}
```

:::note warn
重要なのは、`content_scripts`の`matches`で拡張機能を使用するURLを指定している点と`js`で実行するJavaScriptのファイルを指定している点です。
これを含めないと、拡張機能が機能しなかったり、JavaScriptが実行されなかったりします。
:::

#### content.js

```js
const API_URL = 'https://hogehoge.backlog.jp';
// BacklogでAPIキーを取得して設定
const API_KEY = '***************************************';

const observer = new MutationObserver((mutations) => {
  mutations.forEach(async (mutation) => {
    const targetElement = document.querySelector(".comment-editor__edit-area");
    const status1Button = document.querySelector("#change-status-1");

    // 課題詳細画面で追加ボタンがなく、かつ課題エリアが追加された場合に実行
    if (targetElement && !status1Button && checkAddedNodesFirst(mutation.addedNodes[0])) {

      // ステータス一覧取得
      const statuses = await getStatuses();

      for(let i = 0; i < statuses.length; i++){
        const status = statuses[i];
        const statusId = status.id;
        const buttonId = `change-status-${statusId}`;
        const thisButton = document.querySelector(`#${buttonId}`);
        if (thisButton) {
          return;
        }
        const changeStatusButton = document.createElement('button');
        const className = `status-label--${statusId}`;
        changeStatusButton.style.margin = '5px';
        changeStatusButton.style.borderRadius = '50%';
        changeStatusButton.style.backgroundColor = status.color;
        changeStatusButton.className = className;
        changeStatusButton.setAttribute('id', buttonId);
        // ボタンのクリックイベント
        changeStatusButton.addEventListener('click', async (event) => {
          event.preventDefault();
          await changeStatus(statusId);
        });

        targetElement.appendChild(changeStatusButton);
      }
    }
  });
});

observer.observe(document.body, { childList: true, subtree: true });

// 課題IDをURLから取得
function getTaskIdFromUrl() {
  const skipSharp = window.location.pathname.split('#')[0];
  const urlParts = skipSharp.split('/');
  return urlParts[urlParts.length - 1];
}

// プロジェクトIDを取得
function getProjectId() {
  const taskId = getTaskIdFromUrl();
  return taskId.split('-')[0];
}

// プロジェクトのステータス一覧を取得
async function getStatuses() {
  const projectId = getProjectId();
  const apiUrl = `${API_URL}/api/v2/projects/${projectId}/statuses?apiKey=${API_KEY}`;
  const response = await fetch(apiUrl);
  return response.json();
}

// ステータスを変更
async function changeStatus(statusId) {
  const taskId = getTaskIdFromUrl();
  const apiUrl = `${API_URL}/api/v2/issues/${taskId}?apiKey=${API_KEY}`;

  const response = await fetch(apiUrl, {
    method: 'PATCH',
    headers: {'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams({
      'statusId': statusId
    })
  });
}

function checkAddedNodesFirst(nodesFirst) {
  if (typeof nodesFirst === 'undefined') {
    return false;
  }
  const hasQuerySelectorFunction =  typeof nodesFirst.querySelector === 'function'
  if (!hasQuerySelectorFunction) {
    return false;
  }
  const hasIssueArea = nodesFirst.querySelector('#issueArea');
  if (!hasIssueArea) {
    return false;
  }
  return true;
}

```

### Backlog APIキーの取得

上述の`content.js`の`API_KEY`には、BacklogのAPIキーを取得して、値をセットする必要があります。
APIキーの取得フローは以下のとおりです。

1. Backlogの右上のアイコンを選択
1. `Personal Settings`を選択
1. 左のエリアから`API`を選択
1. Submitを選択(中央のMemoは任意)
1. Registered API keysの[API key]の値を取得

![スクリーンショット 2024-10-18 17.05.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323069/494ba0aa-f67c-561f-5b3b-844ed07b558c.png)

### content.jsのAPI_KEYの書き換え

`content.js`の以下の部分の値を取得した値に変更しておきます。

```diff_javascript
- const API_KEY = '***************************************';
+ const API_KEY = '<取得したAPI key>';
```

### Google Chrome 拡張機能の追加

では、作成した拡張機能を追加していきます。

まず、以下から、拡張機能の一覧画面を開きます。
この画面では、すでに自分でインストールした拡張機能を確認することができます。

chrome://extensions/

こちらの左上の「パッケージ化されていない拡張機能を読み込む」をクリックします。

![スクリーンショット 2024-11-19 17.27.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323069/f46f9cca-772e-0ad4-7ef3-9c7a2e4fb86c.png)

:::note info
「パッケージ化されていない拡張機能を読み込む」が表示されない場合は、デベロッパーモードがONになっていない可能性があります。
上の画像の「デベロッパーモード」をONにすると、表示されるようになります。
:::

その後、`manifest.json`と`content.js`が含まれるフォルダを選択してください。

うまくいくと、以下の画像のように拡張機能の一覧に作成した拡張機能が表示されます。

![スクリーンショット 2024-11-19 17.32.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323069/e1695954-4da9-5ce0-d217-5fe8b5e4368f.png)

## 動かした結果

やっと実装と設定が終わったので、使用していきます。
Backlogの課題ページにいくと、右下に各ステータスに対応する色のボタンが表示されています。
これを押すと、右上の表示がその色に関連するステータスに正しく切り替わります。:smile:

![backlog.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3323069/755a10c2-0bae-459b-52ea-42c70de82826.gif)

これで、4クリックの地獄から解放されました！🎉
もう詳細画面を確認した流れで、直感的にステータスを変更できるようになり、作業効率が劇的にアップしました。

# 最後に

今回は、作業効率を向上させるChrome拡張機能の作成方法を紹介しました。

課題の細かなフローにストレスを感じている方や、Backlogをもっと便利に使いたい方にとって、今回の内容が少しでも参考になれば嬉しいです。

## エンジニア募集

Gakken LEAP では教育をアップデートしていきたいエンジニアを絶賛大募集しています！！
ぜひお気軽にカジュアル面談へお越しください！！

https://gakken-leap.co.jp/recruit/