タイムカードAPIとredmine APIを組み合わせて日報をつける

こんばんは!株式会社スマレジ、開発部のmasaです。

また、更新が空いてしまい申し訳ないです。。。

今日はうちのチームのエンジニアがつけている日報を自動化する試みにチャレンジしており、そこで作ったツールの紹介をします。

背景

結論から言えば、「日報とRedmineをリンクさせて、Redmine上のタスクについては日報を自動生成してしまう」というのがこの記事の趣旨です。ただ、それが必要となる背景について簡単にお話しします。

生産性の見える化見える化のための工数のジレンマ

タスクが入り乱れやすい性質のプロジェクトでは、得てして生産性がはっきりせず、チーム内部と外部で作業スピードで温度差が出ることがあります。 タスクの入り乱れの原因は、社内ステークホルダが多様化にあります。 例えばスマレジのようなSaaSの場合、

  • 新規機能を推し進めて売上達成をしたい営業
  • 品質改善によって問い合わせ負荷を軽減したいCS・開発メンバー
  • APIサードパーティ向け機能を拡充して、パートナー開拓、プラットフォームの活性化、連携コスト低減による導入のスケールメリット拡大を狙いたい技術営業や大手法人担当の営業

大体この辺りの思惑がプロダクトのスケジュールへ向けられます。

例えば弊社の場合だと、スマレジ・POS関係の開発チームが上記のような傾向になりがちで、タスクの種類や性質が多様化するため、開発側でのタスクも多様化します。 こんな風に、多様なタスクを抱えるチームの場合は開発作業とそれ以外の作業をするメンバーを役割わけし、定期的に役割をローテーションさせていくことになるのですが、 実際のところは簡単な話ではありません。

というのも、誰か一人のタスクのバケツが溢れたら他のメンバーでフォローするため、綺麗に役割を分けるのはマンパワーとそれを組織するための体制が安定するまでは、個人のタスクは多様化する傾向にあるためです。

こういった状況で「何にそんなに時間がかかっているか分からないから、もっと生産性を見える化しろ!」といっても、多様なタスクに振り回されている開発メンバーに10分単位のタスクについていちいち記録をつけて行ってもらうことは現実的ではありません。限界のラインが1日の終わりにタスクを振り返って日報をつけることくらいです。

ボトムアップ見積もりによる予実管理(Redmine)

以前のブログでも触れましたが、弊社はプロジェクト管理ツールとしてRedmineを使用しています。

redmine.jp

過去ブログは↓から。

masa2019.hatenablog.com

masa2019.hatenablog.com

masa2019.hatenablog.com

スマレジではRedmineボトムアップ見積もり型のプロジェクト管理ツールとして利用しています。 (チケット駆動開発はタスクの単位での開発手法に関する議論で、これをベースにプロジェクトを計画するなら、プロジェクト見積もりになるのかなとmasaは考えています)

ボトムアップ見積もりはこちら。

ssaits.jp

チケット駆動開発についてはこちら。

www.atlassian.com

どちらも、タスクレベルで課題を明らかにしてから進行するという意味で共通しています。 このタスクが前述のように多様化するので、記録するのも一手間。とはいえある程度の粒度で残す必要はある。だけど楽はしたい。。。 そこで、毎日つける日報もタスクベースでの記載になることに着目して「日報とRedmineをリンクさせて、Redmine上のタスクについては日報を自動生成してしまう」というのがこの記事の趣旨です。

プログラム

今回はNode.jsのバッチで作成しました。 今回、少しプログラムが長いので、折りたたみにします。 社内ツールとして作ったものなので、コメントがなかったり、メソッドわけが多少雑になっていますがご容赦ください汗 (暇があれば、そのうちgithubにあげておきます。)

これを運用してみて、うまく行ったかどうか、課題点などがあるかについては、また後日報告させてもらえればと思います。

masaのプログラミング遍歴

こんにちは!株式会社スマレジ 、開発部のmasaです。

また更新が空いてしまい申し訳ないです汗 ここ最近恐ろしく忙しくて、自分の開発の時間がなかなか取れず。。。

というわけではないのですが、NestJSの開発のお話から外れて、masaのプログラミング覚え始めから今に至るまでのお話とかしてみようかと思います。

プログラミングを始めたのは大学1年生

