Formulário de feedback para um site ou app

Formulário de feedback para um site ou app6 min de leitura

Adicione um formulário de relato de bug que captura a página, o navegador e os passos

Um formulário de relato de bug que pede às pessoas os passos e a gravidade, e deixa a página adicionar sozinha a URL, o navegador, o viewport e os erros recentes.

Preet SutharFundador, Ikiform

Peça a quem relata só o que essa pessoa sabe: o que deu errado, o que ela fez antes e o quanto isso a bloqueia. Deixe a página preencher o resto no momento do envio: a URL da página, o user agent do navegador, o viewport e os últimos erros de JavaScript. Envie a um backend de formulários e encaminhe cada relato para o seu issue tracker por webhook.

O que muda num relato de bug

Um relato de bug só é útil se você consegue reproduzi-lo, e quem relata raramente sabe os detalhes de que você precisa. Pergunte qual navegador a pessoa usa e você ouve "Chrome" ou "o normal". Peça a URL e você recebe o nome da página. Então divida o formulário em dois: perguntas que só quem relata sabe responder, e contexto que a página já conhece.

Quais partes de um relato de bug a pessoa digita e quais a página adicionaquem relata digitaa página adiciona no envioresumouma linha, 8 a140 caracterespassoso que fez logoantesgravidadebloqueante,grave ou levelink dacapturaopcional, umaURL, nãoarquivopage_urlorigem ecaminho, semqueryuser_agentnavegador e SOviewporttamanho edensidade depixelsrecent_errorsúltimos cinco,sem hostsQuais partes de um relato de bug a pessoa digita e quais a página adicionaquem relata digitaa página adiciona no envioresumouma linha, 8 a 140 caracterespassoso que fez logo antesgravidadebloqueante, grave ou levelink da capturaopcional, uma URL, não arquivopage_urlorigem e caminho, sem queryuser_agentnavegador e SOviewporttamanho e densidade de pixelsrecent_errorsúltimos cinco, sem hosts
  1. Quem relata escreve um resumo de uma linha.
  2. Descreve o que fez logo antes de quebrar.
  3. Escolhe o quanto isso a bloqueia.
  4. Pode colar um link para uma captura de tela ou gravação.
  5. No envio, a página adiciona o próprio endereço sem a query string.
  6. Adiciona o user agent do navegador.
  7. Adiciona o tamanho da janela e a densidade de pixels.
  8. Adiciona os últimos erros de JavaScript que viu, sem os hostnames.
A linha de cima é tudo o que quem relata vê. A linha de baixo são inputs ocultos, preenchidos no momento em que a pessoa clica em Enviar.

Três restrições moldam o código:

  • O referrer não é a página. Por padrão, os navegadores mandam só a sua origem num envio entre sites, então meta.referrer não vai dizer o caminho. Envie page_url você mesmo.
  • Um envio pelo servidor esconde o navegador. O Ikiform registra o user agent de quem faz a requisição. A partir de uma server action, esse é o seu servidor. Encaminhe o header user-agent do visitante como um campo.
  • Stack traces são cheios de links. Cada frame tem a URL de um script, e três ou mais links somam 2 à pontuação de spam. Remova os hosts antes de enviar os erros, ou um relato com um link de captura colado pode cair em spam.

Os campos

Prévia

Roda o validador do Ikiform no seu navegador. Nada é enviado.

O quanto isso bloqueia você?
Os últimos cinco campos são inputs ocultos no formulário real. Com eles definidos, o Ikiform confere o tamanho e o tipo deles como de qualquer outro campo.

A gravidade é formulada como o efeito sobre quem relata, não como um rótulo de prioridade. As pessoas escolhem "Não consigo continuar" com precisão; escolhem "Crítico" para tudo. Os passos têm um mínimo de 20 caracteres, o que transforma "está quebrado" numa frase útil.

Capture o contexto

O navegador preenche os campos ocultos no handler de submit, depois que a pessoa terminou de digitar, então a lista de erros inclui qualquer coisa que deu errado enquanto ela escrevia o relato.

