- docs/DESIGN-2.0.md: decisões fechadas (tags PT, editor linha-a-linha,
escopo global, dry-run com preview) e arquitetura Motor x Template
- PluginRedmineTemplateengine: render mustache-like ({{ var | filtro }}),
regra de tipos (tag inteira = tipo nativo), lookup indexado
(map.status[chamado.status]), filtros (truncar, sem_html, minusculo,
identificador, padrao), prune de nulls, seeds 1.x-equivalentes e
descoberta de custom fields obrigatórios
- tabela glpi_plugin_redmine_templates (operation único, is_active)
- wiring com precedência de template + fallback no render em
create_issue, log_time e update_status
- validado: 12/12 unit + E2E (template ativo cria issue/tempo; template
quebrado cai no motor 1.x sem perder lançamento)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4.8 KiB
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_typeproject/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
- 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. - UI: editor linha-a-linha + paleta + "Gerar template" + wiring do
create_project(contextoform.*). - Validação: preview de render + dry-run opcional com tradução de 422.
- 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.