ウェブサイトやアプリのフィードバックフォーム

ウェブサイトやアプリのフィードバックフォーム2 分で読めます

ページ、ブラウザー、手順を記録するバグ報告フォームを追加する

手順と深刻度は本人に尋ね、URL、ブラウザー、ビューポート、直近のエラーはページが自動で追加するバグ報告フォームです。

Preet SutharIkiform 創業者

報告者には本人が知っていることだけを尋ねます。何がおかしかったか、その直前に何をしたか、どれほど作業が止まるかです。残りは送信時にページが埋めます。ページの URL、ブラウザーのユーザーエージェント、ビューポート、直近の JavaScript エラーです。フォームバックエンドに送信し、各報告を Webhook で課題管理ツールに転送します。

バグ報告の違い

バグ報告は再現できて初めて役に立ちますが、報告者が必要な詳細を知っていることはまれです。どのブラウザーかと尋ねれば「Chrome」や「いつものやつ」と返ってきます。URL を尋ねればページの名前が返ってきます。そこでフォームを 2 つに分けます。報告者にしか答えられない質問と、ページがすでに知っている情報です。

バグ報告のうち、本人が入力する部分とページが追加する部分報告者が入力送信時にページが追加要約1 行、8〜140 文字手順直前にしたこと深刻度blocker、major、minorスクリーンショットのリンク任意、ファイルではなくURLpage_urloriginとパス、クエリなしuser_agentブラウザーと OSビューポートサイズとピクセル比recent_errors直近 5 件、ホスト除去バグ報告のうち、本人が入力する部分とページが追加する部分報告者が入力送信時にページが追加要約1 行、8〜140 文字手順直前にしたこと深刻度blocker、major、minorスクリーンショットのリンク任意、ファイルではなく URLpage_urlorigin とパス、クエリなしuser_agentブラウザーと OSビューポートサイズとピクセル比recent_errors直近 5 件、ホスト除去
  1. 報告者が 1 行の要約を書きます。
  2. 壊れる直前に何をしたかを書きます。
  3. どれほど作業が止まるかを選びます。
  4. スクリーンショットや録画へのリンクを貼れます。
  5. 送信時に、ページがクエリ文字列を除いた自分のアドレスを追加します。
  6. ブラウザーのユーザーエージェントを追加します。
  7. ウィンドウサイズとピクセル比を追加します。
  8. 直近に発生した JavaScript エラーを、ホスト名を除いて追加します。
報告者に見えるのは上の段だけです。下の段は hidden 入力欄で、送信を押した瞬間に埋まります。

コードを形づくる制約が 3 つあります。

  • リファラーはページではありません。 ブラウザーはクロスサイトの送信ではデフォルトで origin しか送らないので、meta.referrer からパスはわかりません。page_url を自分で送ってください。
  • サーバーからの送信ではブラウザーが隠れます。 Ikiform はリクエストを送った側のユーザーエージェントを記録します。サーバーアクションからなら、それは自分のサーバーです。訪問者の user-agent ヘッダーをフィールドとして転送してください。
  • スタックトレースはリンクだらけです。 各フレームにスクリプトの URL があり、リンクが 3 つ以上あるとスパムスコアに 2 が加わります。エラーを送る前にホストを除去しないと、スクリーンショットのリンクを貼った報告がスパムに傾くことがあります。

フィールド

プレビュー

Ikiform のバリデーターをブラウザーで実行します。何も送信されません。

どれほど作業が止まりますか?
実際のフォームでは、最後の 5 つのフィールドは hidden 入力欄です。定義しておけば、Ikiform はほかのフィールドと同じように長さと型をチェックします。

深刻度は優先度のラベルではなく、報告者にどう影響するかで表現します。「先に進めない」なら人は正確に選びますが、「Critical」だと何でもそれを選びます。手順は最低 20 文字なので、「壊れてる」が対応できる文章に変わります。

状況を記録する

ブラウザーは、本人が入力し終えたあとの submit ハンドラーで hidden フィールドを埋めます。そのため、報告を書いている間に起きたエラーもエラー一覧に含まれます。

フォームは普通の方法で送信し、スクリプトは submit の間、ブラウザーがリクエストを組み立てる前に hidden 入力欄を埋めるだけです。ビルド時に <html> 要素に data-app-version を設定してください。ブラウザーが直接送信するので、Ikiform 自身の meta.user_agent は user_agent フィールドと一致します。

report-bug.html
<form id="bug" action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
  <label for="summary">What went wrong?</label>
  <input id="summary" name="summary" required minlength="8" maxlength="140" />
  <label for="steps">What did you do right before?</label>
  <textarea id="steps" name="steps" rows="5" required minlength="20"></textarea>
  <label for="expected">What did you expect to happen?</label>
  <textarea id="expected" name="expected" rows="3"></textarea>
  <fieldset>
    <legend>How much does it block you?</legend>
    <label><input type="radio" name="severity" value="blocker" required /> I can't continue</label>
    <label><input type="radio" name="severity" value="major" /> I found a workaround</label>
    <label><input type="radio" name="severity" value="minor" /> It's cosmetic</label>
  </fieldset>
  <label for="screenshot_url">Link to a screenshot or recording</label>
  <input id="screenshot_url" name="screenshot_url" type="url" />
  <label for="email">Email for follow-up questions</label>
  <input id="email" name="email" type="email" />
  <input type="hidden" name="page_url" />
  <input type="hidden" name="user_agent" />
  <input type="hidden" name="viewport" />
  <input type="hidden" name="app_version" />
  <input type="hidden" name="recent_errors" />
  <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
  <button>Send report</button>
