GitHub Actionsで静的Webサイトのリリース手順を簡略化した話

こんにちは、freeeのアクセシビリティーおじさんの中根です。

先日から紹介しているfreeeアクセシビリティー・ガイドラインですが、一般公開に当たって、リリース作業をいかに簡単にするかというところで少しがんばってみたので、今日はそのことについて紹介します。

タイトルの通り、GitHub Actionsを活用しています。

リリースに当たって必要な作業

まず、今ガイドラインの新バージョンをリリースする際の手順をまとめておきます。

  1. 検討用に社内向けに公開しているソースを、一般公開用のリポジトリーに反映
  2. リリースのタグを追加
  3. そのソースからHTMLファイルを生成
  4. そのHTMLを公開サイトにコピー (サイトはAmazon S3)
  5. そのHTMLファイルをまとめたzipファイルを作成
  6. GitHubのリリース・ページでリリースを作成
  7. 5のzipファイルを添付
  8. リリース・ノートを追加

ざっとこんな感じです。 このうち、2の実行をトリガーにして、3~7を自動化しています。

GitHub Actionsでの実装

では、以下本稿執筆時に実際に使っているGitHub Actionsの設定ファイルを見ながら説明します。

name: Publish HTML

ワークフローの名前です。 GitHub Actionsのページに表示されます。

ちょっと実体と一致しない名前になっていますが、最初は上記の4までやれれば良いと思って始めたのでこういう名前になっています。 いずれ変えます。

on:
  push:
    tags: [ "*" ]

このワークフローが実行される条件です。 ここで指定することで、特定のブランチにpushされた場合だけとか、特定のパターンにマッチするタグがpushされた場合といった条件を指定することができます。 ただ、ブランチとタグの両方を指定した条件はここでは書けないらしいです。 そういう指定をしたい場合については後述します。

jobs:
  build:
    runs-on: ubuntu-latest

ここから具体的なタスクの記述が始まります。 "build" というIDのジョブを定義していて、ubuntu-latest上で実行することを指定しています。

1つのワークフローの中には、複数のジョブを記述できます。 複数のジョブがある場合は、基本的には並列に実行されるということですが、今回の場合はそういう込み入ったことは必要ないので、単一ジョブの中に順番にstepを記述していきます。

    steps:
    - name: Extract Branch/Tag Names
      run: |
        echo "::set-env name=NAME::${GITHUB_REF#refs/*/}"
        echo "::set-env name=BRANCH::${GITHUB_REF#refs/heads/}"
        echo "::set-env name=TAG::${GITHUB_REF#refs/tags/}"

最初のステップでは、ブランチ名やタグを環境変数に保存しています。 同じジョブ中であれば、先のステップで設定した環境変数を後のステップ中で参照することができます。

ここで設定した環境変数の値をチェックすることで、以後のステップについて特定のタグやブランチのときだけ実行するようにする、といったことが可能になります。

具体的には、

if: startsWith({{ env.BRANCH }}, "master")

のような感じで指定します。

    - uses: actions/checkout@v2

一般に公開されているactionを使用して、実行環境にリポジトリーをチェックアウトしています。

    - uses: actions/setup-python@v1
      with:
        python-version: '3.7.x'

Python 3.7.xを使うことを指定しています。

    - uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ap-northeast-1

awscliを実行するために必要な認証手順です。

${{ secrets.AWS_ACCESS_KEY_ID }}${{ secrets.AWS_SECRET_ACCESS_KEY }} はそれぞれ、リポジトリーのSettingsの中のSecretsの画面で登録している値と置き換えられます。 Secretsを活用することて、公にできない認証情報を埋め込むことができます。

    - name: Install the Latest pip
      run: python -m pip install --upgrade pip

    - name: Install required modules
      run: python -m pip install -r requirements.txt

最新のpipをインストールして、必要なモジュールをインストールしています。 ここまでで環境設定は完了です。

run: で、シェル・コマンドを指定して実行させることができます。 Ubuntuの場合は、bashが使われます。

    - name: Build HTML with Sphinx
      run: make html

    - name: Publish to S3
      env:
        AWS_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
      run: aws s3 sync build/html/ s3://${AWS_BUCKET}/ --quiet

SphinxでHTMLファイルを生成して、amazon S3のファイルをawscliで更新しています。

    - name: Prepare the HTML Archive
      run: |
        mv ./build/html ./freee-a11y-guidelines-${TAG}
        zip -r ${GITHUB_WORKSPACE}/freee-a11y-guidelines-${TAG}-html.zip ./freee-a11y-guidelines-${TAG}

リリースに添付するために、生成したHTMLファイルをzipファイルにまとめています。

    - name: Create Release
      id: create_release
      uses: actions/create-release@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        tag_name: ${{ github.ref }}
        release_name: Ver. ${{ github.ref }}
        body: |
          Release note here
        draft: true
        prerelease: false

