皆さん、こんにちは。サポートエンジニアの岡本(通称:ひげ)です。もしアトラシアンの日本オフィスで髭を生やしている人を見かけたら、それはほぼ間違い無く私ですのでお声がけ頂ければ幸いです。

サポートエンジニアという職種柄、今まであまりメディアに露出することは無かったのですが、これからちょくちょく技術ブログなんかを書いて公開していければと考えているので、お時間のある時にでも是非読んでみて頂ければと思います。

ちなみに今回のブログのテーマはタイトル通り、Chrome Extension(Chrome の機能拡張)を自作することで、アトラシアン製品を利用した業務の効率化を図る、といった趣旨のものとなっておりますが、「前置きはいいから、早く実現方 法が知りたい!」という方は事前準備の章から読んでみて頂ければと思います。

もくじ

日本のサポートエンジニアの悩み

皆さん、アトラシアンのサポートエンジニアが普段どんな仕事をしているかご存知ですか?

アトラシアンのサポートエンジニアは、アトラシアン製品のアフターサポートに従事しており、お客様が製品を導入された後に何らかの技術的問題が発生した場合に、原因の特定と解決策の提案を行います。(アトラシアンサポートについてさらに詳しく知りたい方は、こちらのページをご覧ください!)

また、アトラシアンではタイムゾーンの異なる複数の国・地域にサポートの拠点を設けることで、24 時間のサポート対応が行える体制を敷いています。(日本語でのサポートについては例外を含みます。)ただ、それらのどの拠点においても基本的にサポートは英語でしか提供しておらず、日本でのみ 例外的に現地語(日本語)でのサポートを行っています。

現地語でのサポートを行う際には、英語でサポートを行うのと違って気をつけなければいけない点があります。例えば、マルチバイト文字に起因するシステムの 不具合等がその一つとして挙げられます。特定の言語設定でしか発生しない文字化けのような問題が、システムの設計時に想定していたテストケースからは漏れ ていて、本番環境でいきなり豆腐文字が表示されて青ざめる、等といった問題を経験されたことのある方もいらっしゃるのではないでしょうか?

ただ実際問題、どれだけ細心の注意を払っていてもその類の問題が起こってしまうことは往々にしてあります。アトラシアン製品でもごく稀にこのような文字化 け問題が報告されることがありますが、そういった場合には、サポートエンジニアは製品の言語設定を英語ではなく文字化けの発生した言語に切り替えて、問題の再現を確認する必要があります。また、文字化けに限らず特定の言語設定でのみ発生する問題は少なからず存在するため、特に日本のサポートエンジニアは製品の言語設定をこまめに切り替えながら問題の調査を行うことが多いです。(通常の利用用途であれば、製品の言語設定なんて一度行えば、基本的に二度と変更することなんて無いですよね。)

この言語設定の切り替え作業についてですが、例えば JIRA(JIRA Family 全般)を例にとってみると、今開いている JIRA ページを別の言語で表示しようと思った場合、

  1. 自身の プロファイル ページを開く
  2. ユーザー設定 > 言語 を編集する
  3. ブラウザバック機能で、元々開いていたページに戻る

という三つの手順が必要になります。一日に一度や二度の切り替えであれば、この作業も苦にはならないのですが、日に数十回と繰り返してみると、以外と面倒な作業であることがわかって頂けるかと思います。。。

この面倒な言語設定の切り替え作業、ずっと何とかならないものかとは考えていたのですが、せっかくブログを書くこの機会に何とかしてみることにしました!

事前準備

私個人としては JIRA のサポートを行う機会が一番多いので、とりあえず今回は JIRA に限定した解決方法を考えてみることにします。

まずは、簡単に今回の実現したいことに対する要件の洗い出しを行ってみました。(かなりザックリと、です。)

  • 要件 1 : 開いている JIRA ページから 画面遷移は行わずに、言語設定のみを切り替えたい
  • 要件 2 : 複数の JIRA インスタンスに対して実現したい
  • 要件 3 : あまり時間をかけないで解決したい

