Issue Metrics Action で週に Close した Issue を毎週レポートとして出力する

Issue Metrics GitHub Action を使うと、Issue、Pull Request、Discussion に関するメトリクス(初回レスポンスまでの時間、クローズまでの時間など)を簡単に追跡・監視できる。

github.blog

これを使って週ごと(スプリント)で Close された Issue のメトリクスを出力するワークフローを作成してみる。

Weekly issue metrics

対象とする Issue の絞り込み方は次のドキュメントに記載されている。

github.com

サンプルコードでは peter-evans/create-issue-from-file を使っているが、GitHub CLI で代用できるのでそちらを利用した。

このワークフローを実行すると週ごとに Close された Issue のメトリクスがレポートとして毎週登録される。

GitHub Projects で Issue を管理している場合、特定の Status のすべての Issue を Close するワークフローと併用するのがおすすめ。カンバンを使っているだけで自動でメトリクスがとれるようになる。

dev-moyashi.hatenablog.com

GitHub Projects で Status が "Done" または "Canceled" のすべての Issue を Close する

GitHub Projects で特定の列の Issue を自動で Close するワークフローが欲しくて調べたときのメモ。GitHub CLI でサクッと実現できると読んでいたんだけど API 叩かないと無理そうだった。

github.com

Discussion のコメントを参考に Status が "Done" または "Canceled" の Issue を Close するスクリプトを作成してみる。

Close all "Done" and "Canceled" issues

作ったスクリプトGitHub Actions のワークフローから呼び出す。GitHub Actions から呼び出すには PAT か GitHub Apps で projects(beta) へのアクセス権を付与する必要がある。

docs.github.com

以下は GitHub Apps で projects(beta) へのアクセス権を付与した場合のワークフロー。

Close all issues workflow

Status が "Done" と "Canceled" の Issue が3時間ごとに自動で Close されるようになった。

チームの成長と向上を促すふりかえりの場を作るために意識していること

自分がスクラムマスターとしてふりかえりの場を設計するときに意識していることをまとめました。

1. ふりかえりを実施する目的を共有する

何のためにふりかえるのでしょうか?目的が腹落ちしていないと「ふりかえりの時間って無駄じゃないですか?」「ふりかえりの時間を削ってタスクを進めませんか?」といった提案が出てきます。自分もこのような提案をしていた1人です。

ふりかえりはチームが向上し続けるために役立つアクティビティですが、やらされ感が強いと期待した効果は得られません。ふりかえり以外のすべてのアクティビティに対しても言えることですが、何のために実施するのか目的を共有して、メンバーに納得してもらった上で参加してもらうと効果的な場を作ることができます。

2. ふりかえりの基本構成と時間を決める

何もない真っ白な状態からふりかえりを実施するのは初心者にとって難易度が高いです。事前にフォーマットを準備しましょう。自分は反復的ライフサイクルにおけるレトロスペクティブのステップの流れを意識して場を設計しています。

反復的ライフサイクルにおけるレトロスペクティブのステップ
反復的ライフサイクルにおけるレトロスペクティブのステップ

引用: アジャイルレトロスペクティブズ 強いチームを育てる「ふりかえり」の手引き

自分がチームのふりかえりで使用しているフォーマットはチームメトリクスと感情データを活用した「ふりかえり」の手引きで紹介しています。

世の中にはふりかえりのフォーマットが沢山あるので、それを使うのもいいと思います。

speakerdeck.com

ただし、採用したフォーマットによっては偏った意見しか出てこなくなるので注意が必要です。チームが安定するまでは、なるべく多くの情報が場に集まるフォーマットを採用することをおすすめします。

採用したフォーマットをチームにそのまま適用することが難しい場合もあります。そのときは参加者からの意見を取り入れて、チームに合わせたフォーマットを作っていきましょう。提案してもらった意見を取り入れて改善が進むと参加者の主体性が高まり、より積極的に意見を出してくれるようになります。

3. 感情データを拾う

問題発見のきっかけとなる感情データを場に集めるために、意見を出すための心理的なハードルを下げていきます。問題や課題は感情データから見つけることができます。