masaが初めて触れたプログラミング言語C++でした。触れるきっかけになったのは大学の講義です。 以前教育学部卒みたいなお話をしたかもしれないですが、masaは工学部と教育学部の両方を並行して履修していたんです。

動機は結構不純で、「先生だけだと、仕事が嫌になった時に辛いから、先生とは180度違うこともできるようにしておきたい」というものでした笑。 当時から、モンスターペアレントがあーだとか、教師のメンタルヘルス的な話はいっぱいあったのであまり教師という仕事に1年生の時からあまり期待していなかったんです。

じゃあなんで教育学部なんだよって突っ込まれそうですが、理由は教員免許が取りやすいからで、なんで教師に期待してないのに免許が欲しいんだっていうのは、「潰しが効くから」ってくらいのものでした。リーマンショックと震災の直後にmasaは大学生になってたんで、「何がしたい」よりも「生きる術」を身につけるために大学に入ったっていうニュアンスが強かったんですよね。(実家も食うに困るほどではないですが、裕福ではなかったので)

大学の講義でいわゆる制御構文の基礎をC++で習って、実習で課題のプログラムを作って・・・みたいな感じでした。情報系の学生あるあるじゃないかな。プログラミングは通期であって、後期になってポインタとデータ構造やアルゴリズムが入ってきて「わかんねぇぇぇぇぇ」って言いながら友達と毎夜計算機室で唸ってました笑。なので年間でも3000行も書いてなかったと思います。

ちなみに計算機室のOSはRedHatだったので、linuxのコマンドの基本とかもここで覚えています。

独学で初めて学んだのがJavaScript

1年の前期が終わって、C++以外の言語にも興味が出てきて、そこで選んだのがJavaScriptでした。 なんでJSなのかというと、無料のエディタとブラウザがあれば開発できるから。当時はLinuxを自力で自分のパソコンに入れるほどの技術力と経済力(パソコン1台しかなく、それでレポートとかもやるのでWindowsは1台必要だった)がなかったので、Windowsで気軽に開発できるのが魅力だったんです。

そこで初めて、「動的型付け」言語に触れてそのいいところと悪いところをなんとなく書きながら感じていました。 ちなみに勉強した本は↓

https://www.amazon.co.jp/dp/B01LYO6C1N/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

この本の良い悪いは微妙なところ(当時の版はプログラミング初心者にはちょっと難しい表現が多かった)ですが、リテラルについて丁寧に説明があったので、内部的にデータ型を持っていることと、それを「いい感じに」整理してくれることはわりとスッと入ってきました。

JQueryの触りくらいまではここで勉強しました。ただ、個人でちょっと動かすだけだとjQueryのありがたみがわからない部分があって、バニラで書いていることが多かったですね。ただ根っこになるDOMの考え方とかは面白くて、div領域の色を変えたり、ドラックドロップしたりして遊んでいたのは覚えています。

3年の講義でSQLとC

データベース論の授業でpostgreSQLを触ったのが僕のSQLの始まりでした。 ここで学んだことは結構効いてきているのが最近感じます。

あと、C言語もここで初めて触れました。触れたきっかけは理論物理の授業でシミュレーション課題などがあって、 C言語運動方程式を時間発展させてグラフに描画する、みたいなことをやっていました。大学で物理やった人あるあるかな・・・? 地味にMakefileとかもここで教わりました。

4年生〜院生でPHPPerl

4年生になって、卒業研究をするようになって初めてPHPPerlに触れました。 以前のブログで取り上げたBM25を使って、大学のシラバスを相互に類似度をスコアで出して、 それを運動モデルのパラメータに落とし込んで可視化する、みたいなことをやってました。この時の計算にPerl, Webサービスとして作成したので、サーバの処理はPHP, グラフの描画はJSでやっていました。

こう書くと色々やってんなって感じですが、フレームワークも知らない、オブジェクト指向もおぼつかないときにゴリゴリ描いたものなので、まあスパゲッティコードになりました。

性質は違いますが、院生の時もPHPを使ってWebサービスを作っていました。(後Node.jsもちょっと触っていました)

就職して、Java,VB,COBOLに触れる