要件 1 についてですが、こちらはすでに前述した通りで、現状だと言語設定を切り替えるためだけにわざわざ プロファイル ページに移動し、言語設定を編集した後、また元のページに戻る、という最低でも 2 回の画面遷移が存在することが面倒くささの要因だったので、まずこれを何とかしようと考えました。

要件 2 については、通常アトラシアンのサポートエンジニアは、お客様の環境に合わせて複数のバージョンの JIRA インスタンスを自分のローカルの端末に用意して利用しているため、それらの全てのインスタンスにて言語設定の切り替えが素早く行える必要がありました。

要件 3 については言わずもがな、ですね!

まず、真っ先に思い浮かんだのが REST API の利用で、ユーザーの言語設定を切り替える API というものが存在していないかを調べてみました。するとこちらの思惑通り、api/2/mypreferences という API が目的に合致するようで、以下の用法でユーザーの言語設定の取得・切り替えを行えることが確認できました。

言語設定の取得

1
$ curl -X GET -u <JIRAユーザー名>:<パスワード> -s '<ベースURL>/rest/api/2/mypreferences?key=jira.user.locale'

言語設定の切り替え

1
$ curl -X PUT -H 'Content-Type: application/json' -u <JIRAユーザー名>:<パスワード> -s '<ベースURL>/rest/api/2/mypreferences?key=jira.user.locale' -d '<言語ロケール>'

※ <JIRAユーザー名>, <パスワード>, <ベースURL>, <言語ロケール> は、ご自身の環境のものに置き換えてください。

目的の JIRA ページを開いた状態で、ターミナルを開いて上記の curl コマンドを実行し、ブラウザをリロードすることで、とりあえずは画面遷移無しに言語設定を切り替えるところ(要件 1)までは実現できました。

が、しかし!アクセスする JIRA インスタンス、言語設定を切り替えたい JIRA ユーザーによっても実行するコマンドも変える必要があり、仮にこれらのコマンドをスクリプト化したとしてもいちいちターミナルを開いて実行するのが面倒で、何よりも解決策というにはあまりにも イケてない 気がしますよね。

そこで、JIRA のアドオンとして上記の REST API を使用した機能を実装する、という案を思いつきましたが、そちらの方法だとアドオンをインストールした JIRA インスタンス上でしか言語設定の切り替えを行うことができないので、要件 2 の観点で見るとあまり良い案だとは思えませんでした。(正直に言うと、アドオンをビルドする環境を用意するのに多少手間がかかるのも理由の一つで、要件 3 の観点からもボツにしました。)

このような試行錯誤を繰り返した後、最終的に要件 1 〜 要件 3 を満たす案として思い浮かんだのが、ブラウザの拡張機能としてユーザーの言語設定の切り替え機能を実装する、という手法でした。ブラウザの拡張機能から REST API を実行するのであれば、ブラウザ内に全ての処理が完結するため、別途スクリプトを起動する必要等はありませんし、ブラウザの持つセッション情報等を上手く 使用することで、ブラウザで開いているタブ毎に複数の JIRA インスタンスも制御できるのでは無いかと考えました。要件 3 の実装コストの観点でも、JIRA のアドオンとして実装する場合だと環境構築等がいろいろと面倒ですが、ブラウザの拡張機能であれば、HTML と JavaScript を少量記述するだけで実現できそうだったので、こちらの手法を選択しました。

という訳で前置きが長くなりましたが、作成したブラウザの拡張機能について説明していきたいと思います!

Chrome Extension の実装

まず、目的の機能をどのブラウザの拡張機能として実装するかについてですが、個人的に一番利用する機会の多い Chrome に決めました。Chrome Extension(Chrome の拡張機能)の実装は今回が初めてだったので、初めから特にカッチリと設計を引くこともなく、いろいろと手探りな感じで実装したので、その辺の試行錯誤も 含めて説明していこうと思います。

1. manifest.json を作成する