O formulário envia do jeito tradicional; o script só preenche os inputs ocultos durante o submit, antes de o navegador montar a requisição. Defina data-app-version no elemento <html> no momento do build. O navegador envia direto, então o meta.user_agent do próprio Ikiform bate com o campo 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>
As versões no servidor enviam com uma chave secreta, então o Ikiform pula Allowed domains e a verificação humana. Todo envio passa a vir do IP do seu servidor, e o limite Standard de 5 por minuto por visitante conta todos os seus visitantes juntos. Escolha o limite de taxa Relaxed ou Custom para um formulário que você envia por um servidor, e mantenha IKIFORM_SECRET_KEY em variáveis de ambiente só do servidor.

A mensagem de sucesso mostra o id do envio. Quando quem relatou escrever ao suporte depois, esse id encontra o relato numa única busca. O modelo de relato de bug tem uma versão mais curta, sem o contexto capturado.

Para o seu issue tracker

Os relatos devem ficar onde a correção será acompanhada. Adicione um webhook apontando para um relay que cria uma issue: summary como título, os passos e o contexto como corpo, severity como label. O guia de Linear e GitHub Issues tem esse relay, com verificação de assinatura e novas tentativas. A documentação de webhooks lista todos os valores de meta que o payload traz. Coloque os campos capturados num bloco recolhido no fim do corpo da issue para que os passos continuem legíveis.

CampoNa issue
summaryTítulo
steps, expectedCorpo, primeiro
severityUm label, para filtrar os bloqueantes
page_url, user_agent, viewport, app_versionUm bloco details abaixo dos passos
recent_errorsUm bloco de código, por último

Confira se funciona

  1. Abra a página num navegador desktop, rode undefinedFunction() no console e depois envie um relato.
  2. Abra o envio no painel. recent_errors contém o ReferenceError sem nenhum host https://.
  3. user_agent mostra o seu navegador, não node, também nas versões de servidor.
  4. page_url tem o caminho, mas não a query string.
  5. Envie um relato com steps de 10 caracteres. O Ikiform responde 422 apontando steps, e o formulário mostra a mensagem.

Pegadinhas

  • Upload de arquivos ainda não é suportado. Um envio com arquivo recebe 422 files_not_supported. Peça um link para uma captura de tela, ou faça upload da imagem no seu próprio armazenamento antes e envie a URL dela.
  • Cada valor pode ter até 16 KB. Cinco erros cortados em 400 caracteres ficam bem abaixo disso; um dump completo do console não ficaria.
  • Navegadores Chromium congelam partes da string de user agent, como a versão do macOS, que sempre aparece como 10_15_7. Na triagem, use o nome e a versão principal do navegador, não a versão do sistema operacional.
  • Erros de scripts de outra origem mostram só Script error., a menos que esse script seja servido com headers CORS e carregado com crossorigin.
  • Não capture o conteúdo de formulários, o local storage nem cookies no relato. Eles podem conter senhas e tokens, e você os armazenaria em texto puro.

Solução de problemas

ProblemaO que fazer
422 validation_failedO envio não corresponde à definição de campos do formulário. error.fields aponta cada campo e o motivo. Faça o name do input bater com o id do campo.
422 files_not_supportedO formulário tem um <input type="file"> com um arquivo escolhido. Upload de arquivos ainda não é suportado; remova o input ou peça um link.
Relatos com links de captura caem em spamConte os links em todos os campos. Três ou mais somam à pontuação; com uma repetição do mesmo relato, ela atinge o limite. Remova os hosts dos erros e marque o relato como não spam.
403 origin_not_allowed, ou todo envio cai no spamO domínio da página não está em Allowed domains do formulário. Adicione-o, mais localhost para testes locais. Subdomínios de um domínio listado são permitidos.

Perguntas

Travou em algum passo ou quer um guia para o seu caso? Envie um e-mail para hello@ikiform.com.