Skip to content

fix: modal de Importar Colunas, erros silenciosos no upload e reorganização de admin_data_tools - #1090

Merged
Winzen merged 3 commits into
mainfrom
fix/upload-columns-modal-labels
Sep 17, 2026
Merged

Winzen merged 3 commits into
mainfrom
fix/upload-columns-modal-labels

Conversation

@Winzen

@Winzen Winzen commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Corrige modal de Importar Colunas, erros silenciosos no upload e reorganiza admin_data_tools

Resolve: #1089

Contexto

O botão "Importar Colunas" na página de admin de uma Table abre uma modal que, para alguns usuários, carregava com os labels dos campos em branco. Investigando isso, apareceram mais dois problemas na mesma view (upload_columns): erros viravam uma página HTML de traceback em vez de JSON (quebrando o response.json() do front) e uma falha no meio do processamento apagava as colunas existentes da tabela sem recriar nenhuma.

Esse PR resolve os três problemas e aproveita pra migrar o endpoint pra admin_data_tools, junto dos outros botões da mesma página (Checar Metadados, Sincronizar com o BigQuery), e dividir o admin_data_tools/views.py (que já estava com 690 linhas) por domínio.

Diagnóstico

Sintoma Causa raiz
Labels em branco pra alguns usuários </div> sobrando em change_form.html, fechando uma <div> da página base do admin/Jazzmin e quebrando a árvore DOM do resto da página — navegadores recuperam de HTML malformado de formas diferentes, o que explica variar por usuário/navegador
SyntaxError: JSON.parse: unexpected character... no navegador upload_columns não tinha tratamento de exceção — qualquer erro virava uma página HTML de traceback (500)
Colunas da tabela desaparecendo depois de uma falha na importação selected_table.columns.all().delete() rodava antes de processar a planilha, sem transação
Coluna salva com nome inválido pro BigQuery sem nenhum aviso Nenhuma validação de identificador — um espaço ou caractere inválido só quebraria bem mais tarde, na sincronização com o BigQuery

Erro real reproduzido durante o desenvolvimento, usado como caso de teste: a planilha de arquitetura tinha directory_column com o nome da coluna duplicado (br_bd_diretorios_mundo.pais.sigla_pais_iso3:sigla_pais_iso3 em vez de br_bd_diretorios_mundo.pais:sigla_pais_iso3) — o formato esperado é dataset.tabela:coluna.

O que mudou

change_form.html / ferramentas.js / main.css — modal