GitHub上で公開するリリースのドラフトを作成します。 リリース名はタグから自動的に付けられます。

リリース本文 (というかbody) には、固定で"Release note here"と入れています。 本当は、リポジトリー中の更新情報から当該リリースの情報を入れ込みたかったのですが、固定でない複数行のテキストを入れる方法がどうにも分からず断念しました。

ちなみに、このステップには id の指定がありますが、この次のステップの中でこのステップの実行結果を参照するために必要なものです。

    - name: Upload Release Artifact
      id: upload_artifact
      uses: actions/upload-release-asset@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        upload_url: ${{ steps.create_release.outputs.upload_url }}
        asset_path: ${{ github.workspace }}/freee-a11y-guidelines-${{ env.TAG }}-html.zip
        asset_name: freee-a11y-guidelines-${{ env.TAG }}-html.zip
        asset_content_type: application/zip

前のステップで作ったリリースに、作っておいたzipファイルを添付しています。

実際のリリース時の作業

ということで、このワークフロー導入後は、リリース時の作業はこうなりました:

  1. 検討用に社内向けに公開しているソースを、一般公開用のリポジトリーに反映
  2. リリースのタグを追加
  3. リリース・ノートを追加してリリースを公開

zipを作ったりファイルをアップロードしたりという部分は、ぼんやりしているとミスが混入しやすい部分だと思うので、この部分を自動化できたのは実に良かったと感じます。 リリース作業が面倒だから、という理由でリリース頻度が下がるというようなことも起こらないですし。 (他の理由で頻度が下がることはもちろんあるでしょうけど。)

フィードバック歓迎です

手探りで使ってみたGitHub Actionsのワークフローの改善案、そしてなによりガイドラインの改善案などありましたら、GitHubリポジトリーのIssuesやPull Requestsからお知らせください。

今だからこそ、行きたいお店をリストアップしようではないか。

こんにちは。
デザイナーのkazuです。
お酒大好きゲーマーです。最近はFortniteばかりやってます。

以前、フリーをやめてフリーに入社した話というブログを書いてから2回目のブログです。 定期的に書いていきたいと思ってるんですが、実務がいそがしくそれどころではないです。がんばります!

さて、コロナの影響で自粛生活が続いていますが遊びにもいけない、ショッピングも映画もいけない、大好きなお店で美味しいご飯やお酒も楽しめない。

そんな生活がつづいてけっこう精神的にきついと思います。
私は飲み会が大好きなので、入社した暁にはfreeeメンバーでいろんなところに飲みに行きたいと思っていたので、いきなり自粛になってしまい途方に暮れています。
そんなときだからこそお店に行けるようになったら絶対いくぞリストをつくってみました。
お!これは!?というお店がもしあれば行ってみてください!
今飲食店に限りませんが店舗は大変だと思います。
なのでいくリスト作っていけるようになったらどんどん行ってしまいたいとおもってます。

うなぎ かぶと

うなぎたち
とにかくおいしい
池袋にある名店。
今では予約もなかなかとれずいくのが大変になってしまいました。
もう10年くらい通っている大好きなお店なのですが、2代目になっても味は一品。
蒸さないタイプのうなぎの白焼き、蒲焼、くしなどを楽しめます。
チャンスあれば是非一度は行ってみて欲しいお店です。

tabelog.com

しんぽ

しんぽの料理
ほたるいか食べたい
西荻窪にある和食の名店。
ホタルイカがすごいおいしくてよくある黒いやつではなく透明なやつ。
いくらチャーハンもすごいおいしい!

tabelog.com

ほていちゃん

ほていちゃん
よきせんべろ
所謂せんべろ。
僕がよくいくのは吉祥寺店。他にも色々あるみたいです。
よくある安めの居酒屋で昼間っからやってるので最高ですね。
パクチーメンマがおすすめ!

tabelog.com

レフェルヴェソンス

レフェルヴェソンス
お酒もしらないものばかりでした。
西麻布にあるフレンチ。
初めてのフレンチだったけど、おいしかった。
流石の接客でした。 創作料理もおおくて目でも楽しめた。

tabelog.com

鏑屋

鏑屋の料理
コーンのかき揚げをつみながら酒をのむ!
東武東上線大山駅にある大衆居酒屋。
自宅からお店まで結構とおいですけど、それでもいきたくなるお店。
コーンのかき揚げと煮込みが絶品。からさ選べるんですが大辛がおすすめ!

tabelog.com

麺尊 RAGE

麺尊 RAGEのつけ麺
これはつけ麺。今すぐ食べたい。
西荻窪にあるラーメン。
何も言わずに食べに行って欲しい。
スープはいつの間にか完飲してしまうのでびっくりします。