自分がハードルを下げるために取り組んでいることを3点紹介します。

  • チェックインの実施
    • ふりかえりの初めにメンバーの皆さんからこのスプリントにおける幸福度を教えてもらうなど、簡単に今のメンバーのお気持ちの共有から始める。
  • お気持ちを共有してくれたことに感謝
    • 否定ダメ・絶対。まずは勇気を出して共有してくれたことに感謝。
    • 「問題を見つけるきっかけは、あなたの感情共有から始まる」「メンバーの感情の共有はチームの成長のために役に立つ」ことを伝える。
  • 投稿してくれた内容へのフィードバック
    • コミュニケーションの本質はリアクション。Twitter のいいね!や、Slack のスタンプを返すぐらいの気軽さでいいので何らかのフィードバックをする。
    • 反応がない = 自分の意見が受け入れられていないと感じてしまうとますます意見が出てこなくなる。特に不安を抱えやすい新規メンバーには積極的にフィードバックする。

4. 事実確認を行う

「何 What」「いつ When」「どこ Where」「誰 Who」の4つの疑問詞を用いて質問をする

発生した問題事象の根本原因を探る分析手法として「なぜ Why」という問い掛けを繰り返す「なぜなぜ分析」がありますが、人間は「なぜ?」と聞かれると言い訳する(=最もらしい理由を作り出す)ようにできているため、使いません。

「どう How」の使用も避けます。「どうでした?」という質問は、それに対してどうとでも答えられるだけに、相手を戸惑わせたり、確信のない答えを強要してしまう可能性があります。

この4つの疑問詞のみを用いて事実質問を行う基本技能は、対話型ファシリテーションの手ほどきという本に記載されています。薄い本ですが、今日から使えるファシリテーションの技能が詰まった素晴らしい本なのでぜひ一度読んでみることをおすすめします。

定量データを確認する

ふりかえりで大切なのは感情データと事実データの組み合わせから解決する問題を発見することだと考えています。定量データは事実ベースの共通認識を醸成することに大いに役立ちます。

自分達のチームではカンバンボードのメトリクスや CI/CD ツールから得られる情報などの定量データを収集しています。

5. ネクストアクションを決める

次のスプリントで取り組みたいことを最低1つ決めます。適応やっていきです💪

6. すべての問題を解決しようとしない

ふりかえりの時間は限られているため、1度のふりかえりですべての問題を解決することはできません。もし時間の都合上1つの議題しか取り扱うことができない場合は、チーム活動のボトルネックとなる問題について話し合います。ふりかえりの時間内で結論が出ないときは、その議題について話し合うMTGをふりかえりの時間とは別で設定します。

メンバーに出してもらった意見の中には決めの問題もあります。決めの問題はオーナーシップを持って取り組む人をさっと決めてしまいます。成功したら Happy!失敗してもまたふりかえりで話せばいいので気負いせずに取り組んでもらいます。成長には適度なプレッシャー、失敗も大事な経験です。困ったらチームメンバーが助けてくれるので大丈夫です。積極的に頼っていきましょう。

7. ふりかえりのふりかえりをする

チームの成長と向上を促すために継続的にふりかえりの場を改善します。何を改善すると良いのかチームメンバーに聞いてみるのも1つの手ですが、参加者は議論に集中しているため、より良い場を作っていくことに対して意識が向かないことが多いです。そのため、+αでふりかえりの場をより良くしていくためには、スクラムマスターが参加者の行動を観察し、仮説を立てて検証する必要があると考えています。

useRefを使ってDOMノードを操作している子コンポーネントに親コンポーネントから ref を渡して DOM ノードを取得する

汎用コンポーネントを作っていると、フォーカス制御やキーボード操作のために ref を使っている子コンポーネントに親コンポーネントから ref を渡して DOM ノードを取得したい場面があります。

これは forwardRefuseImperativeHandle を組み合わせて、ref が使われた時に親コンポーネントに渡されるインスタンス値を DOM ノードにすることで実現できます。

import { forwardRef, useImperativeHandle, useRef } from "react";