Mudança Detalhe
</div> sobrando removido Corrige a estrutura HTML do bloco dadosModal
class duplicado no botão "Processar" removido Tinha dois atributos class no mesmo elemento
Botão de fechar ganhou id único (dadosCloseButton) Antes usava document.querySelector('.close-button'), que dependia da ordem no DOM (a mesma classe existe nos outros dois modais)
Área de resultado inline (#dadosResultado) Troca o alert() por uma renderização no mesmo estilo já usado pelo "Checar Metadados" (badge + nome da coluna em <code> + detalhe)
Modal alargada (modal-content--wide) Comporta mensagens de erro mais longas
overflow-wrap: break-word em .metadados-sucesso/.metadados-erro-geral Uma mensagem longa sem espaços (ex. um directory_column extenso) estourava a largura fixa da modal

upload_columns — tratamento de erro e proteção de dados

  • Todas as respostas passam a ser sempre JSON (nunca deixa vazar traceback HTML).
  • selected_table.columns.all().delete() e a recriação das colunas agora rodam dentro de uma transaction.atomic() — se qualquer linha da planilha falhar, nada é perdido.
  • A planilha é lida antes de mexer nas colunas existentes (um link inválido nunca mais chega a apagar nada).
  • Nova validação de identificador (_validar_identificador_bq) aplicada ao nome da coluna e a cada pedaço do directory_column (dataset, tabela, coluna) — rejeita espaço, ponto a mais ou qualquer caractere fora de [A-Za-z0-9_], com mensagem apontando exatamente qual pedaço e por quê.
  • Mensagens de erro reescritas pra apontar o problema estrutural (ex. "deveria referenciar a tabela como 'dataset.tabela' (um único ponto)") em vez de só dizer "não encontrada".
  • Erro passa a vir estruturado ({"status": "erro", "erro": "...", "coluna": "..."}), permitindo a UI destacar qual coluna da planilha causou o problema.

Migração pra admin_data_tools + reorganização

upload_columns (função solta em api/v1/views.py, rota /upload_columns/) virou UploadColumnsView (classe, rota /admin-tools/upload-columns/), no mesmo padrão das outras views da página de admin de Table. Aproveitado pra trocar o desempacotamento posicional de request.POST.values() (frágil, dependia da ordem exata dos campos do form) por acesso nomeado.

admin_data_tools/views.py tinha 690 linhas misturando 3 assuntos sem relação direta. Dividido em arquivos irmãos, no mesmo padrão já usado por _prefect3_client.py:

Arquivo Linhas Conteúdo
flow_monitoring.py 299 SyncDeploymentsView, FlowFailedWebhookView e helpers de monitoramento/auto-disable de flow no Prefect
bigquery_sync.py 190 CheckMetadadosView, SyncUpdateLatestView e helpers de comparação/sincronização de Table contra o BigQuery
column_import.py 217 UploadColumnsView, ColumnImportError e helpers de leitura/validação da planilha de arquitetura

urls.py passa a importar direto de cada módulo (era o único arquivo que importava de .views). Nenhuma lógica mudou nessa etapa, só o arquivo onde vive.

Testes

Sem suíte de testes automatizados rodável neste ambiente (sem infra de banco/Django test configurada) — validação feita com o ambiente local de verdade (docker compose, banco real, servidor Django rodando) via curl/manage.py shell, além de ruff check/ruff format/py_compile/manage.py check em todos os arquivos tocados.

Cenário Resultado
Nome de coluna com espaço ('country id') Bloqueado antes de gravar, mensagem aponta o campo exato
directory_column sem : Bloqueado, mensagem explica o formato esperado
directory_column com segmento extra (o erro real da planilha) Bloqueado, mensagem aponta que deveria ter só um ponto
Tabela de diretório inexistente Bloqueado, mensagem clara
Tipo BigQuery desconhecido Bloqueado, mensagem clara
Importação válida (regressão) Coluna criada e associada corretamente, directory_primary_key resolvido
Qualquer um dos casos acima falhando Colunas existentes da tabela preservadas (transação)
table_id inexistente / link de planilha inválido JSON de erro, nunca HTML
Rota antiga /upload_columns/ 404 (removida)
Rota nova /admin-tools/upload-columns/ Funciona igual, mesmo comportamento
CheckMetadadosView / SyncUpdateLatestView (não alteradas nesse PR, só movidas) Testadas de ponta a ponta contra o BigQuery real após a divisão de arquivos — sem regressão
SyncDeploymentsView / FlowFailedWebhookView (não alteradas, só movidas) Guard de bearer token (401 sem token) confirmado após a divisão — não re-executadas com token real por terem efeito em deployments reais do Prefect, fora do escopo desse PR
Labels da modal Confirmado visualmente no navegador pelo usuário

- Remove </div> sobrando no change_form.html que quebrava a árvore DOM
  e deixava os labels do modal em branco pra alguns usuários/navegadores.
- Botão de fechar e área de resultado ganham id/estrutura únicos, no
  mesmo padrão já usado por Checar Metadados e Sincronizar com o BigQuery.
- upload_columns agora sempre responde em JSON (nunca deixa vazar
  traceback HTML), envolve a substituição de colunas em uma transação
  (falha no meio do import não apaga as colunas existentes) e valida
  nome de coluna e directory_column contra as regras de identificador
  do BigQuery antes de gravar qualquer coisa.
Move o endpoint de /upload_columns/ (api/v1) pra
/admin-tools/upload-columns/ (UploadColumnsView), no mesmo padrão de
classe já usado por CheckMetadadosView e SyncUpdateLatestView. Os
helpers de leitura/validação da planilha viram funções privadas do
módulo, junto das demais. Aproveita pra trocar o desempacotamento
posicional de request.POST.values() por acesso nomeado aos campos.
views.py tinha 690 linhas misturando 3 assuntos sem relação direta.
Divide em arquivos irmãos, no mesmo padrão já usado por
_prefect3_client.py:

- flow_monitoring.py: SyncDeploymentsView, FlowFailedWebhookView e
  helpers de monitoramento/auto-disable de flow no Prefect.
- bigquery_sync.py: CheckMetadadosView, SyncUpdateLatestView e
  helpers de comparação/sincronização de Table contra o BigQuery.
- column_import.py: UploadColumnsView, ColumnImportError e helpers
  de leitura/validação da planilha de arquitetura.

urls.py passa a importar direto de cada módulo (único arquivo que
importava de .views). Nenhuma lógica mudou, só o arquivo onde vive.
@Winzen Winzen self-assigned this Sep 17, 2026
@Winzen Winzen added the prod Indica que o Pull Request está com a branch de destino (base) apontando para main label Sep 17, 2026
@Winzen
Winzen merged commit 9d641b0 into main Sep 17, 2026
5 checks passed
@Winzen
Winzen deleted the fix/upload-columns-modal-labels branch September 17, 2026 06:07
github-actions Bot added a commit that referenced this pull request Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

prod Indica que o Pull Request está com a branch de destino (base) apontando para main

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Modal de Importar Colunas com labels em branco e erros silenciosos no upload

1 participant