tabelog.com

米心

米心のごはん
土鍋飯はマスト
渋谷にある隠れ家的和食居酒屋。
日本酒の種類がすごい豊富なので日本酒好きにはたまらないお店。
土鍋飯がおいしいのでいったら頼もう!

tabelog.com

中村 玄

中村 玄の料理
中村 玄は食べると体に良さそうな気がする
恵比寿にある隠れ家の中華(中華でいいのかな?)
なかでも、麻辣香鍋(汁なし火鍋)がとてもおいしい!
これを食べながら飲むハイボールが最高なんですよ。

tabelog.com

炭火焼肉ホルモン 三四郎

三四郎の肉
ハツ刺しがほんとおいしい!
西荻窪にある焼肉・ホルモンのお店。
ハツ刺しが新鮮でおいしいし、タンの盛り合わせも色々なタンを堪能できて最高。
煮込みで食べる白ごはんも最高なのでやばいです。

tabelog.com

ふぐ 牧野

牧野の料理
ふぐ料理ももちろん絶品
浅草の手前、稲荷町駅にあります。
店の前からはスカイツリーも見えるのでライトアップされた綺麗なスカイツリーもみれます。
ここはなんといっても毛蟹大根鍋。
これの大根が毛蟹の出汁を全部吸って最高の一品に仕上がってます。
シメで食べる雑炊やラーメンがさらに最高で全て最高。
チームの忘年会でいきたいNO1

tabelog.com

晩杯屋

最高のセンベロ。
大晦日も通っていたくらい一人でのサク飲みには最高でした。
黒ホッピーセットと新生姜。
ただ、よくいってた西荻窪店がなくなってしまったので悲しいですが、弊社の五反田にはあるのでいくぞ!

tabelog.com

焼肉ヒロミヤ

ヒロミヤの肉
肉質最高
曙橋にあります。
お酒はその辺に置いてあるのを自分で作って飲むスタイル。
とにかく肉の量がすごいし肉質もよかったしで最高でした。

tabelog.com

鳥かど

鳥かどの焼き鳥
ストップを言わないと延々とでてくる
目黒にある焼き鳥の名門。
焼き鳥は庶民的なものだ。という意見もありますがこういう高級な焼き鳥もたまには食べたいものです。
鳥しきの姉妹店のこのお店は斬新なメニューもあるのでたのしかったです。

tabelog.com

みやこんじょ

みやこんじょの料理
画像でお酒が飲める
新宿は歌舞伎町にある宮崎の郷土料理のお店。
鳥のタタキとチキン南蛮はマスト。
お店の場所が若干わかりづらいですw

tabelog.com

麗郷

麗郷の料理
麗郷の料理ぜんぶおいしいよ!
渋谷にある台湾料理のお店。
しじみとビーフンは絶対に頼もう。
それだけで紹興酒を2瓶あけれるぞ!
お店結構ひろいから打ち上げとかで行きたい。

tabelog.com

二の鉄

二の鉄
クセのあるモツ食べたい
溝口にあるめっちゃやすいホルモン。
結構食べたつもりでもお会計全然いってなくて毎回びびってます。
雰囲気もいい雰囲気なのでいい空間でお酒を楽しめます。

tabelog.com

魚がし 福ちゃん 2号店

魚がし 福ちゃん 2号店の料理
何気にマカロニサラダもうまい
渋谷にある立ち飲みの店。
お皿に大量の刺身がのってでてきます。
これを知らないで頼み過ぎるとおそらく食べれないです。 いつも混んでるのでいくなら、開店直後がおすすめ!

tabelog.com

西尾さん

西尾さんの料理
久しぶりにいきたすぎる。
新宿にある居酒屋。
ご主人が一人できりもりしているので時間をたっぷりつかって美味しい料理とお酒を楽しみましょう。
ここで飲んだ出汁割りがアツい酒のなかに出汁のでそうなの色々入っていてでてくるスタイルでおいしかった。

tabelog.com

根津 釜竹

根津 釜竹の料理
このうどんを頬張りたい
うどんといえば丸香とか有名ですけど、こちらもおすすめ。
うどん以外にもおいしい肴があるのでそれをいただきつつお酒をのみ、シメにうどんをたべると幸せになります。

tabelog.com

長くなりすぎた....

おわりに

いきたいお店はもっともっとあるんですけど、今でも長過ぎるのでまた機会があれば.....

自粛でどこにも行けないで家で飲むしかないのでストレスもたまりますがそれを超えてから行くお店は最高だろうなぁとおもい待っています。
みなさんも今のうち行きつけのお店や行きたいお店のリスト作って楽しみにまつ!というのもやってみるといいかもしれませんよ! 

弊社、飲みが好きな方々いるんで一緒にハモニカ横丁とかいって適当に飲みたい....