一者目が大きなSIerで、保守しているサービスも昭和の頃から続いているようなものも多く、そのメンテのためにCOBOLVBなどを研修で教わりました。ただ、この辺りはほぼ使わない(実装することが少ない会社だったので)のでほとんど忘れました。

スマレジ に就職してPHPガッツリ、VueとかTypeScriptとかAltJSにも触れる。

スマレジ に来てからは独習でPythonとかGoとかは触っていますが、お仕事ではPHPSQLがほとんどです。 同じプロダクトをメンテするので、使う言語も自然と一つに定まります。

あと、フロントエンドエンジニアさんの代わりにフロントをいじることもあります。そういう意味ではフロントの方が技術の勉強をすることは多かったかもです。。。(プログラムの組み方とか、バグの取り方とか、そういった実践的な部分はPHPですが)

最後に

こう振り返ってみると、色々と触ってきているんだなと思うんですが、やっぱり「一つの言語をしっかりじっくり使っていく」のが一番だなというのは感じます。 ただ、これからプログラミングを始める方は、(案件の都合でPHPとかも必要だとは思うんですが)基本静的型づけ言語にしていたほうがいいかなと思います。。。仕事で使っているので思うんですが、やっぱり動的言語はメンテが大変なことが多いかと思います。。。(チェック内容も増えるし)

NestJSで簡単なアプリを作ってみる(2)WebSocketでチャット機能を作る

こんばんは! 株式会社スマレジ 、開発部のmasaです。

皆さんはリモートワークでBGMかけていますか?masaはかけています。 かけるBGMはそれこそ気分次第なのですが、朝は無印良品の店内BGMをかけています笑

www.youtube.com

masaは夜になるにつれて集中力が増していくので、朝は頭が回っていないことが多いです。 それもあって登山とか、生活習慣を整えられそうなことをしてたりするのですが・・・(登山は朝早く出て、体を動かすので自然と早寝早起きになる)

意外とBGMひとつで気分も結構変わるのでバカにできないですよね。

WebSocketの練習の鉄板「チャットアプリ」を作る

WebSocketの入門で作った人も多いのではないでしょうか。今回は鉄板のチャットアプリをNestJS+WebSocketで作ります。 昔masaも作りました。

いきなりWebSocket触るわけですが、それ以前の基本については公式ドキュメントやyoutubeなどでざっくり勉強してサンプルを作って動かしています。

docs.nestjs.com

www.youtube.com

で、まず、WebSocketの公式ドキュメントを見てみます。

docs.nestjs.com

想像通りですが、エンドポイントに当たるgatewayを作って、ソケット監視する仕組みのようです。 しかし、公式で書いてあるコードが断片的で、全貌がよーわからんのです。

と思っていたら、一番下にサンプルのgithubを書いてくれていました。

github.com

一応チェックアウトすればすぐ動くみたいですが、このコードをベースに自分なりにいじっていきます。 決して今作っているプロジェクトと別にサンプルをチェックアウトするのがめんどくさいとか、そういう理由ではないのです。

で、サンプルを見ていて「あれ?」と思ったのがここ。

github.com

え、結局Socket.io使うのか。 いや使うにしても、NestJSの仕組みの中でラップしているとか、そういうんじゃないのか。

ちょっと拍子抜けでした。ちゃんとフレームワーク比較するときにちゃんと調べているわけではなかったのでただのmasaの勘違いなのですが。 (でもそれならそれで、公式ドキュメントにSocket.ioを使う想定だよって書いておいてもいいような。Node.jsのWebSocketのライブラリってもしかして他にも結構あるのかな・・・?)

ソースを見た感じ、SubscribeMessageデコレータでどのイベントにどのメソッドが紐づくのかを直感的に指定できるようになっていますね。 確かにこれは便利。それに他のサービスなどのデコレータの使い方と似ているから使いやすい。

作っていく

まずはnestコマンドでgatewayのテンプレを作成。

nest g gateway Events 

で、サンプルをもとにgatewayを実装。

import {
  MessageBody,
  SubscribeMessage,
  WebSocketGateway,
  WebSocketServer,
} from '@nestjs/websockets';
import { Server } from 'socket.io';

@WebSocketGateway({
  cors: {
    origin: '*',
  },
})
export class EventsGateway {
  @WebSocketServer()
  server: Server;
  wsClients=[];

