bug_bunny 4.9.0 → 4.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/lib/bug_bunny/consumer.rb +19 -8
- data/lib/bug_bunny/controller.rb +7 -2
- data/lib/bug_bunny/middleware/raise_error.rb +8 -0
- data/lib/bug_bunny/producer.rb +9 -2
- data/lib/bug_bunny/remote_error.rb +55 -0
- data/lib/bug_bunny/resource.rb +12 -0
- data/lib/bug_bunny/version.rb +1 -1
- data/lib/bug_bunny.rb +1 -0
- data/skills.lock +4 -1
- data/skills.yml +4 -0
- data/spec/integration/resource_spec.rb +44 -0
- metadata +4 -14
- data/.agents/skills/documentation-writer/SKILL.md +0 -45
- data/.agents/skills/gem-release/SKILL.md +0 -116
- data/.agents/skills/quality-code/SKILL.md +0 -51
- data/.agents/skills/sentry/SKILL.md +0 -135
- data/.agents/skills/sentry/references/api-endpoints.md +0 -147
- data/.agents/skills/sentry/scripts/sentry.rb +0 -194
- data/.agents/skills/skill-builder/SKILL.md +0 -293
- data/.agents/skills/skill-manager/SKILL.md +0 -225
- data/.agents/skills/skill-manager/scripts/sync.rb +0 -356
- data/.agents/skills/yard/SKILL.md +0 -311
- data/.agents/skills/yard/references/tipos.md +0 -144
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: sentry
|
|
3
|
-
description: Experto en la instancia self-hosted de Sentry de Wispro. Úsala SIEMPRE que necesites buscar errores, listar issues, ver stacktraces, resolver o asignar issues en Sentry. Funciona con cualquier proyecto del ecosistema.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Sentry Expert
|
|
7
|
-
|
|
8
|
-
Skill de conocimiento completo sobre la instancia de Sentry self-hosted de Wispro. Consultame para buscar errores, analizar stacktraces, gestionar issues o diagnosticar problemas en producción.
|
|
9
|
-
|
|
10
|
-
## Configuración
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
SENTRY_TOKEN=$SENTRY_TOKEN
|
|
14
|
-
SENTRY_URL="https://sentry.cloud.wispro.co"
|
|
15
|
-
SENTRY_ORG="wispro"
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
**Importante:** Siempre usar `-k` en curl (certificado self-hosted). Nunca exponer el token en las respuestas.
|
|
19
|
-
|
|
20
|
-
## Glosario
|
|
21
|
-
|
|
22
|
-
**Issue** — Agrupación de eventos similares. Tiene un ID numérico y un shortId legible (ej: `PROJECT-123`).
|
|
23
|
-
|
|
24
|
-
**Event** — Ocurrencia individual de un error. Contiene stacktrace, contexto, usuario, breadcrumbs.
|
|
25
|
-
|
|
26
|
-
**Project slug** — Identificador del proyecto en Sentry. Cada microservicio tiene uno. Si no lo sabés, listá los proyectos primero.
|
|
27
|
-
|
|
28
|
-
**statsPeriod** — Período de estadísticas: `24h`, `14d` o vacío para todo el historial.
|
|
29
|
-
|
|
30
|
-
## Flujos de Trabajo
|
|
31
|
-
|
|
32
|
-
### Buscar errores en un proyecto
|
|
33
|
-
|
|
34
|
-
1. Si no tenés el project slug, listá los proyectos primero (ver API).
|
|
35
|
-
2. Consultá issues del proyecto filtrando por período y query.
|
|
36
|
-
3. Para cada issue relevante, consultá los eventos para ver el stacktrace.
|
|
37
|
-
|
|
38
|
-
### Diagnosticar un error desde el código
|
|
39
|
-
|
|
40
|
-
1. Tomá el mensaje de error o excepción del código/logs.
|
|
41
|
-
2. Buscá en Sentry con `query=<mensaje>` en el proyecto correspondiente.
|
|
42
|
-
3. Si hay match, mostrá el stacktrace y la frecuencia.
|
|
43
|
-
4. Si no hay match, indicá que el error no está reportado en Sentry.
|
|
44
|
-
|
|
45
|
-
### Gestionar issues
|
|
46
|
-
|
|
47
|
-
- **Resolver**: `status=resolved`
|
|
48
|
-
- **Ignorar**: `status=ignored`
|
|
49
|
-
- **Asignar**: `assignedTo=<username>`
|
|
50
|
-
- **Marcar como visto**: `hasSeen=true`
|
|
51
|
-
|
|
52
|
-
### Análisis de tendencias
|
|
53
|
-
|
|
54
|
-
1. Consultá issues con `statsPeriod=14d` para ver tendencias.
|
|
55
|
-
2. Ordená por `count` o `userCount` para priorizar.
|
|
56
|
-
3. Revisá `firstSeen` y `lastSeen` para detectar regresiones.
|
|
57
|
-
|
|
58
|
-
## API — Endpoints principales
|
|
59
|
-
|
|
60
|
-
Todos los endpoints usan base URL `$SENTRY_URL/api/0/` y header `Authorization: Bearer $SENTRY_TOKEN`.
|
|
61
|
-
|
|
62
|
-
Ver catálogo completo en [references/api-endpoints.md](references/api-endpoints.md).
|
|
63
|
-
|
|
64
|
-
### Listar proyectos
|
|
65
|
-
```bash
|
|
66
|
-
curl -sk -H "Authorization: Bearer $SENTRY_TOKEN" \
|
|
67
|
-
"$SENTRY_URL/api/0/organizations/$SENTRY_ORG/projects/" | jq '.[] | {slug, name}'
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Listar issues de un proyecto
|
|
71
|
-
```bash
|
|
72
|
-
curl -sk -H "Authorization: Bearer $SENTRY_TOKEN" \
|
|
73
|
-
"$SENTRY_URL/api/0/projects/$SENTRY_ORG/{PROJECT_SLUG}/issues/?statsPeriod=24h&query=is:unresolved"
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Ver detalle de un issue
|
|
77
|
-
```bash
|
|
78
|
-
curl -sk -H "Authorization: Bearer $SENTRY_TOKEN" \
|
|
79
|
-
"$SENTRY_URL/api/0/organizations/$SENTRY_ORG/issues/{ISSUE_ID}/"
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### Ver eventos de un issue (con stacktrace)
|
|
83
|
-
```bash
|
|
84
|
-
curl -sk -H "Authorization: Bearer $SENTRY_TOKEN" \
|
|
85
|
-
"$SENTRY_URL/api/0/organizations/$SENTRY_ORG/issues/{ISSUE_ID}/events/?full=true&limit=1"
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### Buscar por mensaje de error
|
|
89
|
-
```bash
|
|
90
|
-
curl -sk -H "Authorization: Bearer $SENTRY_TOKEN" \
|
|
91
|
-
"$SENTRY_URL/api/0/projects/$SENTRY_ORG/{PROJECT_SLUG}/issues/?query={MENSAJE}&statsPeriod=24h"
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### Resolver un issue
|
|
95
|
-
```bash
|
|
96
|
-
curl -sk -X PUT -H "Authorization: Bearer $SENTRY_TOKEN" \
|
|
97
|
-
-H "Content-Type: application/json" \
|
|
98
|
-
-d '{"status":"resolved"}' \
|
|
99
|
-
"$SENTRY_URL/api/0/organizations/$SENTRY_ORG/issues/{ISSUE_ID}/"
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
## FAQ
|
|
103
|
-
|
|
104
|
-
### ¿Cómo encuentro el project slug de mi servicio?
|
|
105
|
-
Ejecutá el endpoint de listar proyectos. El campo `slug` es lo que necesitás. Generalmente coincide con el nombre del repo en minúsculas.
|
|
106
|
-
|
|
107
|
-
### ¿Cómo veo el stacktrace completo?
|
|
108
|
-
Usá el endpoint de eventos con `full=true`. El stacktrace está en `entries[].data.values[].stacktrace.frames[]`. Cada frame tiene `filename`, `lineNo`, `function` y `context`.
|
|
109
|
-
|
|
110
|
-
### ¿Cómo filtro por nivel de error?
|
|
111
|
-
Agregá `query=level:error` o `query=level:warning` al endpoint de issues.
|
|
112
|
-
|
|
113
|
-
### ¿Cómo pagino resultados?
|
|
114
|
-
La API devuelve un header `Link` con cursores. Usá el parámetro `cursor` del link `next` para la siguiente página.
|
|
115
|
-
|
|
116
|
-
## Antipatrones
|
|
117
|
-
|
|
118
|
-
**Buscar sin project slug** — No consultes issues a nivel organización si sabés el proyecto. Es más lento y devuelve ruido de otros servicios.
|
|
119
|
-
|
|
120
|
-
**Ignorar la paginación** — Por defecto la API devuelve pocos resultados. Si necesitás más, paginá con el cursor.
|
|
121
|
-
|
|
122
|
-
**Hardcodear issue IDs** — Los IDs cambian. Siempre buscá por query o filtrá por período.
|
|
123
|
-
|
|
124
|
-
## Errores comunes
|
|
125
|
-
|
|
126
|
-
**401 Unauthorized** — Token inválido o expirado. Verificá que `$SENTRY_TOKEN` esté en el entorno.
|
|
127
|
-
|
|
128
|
-
**404 Not Found** — Project slug incorrecto. Listá los proyectos para verificar.
|
|
129
|
-
|
|
130
|
-
**SSL Certificate Error** — Falta el flag `-k` en curl. Es necesario porque es self-hosted.
|
|
131
|
-
|
|
132
|
-
## Referencias
|
|
133
|
-
|
|
134
|
-
- [API Endpoints](references/api-endpoints.md) — Catálogo completo de endpoints con parámetros
|
|
135
|
-
- [sentry.rb](scripts/sentry.rb) — Script helper para queries comunes
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
# Sentry API — Catálogo de Endpoints
|
|
2
|
-
|
|
3
|
-
Base URL: `$SENTRY_URL/api/0/`
|
|
4
|
-
Auth: `Authorization: Bearer $SENTRY_TOKEN`
|
|
5
|
-
Siempre usar `-k` en curl (certificado self-hosted).
|
|
6
|
-
|
|
7
|
-
## Proyectos
|
|
8
|
-
|
|
9
|
-
### Listar proyectos de la organización
|
|
10
|
-
```
|
|
11
|
-
GET /organizations/{org}/projects/
|
|
12
|
-
```
|
|
13
|
-
Respuesta: array de objetos con `slug`, `name`, `id`, `platform`, `dateCreated`.
|
|
14
|
-
|
|
15
|
-
### Obtener detalle de un proyecto
|
|
16
|
-
```
|
|
17
|
-
GET /projects/{org}/{project_slug}/
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Issues
|
|
21
|
-
|
|
22
|
-
### Listar issues de un proyecto
|
|
23
|
-
```
|
|
24
|
-
GET /projects/{org}/{project_slug}/issues/
|
|
25
|
-
```
|
|
26
|
-
Query params:
|
|
27
|
-
- `statsPeriod` — `24h`, `14d` o vacío (default: `24h`)
|
|
28
|
-
- `query` — Búsqueda estructurada (default: `is:unresolved`)
|
|
29
|
-
- `shortIdLookup` — `true` para buscar por shortId
|
|
30
|
-
- `cursor` — Paginación
|
|
31
|
-
|
|
32
|
-
Respuesta: array de issues con `id`, `title`, `culprit`, `count`, `userCount`, `firstSeen`, `lastSeen`, `level`, `status`, `shortId`, `project`.
|
|
33
|
-
|
|
34
|
-
### Listar issues de la organización
|
|
35
|
-
```
|
|
36
|
-
GET /organizations/{org}/issues/
|
|
37
|
-
```
|
|
38
|
-
Mismos query params. Devuelve issues de todos los proyectos.
|
|
39
|
-
|
|
40
|
-
### Obtener detalle de un issue
|
|
41
|
-
```
|
|
42
|
-
GET /organizations/{org}/issues/{issue_id}/
|
|
43
|
-
```
|
|
44
|
-
Respuesta: issue completo con `activity`, `assignedTo`, `count`, `firstSeen`, `lastSeen`, `firstRelease`, `lastRelease`, `participants`, `userReportCount`, `stats`.
|
|
45
|
-
|
|
46
|
-
### Actualizar un issue
|
|
47
|
-
```
|
|
48
|
-
PUT /organizations/{org}/issues/{issue_id}/
|
|
49
|
-
```
|
|
50
|
-
Body (JSON, todos opcionales):
|
|
51
|
-
- `status` — `resolved`, `resolvedInNextRelease`, `unresolved`, `ignored`
|
|
52
|
-
- `assignedTo` — username o team
|
|
53
|
-
- `hasSeen` — boolean
|
|
54
|
-
- `isBookmarked` — boolean
|
|
55
|
-
- `isSubscribed` — boolean
|
|
56
|
-
- `isPublic` — boolean
|
|
57
|
-
|
|
58
|
-
### Eliminar un issue
|
|
59
|
-
```
|
|
60
|
-
DELETE /organizations/{org}/issues/{issue_id}/
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### Bulk update issues de un proyecto
|
|
64
|
-
```
|
|
65
|
-
PUT /projects/{org}/{project_slug}/issues/
|
|
66
|
-
```
|
|
67
|
-
Query params:
|
|
68
|
-
- `id` — lista de issue IDs a actualizar
|
|
69
|
-
|
|
70
|
-
Body: mismos campos que update individual.
|
|
71
|
-
|
|
72
|
-
## Eventos
|
|
73
|
-
|
|
74
|
-
### Listar eventos de un issue
|
|
75
|
-
```
|
|
76
|
-
GET /organizations/{org}/issues/{issue_id}/events/
|
|
77
|
-
```
|
|
78
|
-
Query params:
|
|
79
|
-
- `full` — `true` para incluir body completo con stacktrace
|
|
80
|
-
- `statsPeriod` — período de filtro
|
|
81
|
-
- `environment` — filtrar por entorno
|
|
82
|
-
- `query` — búsqueda dentro de eventos
|
|
83
|
-
- `cursor` — paginación
|
|
84
|
-
|
|
85
|
-
Respuesta: array de eventos con `id`, `eventID`, `groupID`, `title`, `message`, `dateCreated`, `tags`, `user`, `location`, `culprit`.
|
|
86
|
-
|
|
87
|
-
### Listar eventos de error de un proyecto
|
|
88
|
-
```
|
|
89
|
-
GET /projects/{org}/{project_slug}/events/
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### Obtener evento específico de un proyecto
|
|
93
|
-
```
|
|
94
|
-
GET /projects/{org}/{project_slug}/events/{event_id}/
|
|
95
|
-
```
|
|
96
|
-
Respuesta completa con stacktrace en `entries[].data.values[].stacktrace.frames[]`.
|
|
97
|
-
|
|
98
|
-
Cada frame contiene:
|
|
99
|
-
- `filename` — archivo
|
|
100
|
-
- `lineNo` — línea
|
|
101
|
-
- `function` — método
|
|
102
|
-
- `context` — líneas de código alrededor
|
|
103
|
-
- `inApp` — boolean, si es código de la app o dependencia
|
|
104
|
-
|
|
105
|
-
## Tags
|
|
106
|
-
|
|
107
|
-
### Valores de un tag en un issue
|
|
108
|
-
```
|
|
109
|
-
GET /organizations/{org}/issues/{issue_id}/tags/{tag_key}/values/
|
|
110
|
-
```
|
|
111
|
-
Tags comunes: `environment`, `server_name`, `browser`, `os`, `release`.
|
|
112
|
-
|
|
113
|
-
### Detalle de un tag en un issue
|
|
114
|
-
```
|
|
115
|
-
GET /organizations/{org}/issues/{issue_id}/tags/{tag_key}/
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Hashes
|
|
119
|
-
|
|
120
|
-
### Listar hashes de un issue
|
|
121
|
-
```
|
|
122
|
-
GET /organizations/{org}/issues/{issue_id}/hashes/
|
|
123
|
-
```
|
|
124
|
-
Útil para entender qué variantes de stacktrace se agrupan en el mismo issue.
|
|
125
|
-
|
|
126
|
-
## Paginación
|
|
127
|
-
|
|
128
|
-
La API usa cursor-based pagination. El header `Link` de la respuesta contiene:
|
|
129
|
-
|
|
130
|
-
```
|
|
131
|
-
Link: <url>; rel="previous"; results="false"; cursor="...",
|
|
132
|
-
<url>; rel="next"; results="true"; cursor="..."
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
Usar el valor de `cursor` del link `next` como query param para la siguiente página. Cuando `results="false"` en `next`, no hay más páginas.
|
|
136
|
-
|
|
137
|
-
## Queries estructuradas
|
|
138
|
-
|
|
139
|
-
El parámetro `query` soporta:
|
|
140
|
-
- `is:unresolved` / `is:resolved` / `is:ignored`
|
|
141
|
-
- `level:error` / `level:warning` / `level:info`
|
|
142
|
-
- `assigned:username` / `assigned:me`
|
|
143
|
-
- `bookmarks:me`
|
|
144
|
-
- `firstSeen:>2024-01-01`
|
|
145
|
-
- `lastSeen:<24h`
|
|
146
|
-
- `times_seen:>100`
|
|
147
|
-
- Texto libre para buscar en título/mensaje
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Helper para interactuar con la API de Sentry self-hosted de Wispro.
|
|
5
|
-
# Uso: ruby sentry.rb <comando> [opciones]
|
|
6
|
-
#
|
|
7
|
-
# Comandos:
|
|
8
|
-
# projects — Lista todos los proyectos
|
|
9
|
-
# issues <project_slug> [opciones] — Lista issues de un proyecto
|
|
10
|
-
# issue <issue_id> — Detalle de un issue
|
|
11
|
-
# events <issue_id> [--full] — Eventos de un issue
|
|
12
|
-
# search <project_slug> <query> — Busca issues por texto
|
|
13
|
-
# resolve <issue_id> — Resuelve un issue
|
|
14
|
-
# ignore <issue_id> — Ignora un issue
|
|
15
|
-
# assign <issue_id> <username> — Asigna un issue
|
|
16
|
-
|
|
17
|
-
require 'net/http'
|
|
18
|
-
require 'uri'
|
|
19
|
-
require 'json'
|
|
20
|
-
require 'openssl'
|
|
21
|
-
|
|
22
|
-
module Sentry
|
|
23
|
-
URL = ENV['SENTRY_URL'] || 'https://sentry.cloud.wispro.co'
|
|
24
|
-
TOKEN = ENV['SENTRY_TOKEN']
|
|
25
|
-
ORG = ENV['SENTRY_ORG'] || 'wispro'
|
|
26
|
-
|
|
27
|
-
class << self
|
|
28
|
-
def run(args)
|
|
29
|
-
unless TOKEN
|
|
30
|
-
puts "ERROR: SENTRY_TOKEN no configurado en el entorno."
|
|
31
|
-
exit 1
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
command = args.shift
|
|
35
|
-
case command
|
|
36
|
-
when 'projects' then projects
|
|
37
|
-
when 'issues' then issues(args)
|
|
38
|
-
when 'issue' then issue(args.first)
|
|
39
|
-
when 'events' then events(args)
|
|
40
|
-
when 'search' then search(args)
|
|
41
|
-
when 'resolve' then update_status(args.first, 'resolved')
|
|
42
|
-
when 'ignore' then update_status(args.first, 'ignored')
|
|
43
|
-
when 'assign' then assign(args[0], args[1])
|
|
44
|
-
else
|
|
45
|
-
puts USAGE
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
private
|
|
50
|
-
|
|
51
|
-
USAGE = <<~TEXT
|
|
52
|
-
Uso: ruby sentry.rb <comando> [opciones]
|
|
53
|
-
|
|
54
|
-
Comandos:
|
|
55
|
-
projects Lista todos los proyectos
|
|
56
|
-
issues <project_slug> [--period=24h] Lista issues (default: 24h, unresolved)
|
|
57
|
-
issue <issue_id> Detalle de un issue
|
|
58
|
-
events <issue_id> [--full] Eventos de un issue
|
|
59
|
-
search <project_slug> <query> Busca issues por texto
|
|
60
|
-
resolve <issue_id> Resuelve un issue
|
|
61
|
-
ignore <issue_id> Ignora un issue
|
|
62
|
-
assign <issue_id> <username> Asigna un issue
|
|
63
|
-
TEXT
|
|
64
|
-
|
|
65
|
-
def projects
|
|
66
|
-
data = get("/organizations/#{ORG}/projects/")
|
|
67
|
-
data.each do |p|
|
|
68
|
-
puts " #{p['slug'].ljust(30)} #{p['name']}"
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def issues(args)
|
|
73
|
-
slug = args.shift
|
|
74
|
-
period = extract_flag(args, '--period') || '24h'
|
|
75
|
-
|
|
76
|
-
data = get("/projects/#{ORG}/#{slug}/issues/?statsPeriod=#{period}&query=is:unresolved")
|
|
77
|
-
print_issues(data)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def issue(issue_id)
|
|
81
|
-
data = get("/organizations/#{ORG}/issues/#{issue_id}/")
|
|
82
|
-
puts " ##{data['shortId']} — #{data['title']}"
|
|
83
|
-
puts " Level: #{data['level']} | Count: #{data['count']} | Users: #{data['userCount']}"
|
|
84
|
-
puts " First: #{data['firstSeen']} | Last: #{data['lastSeen']}"
|
|
85
|
-
puts " Status: #{data['status']}"
|
|
86
|
-
puts " Assigned: #{data.dig('assignedTo', 'name') || 'nadie'}"
|
|
87
|
-
puts " Link: #{data['permalink']}"
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def events(args)
|
|
91
|
-
issue_id = args.shift
|
|
92
|
-
full = args.include?('--full')
|
|
93
|
-
params = full ? '?full=true&limit=3' : '?limit=5'
|
|
94
|
-
|
|
95
|
-
data = get("/organizations/#{ORG}/issues/#{issue_id}/events/#{params}")
|
|
96
|
-
data.each do |event|
|
|
97
|
-
puts "\n Event #{event['eventID'][0..7]} — #{event['dateCreated']}"
|
|
98
|
-
puts " #{event['title']}"
|
|
99
|
-
|
|
100
|
-
next unless full && event['entries']
|
|
101
|
-
|
|
102
|
-
event['entries'].each do |entry|
|
|
103
|
-
next unless entry['type'] == 'exception'
|
|
104
|
-
|
|
105
|
-
entry.dig('data', 'values')&.each do |exc|
|
|
106
|
-
puts " Exception: #{exc['type']}: #{exc['value']}"
|
|
107
|
-
frames = exc.dig('stacktrace', 'frames') || []
|
|
108
|
-
frames.select { |f| f['inApp'] }.last(5).each do |frame|
|
|
109
|
-
puts " #{frame['filename']}:#{frame['lineNo']} in #{frame['function']}"
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def search(args)
|
|
117
|
-
slug = args.shift
|
|
118
|
-
query = args.join(' ')
|
|
119
|
-
data = get("/projects/#{ORG}/#{slug}/issues/?query=#{URI.encode_www_form_component(query)}&statsPeriod=24h")
|
|
120
|
-
print_issues(data)
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def update_status(issue_id, status)
|
|
124
|
-
data = put("/organizations/#{ORG}/issues/#{issue_id}/", { status: status })
|
|
125
|
-
puts " Issue ##{issue_id} → #{data['status']}"
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def assign(issue_id, username)
|
|
129
|
-
data = put("/organizations/#{ORG}/issues/#{issue_id}/", { assignedTo: username })
|
|
130
|
-
puts " Issue ##{issue_id} → asignado a #{data.dig('assignedTo', 'name') || username}"
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def print_issues(data)
|
|
134
|
-
if data.empty?
|
|
135
|
-
puts " Sin issues encontrados."
|
|
136
|
-
return
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
data.each do |i|
|
|
140
|
-
level = i['level'].upcase.ljust(7)
|
|
141
|
-
count = "x#{i['count']}".ljust(6)
|
|
142
|
-
puts " #{level} #{count} ##{i['shortId'].ljust(15)} #{i['title'][0..80]}"
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
# --- HTTP ---
|
|
147
|
-
|
|
148
|
-
def get(path)
|
|
149
|
-
request(:get, path)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def put(path, body)
|
|
153
|
-
request(:put, path, body)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def request(method, path, body = nil)
|
|
157
|
-
uri = URI.parse("#{URL}/api/0#{path}")
|
|
158
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
159
|
-
http.use_ssl = uri.scheme == 'https'
|
|
160
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
161
|
-
|
|
162
|
-
req = case method
|
|
163
|
-
when :get then Net::HTTP::Get.new(uri)
|
|
164
|
-
when :put then Net::HTTP::Put.new(uri)
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
req['Authorization'] = "Bearer #{TOKEN}"
|
|
168
|
-
req['Content-Type'] = 'application/json'
|
|
169
|
-
req.body = JSON.generate(body) if body
|
|
170
|
-
|
|
171
|
-
response = http.request(req)
|
|
172
|
-
|
|
173
|
-
unless response.code.start_with?('2')
|
|
174
|
-
puts " ERROR: HTTP #{response.code} — #{response.body[0..200]}"
|
|
175
|
-
exit 1
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
JSON.parse(response.body)
|
|
179
|
-
rescue StandardError => e
|
|
180
|
-
puts " ERROR: #{e.message}"
|
|
181
|
-
exit 1
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
def extract_flag(args, flag)
|
|
185
|
-
idx = args.index { |a| a.start_with?(flag) }
|
|
186
|
-
return nil unless idx
|
|
187
|
-
|
|
188
|
-
value = args.delete_at(idx)
|
|
189
|
-
value.include?('=') ? value.split('=', 2).last : args.delete_at(idx)
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
Sentry.run(ARGV.dup) if __FILE__ == $PROGRAM_NAME
|