const Input = forwardRef<HTMLInputElement | null>((_, ref) => {
  const inputRef = useRef<HTMLInputElement>(null);

  useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(
    ref,
    () => inputRef.current
  );

  const onClick = () => {
    inputRef.current?.focus();
  };

  return (
    <div>
      <input type="text" ref={inputRef}></input>
      <button type="button" onClick={onClick}>
        Focus Button
      </button>
    </div>
  );
});

export const App = () => {
  const ref = useRef<HTMLInputElement>(null);

  return <Input ref={ref} />;
};

export default App;

サイボウズを退職しようとして次の転職先を探したらサイボウズになった話

この記事はサイボウズアドベントカレンダー17日目の記事になります。


こんにちは。サイボウズで働いているエンジニアのもやし丸 です。

エンジニアとして働き始めて今年で3年目になります。

入社1年目のときは自分の3年目の姿なんて全く想像できませんでした。どんなエンジニアに成長しているかなー。そもそも仕事続けられているのかなー。と期待と不安が入り交じる感情を抱きながら仕事をしていたことを覚えています。

今はどうでしょう?いざ3年目になってみたら転職を考えていました。

ちなみに1年目の活動はブログに書いてあるので、もしよければ読んでみてください。

dev-moyashi.hatenablog.com

仕事では kintone のフロントエンド刷新プロジェクトに携わっています。プロジェクトの概要はkintoneのフロントエンド刷新に向けた取り組みに記載しています。

blog.cybozu.io


2021年9月から転職活動をはじめました。年始、遅くても2022年の3月には別の会社で働く予定で活動していました。しかし、転職活動の過程で、働く目的や譲れないもの、価値観などを改めて考えた結果、サイボウズで働き続ける選択をしました。

この記事では、転職活動を始めたきっかけからサイボウズで働き続ける選択をするまでの過程を書こうと思います。

転職活動を始めたきっかけ

所属しているチームのあり方が自分に合っていないのではないかと感じていました。

自分が所属しているkintone開発チームはメンバーが全体で60人以上いるチームです。職能別にチームが別れていて、その中に更に小さいチームがある体制になっています。縦割りはなく、必要に応じて小さいチーム同士が連携しながら仕事をしています。

僕は大きくなり続けるアプリケーションのすべてを理解することに限界を感じていました。自分が認知できる範囲以上のものを扱っていると日々感じていました。アプリケーションの全体を見ることができていないので、何か発言するときも、偏った意見になっていないか、考慮不足なのではないか、と自分自身の意見に自信を持つことができず、常に不安な気持ちを抱えていました。全員の前で無知を晒して恥をかくことに怯えていました。

僕はチーム全体に関わることを話す心理的ハードルが高いと感じていました。しかし、心理的ハードルの感じやすさは人それぞれです。同じ環境でも主体的にチーム全体に疑問を投げかけ、物事を前に進められる人もいました。だから、僕はいつしかチーム全体に関わることは、全体を俯瞰して見ることができて、みんなをまとめられる人にお任せするようになりました。すると、仕事への向き合い方が悪い方向へと変化しました。チームの関心事を自分事として捉えることができず、チーム全体に関わることについて考えることを放棄するようになりました。自分の仕事のことだけを考えるようになりました。徐々に仕事がつまらなくなっていきました。自分の役割がよくわからなくなり、僕はチームに居ても居なくてもどちらでもいい存在だと考えるようになりました。

このような体験を経て、僕は自分の見える範囲で責任を持って仕事がしたいと考えるようになりました。まだ成熟していないアプリケーションかつ、規模の小さいチームで意思決定して進められる環境だったら、責任感も芽生えて主体性を発揮できるかもしれない、仕事が楽しくなるかもしれない、と考えていました。だから、そのようなチームで、興味のあるプロダクト、技術領域がマッチしているチームを探しました。しかし、残念ながら希望が叶うチームをサイボウズで見つけることはできませんでした。僕は己の未熟さ故にサイボウズで働き続けることができないことを悟りました。

転職するために何をしていたか