勤怠打刻操作を意識しないAPI打刻をする

おはこんばんちは、ソフトウエアエンジニアの橋本 (@af12066) です。コロナウイルスによる自粛期間もあり4ヶ月ほど散髪していないので、そろそろ行きつけの美容室に行きたい気持ちです。

これまでにfreee Developers Blogでは、人事労務freee APIを使用したさまざまな勤怠打刻方法がいくつか提案されてきました。
2018年にはIFTTTおよびGoogleアシスタントを利用した音声操作による打刻やUnity+Oculusを活用したVR打刻、2019年にはAWS IoTエンタープライズボタンを利用した打刻が紹介されました。

developers.freee.co.jp developers.freee.co.jp developers.freee.co.jp

本記事では打刻シリーズ第4弾として、私が開発した「打刻を意識しない打刻方法」を紹介したいと思います。
なお、この記事は「勤怠を自動化する技術」LT Nightで紹介した内容の文字起こし*1、および紹介しきれなかった内容の補足説明となります。

モチベーション

打刻は勤怠実績を残す重要な動作であり、時刻の正確性が要求されます。打刻の正確さと手軽さを両立させるために、たとえば人事労務freeeのトップ画面に打刻ボタンが設置されていたり、iOSアプリでは通知センターからワンタップで打刻することもできます。 一方、私のケースでは打刻ボタンを押す習慣がつく前に忘れてしまう、打刻リマインダを設定してもあとまわしにした結果打刻しない、といったことがたびたび発生していました...。

そこで、いつもどおり仕事をするだけで勝手に打刻される方法はないか?と考えた結果、本記事の手法が生まれました。

前提および事前準備

動作環境

この記事ではmacOSの機能を利用した方法を説明するため、それ以外の環境は対象外となります(すみません)。同様のサービス管理方法があれば実現可能であると思われます。

また、事業所にてfreeeアプリストアからアプリケーションを作成し、Client ID, Client Secret, 認可コードを取得しておいてください。手順の詳細は、freee APIチュートリアルをご参照ください。

developer.freee.co.jp

勤怠の定義

今回は自動打刻の都合上、勤怠は会社支給のラップトップ上で作業した時間のみを対象とします。つまり、ラップトップを起動しログインしている間は勤務しているとみなすことにします。

自動打刻の概観

打刻に関するAPIドキュメント/create)によると、打刻には次の4種類が存在します。

  • 出勤
  • 休憩開始
  • 休憩終了
  • 退勤

一日あたり8時間労働とすると、上記4つすべての打刻を行なう必要があります*2

出勤は勤怠の定義そのままで、ログインに成功した段階で打刻を行ないます。休憩は昼ごろに1時間確保しますが*3、具体的な時間の規定はないため、適当な時間に確保することにします。退勤もまた、ログアウト完了までに打刻をする必要があります。

ざっくり状態遷移は次のようになります。ただしAPIの具体的な仕様の説明ではないため、正確でない場合があります。

勤怠の状態遷移。出勤から開始し、休憩開始および休憩終了を経て退勤となる。休憩開始および休憩終了はくりかえすことができ、または休憩を行なわずに出勤・退勤のみ打刻することもできる
勤怠の状態遷移

休憩開始の直後に退勤を打刻したり、退勤のあとに休憩終了しようとするとAPI側でエラーとなることから、上記の状態遷移となることが推定されます。

次のようなエラーがAPIのメッセージとして返ってくる:「休憩開始時刻の後に退勤時刻を指定することはできません、退勤時刻の後に休憩終了時刻を指定することはできません」
不適切なAPI打刻の例

自動打刻の実装

トークンの取得および機密情報の格納

Client ID, Client Secret, 認可コードが取得済みであるとして、これらを用いてリフレッシュトークンを取得する必要があります。また、アクセストークンの取得・更新のために、Client ID、Client Secret、リフレッシュトークンも合わせて保存する必要があります。

developer.freee.co.jp

トークンは任意のファイルに書き込んでから暗号化して保存したり環境変数に保存してもよいですが、Macにはキーチェーンアクセスという機密情報を保存するためのアプリケーションがあるため、これを活用してみます。

support.apple.com

シェルからキーチェーンアクセスを操作するために、securityコマンドがあります。使い方の詳細はman 1 securityを参照してください。

リフレッシュトークンの取得からキーチェーンアクセスへの登録までをまとめると、以下のようなスクリプトになります。リフレッシュトークンの更新は別途行なうため、以下のスクリプトを実行するのは初回のみとなります。

# 初回のリフレッシュトークンを取得するために必要な設定をおこなう

export FREEE_CLIENT_ID="<freee APIのClient ID>"
export FREEE_CLIENT_SECRET="<freee APIのClient Secret>"
export AUTHORIZATION_CODE="<freee APIの認可コード>"
#!/bin/bash