  handleConnection(client: any) {
    this.wsClients.push(client);
  }

  @SubscribeMessage('chat')
  chat(@MessageBody() data: any) {
    console.log(data);
    this.broadcast('chat', data.message);
  }

  @SubscribeMessage('testing')
  emitLoginMessage(@MessageBody() data: any) {
    console.log(data);
    this.broadcast('login', data + 'さんがログインしました。');
  }

  private broadcast(event, message: string) {
    const broadCastMessage = message;
    for (let c of this.wsClients) {
      c.emit(event, broadCastMessage);
    }
  }

}

でクライアントhtmlも用意。

<html>
<head>
</head>

<body>
<div id="test-area">

</div>
<input type="text" id="input" />
<button onclick="sendMessage()">send</button>
<script src="https://cdn.socket.io/4.3.2/socket.io.min.js" integrity="sha384-KAZ4DtjNhLChOB/hxXuKqhMLYvx3b5MlT55xPEiNmREKRzeEm+RVPlTnAn0ajQNs" crossorigin="anonymous"></script>
<script>
    const socket = io('http://localhost:8080');
    const userId = Math.random().toString(32).substring(2);
    socket.on('connect', function() {
        console.log('Connected');
        socket.emit('testing', userId);
    });
    socket.on('login', function(data) {
        let textArea = document.getElementById("test-area");
        textArea.innerHTML += '<div style="color: blue;">' + data + '</div>';
        textArea.innerHTML += "<br />";
    });
    socket.on('chat', function(data) {
        let textArea = document.getElementById("test-area");
        textArea.innerHTML += data;
        textArea.innerHTML += "<br />";
        console.log('event', data);
    });
    socket.on('exception', function(data) {
        console.log('event', data);
    });
    socket.on('disconnect', function() {
        console.log('Disconnected');
    });

    function sendMessage() {
        let message = document.getElementById("input").value;
        console.log(message);
        socket.emit('chat', { message: userId + ": " +message });
    }
</script>
</body>
</html>

今回はブロードキャストの動作確認もするので、webstormのローカル実行機能で、ローカルでブラウザを二つ立ち上げます。

動かしてみるとこんな感じ

ブラウザにアクセスすると、ランダム文字列でuserIdが決定され、loginイベントが発火し、ログイン通知がブロードキャストされます。 その後チャット欄に文字を入れてsendボタンを押せば、そのときブラウザを開いているユーザ全員に通知がいくようになっています。

このブロードキャスト処理の実態は、接続時にwsClientsに接続情報を保存しておき、ループでクライアント全員にemitを送っているだけなので、接続数が増えると遅延なども出てきそうです。

NestJSで簡単なアプリを作ってみる(1)開発環境構築

こんにちは!株式会社スマレジ 、開発部のmasaです。

この一週間で一気に初夏らしい気候になってきましたねー。 masaも先日、趣味の登山で、滋賀県武奈ヶ岳まで登ってきたのですが、 思ったよりも道中暑くてキツかったです汗

yamap.com

さて、本日からはNestJSを触ってみます。

NestJSとは?

NestJSはNodeJSのフレームワークの一つです。

nestjs.com

どうでもいいですが、ロゴがかっこいいですね。 スポーツウェアブランドみたい。

ちなみにmasaはNodeJSはExpressを何度か触ったことがあります。PHPほど歴は長くないですけど。(JS好きなので) なのでNode.js初心者というわけではないです。

選定理由

明確な理由はないです笑 ただ、

  • TypeScriptが使える(他のフレームワークも使えるやつありそうだけど)
  • WebSocket関連の公式記事が充実してそう
    • 今回作るアプリはいわゆる投票アプリ的なのを考えているのでブロードキャスト配信を含むソケット通信ができる仕組みがいいなと。
  • ControllerとServiceが分かれている

ので選びました。あとmasaはJSが好きなのです。(動的型づけは微妙だけど、応用事例がたくさんあって楽しい)

環境構築

というわけで環境構築。 Node.jsのフレームワークなので、プロジェクト作成したらnpmでインストール

$ npm i @nestjs/cli
$ nest new <プロジェクト名>

TypeScriptは既に導入済みだったので作業なし。

しばらくしてプロジェクトが完成するのでローカルで動かす。

$ npm run start:dev