Findyで いいね を付けてくれた企業と面談したり、転職ドラフトに登録したり、リファラル採用を受けたりしていました。

転職ドラフトでは現在の年収+50万円を希望額として提示しました。結果、19社からオファーをいただきました。指名していただいた企業の皆様ありがとうございました。

転職ドラフトの結果
転職ドラフトの結果

すべての企業と面談する時間はなかったので、しっかりレジュメを読んでコメントしてくれていると感じた企業に絞って面談させていただきました。

転職活動中の気付き

転職活動中は様々な企業の方と面談させていただいて、自分を見つめ直すきっかけになりました。面接、面談では「チームワーク向上に繋がる製品(チームメンバーの信頼関係の構築を支援するソフトウェア)を作っている企業で、エンジニアとして良いチームを作りながら開発したい」と話していました。では、なぜサイボウズでそのようなチームが実現できないのか?説明する必要があるので理由を考えていたら、誰かが環境を整えてくれることを待っている自分に気が付きました。

理想のチームは与えられるものではなく、自分たちでつくるものですよね。転職活動での気付きを経て、今の開発チームを一人ひとりがオーナーシップを持って開発できるチームに変えられないか考えるようになりました。上手くいかなかったら転職すればいいという気持ちが背中を押してくれました。

一人ひとりがオーナーシップを持って開発できるチームを目指して

メンバー全員が主体性を発揮できるチームにしたいと思っていました。一人ひとりがオーナーシップを持って、小さいチームで意思決定をして物事を進められるようにするには、小さいチームが見る範囲を絞る必要があると考えていました。各々の小さいチームが常にアプリケーション全体を見なくても共通の目標に向かうことができる。小さいチームの担当範囲が明確である。小さいチームの意思決定が他の小さいチームに影響しない組織構造を求めていました。

まずはチームの理想の形を考えていたマネージャーに今のチームの形を変えられないか相談しました。新しいチーム体制への案はあったものの、実現の目処が立っていなかったので、「今すぐ実現しませんか?」と提案してみることにしました。他のメンバーも現状のチーム体制についてどのように感じているのか聞いてみたかったので、話しかけやすいメンバーとざつだんする時間も作りました。結果、新しいチーム体制に関心があるメンバーを集めて議論を重ねることになり、フロントエンド刷新プロジェクトチームから新体制へと移行する目処を立てることができました。

一方で、チームの担当範囲を明確にして、チームの規模を小さくすると本当にメンバーが主体性を発揮できるようになるのか確信が持てていませんでした。主体性を持てるかは人によると思いますが、まずは自分が主体性を持てる人間なのかを明らかにしたい気持ちが強くなっていました。そこで、少人数で取り組むことができて、影響範囲が小さい仕事を探してみることにしました。見つけたのは kintone のリッチテキストエディタの開発です。当時 kintone 本体とは別リポジトリで開発が進んでいたため、開発、提供、運用を見据えて独立して動くことができると判断しました。開発にはQAエンジニア をお招きして、リッチテキストエディタのテスト自動化を目的に、QAエンジニア が管理している手動テストの試験一覧を一緒に眺めて自動テスト用の試験に置き換えたり、QAエンジニア も自動テストを書けるようにテストツールの調査、整備を行いました。最終的にはリッチテキストエディタの品質の責任を Webエンジニア、QAエンジニア両方が持てることを目指しました。

これらの活動は 12/11 に開催された Developers Boost にて発表させていただきました。

speakerdeck.com

サイボウズで働き続ける選択をした理由

現在、自分の所属しているフロントエンド刷新プロジェクトチームを6~8名規模の機能横断型チームに分割して活動を始めています。

チームの体制が変わったことで,当時感じていた不安がなくなり,転職したい気持ちもなくなりました。自分たちでチャレンジして変化を起こせる環境がサイボウズにあることを改めて実感することができました。

1からチームを作るのは大変なことも多いですが、挑戦してみたかったことでもあるのでモチベーション高く取り組むことができています。チームメンバー1人ひとりが自分の専門分野で専門知識を使うことに自信と誇りと責任を持ち、チームメンバー全員で問題解決・意思決定を行える状態を目指しています。

