release: 2.0.0 — motor de templates de payload (produto ambiente-agnóstico)
- bump 2.0.0 (final) + CHANGELOG datado - docs/TEMPLATES.md (guia do usuário) + seção no README - docs/API.md: seção do motor de templates (componentes, precedência, descoberta) Retrocompatível: sem template ativo, comportamento idêntico ao 1.x. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7bf8cdab4e
commit
72761f514b
5 changed files with 140 additions and 2 deletions
|
|
@ -4,7 +4,7 @@ Todas as mudanças relevantes deste plugin são documentadas aqui.
|
||||||
O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/1.0.0/)
|
O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/1.0.0/)
|
||||||
e o versionamento segue [SemVer](https://semver.org/lang/pt-BR/).
|
e o versionamento segue [SemVer](https://semver.org/lang/pt-BR/).
|
||||||
|
|
||||||
## [2.0.0-dev] — em desenvolvimento (branch main; estável = stable/1.x)
|
## [2.0.0] - 2026-07-03
|
||||||
|
|
||||||
### Adicionado
|
### Adicionado
|
||||||
- **Motor de templates de payload** (Fase 1, invisível na UI — ver `docs/DESIGN-2.0.md`):
|
- **Motor de templates de payload** (Fase 1, invisível na UI — ver `docs/DESIGN-2.0.md`):
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ TicketTask "Feito" ────────────────────
|
||||||
- ✅ **Observadores padrão** configuráveis nas issues.
|
- ✅ **Observadores padrão** configuráveis nas issues.
|
||||||
- ✅ Chave de API **criptografada** em repouso (GLPIKey).
|
- ✅ Chave de API **criptografada** em repouso (GLPIKey).
|
||||||
- ✅ Interface 100% nativa do GLPI 11 (componentes Twig).
|
- ✅ Interface 100% nativa do GLPI 11 (componentes Twig).
|
||||||
|
- ✅ **Templates de Payload (2.0)** — personalize o JSON enviado ao Redmine em cada operação, com **variáveis do GLPI** e **descoberta automática dos campos do seu Redmine** (inclusive campos personalizados obrigatórios). Editor visual + testador embutido. Veja [`docs/TEMPLATES.md`](docs/TEMPLATES.md).
|
||||||
|
|
||||||
## Requisitos
|
## Requisitos
|
||||||
|
|
||||||
|
|
|
||||||
31
docs/API.md
31
docs/API.md
|
|
@ -280,3 +280,34 @@ Content-Type: application/json
|
||||||
3. (1ª vez) `POST /issues.json` cria a issue — autor = técnico atribuído (impersonation + auto-membership).
|
3. (1ª vez) `POST /issues.json` cria a issue — autor = técnico atribuído (impersonation + auto-membership).
|
||||||
4. `POST /time_entries.json` lança as horas — autor = quem lançou a task.
|
4. `POST /time_entries.json` lança as horas — autor = quem lançou a task.
|
||||||
5. Ao mudar o status do chamado, `PUT /issues/{id}.json` reflete no Redmine.
|
5. Ao mudar o status do chamado, `PUT /issues/{id}.json` reflete no Redmine.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Motor de Templates de Payload (2.0)
|
||||||
|
|
||||||
|
A partir da 2.0, o corpo de cada operação pode vir de um **template** editável em
|
||||||
|
vez de ser montado no código. Guia do usuário: [`TEMPLATES.md`](TEMPLATES.md).
|
||||||
|
|
||||||
|
### Componentes internos
|
||||||
|
- **`PluginRedmineTemplateengine`** (`inc/templateengine.class.php`): armazenamento
|
||||||
|
(`glpi_plugin_redmine_templates`), `defaultTemplate()`/`mergeDiscoveredCustomFields()`
|
||||||
|
(seeds), `variableCatalog()` e `fieldDictionary()` (paleta "DE" e dicionário "PARA"),
|
||||||
|
`buildContext()`/`buildFormContext()`/`sampleContext()` (contextos), e o renderizador
|
||||||
|
`render()` (tags mustache-like, tipo nativo p/ tag inteira, filtros, `prune` de nulos).
|
||||||
|
- **`PluginRedmineRedmineapi::createIssueFromPayload()` / `logTimeFromPayload()`**:
|
||||||
|
enviam um payload já renderizado (com fallback de impersonation → admin).
|
||||||
|
- **`PluginRedmineRedmineapi::rawRequest()`**: requisição crua (código+corpo) usada
|
||||||
|
pelo testador — o erro **é** a informação.
|
||||||
|
- **`ajax/template_test.php`**: `action=preview` (render com `sampleContext`) e
|
||||||
|
`action=dryrun` (cria+apaga objeto de teste, traduz 422 em dicas).
|
||||||
|
|
||||||
|
### Precedência (motor 2.0 × 1.x)
|
||||||
|
Em cada operação do hook/controllers:
|
||||||
|
1. Existe template **ativo** para a operação? Renderiza e envia esse payload.
|
||||||
|
2. Falha no **render** (JSON/tag inválidos)? Loga e **cai no comportamento 1.x**.
|
||||||
|
3. Sem template ativo? Comportamento **1.x** (idêntico às versões anteriores).
|
||||||
|
|
||||||
|
### Descoberta de campos
|
||||||
|
`GET /custom_fields.json` (admin) é cacheado por `syncMetadata()` — os campos
|
||||||
|
personalizados de `project`, `issue` e `time_entry` alimentam tanto o seed do
|
||||||
|
"Gerar template" (só os **obrigatórios**) quanto o dicionário "PARA" (todos).
|
||||||
|
|
|
||||||
106
docs/TEMPLATES.md
Normal file
106
docs/TEMPLATES.md
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
# Templates de Payload (2.0)
|
||||||
|
|
||||||
|
A partir da versão 2.0, o plugin deixa de ter o corpo das requisições ao Redmine
|
||||||
|
"chumbado" no código: cada operação passa a ter um **template de payload** editável,
|
||||||
|
para que o plugin se adapte às **peculiaridades de cada Redmine** (campos
|
||||||
|
personalizados obrigatórios, campos extras, textos fixos) sem precisar de alteração
|
||||||
|
de código.
|
||||||
|
|
||||||
|
> **Retrocompatível:** sem template **ativo**, o plugin se comporta **exatamente**
|
||||||
|
> como nas versões 1.x. Você adota os templates quando (e se) quiser.
|
||||||
|
|
||||||
|
## Onde fica
|
||||||
|
|
||||||
|
**Configurar → Plugins → Redmine Integration → "Templates de Payload (avançado)"**.
|
||||||
|
|
||||||
|
Há um card por operação:
|
||||||
|
|
||||||
|
| Operação | Quando dispara | Endpoint Redmine |
|
||||||
|
|---|---|---|
|
||||||
|
| **Criar tarefa (issue)** | 1º lançamento de tempo de um chamado | `POST /issues.json` |
|
||||||
|
| **Lançar tempo** | tarefa (TicketTask) marcada como *Feito* | `POST /time_entries.json` |
|
||||||
|
| **Atualizar status** | mudança de status do chamado | `PUT /issues/{id}.json` |
|
||||||
|
| **Criar projeto** | vínculo "criar novo" na aba Redmine | `POST /projects.json` |
|
||||||
|
|
||||||
|
## O conceito: "DE → PARA"
|
||||||
|
|
||||||
|
- **DE (variáveis do GLPI):** o que o plugin tem para oferecer — `chamado.titulo`,
|
||||||
|
`tarefa.horas`, `vinculo.projeto_redmine`, etc. Aparecem como **autocomplete** no
|
||||||
|
campo de valor de cada linha (e há uma lista de referência colapsável).
|
||||||
|
- **PARA (campos do Redmine):** o que **o seu Redmine** espera em cada operação —
|
||||||
|
descoberto automaticamente (campos padrão + **campos personalizados** via
|
||||||
|
`GET /custom_fields.json`), com marcação de **obrigatório** e valores possíveis.
|
||||||
|
Clicar num campo do "PARA" adiciona a linha no editor com o caminho já pronto.
|
||||||
|
|
||||||
|
## Fluxo recomendado
|
||||||
|
|
||||||
|
1. **Sincronizar Metadados** (aba Conexão) — é o que descobre trackers, status,
|
||||||
|
atividades e **campos personalizados** do seu Redmine.
|
||||||
|
2. No card da operação, clique em **Gerar template** — cria o ponto de partida
|
||||||
|
equivalente ao comportamento padrão **+ os campos obrigatórios do seu ambiente**.
|
||||||
|
3. Ajuste as linhas (o autocomplete ajuda a achar as variáveis do GLPI).
|
||||||
|
4. Clique em **Testar**:
|
||||||
|
- **Preview** — mostra o JSON final renderizado com dados de exemplo (seguro).
|
||||||
|
- **Teste real** (opcional) — cria e apaga um objeto de teste no Redmine e,
|
||||||
|
se recusado, **traduz o erro** ("Atividade não pode ficar vazio → mapeie esse campo").
|
||||||
|
5. Marque **Ativo** e clique em **Salvar** (o botão único no fim da página grava tudo).
|
||||||
|
|
||||||
|
## Sintaxe das tags
|
||||||
|
|
||||||
|
```
|
||||||
|
{{ variavel }} → valor da variável
|
||||||
|
{{ variavel | filtro }} → aplica um filtro
|
||||||
|
{{ variavel | filtro:arg }} → filtro com argumento
|
||||||
|
{{ mapa[chave.dinamica] }} → lookup indexado (ex.: map.status[chamado.status])
|
||||||
|
```
|
||||||
|
|
||||||
|
**Regra de tipos:** quando a tag ocupa **todo** o valor, o resultado sai com o
|
||||||
|
**tipo nativo** da variável (número, array, booleano — sem aspas no JSON). Quando a
|
||||||
|
tag está no meio de um texto, vira interpolação de string.
|
||||||
|
Campos que resolverem para vazio/nulo são **omitidos** do payload.
|
||||||
|
|
||||||
|
### Filtros disponíveis
|
||||||
|
|
||||||
|
| Filtro | Efeito |
|
||||||
|
|---|---|
|
||||||
|
| `truncar:N` | corta a string em N caracteres (ex.: `subject` no limite de 255) |
|
||||||
|
| `sem_html` | remove HTML/entidades |
|
||||||
|
| `minusculo` | tudo minúsculo |
|
||||||
|
| `identificador` | normaliza para identificador Redmine válido (minúsculas, `-`) |
|
||||||
|
| `padrao:'texto'` | valor fixo quando a variável estiver vazia |
|
||||||
|
|
||||||
|
## Variáveis do GLPI (o "DE")
|
||||||
|
|
||||||
|
| Grupo | Variáveis |
|
||||||
|
|---|---|
|
||||||
|
| `chamado.*` | `id`, `titulo`, `descricao`, `status`, `entidade`, `tecnico` |
|
||||||
|
| `tarefa.*` | `id`, `horas`, `segundos`, `descricao`, `comentario`, `data`, `autor` |
|
||||||
|
| `vinculo.*` | `projeto_redmine`, `issue_redmine`, `categoria_padrao` |
|
||||||
|
| `config.*` | `tracker_padrao`, `prioridade_padrao`, `atividade_padrao`, `observadores` |
|
||||||
|
| `map.status` | mapa de status GLPI→Redmine (use como `map.status[chamado.status]`) |
|
||||||
|
| `form.*` (criar projeto) | `nome`, `identificador`, `descricao`, `publico`, `subprojeto_de`, `trackers`, `modulos`, `cf_<id>` |
|
||||||
|
|
||||||
|
> **Autoria:** `chamado.tecnico` e `tarefa.autor` são **texto** (para usar em
|
||||||
|
> comentários, por exemplo). A **autoria real** da issue/tempo continua automática,
|
||||||
|
> via *impersonation* — não é um campo do template.
|
||||||
|
|
||||||
|
## Segurança e robustez
|
||||||
|
|
||||||
|
- As tags são um mini-motor de substituição (mustache-like), **não** executam código
|
||||||
|
(não é Twig completo) — seguro para edição por administradores.
|
||||||
|
- Template com JSON inválido **não é salvo** (aviso na tela).
|
||||||
|
- Se um template ativo falhar no **render** em produção, o plugin **cai no
|
||||||
|
comportamento 1.x** e registra no log — nunca perde um lançamento.
|
||||||
|
|
||||||
|
## Exemplo (o que o "Gerar template" produz para `log_time`)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"time_entry": {
|
||||||
|
"issue_id": "{{ vinculo.issue_redmine }}",
|
||||||
|
"hours": "{{ tarefa.horas }}",
|
||||||
|
"comments": "{{ tarefa.comentario }}",
|
||||||
|
"activity_id": "{{ config.atividade_padrao }}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
define('PLUGIN_REDMINE_VERSION', '2.0.0-dev');
|
define('PLUGIN_REDMINE_VERSION', '2.0.0');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the Mindplace License class if the autoloader has not run yet.
|
* Load the Mindplace License class if the autoloader has not run yet.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue