dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

React公式ドキュメントの「You Might Not Need an Effect」を読んだ

少し前に話題になっていた以下のドキュメントをようやく読んだ。

beta.reactjs.org

記事を書いた時点でドキュメントはまだベータという位置づけ。

長いので先に目次を。
⭐ は中でも特にへえ〜って思ったやつ。💬 はコメント。

続きを読む

スプレッドシートのピボットテーブルで「総計を表示」しているかどうかをGASで判定する

超小ネタ。

Google スプレッドシートでピボットテーブルを作ったとき、行または列の項目ごとに「総計を表示」するかどうか選択できる。
この値を GAS で取得したい。

なんでこんなこと思ったかというと、総計を表示するかどうかで行・列数が変わるため、処理に分岐が必要だったから。

方法

PivotGroup クラスに totalsAreShown() メソッドがある のでそれを使う。

function getTotalsAreShown() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('foo');

  const pivotTables = sheet.getPivotTables();
  const pivotTable = pivotTables[0];

  // 列の場合。行の場合は getRowGroups()
  const pivotGroups = pivotTable.getColumnGroups();

  pivotGroups.forEach(pivotGroup => {
    console.log(pivotGroup.totalsAreShown());
  })
}

SheetPivotTablePivotGroup という3つのクラスが関係しており、それぞれの関係性は下の図のとおり。

なお、 showTotals(boolean) というメソッドで ON/OFF の設定もできる。

参考リンク

Backyard Hatena「#12 id:missasan に聞くMackerelのカスタマーサクセス」を聴いたメモ

貴重な CRE 事例のお話ということで聴いた。以下メモ。

  • [1:50] CREとしての missasan はどんなことやってるの?
    • MackerelのCREは大きく2軸
      • テクニカルサポート
      • カスタマーサクセス←← missasan さんはこっち
  • [2:30] カスタマーサクセスって言葉も耳慣れないかもだけど、どんなことをやる?
    • サブスクリプションモデルのSaaSだと浸透してきたよね
    • 契約した「後」のお客様に対し、プロダクトを利用することによって得られる成果(=成功)を期待通り、期待以上にする
    • 非エンジニアがやってる会社も多い
  • [4:41] CR”E” なのでエンジニアでもあるんですよね?
    • Mackerelはサーバー監視ツールなのでユーザーはエンジニア
    • プロダクトの特性的に自然とエンジニアの知識が求められる→自然とMackerelのCREはエンジニアに
  • [6:00] Mackerelが提供する「サクセス」
    • 難しい。まさに今定義していってるところ
    • サーバー監視でビジネス上どういう成功につながるか。サーバー監視ってプラスというよりかは守り、保険みたいなイメージなので
    • SREとかDevOpsの知識を取り入れてプラスの面を出していきたい
    • Mackerel使うとこういうふうにステップアップできますよ、という良い世界感を作っていく、ということを考えてる
  • [10:10] 世界観を作っていくためにやっていること
    • いろんなロールの人と話している。SREの方とかと定期的に1on1
    • Mackerelのカスタマーがどう成功していくのかちゃんと考えたい→ジャーニーとか成熟度とか、どういう監視の考え方があるのか
    • 一方で、CREがカスタマーサクセスとして日々お客様と接していて、そこで得たユーザー像を開発チームにもっとインプットしていこうと考えて、インプット先として選ばれたのがデザイナーチーム
      • こういうユーザーがいて、こういうペルソナで、こういうカスタマージャーニーで、を伝える、または一緒に作る会
      • そこで作ったカスタマージャーニーをまたSREにフィードバック
    • motemen: いろんな人と話してるんですね
      • ですね。ほぼ全職種
      • こないだまでは登壇もあったのでマーケと連携したりとか、プリセールスみたいな仕事もやってるのでセールスの人とも話したりとか
  • [15:00] お客様のフロントに立ってお客様の声を聞く立場だから、その声をデザイナーや開発チームにフィードバックしている、と
    • ですね
    • プロダクトチームが「お客様にこうなってほしい」って考えて出した機能、意外とお客様に届かない。そこを「こういう機能ありますよ」って気づいてもらうとか「お客様の場合はこう使うと便利ですよ」みたいなアシストをする
    • 逆にお客様はお客様で「こうなりたい」というものを持っている。両者を近づけるのがCREの役目
  • [17:00] お客様の要望・困りごとを開発チームに届けるのはどうやってやっている?
    • いろんな変遷があったが、最近は開発チームのバックログとは分けて管理。セールスやカスタマーサクセス担当から挙がってくるものをなんでもかんでもストックしていく
    • それだけだと開発チーム全部読んで判断するの難しいので、トピックやセグメントで絞ったものをレポートしてる
      • 月1で共有会。開発チーム全員任意参加
    • トピックで絞る、とは?
      • わかりやすいのでいうと機能
      • あとはお客様がMackerelを使うシーンでの分類。初期設定の話なのか障害対応中なのか障害後のふりかえりなのか
  • [21:20] 要望を開発チームにフィードバックすることで良い変化はあった?
    • 一番うれしい変化は実際に開発されること
      • 当時は「難しいよね」で眠ってたものが掘り起こされてリリースに至った、とか
    • 関連する機能を作るときに「そういえばこういう要望あったな」というのもアイディアの種になると思っている
    • 「ユーザーのことで知りたくなったらCREに聞けば何か返ってくる」という認知ができた
      • CRE、いろいろやってて社内でも何してるかわからなかった
  • [25:15] missasan がエンジニアとして手を動かすことってある?
    • あります
    • お客様向けにMackerelの機能紹介をするとき、デモ用の環境作るのでそのときは手を動かす
    • エンジニアの要素も必要
      • 今後、ビジネス要素の強い部分とエンジニア要素の強い部分とで業務を分けて、それぞれで専門性高い人に入ってもらえるようにするというのも検討している
      • Mackerelユーザーのインフラ事情も多種多様。オンプレ、パブリッククラウド、etc.
  • [29:30] missasan: はてなのSREと一緒に仕事できる、現場の運用やってるエンジニアと会話しながら仕事できるというのがMackerel CREの面白いところかな
  • [30:30] 逆に missasan から僕に聞きたいことありますか?
      1. CREがこうなってくれたらいいな、みたいな期待値あります?
      2. 今 missasan がカスタマーサクセスに興味持たれて、そこに注力してるのは一歩前進してるなーと感じてる
      3. Mackerel使うとこんなふうにビジネス・サービス成長しますよ、を提示できるようになってくと良い
      4. SRE的な知識に裏打ちされたプロダクトビジョンに、お客様の声をうまく反映できるといい

感想

弊社でいうとカスタマーサクセスチームの責務・立ち回りにかなり近いなと感じた。お客様からの声をプロダクトチームにフィードバックするような動きとか。

