Redmine/docs/DESIGN-2.0.md
Gemini 7ac1f8ac44 feat(2.0): Fase 1 — motor de templates de payload (design + engine + fallback 1.x)
- 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>
2026-07-02 15:25:32 -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: 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.