usakoをローカルで開発できるようにしました
概要
前回疎結合化したGASアプリケーションのusakoを、ローカルで開発できるようにしました。
そのためにやったことを紹介します。
また、作業中に見たリンクは記事の最後につけておきます。
やったこと
claspを導入しローカルで開発できるようにする
ずっとブラウザから開発してたんですが、やっぱりやりにくいのでローカルで開発できるようにしました。
合わせて、ディレクトリもそれっぽい構成にできるように準備。
BEFORE | AFTER |
---|---|
すべてのファイルを同改装に配置 | ソースのファイルは src 以下に |
すでにソースファイルがあるので、 create
ではなく clone
です。
各ライブラリをクラスにする
疎結合化するために各機能をライブラリに外出ししたのですが、ローカルで開発できるようになると逆にやりにくかったので、再度同じリポジトリ内にもってきました…。
前にやったこと無駄になったなと思いつつも、逆に別ライブラリに切り出せるほどになってるので移植自体はそんなに苦労しませんでした。前の作業無駄じゃない!はず!!!
合わせて、ディレクトリもそれっぽくして、内部の実装も軽くリファクタしました(変数の表現を揃えたり、無駄そうな処理をなくすなど…)
やっぱりこの作業が一番大変 & 楽しかった & 時間かかった。
BEFORE | AFTER |
---|---|
ソース・ファイルはすべて src 以下 |
クラス定義は src/classes 。バッチは src/tasks に配置 |
lintツールを導入する
クラスにしたときに一部リファクタ失敗してしまってバッチがこけちゃったりしたので、ESLintを導入しました。
普段はphpなのでESLintの設定は初めてでしたが割とスッと出来てびっくり。
Sheet
クラスなどのGAS独自のクラスがまあまあありますので、ESLintと合わせて eslint-plugin-googleappsscript
も導入します。
npm install --save-dev eslint eslint-plugin-googleappsscript
導入後、各クラスでの宣言に no-unused-vars
等のエラーが出るようになったので、それぞれ無視する設定を個別に挿入しました。
まとめて設定出来たりもするかもしれないですが、意図しないところを無視されても困るので初めだけと割り切って粛々と進めました。
以下はバッチ用クラスの例です。例えば継承に利用している commandBase
なんかがエラーだったのが、スキップされるようになります。
class NoticeTrashDay extends commandBase { // eslint-disable-line no-unused-vars, no-undef constructor() { super('ゴミの日通知バッチ'); } main () { super.main(); } /** * ゴミ捨て通知 * 毎日 PM8〜9時 */ run() { Logger.log('called ' + this.constructor.name + ':run()'); const dt = new Date(); dt.setDate(dt.getDate() + 1); var comment = []; // 翌日が第何週目かを求める var isTheWhatWeekly = Math.floor((dt.getDate() - 1 ) / 7) + 1; switch(dt.getDay()) { // 月曜日のごみ case 1: comment.push('可燃ごみ'); break; // 中略 // ごみの日じゃない場合は何もしない if (comment.length < 1) { return; } const line = new LineMessagingApi(); // eslint-disable-line no-undef line.pushAll('明日は ' + comment.join('、') + ' のゴミの日だよ!\n準備忘れずに!'); } } function noticeTrashday () { // eslint-disable-line no-unused-vars const batch = new NoticeTrashDay(); batch.main(); }
github Actionsを回す
lintができるようになったので、プルリクエストのマージ条件にlintの通過を追加しました。
自分ひとりでプルリク作成→マージでいつ事故るか怪しいので、こういうのができるのは助かります。
現行はこんな感じ。プルリクのマージ先である master
ブランチは除外してもいいかもしれませんが。
name: CI on: push jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: node-version: '12.16.2' - run: npm install - run: npm run lint # eslint src をタスクとして登録してあります
usako/main.yml at master · miyakona/usako · GitHub
余談: デプロイはできなかった
せっかくなので master
ブランチにマージしたらデプロイしたいなーと思ったのですが、うまくいきませんでした。
.crasprc.json
や .clasp.json
を配置しても、特定のプロジェクトに紐づくコマンドを発行すると以下のように権限のエラーが出てしまいます。
Run clasp versions1s ##[error]Process completed with exit code 1. Run clasp versions Could not read API credentials. Are you logged in globally? ##[error]Process completed with exit code 1.
https://github.com/miyakona/usako/runs/619462984?check_suite_focus=true
ちゃんとデバッグしたわけではないですが、コードを読んでると以下のissueと同症状なようでした。
github.com
利用するnodejsのバージョンを12系から10系に落としてみたりもしましたが、うまくいかず…まあそのうちで良いです。
せめて動くバージョンがわかればいいんですけどね。
参考リンク
- GAS のGoogle謹製CLIツール clasp - Qiita
- claspを使い、Google Apps Scriptプロジェクトをgitでバージョン管理する - Qiita
- eslint-plugin-googleappsscript - npm
- Google App Scriptをモダンな開発っぽくする環境を作ってみた(ES6, browserify, eslint, mocha) - nagashigaki
- ESLint をグローバルにインストールせずに使う - Qiita
GWの連休中にデプロイまで終わらせるぞー!と思ったんですが、デプロイがうまく動かないということに連休前に気づいてしまったので、連休中はゲームと料理三昧で過ごしました。
自粛期間もう少し延びそうなので、なにか機能を追加したりしようかなあ。考え中です。
usakoのV8 Runtime対応+疎結合化を行いました
概要
自粛期間中なのでプライベートで運用しているusakoをgithubに公開し、その勢いで結合度を下げ、V8 Runtime対応をしました。
自分的にはとても頑張ったのですが、仕事じゃない分誰かに見てもらうことは無く…でも頑張ったので見てほしく…。
そんな思いの丈をブログにしたためます。
前提知識
usakoとは
2016年頃からプライベートで運用しているLINEbotのことです。
2017年のものですが、以下に大雑把な概要と構成を書きました。
もはや懐かしい…
inside.dmm.com
ずっとソースを公開したかったのですがGASのためコピペ以外の公開の術がなく…。
別の言語にリプレイスしようと思いつつも、とはいえ普通のサーバーサイドの言語にするとサーバー費用がかかってしまうことが気になって、なかなかリプレイス作業が進んでいませんでした。
そんな中、以下の記事を発見しGASのまま公開に至ることができました(ありがたい〜〜〜)。
qiita.com
gitに載せる際に公開したくない情報を別で切り出していったのですが
流石に4年経ったコードで目に余り、勢いに乗って機能ごとにリポジトリを分けられるように改修を進めることにしました。
V8 Runtimeとは
GASではES6の構文が使えなかったのですが、V8 Runtimeの導入によって利用が可能になりました!
V8 Runtimeで動くようにすると動かなくなる構文もあったので、
リポジトリに切り出すついでにV8 Runtimeで動くように修正しました。
…とはいえ、最低限の対応しかしていないので変数宣言がほとんど var
のままだったりはありますが…
V8 Runtimeについては以下に記載があります。
developers.google.com
対応したこと
プロジェクトを細かく分断しました
usakoはこれまで、以下のような構成でした。
- メインのプロジェクト - LINEのwebhookに対応するため `doPost()` が書いてあるもの - 家事管理機能 - サマリ通知機能 - リマインド機能 - ログローテート機能 - 家計簿管理機能 - サマリ通知機能 - リマインド機能 - ゴミの日通知機能 - ランダムメッセージ返却機能 - LINE周りの処理をまとめたプロジェクト - 買い出しリスト周りのプロジェクト
これらを、以下のように分けました。
- メインのプロジェクト - LINEのwebhookに対応するため `doPost()` が書いてあるもの - 家事管理機能をまとめたプロジェクト - 家計簿管理機能をまとめたプロジェクト - メッセージ周りをまとめたプロジェクト - ゴミの日通知機能 - ランダムメッセージ返却機能 - ※各リマインドは各プロジェクト内に格納 - LINE周りの処理をまとめたプロジェクト - 買い出しリスト周りのプロジェクト - 横断で利用する処理をまとめたプロジェクト - 非公開情報を取得するためのプロジェクト
とにかくメインのプロジェクトが大きかったので、大変でした…。
usakoはスプレッドシートをDBのように利用しているのですが、 メインのプロジェクト
はそのスプレッドシートのスクリプトとして設定しているため、各リポジトリに分けるときにシートの取得方法も変えなくてはならず…。
1度わかれば後は大丈夫ですが、Activeなシートを編集したいときに SpreadSheetApp
クラス、 SpreadSheet
クラス、 Sheet
クラスの3段階を経ないといけないという部分はハマりました…。
// フォームと連携しているシートをアクティブにする const currentSpreadSheet = SpreadsheetApp.openByUrl(secret_obj.getMainSheet()); // スプレッドシートオブジェクトをActiveにしてからシートオブジェクトをActiveにしなければならない(GASの仕様っぽい) SpreadsheetApp.setActiveSpreadsheet(currentSpreadSheet); const currentSheet = currentSpreadSheet.getSheetByName('家事代_今月'); SpreadsheetApp.setActiveSheet(currentSheet); // 年月(YYYYMM)の名前でシートをアーカイブ const month = dt.getMonth() == 0 ? 12 : dt.getMonth(); const year = month == 12 ? dt.getFullYear() - 1: dt.getFullYear(); // 以下の dupulicateActiveSheet のためにActiveにしないといけない SpreadsheetApp.getActiveSpreadsheet().duplicateActiveSheet().setName('家事代_' + String(year) + String(("0"+(month)).slice(-2)));
このブログを書くために改めてリファレンスを読んでますが、もしかして copy()
ならActiveにしなくてもよかった…?
V8 Runtime対応
JSを十分に理解して書いているとは言えないレベルのコードなので、繰り返し処理一つとっても for()
for each()
array.forEach()
などバラバラ…。
今回の対応では主に、V8 Runtimeでは廃止になっている for each()
を for()
に変更するのがメインでした。
この他、たまーに紛れ込んでいる getYear()
を getFullYear()
に変更したり、 const
let
に置き換えられそうなところを置き換えたり…というようなことをやりました。
普段メインで書いてる言語はphpなので、 const
let
の使い分けを理解するいい機会になりました。
usakoは長文のメッセージを返却することが多いのでテンプレートリテラルについても取り入れていきたいですね。
月ごとの出費をグラフでも出力するように
もともと月次でいくら使ったか通知する仕組みはあったのですが、バグがありしばらく動いてませんでした(かわいそう)
これを機にバグを修正し、またこれまでほっといたことへの懺悔も込めて機能を追加しました。
スプレッドシートにはすでにグラフを公開する機能があります。
※なんとグラフに利用している数値が更新されたら、自動でグラフのほうも更新してくれます 👏
support.google.com
この機能を利用して、毎月直近12ヶ月のグラフ画像を更新する処理と、通知内容に公開したグラフのURLをつけるようにしました。
こんな感じで、グラフに利用する数値を毎月アップデートすることでグラフの画像を更新しています。
// グラフシート更新(全件削除→対象期間分記載) const targetTerm = 12; // 直近12ヶ月分 const targetData = summarySheet.getRange(1, summarySheet.getLastColumn() - targetTerm, summarySheet.getLastRow(), targetTerm).getValues(); for (var key in targetData) { targetData[key].unshift(indexes[key][0]); } graphSheet.getRange(1, 1, summarySheet.getLastRow(), targetTerm + 1).setValues(targetData);
はじめにグラフを手動で設定する必要はありますが、以降は何もせずともグラフを更新してくれるので助かりますね。
基本的にハマりどころはないですが、強いて言えば以下2点にご留意ください。
- 共有形式は
インタラクティブ
ではなく画像
を設定するのがおすすめです- LINEのアプリ上でURLをクリックしてグラフを表示するのですが、
インタラクティブ
だとうまく表示できないようです
- LINEのアプリ上でURLをクリックしてグラフを表示するのですが、
- スプレッドシートの該当範囲が更新されてから、公開したURLで表示される画像が更新されるまでにラグがあります
- 反映までにラグもあるし、何度もページを更新すると前の状態が一瞬出たりもしました
- ゆっくり落ち着いてデバッグしましょう
うちの環境では家賃以外をグラフの描画対象にしています。
LINEアプリの方でサムネイルも出してくれるとありがたいのですが、今のところは出ないようですね…。
今後のアップデートに期待です。
今後やりたいこと
CI構築
以下の記事を参考に、CI環境を構築してみたいです。 undersooon.hatenablog.com 今まではgit管理できていなかったのでそういう発想もありませんでしたが…
- GASのwebエディタから脱却
- ステージング環境を用意
の2点はぜひ取り組んでいきたいところです。
ただ個人のアプリケーションでLINEのアカウントを2つに分けてまでステージングが欲しいかというとそうでもないので
ステージング環境については構成を考え中です…。
claspを使えばエディタは自分の好きなものにできても実行環境はやはりGAS上にないと…ということらしいので、合わせて考え中です。
今はやりかたがわかってませんが、そのうち単体テストを流せるようになるといいな…。
コーディングスタイル統一
ある程度は統一したつもりなのですが、関数名・変数名ともにキャメルケースとスネークケースが混ざっており…。
phpであれば PSR
があるので、GASもしくはJSでそういうのがあるならそれに寄せていきたいなと思ってます。
なくても一定のルールは引いておきたいなあとは思いつつ、今回の対応では妥協してます。
スターターキット生成ツールの構築
usakoはスプレッドシートやGoogle Formなど必要なツールが地味に多く、また初見では理解しにくいものが多いと思っています。
でもせっかく公開したので興味を持ってもらえたら利用してもらえると嬉しいので、個人の設定を入れたら必要最低限のものについては自動で構築してくれるツールを公開したいなと…。
もとがGASだしどれもGoogleのツールなので、多分作れるだろうとは思ってますが詳細については考え中です。
GAのイベントを環境ごとにフィルタリングする
概要
新しくサーバ側からGAイベントを送るように仕込んでみたけど、ステージング環境でも何故か計測されるぞ?!というプチトラブルに遭いました。
今まで言われるがまま仕込んでたせいで自分で全然理解してないままだったので、事の顛末を備忘として残します。
GAのイベントを仕込むには
この辺をどうぞ developers.google.com kyanite.hatenablog.com
何が起こったか
ステージングではイベントを計測されない設定のはずなのに、されてるぞ?!
実装者(私)は概要にある通り、今まで理解してGAを設定していたわけではないので、
そもそも「ステージングでは計測されない」という前提も知らず…。
計測された = 自分の実装があってると思っていたのですが、計測されるということ自体が誤りでした。
また、チームの中にはここについて正確に知る人がおらず…。
「計測されないはずだけど、なんでされてるんだろう????」という状態に陥りました。
イベントをフィルタリングするための設定
GAのイベントのフィルタリングにはビューの設定のうち フィルタ
が使われます。
support.google.com
誤解① ウェブサイトのURL
実はビューの設定の ウェブサイトのURL
という項目があります。
今回はステージングと本番のURLが違い、 ウェブサイトのURL
には本番のURLが設定してありました。
これにより、ステージングは計測されないと思い込んでいる状況にありました。
ここの設定、以下のリンクを参照すると単純にリンク先のドメインとして利用できるってだけなのですかね…?
www.oro.com
一応、公式も貼っておきます(ちょっと曖昧な表現に感じ、私はピンときませんでしたが…)
support.google.com
誤解② リモートワーク
すでに運用しているイベントだったので、社内からのアクセスが計測されない設定になっている状態のビューでした。
が…。
コロナウィルスの影響を受けて、各自宅からステージング環境にアクセスしていたため、
全て除外されているIP以外からのアクセスとなり「ステージングでもなぜか計測される」と誤解してしまいました。
VPNを用意しているものの回線が太くなく、必要がないときにはVPNを切断して開発していました。
そのため、普段利用していないVPNの存在を忘れてアワアワしてしまいました。
誤解③ サーバ側からの送信
今回新たにつけたイベント発行処理はサーバ側からの送信でした。
(詳しいことはこちらを参照ください : php側でGAイベントを送る - 唐揚げ食べたい )
勘の良い方はもうおわかりかと思いますが、このサーバのGIPについてもフィルタの除外設定に入れる必要があったのです。
幸い(?)弊社のステージング環境は1台のEC2しかなくGIPも持ってたので、除外設定を入れて事なきを得ました。
以上です。今後は何事も理解して使うようにしていきたいです。
AWS Cognito + ALBを使って認証を入れる
概要
コロナウィルスの影響で突然リモートワークをすることになり、普段外部に公開していない環境(ステージングとか)でも社外からアクセスできるようにする必要が出てきました。
その対応をしたので備忘録。
実際の作業時はすでに他のメンバーが作ってくれた手順書を凝視しまくって実施したのでこのブログでは手順は載せず、その時見たリンクを貼ります。
ALB、初めてまともに設定したけどすごい便利ですね。
リンク集
要するにこれをやった dev.classmethod.jp
ALBのルールは大体こんな感じで設定した(実際には手順書を見たけど) dev.classmethod.jp
ALBとは www.wafcharm.com
ハマったところ
スクショ盛りだくさんの優しい手順書があったのにハマった。
設定したけど社外IPではCognitoにすらたどり着けない
ALBについてたセキュリティグループが
- default
- 社内オンリー
で、社内オンリーだけを外したら、社内外問わずアクセスできなくなってしまった…。
結局、
- https, http なら許容する
っていう設定のやつをつけて事なきを得ました。
セキュリティグループの更新が反映されてないんじゃ?!って思ってアワアワした。
挙げ句の果てEC2のインスタンスも再起動したけど全く関係なかったです。
Cognitoまで来たけどエラー
上の赤い四角のところ(有効なIDプロバイダ)、チェックつけ忘れてエラーになっちゃってアワアワしてた。
エラーがシンプルすぎて「どっかにログ出てないのか?!?!」ってやってたら、親切なエンジニアが教えてくれました。
いっつもこういうのでアワアワしてるぞ!
以上です。
php側でGAイベントを送る
概要
デフォルトでは <a href="hogehoge" onClick="ga('send', 'event', 'Videos', 'play', '');">link</a>
みたいな感じでGAを仕込むと思いますが、それをphpでやる方法。
ユースケース
たとえばこういう…
DAUを計るためにログインボタンに
ga()
を仕込んでるんだけど、ログアウトしない限りは翌日もログイン状態が保持される。
そうなると翌日はログインボタンを押下されないから、ログインボタンにga()
仕込むだけじゃ足りないじゃん!
→ じゃあサーバー側でログインチェックしたときにサーバー側(今回はphp)からイベント送ればいいじゃん!!
書いてて思ったけど、じゃあずっとphpで送れば?っていう。
いいんです細かいことは。
実装方法
これを使います。 developers.google.com
リファレンスがきれいなので、基本的にはリファレンス通り実装すれば問題ないです。
ハマりそうなのは、クライアントIDの取得部分。
今回のユースケースではログインチェックのためにAPIを叩くので、フロント側で発行されたクライアントIDを使いまわします(本来はフロントでやりたかったはずなのでいいかなって)。
クライアントIDは ga()
で取得できます。
追記。 POSTで送る場合のパラメータ付与、ハマっちゃいました(一回間違えた例で公開するほど…)
ということなのでエンコードしたものをBODYとして送信してください。
jsonとかで送らないでくださいね(自戒)
フロント側
ajaxの宛先はサーバー側にいく想定です。
ga()
の中に ajax
を入れて tracker.get()
してるのがポイントです。
ついでにUAもとっておく。
<script type="text/javascript"> window.addEventListener('DOMContentLoaded', function() { ga(function(tracker) { $.ajax({ type: 'POST', url: 'https://hogehoge/islogin' data: { client_id: tracker.get('clientId'), user_agent: window.navigator.userAgent } }).done({ // 成功時の処理 }); }); }); </script>
サーバー側
単純にcurlします。
パラメータを作るときにはこちらがおすすめ(GETになっちゃいますが)
$url = 'https://www.google-analytics.com/collect'; $params = 'v=1'; $params .= '&t=event'; $params .= '&tid=UA-XXXX-Y'; // 自分のGAの管理画面で確認してね $params .= '&cid='.urlencode($_POST['client_id']); $params .= '&ua='.urlencode($_POST['user_agent']); $params .= '&ec='.urlencode('Videos'); // この例だとencodeする必要ないけど… $params .= '&ea='.urlencode('play'); // この例だとencodeする必要ないけど… $ch = curl_init(); curl_setopt($ch , CURLOPT_POST, true); curl_setopt($ch , CURLOPT_POSTFIELDS, json_encode($params)); curl_setopt($ch , CURLOPT_USERAGENT, $_POST['user_agent']); curl_exec($ch); curl_close($ch);
この記事を書くために改めてリファレンス読み返してみると、 ua
というパラメータもあるのですね。
プロトコルの方にもUA設定できるけどどう違うんだろう。
おまけ
curlをわざわざ書きたくないよという人はこういうの使うのもいいと思います。
私は使ってないので使い心地とかは知りません
github.com
追記
サーバ側の実装例が間違ってたので修正しました。ごめんなさい。
以上です。
IP制限を変更したいときに見るところ
概要
自分が構築したわけでもない、途中から運用に参加したサービスで資料もあんまりないという環境で 「いま動いてるサービスのIP制限の設定を変えてほしい」と言われたときに確認するところをまとめます。
初歩初歩だけど、困っちゃったので、備忘に。
確認するところ
webサーバのconf
apacheであれば /etc/httpd/*
nginxであれば /etc/nginx/*
とにかくまずはここを見る
iptablesの設定
AWSが多い今ではあまり設定されてることないと思うけど…
/etc/sysconfig/iptables
hosts.allow, hosts.deny
いずれも /etc
以下にある。
ここに書いてあるのは見たことないけど…
ALBの設定
※AWSの場合に限る
今回はこれだった
セキュリティグループの設定
※AWSの場合に限る
どうでもいいけど、ようやく見方がわかってきた。便利ですよねこれ
転職ドラフトに参加しました
ちょっと間があいちゃいました。いのもえです。
六本木でエンジニアとして勤務しています。
間が空いている間に何をしていたかというと、
第20回転職ドラフトに参加したり、
LINEスタンプの新作を作ったりしてました!
store.line.me
よかったら買ってください!
また、来年からは LINEスタンプ プレミアム
の対象にもしてもらえるみたいです。
せっかく作ったのでたくさん使ってもらえたら嬉しいです。
今回の話題
転職ドラフトに初めて参加したので、良かったことや反省点を振り返ってみたいと思います。
7/31に指名期間が終わり、指名いただいた企業さまには全社返答を終えたところです。
参加した経緯
以下を確かめるために参加しました。
- 自分の市場価値
- 自分が培ってきたものがどれほど魅力的なのか
- どのくらいの年収が提示されるものなのか
- 自分のアピール力
- やってきたことをちゃんと文章で書いてみたかった
結果
8社から指名をいただけました!ワーイ
結構一生懸命書いたので、8社という多めの指名数は報われた感じがします…!
思い出せる範囲で4つの案件について詳細に書いたので、単純に「いろいろやってそう」には見えたのかなと思ってます…
一方年収のグラフでは中央値に近い部類に入ってました。
今回頂いた指名で平均すると603万程度で、平均と比較すると下回ってしまいました。
記載した案件の内容で技術的に触れられている箇所が少ない/物足りなかったのかなと反省しています。改善の余地ありですね。
最大提示額では655万を超えているので、経験している内容には十分平均以上と言えるものがあるのかなと判断しています。
次回はもっとしっかり書くぞ…
全体のスケジュール
初参加だったので、色々時間がかかりました…。
特にレジュメの記載は迷うことも多かった…次回参加する場合には継ぎ足しで良いのでもっと楽になるはずですが…!
- 7月初旬
- 7/17〜7/31
- 8/3
- 返信
まとめ
自分の経歴を読んでもらって「ぜひ来てください」って声をかけてもらうのは、やっぱりとっても嬉しく思いました!
ただやはり、埋めるのにいっぱいいっぱいな感が否めなかったので、次回までにもう少しブラッシュアップしたいですね…
あと、自分のやってきたことが社外の人から見ても十分魅力的な経験だと言えそうと思えるようになったのも、かなり嬉しい収穫でした。
準備期間もわざと長めにとってみましたが、思ったよりも難しくてちょうどいいくらいでした。
アウトプットに慣れてる人であればもっと短くても良いかもですね。
宣伝
LINEスタンプ販売中! : natsu LINE stickers | LINE STORE
ほしいものリスト公開中! : Amazon.co.jp