この時動作するポート番号の設定は、自動生成されたmain.tsの中に書かれています。

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(8080); // ここ
}
bootstrap();

ローカルホストで動かした

このハローワールドは、app.service.ts内に記載があります。

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

で、このgetHelloをコントローラー側でコールしている。

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

という感じ。 これをmoduleが呼び出しているんだけど、その辺りの仕組みは勉強中。

WebStormでブレークポイントを有効にする。

ちゃんとTSのコードにブレークポイントマッピングしてくれるのか不安だったのですが、できました。 ~/Desktop/clickerは今回のプロジェクトのルートです。

デバッガの設定(WebStorm)

指定するJSファイルはdist内部(tscした後のファイル)を指定するのがポイントです。 (TSファイルは指定できないので) うまくいかない場合はPreferenceからNode.jsのバージョンなどの設定を見直してみてください。 うまくいけば下記のようなデバッグ表示がでます。(usersはデフォルトではないです。masaが追加したものなので同じソースはありませんのでご注意ください)

スケジュールってどうやって立ててますか?

こんばんは!株式会社スマレジ 、開発部のmasaです。

更新が空いてしまい申し訳ないです! 今年は更新間隔が少し開いてしまうかもしれません。

GWは皆さんどんなふうに過ごされましたか? masaは振替で出勤している日も何日か会ったのですが、最終日に葛城山に登ってきました。 (登山いく暇があるならブログ更新しろよ!ってツッコミはなしでお願いします汗) この山、山頂につつじ園があることで有名なのですがmasaが登った時は8分咲きぐらいでした。 それでも、山の一部が真っ赤に染まったように見えて圧巻でした!

葛城山の詳しい情報はyamapから!↓

yamap.com

さて、今日はスケジューリングのお話です。

スケジュールの立て方

SaaSはサービスの性質上、定期的にマイナーアップデート + バグ修正のメンテナンスアップデートをして、 何年かに一回メジャーアップデートをする形になることが多いかと思うんですが、今回はマイナーアップデートのバージョンアップをするときの スケジュールのお話です。

なお、ここではQAスケジュールなどの調整など、他部署・他チームが関係する部分の話は割愛しています。ご了承ください。

メイン機能の工数を出す

スマレジ POSの場合そのアップデートの性質にもよるのですが、大体マイナーアップデートは目玉になる機能の追加(最近だと、削除済み取引閲覧機能とかがそれ)があって、そこが一番工数が大きくなるので、その部分の工数出しからやっていきます。

メイン機能の規模にもよりますが、基本的には設計(SIerでいうところのUI/SS工程くらいまでの作業)をこの段階で行います。 そう聞くと、「要件定義段階である程度工数出さないの?」とSIerの方は思われるかもしれないんですが、ざっくりとは出してます。 (ここでの工数出しや要件期日の調整は開発部幹部+営業で調整することが多いです。)

ただスマレジPOSの場合、すでにたくさん要望をいただいていて、かつ要望自体の精査がPJ開始前に進んでいることが多いので、 要件を固める作業はそこまで大きくなくて、要件はある程度決まった状態で開発部でのバージョン作業が開始することが多いです。

そのため開発部での設計のお仕事は既存機能に違和感なく要件を満たす機能をデザインするところがメインになります。

メイン機能の実装・テスト工数算出

メイン機能の実装・テスト工数の算出は設計をFixさせてから算出します。 実装工数が予め入れられているので設計が膨らんだのに工数調整できず実装工程で炎上・・・みたいなことが前職のSIerだとよくあったんですが、それはほぼないです。(優先度の高い法令対応などが差込タスクが入ったりすると、他のPJ工数が圧迫されることはありますが・・・。特に税法系の対応はもうちょっと公知と施行の間隔を開けて欲しい。多分他のレジ会社も同じこと思ってるはず。)

お客様に出す設計書を作ると言うわけではないので、SIerの方が想像しているエクセルの設計書よりもかなり簡素です。 要はメンバーが共通認識を取れる精度で作るのが大事なので、記載の粒度なども自分たちで自由に決められます。 このあたりがSaaSの強いところだと個人的に思っています。

