feat: ingestão de contribuições do GitHub e retrospectiva da comunidade#304
Conversation
Substitui a retrospectiva provisória (comando que rodava `gh` na mão e subia um JSON para um link temporário) por uma ingestão persistente que respeita a arquitetura modular do projeto. - integration-github: modelo próprio de contribuições (desacoplado de Interaction/gamification), lake bruto de eventos espelhando o do Discord, backfill REST resiliente e ciente de rate limit (helper RateLimit compartilhado), e webhook ao vivo com verificação de assinatura HMAC. - panel-admin: allowlist de repositórios gerenciável + ação "Backfill agora" com notificações amigáveis (rate limit / falha) sem marcar sucesso falso. - portal: página pública de retrospectiva por período (Livewire), contando todos os contribuidores, excluindo bots e distinguindo PRs mergeados dos fechados sem merge. A gamificação fica como seam (evento GithubContributionRecorded), sem dependência direta entre os módulos.
O painel admin é multi-tenant, então tratar a allowlist e as contribuições
como globais quebrava ao paginar (o model não tinha relação `tenant`). Agora
cada comunidade tem o seu recorte, consistente com o resto do sistema.
- github_repositories e github_contributions carregam tenant_id; as uniques
passam a (tenant_id, full_name) e (tenant_id, repo, type, external_ref).
- ProjectGithubEvent faz fan-out: uma entrega do webhook vira uma contribuição
por tenant que acompanha o repo; o lake github_event_logs segue global.
- BackfillRepository::execute recebe o GithubRepository e carimba o tenant_id
do repo de origem.
- A retrospectiva do portal filtra por tenant, resolvido por slug de rota
(/comunidade/{tenant}/retrospectiva) com default em config('he4rt.main_tenant').
- GithubRepositoryResource volta a ser tenant-scoped; a validação de unicidade
do form é escopada ao tenant atual.
Contribuição de repo compartilhado é duplicada por tenant — trade-off aceito em
favor do isolamento total entre comunidades.
dda959b to
fb4ee60
Compare
🔐 Configuração de produção — token, scopes e ENVsPra ligar a ingestão do GitHub em produção precisa de 2 segredos (+1 opcional) e registrar o webhook na org. Sem isso, o backfill pelo painel ainda funciona (autenticado pelo token); só o tempo real (webhook) fica desligado. 1.
|
Reviews PENDING (rascunho não enviado) não têm submitted_at — a API do GitHub lista o rascunho do próprio usuário do token. Gravar occurred_at = "" estourava um QueryException (invalid input syntax for type timestamp) e abortava o backfill. Agora o backfill e o webhook pulam esses reviews. Bug latente, exposto por dados reais.
O backfill faz centenas de requests REST e estourava o timeout no request HTTP do Livewire (cURL error 28). Agora roda em segundo plano. - BackfillGithubRepository (ShouldQueue, ShouldBeUniqueUntilProcessing): executa o backfill no worker; resumível por idempotência; em rate limit faz release() até o reset (não conta como falha); retryUntil 6h + maxExceptions 3 + timeout 600s. - painel "Backfill agora" agora ENFILEIRA o job e volta na hora (notifica "enfileirado"). - RateLimit::secondsUntilReset() para re-agendar o job até o reset. - comando github:backfill segue síncrono (CLI não tem timeout). Requer fila assíncrona + worker (prod=redis). Testes: job (release/stamp) + painel (dispatch).
…CLI) - BackfillRepository::execute ganha um callback opcional de progresso, chamado por contribuição gravada com o ContributionType (seam agnóstico de UI). Job e painel passam null → comportamento idêntico. - github:backfill refinado com Laravel Prompts (intro/table/outro/warning) + barra de progresso ao vivo por repo, com contador por tipo (PRs · reviews · issues · …). - testes: callback chamado por contribuição (na ordem); comando segue cobrindo rate-limit (FAILURE + sem stamp).
Banco zerado / nenhuma contribuição ingerida mostrava 3 slides com tudo "0". Agora, quando não há contribuição alguma para o tenant (count(repoOptions) === 0), o deck renderiza um único slide: coração + "Métricas indisponíveis" + CTA pra reunião semanal (discord.gg/he4rt). O deck entra em modo `bare` (sem progress, FAB de filtros nem navbar). Gatilho é "sem dado nenhum" (não a janela), então uma semana parada com histórico segue caindo no deck normal com filtros.
Por padrão o backfill agora é incremental: com last_backfilled_at preenchido, só busca o que mudou desde (last_backfilled_at − 1 dia). Sem ele (1ª run) ou com --full, varre o histórico inteiro. Mata a varredura completa toda vez. - /issues, /issues/comments, /pulls/comments, /commits → query `since` (server-side) - /pulls (a API não tem `since`) → sort=updated&direction=desc + early-stop no paginate quando updated_at < corte; só os PRs recentes fazem GetPullRequest+reviews - execute(repository, onProgress, full=false); comando ganha --full; painel/job ficam incrementais por padrão (1º backfill é completo, cliques seguintes rápidos) - params `since` validados na doc oficial do GitHub via context7
danielhe4rt
left a comment
There was a problem hiding this comment.
Falta uns toques de UX mas por agora tá ótimo!
|
LGTM |
…s no backfill O contador "X ingeridas" contava todo updateOrCreate (re-upserts inclusos), então re-rodar o backfill reportava as mesmas ~81 "contribuições" mesmo sem nada novo. Agora record() propaga wasRecentlyCreated e o comando separa "novas" de "atualizadas" — re-run mostra "0 novas · N atualizadas". Feedback ao vivo: cada item ingerido vira uma linha (Item · Autor · Quando · Status) rolando acima de uma barra fixa, via ConsoleSectionOutput. describe() resolve o alvo (target_ref) pra comentário/review mostrarem "→ PR #312" em vez do id cru. humanTime() dá o tempo relativo ("há 3 horas") pra noção de quão recente o backfill alcançou.
…ectiva Os cards de contribuidor quebravam quando uma pessoa tinha muito mais contribuições que a outra: o grid 2-col deixava gaps enormes. Reorganiza os dois slides de pessoas: - "O núcleo" (top 5): carrossel horizontal de cards ricos, sem o #1 destacado. Arrasto com o mouse (drag-to-scroll) com inércia, igual no celular; scroll nativo no touch. Setas e fade só aparecem quando há overflow. Sobrescreve o scroll-behavior:smooth global no trilho (deixava o arrasto borrachudo) e desliga seleção de texto / drag-fantasma da imagem. - "E toda a comunidade" (cauda): cards compactos e uniformes (header + barra + ícones com somatória por tipo, sem títulos) empilhados em grid de 3-4 colunas com altura natural — dispensa o carrossel. - Cap nos refs de PR/issue: mostra os 3 mais recentes + "mais X…"; o agregador ordena os refs por occurred_at desc. Testes: ordenação dos refs, cap "mais X…" na view e card compacto (ícones + somatória, sem títulos).
6f1f76e
O que este PR faz
Hoje a comunidade não tem um jeito automático de saber quem está contribuindo nos repositórios públicos. Este PR cria essa base: o sistema passa a guardar as contribuições do GitHub (pull requests, reviews, issues, comentários e commits) e a mostrar tudo numa apresentação de retrospectiva — no espírito do "Quem fez a He4rt bater".
Como funciona, em linguagem simples
1. De onde vêm os dados
2. Quais repositórios contam
3. Quem aparece
A apresentação (deck)
A retrospectiva é uma apresentação em tela cheia, navegável por teclado (← →) ou pelas setas/bolinhas do rodapé, sem o "chrome" do portal. A sequência conta uma história: capa → panorama → núcleo → comunidade → repositórios → destaques → encerramento.
Filtros (aplicados ao vivo)
Um painel de filtros controla o que a apresentação mostra, e tudo fica refletido na URL — dá pra compartilhar um recorte já pronto:
O que muda pra quem usa
Substitui a versão provisória
A primeira tentativa era um comando solto, rodado na mão, que jogava um JSON num link temporário. Isso foi removido e trocado por essa base que vive dentro da arquitetura do projeto, guarda os dados de verdade e se atualiza sozinha.
Para funcionar em produção (configuração)
Precisa configurar duas chaves (
GITHUB_API_TOKENeGITHUB_WEBHOOK_SECRET) e registrar o webhook na organização do GitHub. Sem isso, o histórico pelo painel ainda funciona; só o tempo real fica desligado.