Redmine/docs/DESIGN-2.0.md
Gemini 7bf8cdab4e feat(2.0): Fase 3 — testador de templates (preview + dry-run com tradução de 422)
- ajax/template_test.php: action=preview (render com sampleContext) e
  action=dryrun (cria+apaga objeto de teste no Redmine, traduz erros 422
  em dicas acionáveis; issue temporária para log_time/update_status)
- RedmineApi::rawRequest (código+corpo crus) e TemplateEngine::sampleContext
- UI: botão "Testar" + painel por card (preview sempre; teste real opt-in
  com seletor de projeto alvo), via fetch com X-Glpi-Csrf-Token
- validado E2E por HTTP: preview, dry-run cria/deleta (204), render inválido
  capturado, tradução de 422; sem resíduo de teste no Redmine

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 08:58:17 -03:00

4.8 KiB
Raw Blame History

DESIGN 2.0 — Templates de Payload (motor de mapeamento dinâmico)

Objetivo: transformar o plugin — hoje moldado ao ambiente Mindtek — em produto: os payloads enviados ao Redmine passam a ser templates editáveis pelo admin, com variáveis do GLPI e campos descobertos do Redmine de destino. Linha estável (clientes atuais): branch stable/1.x. Evolução: main (2.0).

Decisões fechadas (2026-07-02)

Decisão Escolha
Idioma das tags PT-BR ({{ chamado.titulo }}) — público Mindplace
UX do editor Linha-a-linha (campo → valor/tag → obrigatório) com toggle "ver JSON"
Escopo do template Global por operação na 2.0; override por vínculo na 2.1
Dry-run Preview de render sempre; teste real opt-in contra alvo escolhido
Sintaxe Mustache-like ({{ var | filtro }}), sem Twig completo (segurança)

Fronteira Motor × Template

Motor (fixo, não editável): auth, impersonation + auto-membership + fallback admin, idempotência (1 issue/chamado, tempo 1×/task), recuperação de issue órfã, gates (Feito + duração>0), normalização de identifier, logs, self-healing de usuários.

Template (editável): o corpo JSON de cada operação — quais campos vão e com quais valores (tags, valores fixos, custom fields do ambiente).

O admin mapeia dados, nunca lógica.

Operações e seeds

4 operações fixas: create_project, create_issue, log_time, update_status. O botão "Gerar template" produz o seed = comportamento atual do motor 1.x

  • custom fields obrigatórios descobertos via /custom_fields.json (customized_type project/issue/time_entry conforme a operação).

Seed create_issue (exemplo):

{ "issue": {
  "project_id":  "{{ vinculo.projeto_redmine }}",
  "subject":     "{{ chamado.titulo | truncar:255 }}",
  "description": "{{ chamado.descricao | sem_html }}",
  "tracker_id":  "{{ config.tracker_padrao }}",
  "priority_id": "{{ config.prioridade_padrao }}",
  "status_id":   "{{ map.status[chamado.status] }}",
  "category_id": "{{ vinculo.categoria_padrao }}",
  "watcher_user_ids": "{{ config.observadores }}"
} }

Sintaxe das tags

  • {{ namespace.campo }} — resolução por caminho no contexto.
  • {{ mapa[chave.dinamica] }} — lookup indexado (ex.: map.status[chamado.status]).
  • Filtros em pipeline: {{ var | filtro:arg | filtro2 }}.
    • truncar:N, sem_html, minusculo, identificador, padrao:'texto fixo'.

Regra de tipos: tag ocupando o valor inteiro da string → rende com o tipo nativo da variável (número/array/bool, sem aspas no JSON final). Tag embutida em texto → interpolação de string. Valores null/arrays vazios pós-render são removidos do payload (igual ao motor 1.x, que só inclui campos definidos).

Contexto (paleta de variáveis)

Namespace Campos Origem
chamado.* id, titulo, descricao, status, entidade Ticket
tarefa.* id, horas, segundos, descricao, comentario*, data TicketTask
vinculo.* projeto_redmine, issue_redmine, categoria_padrao tabelas do plugin
config.* tracker_padrao, prioridade_padrao, atividade_padrao, observadores formconfig
map.status mapa status GLPI→Redmine formconfig
form.* campos do form de criar projeto (inclui cf_<id>) aba Redmine

* tarefa.comentario = descrição sem HTML ou o fallback "Tempo lançado via GLPI (chamado #N)" — o fallback é responsabilidade do motor (contexto), não do template.

Armazenamento e retrocompatibilidade

Tabela glpi_plugin_redmine_templates (operation único, template MEDIUMTEXT, is_active). Sem template ativo → motor 1.x intocado. Template ativo com erro de render → log + motor 1.x (nunca perde lançamento). Template ativo válido → payload do template é o enviado (sem retry pela via legada em erro de API — respeita a escolha do admin e evita duplicação).

Fases

  1. Motor (esta fase): engine de render + tabela + seeds + wiring com fallback nas operações do hook (create_issue, log_time, update_status). Invisível na UI.
  2. UI: editor linha-a-linha + paleta + "Gerar template" + wiring do create_project (contexto form.*).
  3. Validação (concluída): preview de render + dry-run opcional com tradução de 422.
  4. 2.1: override por vínculo, custom fields de issue/time_entry na paleta, perfis múltiplos (um GLPI → vários Redmines).

Referências

  • Caso de estudo: ambiente Mindtek (KB-PLUGIN-037) — nativo vs "o que o Redmine exigiu" (custom fields 4/5 de projeto, activity/comments obrigatórios no time entry, trackers por projeto).
  • Redmine não tem introspecção de schema: descoberta = esqueleto fixo por operação
    • /custom_fields.json + dry-run com tradução de erros 422.