masaがやるときは、

  1. タスクの洗い出し
    • スプレッドシートに書き出していきます。ある程度出したら用件の依頼者(営業さんとか)に共有して機能レベルで漏れがないかを確認していきます。
  2. ガントチャートにタスクを書いていく
    • masaがよく使っているテンプレートです。スプシのガントチャートだと大体これがデフォルトなんかなと思ってます。
    • ここの「タスクのタイトル」に洗い出したタスクを転記していきます。
    • タスク間の依存度が高い時はアローダイヤグラムを書くこともあります。
  3. タスクの詳細(UI/SS)を詰めていく
    • いわゆる設計作業です。どうやって実現するのかを文書にしていきます。
  4. 工数を入れていく。
    • ガントチャートのテンプレートをちょっといじって工数を入れるようにしています。単位は人時です。
    • リリース日から逆算して工数入れる方が、精度が高くなる気がしています。特に後半になればなるほど、他部署(営業・QAチーム・インフラチームなど)との調整が必要な分、工数がある程度決まってくるので。
  5. 開始日と終了日を入れる
    • 入れた工数をもとに開始日と終了日を入れていきます。
    • masaの場合、工数をマシマシで最初出すので、大体そのままだとはまってくれないので、工数を調整しつつ入れていきます。
  6. メンバーに共有
    • 自分以外にもメンバーに手伝ってもらう場合や、バージョン作業全体のスケジューリングの時はこの段階でメンバーに共有します。
    • この時チーム内で設計レビューをやることが多いです。
  7. 設計レビュー
    • 上司による設計レビューです。機能のインパクトに応じて営業やCSが入る場合もあります。
  8. 設計書をチケットに変換
    • レビューが通ればガントチャートをもとにredmineにチケットを作っていきます。
    • まず中身なしでタイトルと期日と工数だけ入れて起票。中身はそのあとで書いていく or 設計書のリンクを乗っけて終わりにする時もあります。

という感じです。

これでメインの工数が決まって、まだ人員的に余裕がある場合はバグチケットのスケジューリングを行なっていきます。 これは週次でバグ報告状況を確認していて、内容の重さや対応難度・工数に応じて残った工数に引き当てていきます。

という感じでmasaの目線でスケジューリングについてお話ししてみました。 皆さんの会社ではどんな感じで線を引いていますか?

GASでTypeScriptを使うときの注意点(実践編)

こんばんは!株式会社スマレジ 、開発部のmasaです。

また、日が少し空いてしまい申し訳ないです。。。

今回は、前回予告した通り、実際のツールを元にGAS + TypeScriptのプラクティスを紹介します。

サンプルプログラム

今回は、スマレジ の外部会員連携とPFAPI、それにSquareレジのAPIを利用して、取引や会員販売の情報をシートに取り込んで、簡単な集計を出すアプリを作成しています。

github.com

masa's blogではgithub初利用かもです笑

構成

ルートにwebpack.config.jsがあるので分かる通り、webpackを使用しています。

また、src/FrontController.tsにGASのdoGetを記載しており、doGet内では外部からのHTTPリクエストや、処理の振り分けに利用するactionIdなどの値を元に、適切なアクションクラスを呼び出すような作りになっています。

このdoGetの定義が重要で、下記のようにglobalのメンバとして定義してあげることで、webpackでビルドした後も、doGetメソッドをGAS上で認識することができます。

