はじめに
アドカレに乗っかって初Qiitaを書きます。
私の所属先ではSlackに勤怠チャンネルがあり、業務終了時にその日の業務内容について投稿するようにしています。(ミニ日報みたいな感じです。)
投稿する内容はこんな感じです。
【勤務時間】10:00 ~ 19:00
【稼働時間の合計】8時間00分(休憩:1時間00分)
【本日の作業】○○○_初回リリース, △△△_コンテンツ追加
【明日の作業】△△△_コンテンツ追加, □□□_リファクタリング
【共有・相談事項】
特に大変というわけではないのですが、毎日「よし!業務終了だ!」と思ってから、このミニ日報を書くのは少し面倒でした。
そこで、その少し面倒くさい作業で楽するためにCUI上で簡単に日報を書いて投稿できるツールを作成したので、今回はそのツールの作成に使った技術について共有します。(以降作成したツールを退勤ツールと呼びます。)
面倒くさい作業で楽するために退勤ツールで実装したいと思ったことは以下の4つです。
1. 時間を記入するときに対話型で入力する
2. 作業内容はチケット管理ツールからデータを取得して選択式で入力する
3. 日報はCUIから直接Slackのチャンネルに送信する
4. ツールは短いコマンド一つで起動する
今回はこれらの目的を達成するために使った実装方法についてお話しします。
言語はJavaScriptで、nodeのバージョンは16.13.0です。
実装方法
実装方法は4つの目的別にセクション分けして記載しています。
1. 時間を記入するときに対話型で入力する
「対話型」というのはこんな感じです。
これをCUIで再現できるように実装します。
退勤ツールを実行したら、質問が出力され、それに応じて答えを入力したいです。
そこで質問の出力、答えの入力を実装するためにNode.jsのreadline.Interfaceのquestionメソッドを使いました。
const readline = require("readline");
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
// rl.question()のように使います
上記のコードではimportしたreadlineのcreateInterfaceメソッドで入出力の設定を行なっています。
inputにprocess.stdinを、outputにprocess.stdoutを指定することで、questionメソッドの「入力」に標準入力(キーボードで入力)を設定でき、出力に標準出力(ターミナルで出力)を設定できます。
questionメソッドは第一引数に「質問」を第二引数にコールバック関数で「回答」の処理経路を書いていきます。
const readline = require("readline");
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
rl.question('好きな食べ物はなんですか?', (answer) => {
console.log(`なるほど、${answer}が好きなんですね。`)
rl.close() // closeメソッドでインターフェイスを閉じます。
})
上記のコードを実行すると以下のように対話型で入出力を行うことができます。
対話型が実装できたので、あとは「開始時間は何時ですか?」のように稼働時間等を埋めるための質問を出力させて時間を入力すればいいですね。
コールバック関数の中で、条件分岐させれば、「何も入力せずエンターを押した時(answerが空文字の時)は現在時刻を入力する」のような実装もすることができます。
これで【勤務時間】と【稼働時間の合計】の箇所は、Node.jsのreadline.Interfaceのquestionメソッドを使って実装できました。(ついでに日報の【共有・相談事項】の部分も同じくquestionメソッドで実装しました。)
2. 作業内容はチケット管理ツールからデータを取得して選択式で入力する
次は【本日の作業】と【明日の作業】の箇所です。
私の場合、日報の作業内容の欄は、基本的に自分の持ちタスク(担当チケット)で埋めていたので、作業内容を管理しているチケット管理ツールから自分の持ちタスクを一覧で取得して、それらタスク一覧から選択形式で入力できるように実装することにしました。
CUI上で選択形式の入力を行うためにnpmのenquirerをインストールして使いました。
npm i enquirer
enquirerはCUI上で直感的な入力方法を簡単に実装できるパッケージです。今回は中でもMultiSelect Promptを使いました。
MultiSelect Promptは1つの質問と、複数の回答を選択形式で入力できるプロンプトを表示することができます。
使い方は簡単で、importしたenquirerでMultiSelectインスタンスを作成して、質問と回答(複数)を流し込むだけです。
const { MultiSelect } = require("enquirer");
const prompt = new MultiSelect({
message: '好きな食べ物はなんですか?',
choices: [
{ name: '寿司' },
{ name: '焼肉' },
{ name: 'カレー' },
{ name: '唐揚げ' },
{ name: 'ハンバーグ' },
{ name: 'ピザ' }
]
});
prompt.run()
.then(answer => console.log('Answer:', answer))
.catch(console.error);
// Answer: ['寿司', 'カレー', 'ピザ']
上記のコードを実行すると、以下のように質問と選択肢が表示されます。スペースキーで選択してエンターキーで選択内容を決定することができます。
退勤ツールの方では、チケット管理ツールから取得した自分の持ちタスクの一覧をchoices
プロパティに流し込んで、回答して返ってきた値は、"寿司, カレー, ピザ"
のようにカンマ区切りの1つの文字列に変換して利用しました。これで【本日の作業】と【明日の作業】は簡単に入力することができるようになりました。
3. 日報はCUIから直接Slackのチャンネルに送信する
次にCUIからそのままSlackのチャンネルに送信する実装方法です。やはりCUIで作成した投稿内容はコピペしてSlackに投稿するのではなく、エンターキーで気持ち良くSlackに送信したいです。
そこでSlack APIを使って作成した退勤ツールとSlackチャンネル間の橋渡しをするイメージで実装しました。
このSlack APIを使うためには受信側(Slackのワークスペース)と送信側(退勤ツール)のそれぞれに設定が必要です。
受信側の設定
送信する投稿内容をワークスペース側で受け取るためには、ワークスペースに退勤ツール専用の投稿内容を受け取るアプリを設置する必要があります。
このアプリを作成するにはSlack APIのブラウザページにアクセスし、create new appに進んでGUI上でアプリを作成していきます。アプリを作成するときに様々な機能をつけることができますが、ここでは「退勤ツールから送信された投稿内容を受け取って、自分のアカウントで投稿する」機能をつけました。
アプリを作成するとそのアプリ専用のトークンとシークレットIDが発行されるので、それらを用いて外部からのアクセスを安全に行います。
これで受信側の設定が完了しましたので続いて送信側の設定を行います。
送信側の設定
送信側の退勤ツールには@slack/boltというnpmパッケージをインストールしました。
npm i @slack/bolt
importしたパッケージを使って、Appインスタンスを作成し、インスタンスには受信側のアプリで発行されたトークンとシークレットIDを設定しています。今回は対象のSlackチャンネルにテキストを送信したかったのでclient.chat.postMessage
メソッドを使いました。
const pkg = require("@slack/bolt");
const { App } = pkg;
const app = new App({
token: 'ワークスペースに設置したアプリのトークン',
signingSecret: 'ワークスペースに設置したアプリのシークレットID',
});
const channelId = "投稿するチャンネルのID";
module.exports = () => {
try {
app.client.chat.postMessage({
channel: channelId,
text: '投稿内容',
});
} catch (error) {
console.error(error);
}
};
これで受信側の設定も完了して退勤ツールとワークスペースの橋渡しが完了しました。
加えて送信する前に「Slackに送信していいですか?yes/no」の確認を先ほど紹介したreadline.Interfaceのquestionメソッドを使って実装すれば誤送信を防げますね。
以上がSlack APIを使ったCUIから直接Slackのチャンネルに送信する実装方法になります。
4. ツールは短いコマンド一つで起動する
退勤ツールは毎日使うものなので、いくらCUIで日報を作成できるようになっても毎回ツールのディレクトリまで階層を移動し、コマンド(npm start)を実行するのは手間です。そこでツールのディレクトリまでパスを通して、短いコマンドでどこからでも起動できるように実装しました。まずは短いコマンドで起動できるように実装します。
短いコマンドで起動するには、退勤ツールを起動するまでに打つシェルスクリプトが書かれたコマンド名のファイルを用意して、そのファイルをシェルで実行する方法があります。
今回は「report
」というコマンドだけで退勤ツールを起動させたかったのでファイル名を「report(拡張子なし)」としました。
そしてそのreportファイルは退勤ツールのルートディレクトリ直下に作成したbinディレクトリ内に格納しました。というのも退勤ツールの実行コマンドがnpm start
なのでpackage.json
のあるディレクトリで実行する必要があったからです。
まずreportファイルにはこのように書きました。
#!/bin/bash
npm start
#!/bin/bash
はreportファイルのシェルスクリプトをbashで実行するために書いています。
これで退勤ツールのディレクトリまで移動すればnpm start
ではなく、report
だけで実行できるようになりました。
作成したファイルで実行できない時
作成したファイルで実行した時にPermission denied
のエラーが出るかもしれません。
これはファイル別に付与されたアクセス権などを決定している許可属性が関係しています。デフォルトなのかは分かりませんが、作成したファイルには許可属性の「実行可能」の許可がない状態になっていたので、「実行可能」を許可することで実行できるようになりました。
詳細はこちらを参考にしてください。
でもこれだとほんのちょっとコマンドが短くなっただけで、結局cd
でツールのディレクトリ階層まで移動しなければ実行できません。そこでシェルスクリプトにcd
でツールまでディレクトリを移動する部分を書き加えました。
#!/bin/bash
cd $HOME/..../..../ツールのディレクトリ/
npm start
これでどのディレクトリにいても$HOME/..../..../ツールのディレクトリ/bin/report
というコマンドを実行するだけで、シェルスクリプトが実行され、ツールのディレクトリまで移動し、npm startで実行することができるようになりました。
でもこれでは、どこからでも実行できるとはいえ、コマンドが長くなってしまいました。そこでコマンドを$HOME/..../..../ツールのディレクトリ/bin/report
ではなくreport
だけで実行できるように環境変数$PATHにreportファイルまでのパスを登録してあげます。
$PATHにパスを登録するとは?
ここでの環境変数$PATH
はコマンド検索パスのことを指します。$PATHに登録されたパス(ディレクトリの場所を示す情報)はコマンドを実行するときに省略することができるようになります。
例えば、cd
のコマンドを使うときに、わざわざ/usr/bin/cd
とは打たないですよね。cd
を使うときには対象のファイルまでのパスを打たずに実行できますが、それは/usr/bin
が$PATH
に登録されているからです。コマンドを実行した時、シェル側が$PATH
に登録されたパスの一覧から対象のコマンドのファイルがあるかを検索します。
cd
と打った時に、シェルが$PATHを参照して/usr/bin
内のcd
ファイルを検索できたから/usr/bin/cd
を実行できるわけです。
このように$PATHにパスを登録することで、実行するファイルまでのパスを省略することができます。
以下のように.bash_profile
に追記して、$PATHにreport
コマンドのファイルのパスを登録しました。
export PATH=$PATH:$HOME/..../..../ツールのディレクトリ/bin
これでどこからでもreport
だけで退勤ツールを起動できるようになりました。
以上で日々の日報作業でちょっと楽するための退勤ツールは完成です。
おわりに
ちょっと日報作業が楽になるかなと思って作成した今回のツールですが、実際に使ってみると日報を書く小さなストレスがなくなり、毎日ポチポチコマンドを押しながら楽しく日報を埋めることができるようになりました。
そしてツールを作成して色々と勉強になったので(ツールをメンバーに見せて沸いてたのも嬉しかったので)また何か思いついたら作ってみようかなと思います。