readonly REFRESH_TOKEN=$(curl -X POST \
  -H "Content-Type:application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "client_id=${FREEE_CLIENT_ID}" \
  -d "client_secret=${FREEE_CLIENT_SECRET}" \
  -d "code=${AUTHORIZATION_CODE}" \
  -d "redirect_uri=urn:ietf:wg:oauth:2.0:oob" 'https://accounts.secure.freee.co.jp/public_api/token' \
  | jq -r '.refresh_token')

readonly KEYCHAIN_SERVICE_NAME='freee-kintai'

security add-generic-password -a client-id -w "${FREEE_CLIENT_ID}" -s "${KEYCHAIN_SERVICE_NAME}"
security add-generic-password -a client-secret -w "${FREEE_CLIENT_SECRET}" -s "${KEYCHAIN_SERVICE_NAME}"
security add-generic-password -a refresh-token -w "${REFRESH_TOKEN}" -s "${KEYCHAIN_SERVICE_NAME}"

キーチェーンアクセスを開き、パスワードと同様の扱いでClient ID, Client Secret, リフレッシュトークンが保存されていることを確認します。

キーチェーンアクセスでclient-idの詳細を確認しているスクリーンショット
キーチェーンアクセスでclient-idの詳細を確認(パスワードの表示にはMacのログインパスワードの入力が必要)

打刻の汎用スクリプト

人事労務freee APIを操作するための、打刻用のスクリプト (Bash) を書いていきます。出勤・休憩・退勤はすべて同じエンドポイントであるため、単一のスクリプトにまとめて引数で打刻種別を変えるようにしてみます。一回の打刻におけるAPIリクエストの流れは次のようになります。

  1. さきほど登録したClient ID、Client Secret、リフレッシュトークンを使用して認証をおこなう。このとき、アクセストークンおよびリフレッシュトークンが払い出され、リフレッシュトークンはキーチェーンに上書き保存し、アクセストークンは以降のAPIリクエストに使用する
  2. アクセストークンを使用して、事業所IDおよび従業員IDを取得する
  3. アクセストークン、および2.で得られた事業所ID、従業員IDに加えて打刻種別を指定し、打刻APIにPOSTする
Client ID、Client Secret、リフレッシュトークンを使用してアクセストークンを取得する

さきほどキーチェーンに登録したClient ID、Client Secret、リフレッシュトークンを取得し、アクセストークンを取得するエンドポイントにPOSTします。
security find-generic-passwordコマンドでキーチェーンの内容を取得できます。-wオプションを与えることで、標準出力にその登録内容だけを流すことができます。

#!/bin/bash

readonly CLIENT_ID=$(security find-generic-password -a client-id -w -s freee-kintai)
readonly CLIENT_SECRET=$(security find-generic-password -a client-secret -w -s freee-kintai)
readonly TOKEN=$(security find-generic-password -a refresh-token -w -s freee-kintai)

readonly AUTH_RESP=$(curl -X POST \
  -H "Content-Type:application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "client_id=${CLIENT_ID}" \
  -d "client_secret=${CLIENT_SECRET}" \
  -d "refresh_token=${TOKEN}" \
  'https://accounts.secure.freee.co.jp/public_api/token' 2>/dev/null)
readonly ACCESS_TOKEN=$(echo ${AUTH_RESP} | jq -r '.access_token')
readonly REFRESH_TOKEN=$(echo ${AUTH_RESP} | jq -r '.refresh_token')

# add-generic-password -Uでキーチェーンに上書き保存する
security add-generic-password -a refresh-token -w "${REFRESH_TOKEN}" -s freee-kintai -U
事業所IDおよび従業員IDの取得

2020年現在、/api/v1/users/meで取得できます。

人事労務APIリファレンス | freee Developers Community

スクリプトは次のようになります。事業所名は自身の事業所名に置換してください。

#!/bin/bash

readonly COMPANY_RESPONSE=$(curl -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  https://api.freee.co.jp/hr/api/v1/users/me 2>/dev/null)
readonly COMPANY_ID=$(echo ${COMPANY_RESPONSE} | jq -r '.companies[] | select(.name | test("事業所名")) | .id')
readonly EMPROYEE_ID=$(echo ${COMPANY_RESPONSE} | jq -r '.companies[] | select(.name | test("事業所名")) | .employee_id')
打刻APIにPOSTする

2020年現在、/api/v1/employees/${EMPROYEE_ID}/time_clocksにPOSTすることで打刻できます。

人事労務APIリファレンス | freee Developers Community

#!/bin/bash

readonly time_clocks_type=$1

readonly ISO8601DATE=$(date "+%Y-%m-%d")

curl -X POST -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type:application/json" \
  -d "{\"company_id\":${COMPANY_ID},\"type\":\"${time_clocks_type}\",\"base_date\":\"${ISO8601DATE}\"}" \
  https://api.freee.co.jp/hr/api/v1/employees/${EMPROYEE_ID}/time_clocks 2>/dev/null)

time_clocks_type=$1で引数を得ることで、アクセストークンの取得から打刻までを単一のスクリプトをまとめてdakoku.shとして実行権限つきで保存したとき、/path/to/dakoku.sh clock_inのように実行することで出勤打刻を行なうことができます。

打刻スクリプトの実行

作成したスクリプトdakoku.shを、勤怠種別ごとに実行させていきます。

出勤

システム環境設定の「ユーザとグループ」内、「ログイン項目」にて、ログイン直後に開きたいアプリケーションを指定します。ただし、実行権限を与えたシェルスクリプトを直接指定することはできないようです。
そこで、シェルスクリプトをラップしたAutomatorアプリケーションを作成し、それをログイン項目に指定することにします。

support.apple.com

# Automatorアプリケーションのシェルに以下を記述して保存する

export PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin

/path/to/dakoku.sh clock_in
launchctl unload -w ~/Library/LaunchAgents/freee-kintai-taikin.plist # 後述

初期状態では環境変数PATHには/usr/bin:/bin:/usr/sbin:/sbinしか含まれないため、スクリプトで使用するコマンドに応じてPATHを追加してください。
また、初回実行時にはキーチェーンアクセスへのアクセス許可を求められます。

securityコマンドがfreee-kintaiキーチェーンへのアクセスを求める表示があらわれる
securityコマンドからのアクセスを求められるので、つねに許可する

Automatorの設定画面。シェルに/bin/bashを指定し、前述のスクリプトを記述する
Automatorの設定画面

システム環境設定のスクリーンショット
システム環境設定のログイン項目に、Automatorアプリケーションを指定する

休憩開始および終了

厳密な休憩時刻は定められていないため、かりに12時から13時まで休憩することにします。

macOSでは、cronに相当する機能としてlaunchdが提供されています*4。launchdの概観を知るには、"A launchd Tutorial" がわかりやすいので、時間があれば眺めてみてください。

12時に休憩開始を打刻するplistは次のようになります。~/Library/LaunchAgents/freee-kintai-put-break-time.plist といった名称で保存しておきます(~/Library/LaunchAgents/以下であればファイル名はなんでもよい)。
StartCalendarIntervalを使用して平日の12時に打刻する設定となっており、Weekdayは日曜日が0、月曜日が1、...という指定となります。休憩終了も同様に作成します。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>freee-kintai-put-break-time</string>
    <key>ProgramArguments</key>
    <array>
      <!-- 絶対パスでスクリプトを指定する -->
      <string>/Users/your-login-name/path/to/dakoku.sh</string>
      <string>break_begin</string>
    </array>
    <!-- スクリプトの内容に合わせてPATHを更新する -->
    <key>EnvironmentVariables</key>
    <dict>
      <key>PATH</key>
      <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
    </dict>
    <key>StartCalendarInterval</key>
    <array>
      <dict>
        <key>Weekday</key>
        <integer>1</integer>
        <key>Hour</key>
        <integer>12</integer>
        <key>Minute</key>
        <integer>0</integer>
      </dict>
      <dict>
        <key>Weekday</key>
        <integer>2</integer>
        <key>Hour</key>
        <integer>12</integer>
        <key>Minute</key>
        <integer>0</integer>
      </dict>
      <dict>
        <key>Weekday</key>
        <integer>3</integer>
        <key>Hour</key>
        <integer>12</integer>
        <key>Minute</key>
        <integer>0</integer>
      </dict>
      <dict>
        <key>Weekday</key>
        <integer>4</integer>
        <key>Hour</key>
        <integer>12</integer>
        <key>Minute</key>
        <integer>0</integer>
      </dict>
      <dict>
        <key>Weekday</key>
        <integer>5</integer>
        <key>Hour</key>
        <integer>12</integer>
        <key>Minute</key>
        <integer>0</integer>
      </dict>
    </array>
    <key>ExitTimeout</key>
    <integer>30</integer>
    <key>RunAtLoad</key>
    <false/>
  </dict>
</plist>

launchctl loadコマンドにより、plistをlaunchdに登録します。

$ launchctl load -w ~/Library/LaunchAgents/freee-kintai-put-break-time.plist # 休憩開始
$ launchctl load -w ~/Library/LaunchAgents/freee-kintai-put-break-time-end.plist # 休憩終了
$ launchctl list | grep freee # launchdに登録したplistを確認。grepにヒットすればOK
-   0  freee-kintai-put-break-time
-   0  freee-kintai-put-break-time-end
退勤

出勤は「ログイン項目」にて指定できたものの、退勤に相当する「ログアウト項目」は存在しません。

ここで、freeeの退勤打刻を上書きできる仕様*5を活用します。具体的には、

  1. 19:00に退勤を打刻(WebまたはAPI経由で)
  2. 19:01に退勤をAPIで打刻
  3. 19:02に退勤をAPIで打刻

といった操作を順番に行ったとき、結果はすべて成功となり、最終的な退勤時刻は19:02となります(手順1.および2.は上書きされる)。つまり、休憩終了後からログアウトするまで退勤打刻をくりかえすことで、ログアウト時刻を退勤時刻とみなすことができます。

退勤をくりかえすplist (~/Library/LaunchAgents/freee-kintai-taikin.plist) は次のようになります。以下の例ではStartIntervalを使用して2分ごとに退勤操作をくりかえしています。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>freee-kintai-taikin</string>
    <key>ProgramArguments</key>
    <array>
      <string>/Users/your-login-name/path/to/dakoku.sh</string>
      <string>clock_out</string>
    </array>
    <key>EnvironmentVariables</key>
    <dict>
      <key>PATH</key>
      <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
    </dict>
    <!-- 120秒ごとに退勤打刻をおこなう -->
    <key>StartInterval</key>
    <integer>120</integer>
    <!-- ねんのためstdout/stderrを残しておく -->
    <key>StandardOutPath</key>
    <string>/Users/your-login-name/path/to/taikin.out</string>
    <key>StandardErrorPath</key>
    <string>/Users/your-login-name/path/to/taikin.err</string>
    <key>ExitTimeout</key>
    <integer>30</integer>
    <!-- launchctl loadを実行した際にも退勤打刻をする -->
    <key>RunAtLoad</key>
    <true/>
  </dict>
</plist>

ここで、出勤時に~/Library/LaunchAgents/freee-kintai-taikin.plistをunload(launchdから登録解除)していましたが、これは休憩開始前に退勤打刻されるのを防ぐためです。
休憩終了後に再度loadする必要があるため、そのためのplistを休憩打刻と同様に以下のように作成します。休憩終了は13:00としたので、ひとまず14:30から退勤打刻を開始すれば問題ないでしょう。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>freee-kintai-trigger-taikin</string>
    <key>ProgramArguments</key>
    <array>
      <string>launchctl</string>
      <string>load</string>
      <string>-w</string>
      <string>/Users/your-login-name/Library/LaunchAgents/freee-kintai-taikin.plist</string>
    </array>
    <key>EnvironmentVariables</key>
    <dict>
      <key>PATH</key>
      <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
    </dict>
    <key>StartCalendarInterval</key>
    <array>
      <dict>
        <key>Weekday</key>
        <integer>1</integer>
        <key>Hour</key>
        <integer>14</integer>
        <key>Minute</key>
        <integer>30</integer>
      </dict>
      <!-- ...省略 -->
    </array>
    <key>ExitTimeout</key>
    <integer>30</integer>
    <key>RunAtLoad</key>
    <false/>
  </dict>
</plist>

再度launchctl listを確認します。退勤をunload|loadしているため、時間帯によって結果が異なる場合があります。

$ launchctl list | grep freee
-   0  freee-kintai-trigger-taikin
-   0  freee-kintai-put-break-time
-   0  freee-kintai-put-break-time-end

あとは数日間運用しつつ人事労務freeeのタイムレコーダーを確認し、正常に打刻されていることが確認できればOKです。

人事労務フリーのタイムレコーダーのスクリーンショット。出勤・休憩開始・休憩終了・退勤がすべて記録されている
人事労務freeeのタイムレコーダー

打刻の信頼性を向上させる

これまで紹介した実装に加えて、以下の実装を追加で行っています。

  • APIサーバへの簡単な疎通確認を行ない、サーバに到達できないケース、認証に失敗したケース、状態遷移が正しくないケースのハンドリングやリトライ処理を行なう
  • stdout/stderrを即座に確認しづらいため、たとえばosascript -e 'display notification "人事労務freeeで打刻しました" with title "出勤"'を実行することで打刻の成功・失敗に関するフィードバックを通知させる
    • Mac標準の通知機能をAppleScript経由で利用

打刻漏れをしてしまうことがまずいため、それを早期に検知できること、対処方法がわかること(スクリプトやネットワークがまずいのか、打刻種別がよくないのか、あきらめて手動打刻しなければならないのか、など)が重要であると考えます。

まとめ

この記事では、能動的に打刻操作をしない自動打刻方法を紹介しました。別の環境や打刻以外の場面で活用できるかもしれないので、機会があれば参考にしてみてください。

Enjoy!

*1:書こうと思いつつ機会を逃していたら1年経過していたので、復習も兼ねる

*2:労働時間・休憩・休日関係|厚生労働省によると、労働基準法第34条にて、8時間を超える勤務の場合は少なくとも1時間の休憩が定められている

*3:実際には1時間以上確保したり複数回休憩することも可能だが、例外とみなす

*4:スケジューラ以外にもサービス管理などの機能があるが、ここでは紹介しない

*5:仕様であることを確認済み

Google Optimizeを使ったサイト改善

こんにちは。freeeプラットフォーム部でマーケティングを担当しているあおい( @BlueS3124 )です。

今回は、Google Optimizeを使ったWebサイトの改善について記述します。

freeeアプリストアのWebサイト改善の目的

APIの利活用を推進しているプラットフォーム部では、多くのユーザーにfreeeアプリストアを活用していただくことを目標にマーケティング活動を行っています。アプリストアのWebサイト改善もその一環として実施しています。

freeeアプリストアは、2019年1月にリリースされた、拡張機能や外部サービス連携をfreeeに追加できるアプリケーションとユーザーを繋ぐプラットフォームです。 freeeユーザーはアプリストアに訪問することで、課題を解決できるアプリがあるかを簡単に調べて、連携開始することができます。

app.secure.freee.co.jp

アプリストアに訪問するユーザーは、通常 ①TOPページ> ②検索一覧 > ③アプリ詳細ページ > ④連携ボタンのクリックと進んでいきます。出来るだけ次のステップに進むユーザーの比率を高めることがWebサイト改善の目的になります。

利用したツール

Webサイトの改善のため、コンテンツの変更を簡単に実装できるGoogle Optimize を利用してみました。

Google Optimizeは、特定のWebページのコンテンツを上書きして、ABテストを行うことができます。ユーザーの行動や参照元、属性などを元にコンテンツの内容を出し分ける「カスタマイズ(パーソナライゼーション)」といった機能もあります。

エンジニアやデザイナーの工数を取らずに、自分で簡単にWebページのコンテンツを上書きすることができるため、改善やテストをスピーディに回すことができるのが最も大きな利点だと思います。

具体的な改善事例

どんなサイト改善を行ったのか、実際の例を元にご紹介します。

おすすめアプリ行の設置

「新着アプリ」の上に「おすすめアプリ」が表示されている
おすすめアプリ行の設置

概要
  • 連携数を増やしたいアプリ
  • ③アプリ詳細ページ→④連携ボタンのクリックに至る率の高いアプリ

を選別して、意図的に①TOPページの上部に表示するテストを実施しました。

結果

おすすめアプリ行の設置は①TOPページから③アプリ詳細ページ至る訪問の増加、④連携ボタンをクリックする率の増加に繋がりました。 アプリ別で見ると、おすすめ行に掲載すると③アプリ詳細ページへの訪問が1.2~1.5倍、④連携ボタンのクリック数も1.2倍程度になることなども分かりました。

ポップアップバナーの設置

コンテンツにオーバーレイする形でキャンペーンを告知するバナーを表示している
ポップアップバナーの設置

概要

新規のアプリ連携を促進するキャンペーンに合わせて、ポップアップバナーを①TOPページに掲載しました。 新規連携を対象としたキャンペーンなので、初めてアプリストアに訪問したユーザーのみターゲティングするカスタマイズを行いました。

結果

キャンペーンは法人限定でしたが、法人と個人事業主のユーザーを識別して出し分けることができなかったため、バナー自体のクリック率は低めでした。アプリストア全体の④連携ボタンのクリックはキャンペーン期間中増加する結果に繋がりました。

おまけ:新型コロナウィルス関連のお知らせバナーの設置

ページ最上部に「COVID-19の最新情報」バナーが表示されている
新型コロナ関連のお知らせバナーの設置

概要

Google Optimizeでは、新型コロナウィルスへの対応に追われる事業者のために、営業時間やサービス内容などの変更に関するお知らせバナーをWebサイトに簡単に掲載できる機能が追加されました。バナーテンプレートが用意されているため、HTMLやCSSの知識がなくても簡単にWebサイトのヘッダーに表示することができます。

こういったバナー設置などのカスタマイズを同時に実施できる上限は本来最大 10 件ですが、新型コロナウイルスへの対応期間中は必要なだけサイトを更新できるように、制限が一時的に解除されています(詳細はこちら)。

まとめ

Webサイトの改善は、仮説に対して良い結果が得られないこともあります。けれども、筋が悪いことを含め答えを得られることに価値があるので、日々地道にテストを繰り返していくことが重要だと思います。

Google Optimizeは、HTMLとCSSで表現できることはだいたい実装可能で、本当に便利です。スピーディーなWebサイト改善や状況に合わせた情報更新を必要とされる方は、Google Optimizeの活用を検討してみてはいかがでしょうか。