global.doGet = (e: any) => {
...

シートから処理を呼び出したいとき

シートから処理を呼び出したいときは、onopen.jsでactionIdを指定して、doGetを呼び出すことで実現します。

/**
 * ファイルを開いたときのイベントハンドラ
 */
function onOpen() {
    let ui = SpreadsheetApp.getUi();         
    let menu = ui.createMenu('アプリ');
    menu.addItem('会員売上比率', 'displaySearchFormForCustomerSalesPiChart');   
    menu.addToUi(); 
}

function displaySearchFormForCustomerSalesPiChart() {
    callService("displaySearchFormForCustomerSalesPiChart");
}

...(中略)

function callService(actionId, request) {
    let e = {};
    e.parameter = {};
    e.parameter.actionId = actionId;
    if (typeof request !== "undefined") {
        e.parameter.req = request;
    }
    return doGet(e);
}

callService内部でdoGetを呼ぶことができるのは、先述のglobalのメンバとしてdoGetを定義しているからです。 また、このonopen.jsはwebpackの対象ではないので、そのままデプロイされます。

GETリクエストを処理したいとき

doGetがそのままGAS上で認識されるので、FrontController.tsに送られてくるリクエストを元にactionを呼び出してあげればOKです。 FrontController.tsの61行目以降が当該処理になります。

このツールは社内で検証用として利用するツールがベースになっているので、FrontControllerの書き方などは正直ぐちゃっとしているのですが、 まあ、GAS自体ツール作成に用いられることが主なので、読みにくくなければ気楽に書いて使っていいんじゃないかなと思っています。 (でもMVCはふんわり意識した方が実装が楽なのでTSは使いたい。何よりこの手のツールは後で手を加えることが多いので、若干の追加改修に耐えうる程度には整理して作成しておいた方が無難なイメージがあります。)

GASでTypeScriptを使う場合の注意点

こんばんは!株式会社スマレジ 、開発部のmasaです。

更新が空いてしまい申し訳ないです。。。ちょっと忙しい時期が続きましてなかなか更新できなかったです泣

さて、最近シリーズの方が進められていないですが、今回も別の話題です。

GASでガッツリしたツールを開発したいとき

スプシを導入している企業は増加している一方で、大体の場合、GASはシンプルな複写や計算などをさせるだけで込み入った機能などは諦めることが多いと思います。

その理由としてGASのベースがJavaScriptであるところにあります。 JSは手軽に使える一方で、JavaPHP,Pythonなどと異なり、MVCライクな開発をするのには少しクセがあります。 (無理ではないですが汗)

そこで、以前紹介したClasp + TypeScriptを使ったローカルでのGAS開発が採用されるのですが、それはそれでいくつか問題点があります。 以前のブログは↓。

masa2019.hatenablog.com

Clasp + TypeScriptを使用する際の問題点

export importが正常に動作しない

これは下記のブログで紹介されている現象です。

kenchan0130.github.io

平たく言えばJSのimport exportがGASにデプロイした時に動作しないことがあるという問題です。 原因は、下記の公式githubに記載されているのですが、GAS自体がECMAScriptコンパイラではなく、 GAS独自の解釈を行なっているようで、そこに問題がある(というかESとの互換性がない)ことにあります。 (これも上のブログの方が触れられています)

github.com

解決策: webpackを利用

回避手段は3つ提示されていますが、後の問題との関連で3番目の「webpackなどのバンドルツールを使用する」一択になります。

しかし、webpackでビルドしてしまうと、トランスパイルされたGASは個々の処理が関数として定義されない状態になります。

それを回避するために、doGet,doPostなどのHTTP用のメソッドをdeclare functionとして定義してあげる必要があります。

declare function doGet(e) {
...
}

また、production modeでwebpackを実行してしまうと、デフォルトではminifyされてしまいますので、定義した関数名とは異なる関数名として定義されてしまいます。

そのため、webpackを利用する際はdevelopment modeを利用しましょう。 eval()が冗長ではありますが、関数名が自分の決めたもののままになるため、スプシの拡張メニュー用のラッパーメソッドからの呼び出しが可能になります。

doGet,doPostとの共存

いわゆるWebサーバとしてのリクエストを捌く処理と、スプシ上のツールとしての役割を両立させたい場合に発生する問題です。 内部的にはevalで関数として評価されているので、呼び出しは可能ですが、menuに追加するなどは今のままではできません。

解決策: ラッパーメソッドをJSで用意する。

シンプルですがこれが一番確実です。 幸いなことに、doGetもdoPostもリクエストを引数に持つ関数なので、リクエスト引数をでっち上げてデータを渡すことも可能です。

例を示すと、

function task1() {
   let req = {};
   req.actionId = "task1";
   doGet(req);
}

というふうにdoGetをラップして、

declare function doGet(e) {

const actionId = e?.actionId;

switch(actionId) {
    case "task1":
      ...
      break;
   ...
}

というように、渡したリクエストに応じて処理を切り替えるなどが可能です。 こうすれば、task1を拡張メニューに紐づけることができ、TypeScript側のロジックを呼び出すことができます。

脱線が続いて申し訳ないですが、次回は上記の実装例をスマレジ の外部会員連携とプラットフォームAPIを組み合わせてご紹介します。