まず、どんな Extension を作るのにも必ず必要になるのが manifest.json という Extension のマニフェストファイルであり、こちらで Extension の持つ機能の大枠を定義することができるようでした。今回実装する Extension では、下記のような manifest.json を用意してみます。

manifest.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
  "manifest_version": 2,
  "name": "JIRA Language Switcher",
  "version": "1.0.1",
  "description": "This Chrome Extension make it easily that you change your language setting on JIRA.",
  "icons": {
    "16": "images/jira-lang-switcher-logo-16.png",
    "48": "images/jira-lang-switcher-logo-128.png",
    "128": "images/jira-lang-switcher-logo-128.png"
  },
  "browser_action": {
    "default_title": "JIRA Language Switcher",
    "default_icon": "images/jira-lang-switcher-logo-16.png",
    "default_popup": "popup.html"
  },
  "permissions": [
    "tabs",
    "storage",
    "http://*/*",
    "https://*/*"
  ],
  "background": {
    "scripts": [
      "js/jquery-2.2.3.min.js",
      "js/background.js"
    ]
  },
  "options_page": "options.html"
}

簡単にこちらの内容を説明しておくと、3 行目 〜 5 行目で今回実装する Extension の名前、バージョン、説明の定義を、6 行目 〜 10 行目の “icons” という部分で、Extension で使用するアイコン画像の指定を行っています。(余談ですが、本 Extension のアイコンについては弊社のマーケティングスペシャリストの犬山奈穂さんがデザインしてくれました!)

Chrome に Extension をインストールすると、以下のようにツールバーに Extension のアイコンが表示されるようになるのはよく見かけるかと思いますが、11 行目 〜 15 行目の “browser_action” という 部分で、そちらのアイコンをクリックした際に popup.html として実装した画面が、ポップアップとして表示されるように定義しています。

extension_sample

16 行目 〜 21 行目の “permissions” という部分では、Extension からブラウザ自体が提供する機能へのアクセスを制限しています。こちらで定義されている配列はホワイトリストになっていて、配列に含まれるブラウザ機能にはアクセスす ることが可能です。例えば、”tabs” というのはブラウザのタブ情報にアクセスする権限、”storage” というのは Chrome のローカルストレージにアクセスする権限を表しています。

22 行目 〜 27 行目の “background” という部分では、ブラウザが起動している間に裏でずっと待機させておきたい処理を JavaScript として定義することができ、今回の Extension では background.js というファイルにそれらの処理を実装しました。

28 行目の “options_page” という部分では、Extension の設定画面に、options.html として作成した画面を設定しています。

manifest.json の細かい記法については、Google の公式ページや、他にもたくさんの技術ブログが公開されていますので、興味のある方はそちらをご覧ください。

また、manifest.json 内で参照される HTML や JavaScript、またその他の雑多なファイル群についても用意すると、以下のようなディレクトリ構成となりました。

ディレクトリ構成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ tree jira-language-switcher
jira-language-switcher
├── LICENSE.txt
├── README.md
├── css
│   ├── options.css
│   └── popup.css
├── images
│   ├── jira-lang-switcher-logo-128.png
│   └── jira-lang-switcher-logo-16.png
├── js
│   ├── background.js
│   ├── jquery-2.2.3.min.js
│   ├── options.js
│   └── popup.js
├── manifest.json
├── options.html
└── popup.html

ここからは、上記のファイル郡のうち、重要なものについて簡単に説明していきたいと思います!

2. ブラウザのタブ切り替え / ページ遷移のイベントハンドリング

前述した通り、background.js にはブラウザが起動している間に裏でずっと待機させておきたい処理なんかを記述していきます。今回実装する Extension に関して言えば、JIRA ページを開いている時にしか使用されることは無いので、それ以外のタイミングでは、Extension 自体が無効化されていた方が使い勝手が良いと考えました。なので Extension 側では、

  • 現在のアクティブなタブから、すでに JIRA ページが開かれているタブに切り替える
  • 現在のアクティブなタブ上で、JIRA ページに遷移する