そんな感じで、サイボウズを退職しようとして次の転職先を探していたらサイボウズになりました。この選択でよかったのか、たまに悩むことはありますが、悩みを吹き飛ばすぐらい良いチームをつくっていきたいと思います。

これからもよろしくお願いします。

.execCommand(insertText...) .execCommand(insertHTML...) の代替実装を考える

JavaScript でカーソルがある位置に文字列を挿入したいときは execCommand を使用すると簡単に実現することができました。

developer.mozilla.org

しかし、execCommand はブラウザ間での不具合も多く、現在は非推奨のAPIになっています。

今回は非推奨になった execCommand を使用せずに、JavaScript でカーソルがある位置に文字列を挿入する実装を考えてみます。

文字が入力できる要素

ユーザーが文字を入力できる要素には textarea, input などがあります。標準で文字入力できない要素でも、contenteditable 属性を true にすると要素内の文字を編集できるようになります。

execCommand のinsertText,insertHTMLを使った場合は、対象の要素を意識することなく文字列を挿入することができていましたが、DOM操作で execCommand と同じ挙動を実現するには、対象の要素ごとに分岐を入れる必要があります。そのため、textarea, input と contenteditable それぞれの実装方法を考えます。

textarea, input 要素の場合

const text = '入力したい文字列';
const target = document.querySelector('textarea');
const selectionStart = target.selectionStart;
const selectionEnd = target.selectionEnd;
const startText = target.value.slice(0, selectionStart);
const endText = target.value.slice(selectionEnd);
target.value = startText + text + endText;
// 挿入文字列の末尾にカーソルを移動させる
target.selectionEnd = selectionStart + text.length;

テキストの選択開始の位置をselectionStart、選択終了の位置をselectionEndで取得します。

テキストの選択範囲より前の文字列をstartText変数、後の文字列をendText変数に保持して、最後に挿入したい文字列と組み合わせて入力欄を上書きします。選択していない場合はselectionStartselectionEndが同じ値を返します。

contenteditable の場合

const text = '入力したい文字列';
const selection = window.getSelection();
const range = selection.getRangeAt(0);
range.deleteContents();
const node = document.createTextNode(text);
const range.insertNode(node);
// 挿入文字列の末尾にカーソルを移動させる
selection.collapseToEnd();

div や li などの非インタラクティブ要素に contenteditable 属性が付与されているときは textarea や input のようにselectionStartでカーソル位置を取得することができません。この場合は Selectionオブジェクト を使用して、選択範囲やカーソル位置を取得します。

HTML を挿入したい場合は、DOM を生成して innerHTML で書き換えた node を insertNode の引数に渡してあげます。

const html = '入力したいHTML文字列';
const selection = window.getSelection();
const range = selection.getRangeAt(0);
range.deleteContents();

const node = document.createElement('div');
node.style.whiteSpace = 'pre';
node.innerHTML = html;
range.insertNode(node);

// 挿入文字列の末尾にカーソルを移動させる
selection.collapseToEnd();

参考

debounce と throttle の React Custom Hooks を実装してみる

throttle や debounce は連続して大量に繰り返される処理を間引いて負荷を軽減させたりするときに使います。

今回はそれらの間引き処理を React Hooks で実装してみました。

react-use に useDebounce と useThrottle が実装されているので自前で実装する必要性を感じない方はそちらを使用すると良いと思います。 github.com

throttle, debounce とは?

throttle
  • 連続して大量に繰り返される処理を一定感覚で間引く。
  • scroll イベントでよく使用される。
debounce
  • 連続して大量に繰り返される処理が指定時間内に何度発生しても最後の1回だけ実行する。
  • resize イベント、インクリメンタルサーチなどで使用される。

実装してみる

codesandbox.io

間引く間隔はデフォルト1秒に設定しています。

throttle のテキストボックスに文字を入力してみると、一定間隔でイベントが実行されるのが分かります。

一方 debounce は文字入力中はイベントが発火せず、最後のイベントのみ実行されています。