</form>

<script>
  const recent = [];
  function remember(text) {
    recent.push(String(text).replace(/https?:\/\/[^/\s]+/g, "").slice(0, 400));
    if (recent.length > 5) recent.shift();
  }
  window.addEventListener("error", (event) => remember(`${event.message} at ${event.filename}:${event.lineno}`));
  window.addEventListener("unhandledrejection", (event) => remember(`Unhandled: ${event.reason?.stack ?? event.reason}`));

  document.getElementById("bug").addEventListener("submit", (event) => {
    const fields = event.currentTarget.elements;
    fields.page_url.value = location.origin + location.pathname;
    fields.user_agent.value = navigator.userAgent;
    fields.viewport.value = `${innerWidth}x${innerHeight} @${devicePixelRatio}x`;
    fields.app_version.value = document.documentElement.dataset.appVersion ?? "";
    fields.recent_errors.value = recent.join("\n---\n");
  });
</script>
サーバー側の版はシークレットキー付きで送信するので、Ikiform は Allowed domains と人間確認をスキップします。その場合はすべての送信がサーバーの IP から来るため、訪問者ごとに 1 分あたり 5 件という Standard の制限が、全訪問者をまとめて数えることになります。サーバー経由で送信するフォームには Relaxed か Custom のレート制限を選び、IKIFORM_SECRET_KEY はサーバー専用の環境変数に置いてください。

成功メッセージには送信 ID を表示します。報告者があとでサポートに連絡してきたとき、その ID で報告が 1 回の検索で見つかります。バグ報告テンプレートには、状況の記録を省いた短い版があります。

課題管理ツールへ

報告は修正を追跡する場所に置くべきです。課題を作成するリレーを指す Webhook を追加します。summary をタイトルに、手順と状況を本文に、severity をラベルにします。署名チェックとリトライを含むそのリレーは Linear と GitHub Issues のガイドにあります。ペイロードに含まれるすべての meta の値は Webhook のドキュメントに一覧があります。手順が読みやすいよう、記録したフィールドは課題本文の最後の折りたたみブロックに入れてください。

フィールド課題では
summaryタイトル
steps, expected本文の先頭
severityラベル。blocker で絞り込める
page_url, user_agent, viewport, app_version手順の下の details ブロック
recent_errors最後にコードブロック

動作を確認する

  1. デスクトップのブラウザーでページを開き、コンソールで undefinedFunction() を実行してから報告を送ります。
  2. ダッシュボードで送信データを開きます。recent_errors に ReferenceError が入っていて、https:// のホストは含まれていません。
  3. サーバー版でも、user_agent には node ではなく自分のブラウザーが表示されます。
  4. page_url にはパスがあり、クエリ文字列はありません。
  5. steps を 10 文字にして報告を送ります。Ikiform は steps を示して 422 を返し、フォームがメッセージを表示します。

ハマりどころ

  • ファイルアップロードにはまだ対応していません。ファイル付きの送信には 422 files_not_supported が返ります。スクリーンショットへのリンクを尋ねるか、先に画像を自分のストレージにアップロードしてその URL を送信してください。
  • 各値は最大 16 KB です。400 文字に切り詰めたエラー 5 件なら十分に収まりますが、コンソールの全出力は収まりません。
  • Chromium 系のブラウザーはユーザーエージェント文字列の一部を固定しています。たとえば macOS のバージョンは常に 10_15_7 です。トリアージには OS のバージョンではなく、ブラウザー名とメジャーバージョンを使ってください。
  • 別の origin のスクリプトからのエラーは、そのスクリプトが CORS ヘッダー付きで配信され crossorigin で読み込まれていない限り、Script error. としか表示されません。
  • フォームの内容、ローカルストレージ、Cookie を報告に含めないでください。パスワードやトークンが入っていることがあり、それを平文で保存することになります。

トラブルシューティング

問題対処法
422 validation_failed送信がフォームのフィールド定義に合っていません。error.fields に各フィールドと理由が示されます。入力欄の name をフィールド ID に合わせてください。
422 files_not_supportedフォームの <input type="file"> でファイルが選択されています。ファイルアップロードにはまだ対応していません。入力欄を削除するか、代わりにリンクを尋ねてください。
スクリーンショットのリンク付きの報告がスパムに入るすべてのフィールドにあるリンクを数えてください。3 つ以上でスコアが加算され、同じ報告の繰り返しが重なるとしきい値に達します。エラーからホストを除去し、その報告をスパムではないとマークしてください。
403 origin_not_allowed が返る、またはすべての送信がスパムになるページのドメインがフォームの Allowed domains にありません。ドメインと、ローカルテスト用の localhost を追加してください。登録したドメインのサブドメインは許可されます。

よくある質問

手順で詰まったときや、あなたの状況に合うガイドがほしいときはメールでどうぞ: hello@ikiform.com