というイベントを検知して自動で Extension を有効化、その逆の操作が行われたタイミングで無効化する、といった処理を行う必要がありました。これらの処理は background.js 上では下記のように記述することができます。

タブ切り替え / ページ遷移のイベントハンドラ(background.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* タブ切り替えのイベントハンドリング */
chrome.tabs.onActivated.addListener(function(activeInfo) {
 
  If(isJiraPage(activeInfo.tabId)) {
    chrome.browserAction.enable();
  }
  else {
    chrome.browserAction.disable();
  }
 
});
 
/* ページ遷移のイベントハンドリング */
chrome.tabs.onUpdated.addListener(function(tabId) {
 
  If(isJiraPage(tabId)) {
    chrome.browserAction.enable();
  }
  else {
    chrome.browserAction.disable();
  }
 
});

chrome.tabs.onActivated.addListener(), chrome.tabs.onUpdated.addListener() については Chrome 側で提供されているメソッドで、引数には、タブの切り替えやページ遷移のイベントが発生した際に行いたい処理を、コールバックとして記述することが可能で す。実際にイベントが発生した際には、その時点でのアクティブなタブに関する情報がコールバックへ引数として渡って来るので、そちらの情報を元に開いてい るページが JIRA のものであるかを判断する isJiraPage() のような function を用意してやることで、JIRA ページを開いた時のみ Extension を有効にすることが可能となります。

chrome.browserAction.enable(), chrome.browserAction.disable() についても、 Chrome 側で提供されているメソッドであり、Extension の有効化、無効化を行うためのものとなっております。

3. Chrome のストレージを使用した、Extension の設定情報の管理

現在開いているページが JIRA のものであるかを判定する isJiraPage() のロジックについてですが、言語の切り替えを行う必要のある JIRA インスタンスのベース URL を Extension の設定情報として事前に登録しておき、そちらにマッチするページが開かれた場合にのみ JIRA ページである、と判定するように実装しました。

可能であれば、特に事前に登録等が無い状態で、JIRA ページかどうかの判定ができればよかったのですが、判定の条件等がややこしくなりそうだったので、今回はあきらめました。

Extension に対する JIRA ベース URL の登録方法についてですが、Chrome では Extension の設定画面についても比較的簡単に作成することができるので、Extension のユーザーに、設定画面から自身で使用する JIRA のベース URL を事前に登録してもらう形を取りました。設定画面についても、通常の Web ページと同様 HTML と JavaScript で簡単に作成することが可能です。今回は option.html として下記のような設定画面を作成してみました。

上記のページにおける入力フォームから受け取った ベース URL をどのように Extension 内に保持するかですが、Chrome のローカルストレージを 使用することで、Chrome のプロセスが終了した後もこちらの設定情報を保存しておき、次回以降 Chrome が起動された時にも同じ設定で Extension を利用できるように実装可能であることがわかりました。以下に、options.html のフォームから受け取ったデータを処理するために用意した options.js という JavaScript で行っている処理の一部を記載します。

設定情報の保存(options.js)

1
2
3
4
5
jiraBaseUrls.push(jiraBaseUrl); // 登録済みの JIRA ベース URL
 
var entity = {};
entity.jiraBaseUrls = jiraBaseUrls;
chrome.storage.local.set(entity);

上記の処理では、すでに登録済みの JIRA ベース URL を管理する配列 jiraBaseUrls に入力フォームから受け取った jiraBaseUrl を追加して、そちらを chrome.storage.local.set() というメソッドを使用してストレージに保存しています。

また、当初の目的である isJiraPage() の実装の中では、下記手順でストレージに保存されたデータを読み出し、開いているページが JIRA ページかどうかの判定を行っています。

設定情報の読み出し(background.js)

1
2
3
4
5
6
7
8
9
var defaults = {};
defaults.jiraBaseUrls = [];
 
chrome.storage.local.get(defaults, function(items) {
  var jiraBaseUrls = items.jiraBaseUrls;
 
  /* 以降に、現在のアクティブなタブで開いているページの URL と、読み出した JIRA ベース URL を比較する処理が続く */
  ...
});

上記で使用している chrome.storage.local.get() というメソッドの第一引数の defaults は、ストレージに読み出すべきデータが存在しなかった際に使用されるデフォルトの値となっており、第二引数で設定しているコールバックに、読み出した JIRA ベース URL に対する処理を記述することが可能です。

4. 言語設定切り替え機能の実装

さて、ここに来てようやく Extension のメインとなる言語設定切り替え機能の実装です!(ここまでが長かった。。。)

ここまでで、JIRA ページを開いている時以外には Extension 自体が無効化されるように実装されているので、あとは Extension が有効になっているタイミングでアクセスしている JIRA インスタンスに対して、任意の言語に設定を切り替える REST API を発行するだけです。

言語設定切り替え用の UI は、Chrome の ツールバーに表示される Extension のアイコンをクリックした際のポップアップとして実装しました。実装したポップアップは下記のようなものになっています。

popup本 Extension では、上記のポップアップ内のラジオボタンの click イベントが発生した際に、以下のように Ajax を使用して、現在アクセス中の JIRA に対して REST API を発行しています。

ラジオボタンへのイベントハンドラの設定(popup.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var $radio = $(...); // ラジオボタンの JQuery オブジェクト
var jiraBaseUrl = ...; // JIRA ベース URL
 
/* ラジオボタンがクリックされた際の処理 */
$radio.click(function() {
   var restApiUrl = jiraBaseUrl + "/rest/api/2/mypreferences?key=jira.user.locale" // REST API の URL
   var locale = $(this).attr("value"); // 変更先の言語設定
 
   $.ajax({
     type: "PUT",
     url: restApiUrl,
     contentType: "application/json",
     data: locale
  })
  .done(function() {
    /* API の実行が成功した際の処理 */
    ...
 
    chrome.tabs.reload();  // アクティブなタブのリロード
  })
  .fail(function(jqXhr, textStatus) {
    /* API の実行が失敗した際の処理 */
  });
 });

REST API 使用時の認証情報については、現状で開いている JIRA ページにてすでにログインが完了している際には、ブラウザが持つセッションの情報が使用されるので、実装の際には特に気にする必要はありませんでした。また、本ブログでは取り上げませんでしたが、実際に実装した Extension では開いている JIRA ページへのログインが完了していない際にも、Extension が無効になるようなロジックを加えてあります。

まとめ

こんな感じで初めて実装してみた Chrome Extension ですが、すでに Chrome Web Store にて公開しておりますので、もし興味があれば是非使用してみて頂ければ幸いです。(使用方法についてはこちらをご覧ください。)また、ソースコードについても Bitbucket 上で公開しており、コメント・プルリクエストから実装に対する批判まで、何でも受け付けてます!

今回のケースでは、言語設定の切り替えというアトラシアンのサポートエンジニアならではの、かなりニッチなニーズを満たすための Extension となってしましましたが、恐らく皆様の中にも、アトラシアン製品を使用していて、かゆいところに手が届かないような思いをされている方もいらっしゃるので はないかと思います。

今回ご紹介させて頂いた Chrome Extension の実装テクニックは、アトラシアン製品の REST APIを使用した機能拡張全般に有効だと思いますので、是非こちらを参考に皆様独自の Extension を実装して頂ければと思います。また、実装テクニックについても、恐らく私が紹介したものよりももっと良いものが存在するかと思います。実装した Extension や、その時に使用したテクニックはユーザーグループ等で情報共有して頂ければ幸いです!

以上、長くなってしまいましたが、私サポートエンジニア岡本(通称:ひげ)の初ブログでした!

About 岡本 悠希

サポートエンジニア。認定スクラムマスター。Accenture にて、某金融機関の営業支援システム開発プロジェクトにおける構成管理に関する技術提案等に携わった後、2015年6月より現職。

View all posts by 岡本 悠希 »