お客様と日々向き合うことで得られたユーザー像をデザイナーチームと連携してペルソナやカスタマージャーニーを作っていく、という活動はめちゃくちゃいいな〜(小並感

StorybookのInteraction Testをようやく試した

社内の10分勉強会駆動でようやく Storybook の Interaction Test についてまとめることができたので、メモ。

Interaction Test とはなにか

Storybook 上でユーザーのインタラクション(ボタンクリックやフォーム入力など)を再現できる機能。
上の GIF のように Storybook でインタラクション結果を確認できるだけでなく、インタラクションを記述したスクリプト中にアサーションを書いてテストとして機能させることもできる。また書いたスクリプトTest Runner により CLI や CI で実行することも可能。

使い方

公式ドキュメントの

に従う。

また公式ブログの

チュートリアル形式で実際に試せるのでわかりやすい。
(上記のチュートリアルを写経したものを https://github.com/zaki-yama-labs/storybook-interaction-test-example に置いてある)

$ yarn add --dev @storybook/testing-library @storybook/jest @storybook/addon-interactions

で必要なパッケージをインストールした後、Story に対し play function と呼ばれる関数を記述する。

冒頭の GIF に相当する play function は以下。

import React from "react";
import { within, findByRole, userEvent } from "@storybook/testing-library";
import { expect } from "@storybook/jest";
import { InboxScreen } from "./InboxScreen";

export default {
  component: InboxScreen,
  title: "InboxScreen",
};

// ...中略...

export const EditTask = Template.bind({});
EditTask.parameters = { ...Default.parameters };
EditTask.play = async ({ canvasElement }) => {
  const canvas = within(canvasElement);
  const getTask = (name) => canvas.findByRole("listitem", { name });

  const itemToEdit = await getTask("Fix bug in input error state");
  const taskInput = await findByRole(itemToEdit, "textbox");

  await userEvent.type(taskInput, " and disabled state", { delay: 300 });
  await expect(taskInput.value).toBe(
    "Fix bug in input error state and disabled state"
  );
};

https://github.com/zaki-yama-labs/storybook-interaction-test-example/blob/e116e542b1ba652afd4345c1ee8d6f279b39e965/src/InboxScreen.stories.js#L69-L82

react-testing-library でのテストを書いたことがある人からすると、基本的な書き方は大きく変わらないのがわかる。ただ import しているライブラリは @storybook/xxx になっている。

この play function を書いた状態で該当の Story を開くと、下部のペインに Interactions というタブが追加される。インタラクションは Story を開いたときに自動的に開始されるが、 Interactions タブ内の ◀️ ▶️ をクリックするとコマ送りで確認することもできる。

CLI から実行する

@storybook/test-runner をインストールすると test-storybook というコマンドが使えるようになるので、これを CLI から実行する。このとき、

  • play function が定義されていない Story に対しては:Story がエラーなくレンダリングされるかどうかを検証する
  • play function が定義されている Story に対しては:play function がエラーなく実行され、すべてのアサーションが pass するかどうかを検証する

という挙動になる。

Interaction Test で何がうれしいの?

ユースケースを模したテストを目視で確認しながら書けるのでわかりやすいですね、ということに加え、 VRT(Visual Regression Testing)と組み合わせるとよりおいしい んだろうなと思った。

VRT(Visual Regression Testing)

  • UIを画像として保存しておいて差分検出するスナップショットテストの一種
  • マネジドサービスになったものとしては Chromatic が有名(というか他を知らない)
  • OSS でも reg-suit & storycap というツール + S3 などのオブジェクトストレージ組み合わせると比較的簡単に構築できる

Interaction Test + VRT = ?

  • Storybook だけだと難しい「ユーザー入力に伴うUIの状態」を担保してくれる
  • アサーション書いてないところも含めて意図せずUI崩れてないか担保してくれる

ちなみに、VRT って Interaction Test の操作ちゃんと待ってくれるの?flaky にならない?というのが気になったが、こちらの記事で reg-suite と storycap の作者が特に問題なかったと言及していた。

Storybook の play function と VRT - Qiita

わからないこと

というわけで非常に便利そうではあるが、元々 react-testing-library + Jest で書いてたテストとの棲み分けがどうなるのかはちょっとまだ整理ができていない。
play function 内にアサーションも書けるので、極論すべてのコンポーネントのテストを play function に寄せることもできるのでは?と思ったけどどうなんだろう。そうなると実行速度だけが判断基準か。

takepepe さんのこちらの記事では、逆に play function を定義した Story を Jest に取り込む、とある。この場合あくまでテストは Jest 側に集約するイメージだと理解した。

参考リンク

自作Chrome拡張をManifest V3に移行したメモ

という Chrome 拡張を作っているのだが、先日ようやく Manifest V3 に移行したのでそのときにやったことのメモ。

該当の PR はこれ。

また、作業中は Zenn のスクラップにメモを取りながら進めていた。
自作Chrome拡張をManifest v3に対応させる

モチベーション

Manifest V3 移行へのモチベーションはそこまで高くなかったが、vite を触ってみるため webpack からの移行を試してみたいなと思っていた。
そんな折

Chrome拡張 つくりかた 令和最新版

という記事を拝見して、自分もこのとおりにやって乗り換えようと思った。が、どうやら @crxjs/vite-plugin というプラグインは Manifest V3 にしか対応してないということがわかり、先に V3 移行を済ませるようにした、という経緯。

参考:いつまでに Manifest V3 に移行すべきか

The transition of Chrome extensions to Manifest V3 - Chrome Developers

によれば、 2022年1月からすでに新規の Chrome 拡張は Web ストアで公開することができず、既存のものについても 2023年1月以降は動作しなくなるらしい。

どう進めたか

最初に

Manifest V3 migration checklist - Chrome Developers

を見て自分があてはまる項目を確認し、あとはそこから参照されているリンクも読みつつ適宜移行を進めていった。

自分の Chrome 拡張はページのタイトルと URL をコピーするだけのシンプルな機能で、そんなに複雑な API を使っていない。チェックリストであてはまったのは以下:

  • Are you using background pages?
  • Are you using the browser_action or page_action property in manifest.json?
  • Are you using the chrome.browserAction or chrome.pageAction JavaScript API?

やったこと1: browserAction を action に移行

V3 では browser action と page action という区別がなくなり、action で統一されるらしい。
そのため manifest.json 内の xxx_action という記述およびソースコード内の chrome.xxxAction を action に変換する。

コミット:https://github.com/zaki-yama/copy-title-and-url-as-markdown/pull/221/commits/b259acbba3520727e2bc49a5260eff86740580c7?diff=unified&w=0

manifest.json

   "commands": {
-    "_execute_browser_action": {
+    "_execute_action": {
       "suggested_key": {
         "default": "Ctrl+Shift+C",
         "mac": "MacCtrl+Shift+C"
@@ -29,7 +29,7 @@
       "description": "Copy as optional format #2"
     }
   },
-  "browser_action": {
+  "action": {
     "default_icon": "icon.png",
     "default_popup": "popup.html"
   },

background.ts

-      chrome.browserAction.setBadgeText({ text: formatIndex });
+      chrome.action.setBadgeText({ text: formatIndex });

1つポイントとしては、manifest の commands でポップアップのショートカットキーを設定している場合、ここも _execute_action に変える必要がある。

やったこと2: background page を Service Worker 化する

コミット:https://github.com/zaki-yama/copy-title-and-url-as-markdown/pull/221/commits/c57581e1dfa03bafe320ca190698b4f112e28280

V3 からは background page という概念はなくなり、 Service Worker になる。

ここで少々困ったのが、元々ポップアップとは別のショートカットキーでもページのタイトルとURLをクリックできるよう、 background page を使用していた。(コピー時のフォーマットとして、ポップアップ起動時とは別のフォーマットをオプションで設定できる)

そのしくみとして、background page の HTML 内にダミーの textarea 要素を置き、そこに値をセットして select してから document.execCommand("copy") を呼ぶという方法を取っていた。しかし、V3 では HTML がなくなったためこの方法は使えない。

createElement すればいいかと思っていたが、冒頭の

Chrome拡張 つくりかた 令和最新版

を書いた方が公開している拡張のソースコードを見て、なるほどそんなやり方もあるのかと知ったためそ方法を真似させていただいた。

https://github.com/r7kamura/copy-rich-link/blob/main/src/main.ts#L3-L12

また別の問題として、Service Worker から直接は document などのオブジェクトは触れない。そのため、 Scripting API というものを使う。

// copyToClipboard の実装は↑で紹介したものとほぼ同じ
import { escapeBrackets, copyToClipboard } from "./util";

chrome.commands.onCommand.addListener((command) => {
  const queryInfo = {
    active: true,
    currentWindow: true,
  };

  chrome.tabs.query(queryInfo, function (tabs) {
      // (中略)

      chrome.scripting.executeScript({
        target: { tabId },
        func: copyToClipboard,
        args: [options[key], title, escapeBrackets(url)],
      });
   ...

chrome.scripting.executeScriptAPI ドキュメントはここ:
https://developer.chrome.com/docs/extensions/reference/scripting/#method-executeScript

func に呼び出したい関数を渡すことができる。ただし、

This function will be serialized, and then deserialized for injection.

ということで関数はシリアライズされるので、関数内でまた別の関数を呼ぼうとするとエラーになる。

その他ハマったポイント

ローカル開発中、ポップアップ起動のショートカットキー( _execute_action )の変更がなかなか反映されない、という謎の挙動に遭遇した。

何度か拡張の読み込みを試したり、 Chrome ごと再起動したら反映された。詳しい再現手順はわからず。

参考リンク

大量のユーザーストーリーを素早く見積もる「サイレントグルーピング」という手法を知った

職場の同僚にこういう方法もあると教えてもらった。 その人も最近 Twitter で見かけたとおっしゃっていて、おそらく該当のツイートはこれ。

ここで言及されている記事をざっと読んで手法を学んだのでメモ。

記事

https://kenpower.ie/2011/05/22/using-silent-grouping-to-size-user-stories

記事自体は古く、2011年のもの。 XP2011 というカンファレンスで発表されたもののよう。

(2022-06-15 追記)
リンク切れになってしまった...

イントロ

  • ユーザーストーリーを見積もるためのよく知られたテクニックとして「プランニングポーカー」があるが、時間がかかり、不必要な議論に終始する可能性がある
  • これに対し「サイレントグルーピング」という手法はプランニングポーカーを補完するために使用でき、大規模なユーザーストーリーのセットを数分で見積もることができる

サイレントグルーピングのやり方

サイレントグルーピングは以下の4つのパートで構成される。

  1. Preparation
  2. Round 1: Individual Placement
  3. Round 2: Group Placement
  4. Discussion and Reflection

以下、順に見ていく。
前提として、見積もり対象のユーザーストーリーは事前に用意されているものとする。

1. Preparation

  • 下の図のようなボードを用意する。縦線で区切られ、各列には見積もりに使うストーリーポイントが振られている。

画像は記事より引用

  • また、グルーピングできないユーザーストーリーが出てきたときのためにパーキングロットを用意するのも良いアイディア
  • 中規模サイズのユーザーストーリーを1つ決める。これは、最初のいくつかのユーザーストーリーが比較されるベンチマークとなる

2. Round 1: Individual Placement

  • チームメンバーは一人ずつ順番に前に出て、1つのユーザーストーリーをボードに配置する
  • このラウンドの目標は、すべてのユーザーストーリーをボード上に配置することであり、したがって、すべてのユーザーストーリーの初期サイズの見積もりを得ることである
  • このパートを実行する一番簡単な方法は、単にメンバーが列を作って順にユーザーストーリーをボードに配置する方法だと思う

3. Round 2: Group Placement

  • チームメンバーは順番に前に出て、一度に1つのユーザーストーリーを動かす
  • このラウンドの目標は、すべてのユーザーストーリーのサイズについて、チームのコンセンサスを得ることである
  • このラウンドでは、競合が発生することがある。例えば、あるチームメンバーがユーザーストーリーを、例えば5から8に移動させ、別のチームメンバーが5に戻すというように、ユーザーストーリーが何度も移動させられることがある。この観察と意識的考察の行為で意見の相違が解消され、口頭で議論することなくサイズに合意することが非常に多い
  • ユーザーストーリーが2つ(またはそれ以上)のカラムをいったりきたりして、誰も妥協しない場合は、このユーザーストーリーについてもっと議論が必要なサイン。この場合ファシリテーターはこのユーザーストーリーをボードから取り除き、パーキングロットに置く

4. Discussion and Reflection

画像は記事より引用

  • ファシリテーターはチームに、それぞれのサイズに対する自信度を短い時間で議論させる。Fist to Fiveは良いテクニックだろう

サイレントグルーピングの利点("Summary of Benefits"より)

  • むちゃくちゃ早い
  • スケーラブル。著者はこの手法を最大8つのスクラムチーム(55人)と並行して使い、677のユーザーストーリーを28分でサイズアップしたことがある
  • チーム内の不明な点や意見の相違がすぐに浮き彫りになる
  • インクルーシブ。誰もがすべてのユーザーストーリーについてコメントする機会を得ることができる
  • プランニングポーカーを補完するものとしてうまく機能する。最初のベンチマークとなるストーリーはプランニングポーカーで見積もるとか、サイレントグルーピングでコンセンサスが得られなかったものだけプランニングポーカーでサイズを決定するとか
  • 話すことを明確に禁止しているので、内向的なチームメンバーが影響力を発揮しやすくなっている
  • グローバルなチームなどで言語の壁がある場合も有効

感想

👍良いと思った点:
これが有効な場面はあるなと感じた。たとえば過去の経験上も、四半期ごと・半年ごとなどの長めのスパンで粒度の荒い新機能(エピックと呼ぶことが多い)複数をざっと見積もる、というのをやったことがあるので、そういった場面であんまり時間かけずに見積もり出すのには機能しそう。

利点にも書いたとおりプランニングポーカーと比較してどちらかだけを使う、というよりは相互補完的なものなので、選択肢と持っておくのは良い。

あと、議論ベースの見積もりより平等なところも良さそう。性格的に内向的だったり入社して日が浅いメンバーであっても他の人と同じようにボードの前で意思表示しないといけないので、意見の偏りがない。

🤔気になった点:
「そんなうまくいくかぁ〜?」とは正直思った。発言禁止ってことはお互いが持ってる情報を出し合って認識を合わせていくみたいなことが基本できないので、ストーリーが列を行き来しながら徐々に特定の見積もりに収束するということが本当に起きるのか、やや懐疑的ではある。

余談

「エッセンシャルスクラム」でもちょこっとだけ紹介されてた。こちらはスプリントレトロスペクティブの文脈。

書籍「エッセンシャルスクラム」より引用

2022年1〜3月のふりかえり

最初の四半期のふりかえりを完全に忘れていた。書く。

前回: 2021年のふりかえりと2022年にやりたいこと - dackdive's blog

✨ やったこと

CRE(Customer Reliability Engineering)について調べている

現職で CRE という役割もやらせてもらえることになり、今期の特に前半は CRE についての情報を集めたり責務について考えたりすることが多かった。

現職での CRE としての取り組みは弊社のカスタマーサクセスチームが note にめちゃくちゃ詳しくまとめてくれている。

また、CRE 系のイベントにも参加した。

「各社CREチームのサポート体制と独自の取り組みについて【はてな|freee|アンドパッド】」参加メモ - dackdive's blog

ここはまた別途記事にまとめたい。

フロントエンド: テストについて調べていた

フロントエンドに関してはテスト周りを勉強していた。今まで Testing Trophy も曖昧な理解だったし、どこに対してどういう目的のテストを書くと良いかもう少し言語化できるようにしたかったからだ。
そのため Storybook の UI Testing Handbook というドキュメントを読んだり、最近は Storybook の Interactive Stories を調べたりしていた。

今のところまだ Integration Test をどう定義するか、それを E2E テストとどう棲み分けるか、といったあたりが理解不十分なので、来期も引き続き勉強したい。

UoPeople: MATH1280 Introduction to Statistics

今 term は統計学をやった。内容や感想は以前書いた。

📝ブログ・登壇

なんと、今期は2回も社外イベント登壇の機会があった。

そこそこ反応あったし普通に勉強になったので登壇駆動としては大成功。

こちらも自社のフロントエンドについて整理したりこっちからお悩み相談させてもらったりと非常に有意義なイベントとなった。

ブログ書いたものは以下。

💪次の四半期(2022年4〜6月)のテーマ

なにか月に1つぐらい目標決めたいなーと思いつつ4月も半ばなので2つぐらい。
また、次の term は UoPeople の授業受けないことに決めたので今期よりは時間がある。ので技術系の本読むとか Rust 勉強するとかやりたい。

  • 簿記 3 級取る
  • HTML 解体新書 読む
  • Rust 勉強する(なにか具体的な目標がほしいところ)

あたりかな。

💸 買ったもの

良いコーヒーミルが欲しくなって買った。
一時は 1Zpresso だの コマンダンテ だのといった高級品に手を出しかけたが、ネットでコスパ良いと評判だったこいつにした。

以前より力入れずに挽ける感じがするし粉の粒度も揃ってる気がする。気に入って使ってる。