Post

Jira

Dejo esqueletos para scripts en bash, para interactuar con la api de Jira

El primero descubre el serviceDeskId a partir del project key Pagina todas tus requests del service desk. Saca las issueKey a un fichero (una por línea).

get_jsm_keys.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#!/usr/bin/env bash
set -euo pipefail

# === Config por defecto (mismo estilo que usas) ===
JIRA_SITE="${JIRA_SITE:-armoniadelruido.atlassian.net}"
JIRA_EMAIL="${JIRA_EMAIL:-tu_email@dominio.com}"
JIRA_TOKEN="${JIRA_TOKEN:-PON_AQUI_TU_TOKEN}"
PROJECT_KEY="${PROJECT_KEY:-SOC}"      # clave del proyecto JSM, ej. ABK
OUT_FILE="${OUT_FILE:-soc_keys.txt}"   # salida con issueKeys (una por línea)

# (Opcional) cargar desde fichero si le pasas ruta como 1er arg
if [[ $# -ge 1 && -f "$1" ]]; then
  echo "Leyendo configuración desde: $1" >&2
  set -a
  # shellcheck disable=SC1090
  . <(grep -E '^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*=' "$1" | sed -E 's/^[[:space:]]+//')
  set +a
fi

need() { command -v "$1" >/dev/null 2>&1 || { echo "Falta: $1" >&2; exit 2; }; }
need curl; need jq

echo " Buscando serviceDeskId para proyecto ${PROJECT_KEY}..." >&2
SD_ID="$(
  curl -sS -u "${JIRA_EMAIL}:${JIRA_TOKEN}" -H "Accept: application/json" \
    "https://${JIRA_SITE}/rest/servicedeskapi/servicedesk" \
  | jq -r --arg PK "$PROJECT_KEY" '.values[] | select(.projectKey==$PK) | .id' | head -n1
)"

if [[ -z "${SD_ID:-}" ]]; then
  echo " No se encontró serviceDeskId para ${PROJECT_KEY}. ¿Es un proyecto JSM?" >&2
  exit 1
fi

echo " serviceDeskId=${SD_ID}" >&2
: > "${OUT_FILE}"

START=0
LIMIT=50
TOTAL=0
FOUND=0

echo "  Listando requests… (puede tardar)" >&2
while :; do
  R="$(
    curl -sS -u "${JIRA_EMAIL}:${JIRA_TOKEN}" -H "Accept: application/json" \
      "https://${JIRA_SITE}/rest/servicedeskapi/request?serviceDeskId=${SD_ID}&start=${START}&limit=${LIMIT}"
  )"

  CNT="$(echo "$R" | jq '.values | length')"
  (( CNT == 0 )) && break

  echo "$R" | jq -r '.values[].issueKey' >> "${OUT_FILE}"

  (( FOUND += CNT ))
  (( START += CNT ))

  echo "… acumuladas ${FOUND} claves" >&2
done

┌# Quitar posibles duplicados
sort -u "${OUT_FILE}" -o "${OUT_FILE}"

echo " Hecho. Claves: ${FOUND}. Guardadas en: ${OUT_FILE}"

El segundo hace la magia: jira_tool.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
#!/usr/bin/env bash
set -euo pipefail

# ==========================
#  Configuración por defecto
# ==========================
JIRA_SITE_DEFAULT="armoniadelruido.atlassian.net"
JIRA_EMAIL_DEFAULT="tu_email@dominio.com"
JIRA_TOKEN_DEFAULT="PON_AQUI_TU_TOKEN"
TZ_OUTPUT_DEFAULT="Europe/Andorra"
PAGE_SIZE_DEFAULT=50

# Rutas posibles del archivo de configuración (usa la primera que exista)
CONFIG_PATHS=(
  "${1:-}"                       # si pasas un fichero de config como primer arg
  "${HOME}/.config/jira-cli/.env"
  "${HOME}/.jira.env"
)

usage() {
  cat <<'EOF'
Uso:
  # Modo ISSUE ÚNICO (salida en pantalla como antes)
  jira_tool.sh [RUTA_CONFIG] --issue ISSUE_KEY

  # Modo JQL → CSV
  jira_tool.sh [RUTA_CONFIG] --jql 'JQL...' [--out fichero.csv]

  # Modo CLAVES → CSV (una clave por línea en un fichero)
  jira_tool.sh [RUTA_CONFIG] --keys fichero.txt [--out fichero.csv]

Ejemplos:
  jira_tool.sh --issue ABK-1
  jira_tool.sh ~/.config/jira-cli/.env --issue ABK-1
  jira_tool.sh --jql 'project=SOC AND created >= startOfMonth(-1) AND created < startOfMonth()' --out soc_mes_pasado.csv
  jira_tool.sh --keys abk_keys.txt --out abk.csv

Campos del CSV:
  issue_key, created_iso, first_comment_iso, first_comment_body,
  second_comment_iso, second_comment_body, diff_created_to_first_min, diff_first_to_second_min
EOF
  exit 1
}

need_bin() { command -v "$1" >/dev/null 2>&1 || { echo "Falta: $1" >&2; exit 2; }; }
normalize_iso() { echo "$1" | sed -E 's/([0-9]{2})([0-9]{2})$/\1:\2/'; }

load_config_file() {
  local file="$1"
  [[ -f "$file" ]] || return 1
  echo "Leyendo configuración desde: $file" >&2
  set -a
  # shellcheck disable=SC1090
  . <(grep -E '^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*=' "$file" | sed -E 's/^[[:space:]]+//')
  set +a
}

# Comentario “simple”: intenta texto directo; si no, body entero (sin ADF complejo)
extract_comment_text_simple() {
  # $1: JSON del comentario
  echo "$1" | jq -r '.body.content[0].content[0].text // .body // ""'
}

# Requisitos
need_bin curl; need_bin jq; need_bin date

# --------------------------
# Parse de argumentos
# --------------------------
CFG_FILE=""
ISSUE_KEY=""
JQL=""
KEYS_FILE=""
OUT=""
PAGE_SIZE="${PAGE_SIZE:-$PAGE_SIZE_DEFAULT}"

while [[ $# -gt 0 ]]; do
  case "$1" in
    --issue)     shift; ISSUE_KEY="${1:-}";;
    --jql)       shift; JQL="${1:-}";;
    --keys)      shift; KEYS_FILE="${1:-}";;
    --out)       shift; OUT="${1:-}";;
    -*|--help)   usage;;
    *)
      if [[ -z "$CFG_FILE" && -f "$1" ]]; then CFG_FILE="$1"; else usage; fi
      ;;
  esac
  shift || true
done

# Sólo uno de los modos
if { [[ -n "$ISSUE_KEY" ]] && { [[ -n "${JQL:-}" ]] || [[ -n "${KEYS_FILE:-}" ]]; }; } || \
   { [[ -n "${JQL:-}" ]] && [[ -n "${KEYS_FILE:-}" ]]; } || \
   { [[ -z "$ISSUE_KEY" ]] && [[ -z "${JQL:-}" ]] && [[ -z "${KEYS_FILE:-}" ]]; }; then
  usage
fi

# --------------------------
# Cargar configuración
# --------------------------
if [[ -n "$CFG_FILE" && -f "$CFG_FILE" ]]; then load_config_file "$CFG_FILE"; fi
if [[ -z "${JIRA_SITE:-}" || -z "${JIRA_EMAIL:-}" || -z "${JIRA_TOKEN:-}" ]]; then
  for p in "${CONFIG_PATHS[@]}"; do
    [[ -n "$p" && -f "$p" ]] && load_config_file "$p" && break
  done
fi
JIRA_SITE="${JIRA_SITE:-$JIRA_SITE_DEFAULT}"
JIRA_EMAIL="${JIRA_EMAIL:-$JIRA_EMAIL_DEFAULT}"
JIRA_TOKEN="${JIRA_TOKEN:-$JIRA_TOKEN_DEFAULT}"
TZ_OUTPUT="${TZ_OUTPUT:-$TZ_OUTPUT_DEFAULT}"

# --------------------------
# Funciones principales
# --------------------------
get_issue_core_json() {
  # $1: issue key → JSON con {key, created}
  local key="$1"
  local url="https://${JIRA_SITE}/rest/api/3/issue/${key}?fields=created"
  local js
  js="$(curl -sS -u "${JIRA_EMAIL}:${JIRA_TOKEN}" -H "Accept: application/json" "$url")"
  local created
  created="$(echo "$js" | jq -r '.fields.created')"
  [[ "$created" == "null" || -z "$created" ]] && return 1
  jq -n --arg key "$key" --arg created "$created" '{key:$key, created:$created}'
}

get_issue_comments_sorted_json() {
  # $1: issue key → JSON array de comentarios ordenados por .created
  local key="$1"
  local comments_url="https://${JIRA_SITE}/rest/api/3/issue/${key}/comment"
  local start=0 max=100 tmp
  tmp="$(mktemp)"; > "$tmp"
  while : ; do
    local resp
    resp="$(curl -sS -u "${JIRA_EMAIL}:${JIRA_TOKEN}" -H "Accept: application/json" \
      "${comments_url}?startAt=${start}&maxResults=${max}")"
    local total count
    total="$(echo "$resp" | jq -r '.total // 0')"
    count="$(echo "$resp" | jq -r '.comments | length')"
    (( total == 0 )) && break
    echo "$resp" | jq -c '.comments[]' >> "$tmp"
    (( start += count ))
    (( start >= total )) && break
  done
  if [[ ! -s "$tmp" ]]; then
    echo "[]" && rm -f "$tmp" && return 0
  fi
  jq -s '[.[]] | sort_by(.created)' "$tmp"
  rm -f "$tmp"
}

row_json_for_issue() {
  # $1: issue key → emite una fila JSON con todos los campos del CSV
  local key="$1"
  local core
  if ! core="$(get_issue_core_json "$key")"; then
    jq -n --arg key "$key" \
      '{issue_key:$key, created_iso:"", first_comment_iso:"", first_comment_body:"", second_comment_iso:"", second_comment_body:"", diff_created_to_first_min:null, diff_first_to_second_min:null}'
    return 0
  fi
  local created_iso created_norm created_epoch
  created_iso="$(echo "$core" | jq -r '.created')"
  created_norm="$(normalize_iso "$created_iso")"
  created_epoch="$(date -d "$created_norm" +%s)"

  local arr first_iso first_body second_iso second_body
  arr="$(get_issue_comments_sorted_json "$key")"

  local len; len="$(echo "$arr" | jq 'length')"
  if (( len == 0 )); then
    jq -n --arg key "$key" --arg created "$created_iso" \
      '{issue_key:$key, created_iso:$created, first_comment_iso:"", first_comment_body:"", second_comment_iso:"", second_comment_body:"", diff_created_to_first_min:null, diff_first_to_second_min:null}'
    return 0
  fi

  first_iso="$(echo "$arr" | jq -r '.[0].created')"
  first_body="$(extract_comment_text_simple "$(echo "$arr" | jq -c '.[0]')")"

  local first_norm first_epoch
  first_norm="$(normalize_iso "$first_iso")"
  first_epoch="$(date -d "$first_norm" +%s)"

  local diff1_sec diff1_min
  diff1_sec=$(( first_epoch - created_epoch ))
  (( diff1_sec < 0 )) && diff1_sec=$(( -diff1_sec ))
  diff1_min=$(( diff1_sec / 60 ))

  if (( len >= 2 )); then
    second_iso="$(echo "$arr" | jq -r '.[1].created')"
    second_body="$(extract_comment_text_simple "$(echo "$arr" | jq -c '.[1]')")"
    local second_norm second_epoch diff2_sec diff2_min
    second_norm="$(normalize_iso "$second_iso")"
    second_epoch="$(date -d "$second_norm" +%s)"
    diff2_sec=$(( second_epoch - first_epoch ))
    (( diff2_sec < 0 )) && diff2_sec=$(( -diff2_sec ))
    diff2_min=$(( diff2_sec / 60 ))
  else
    second_iso=""; second_body=""; diff2_min=
  fi

  jq -n \
    --arg key "$key" \
    --arg created "$created_iso" \
    --arg first_iso "$first_iso" \
    --arg first_body "$first_body" \
    --arg second_iso "$second_iso" \
    --arg second_body "$second_body" \
    --argjson diff1_min "$diff1_min" \
    --argjson diff2_min "${diff2_min:-null}" \
    '{issue_key:$key, created_iso:$created, first_comment_iso:$first_iso, first_comment_body:$first_body, second_comment_iso:$second_iso, second_comment_body:$second_body, diff_created_to_first_min:$diff1_min, diff_first_to_second_min:$diff2_min}'
}

# ==========================
#  MODO ISSUE ÚNICO
# ==========================
if [[ -n "$ISSUE_KEY" ]]; then
  core_json="$(get_issue_core_json "$ISSUE_KEY")" || { echo " No se pudo obtener el issue ${ISSUE_KEY}."; exit 1; }
  created_iso="$(echo "$core_json" | jq -r '.created')"
  created_norm="$(normalize_iso "$created_iso")"
  created_epoch="$(date -d "$created_norm" +%s)"

  arr="$(get_issue_comments_sorted_json "$ISSUE_KEY")"
  len="$(echo "$arr" | jq 'length')"

  echo " Ticket: ${ISSUE_KEY}"
  echo " Creado (ISO):             ${created_iso}"

  if (( len == 0 )); then
    echo " No hay comentarios en este ticket."
    exit 0
  fi

  first_iso="$(echo "$arr" | jq -r '.[0].created')"
  first_norm="$(normalize_iso "$first_iso")"
  first_epoch="$(date -d "$first_norm" +%s)"
  first_body="$(extract_comment_text_simple "$(echo "$arr" | jq -c '.[0]')")"

  diff1_sec=$(( first_epoch - created_epoch ))
  (( diff1_sec < 0 )) && diff1_sec=$(( -diff1_sec ))
  diff1_min=$(( diff1_sec / 60 ))
  diff1_hr=$(awk "BEGIN {printf \"%.2f\", $diff1_sec/3600}")

  echo " 1er comentario (ISO):     ${first_iso}"
  echo "  Creación → 1º:          ${diff1_min} min (${diff1_hr} h)"
  echo ""
  echo " Zona local (${TZ_OUTPUT}):"
  echo "   Creado:                   $(TZ=${TZ_OUTPUT} date -d "${created_norm}" '+%Y-%m-%d %H:%M:%S %Z')"
  echo "   1er comentario:           $(TZ=${TZ_OUTPUT} date -d "${first_norm}" '+%Y-%m-%d %H:%M:%S %Z')"
  echo ""
  echo " Contenido 1er comentario:"
  echo "-----------------------------------"
  echo "${first_body}"
  echo "-----------------------------------"

  if (( len >= 2 )); then
    second_iso="$(echo "$arr" | jq -r '.[1].created')"
    second_norm="$(normalize_iso "$second_iso")"
    second_epoch="$(date -d "$second_norm" +%s)"
    second_body="$(extract_comment_text_simple "$(echo "$arr" | jq -c '.[1]')")"
    diff2_sec=$(( second_epoch - first_epoch ))
    (( diff2_sec < 0 )) && diff2_sec=$(( -diff2_sec ))
    diff2_min=$(( diff2_sec / 60 ))
    diff2_hr=$(awk "BEGIN {printf \"%.2f\", $diff2_sec/3600}")

    echo ""
    echo " 2º comentario (ISO):      ${second_iso}"
    echo "  1º → 2º:                ${diff2_min} min (${diff2_hr} h)"
    echo "   2º comentario (local):    $(TZ=${TZ_OUTPUT} date -d "${second_norm}" '+%Y-%m-%d %H:%M:%S %Z')"
    echo ""
    echo " Contenido 2º comentario:"
    echo "-----------------------------------"
    echo "${second_body}"
    echo "-----------------------------------"
  else
    echo ""
    echo " No hay segundo comentario en este ticket."
  fi

  exit 0
fi

# ==========================
#  MODO KEYS → CSV
# ==========================
if [[ -n "${KEYS_FILE:-}" ]]; then
  [[ -f "$KEYS_FILE" ]] || { echo " No existe --keys $KEYS_FILE" >&2; exit 1; }
  ROWS_JSON="$(mktemp)"; trap 'rm -f "${ROWS_JSON:-}"' EXIT
  > "$ROWS_JSON"

  while IFS= read -r k; do
    k_clean="$(echo "$k" | tr -d '\r' | xargs)"
    [[ -z "$k_clean" ]] && continue
    row_json_for_issue "$k_clean" >> "$ROWS_JSON"
  done < "$KEYS_FILE"

  CSV_HEADER='["issue_key","created_iso","first_comment_iso","first_comment_body","second_comment_iso","second_comment_body","diff_created_to_first_min","diff_first_to_second_min"]'
  if [[ -n "${OUT:-}" ]]; then
    jq -r --slurp \
      "$CSV_HEADER, (.[] | [ .issue_key, .created_iso, .first_comment_iso, .first_comment_body, .second_comment_iso, .second_comment_body, (.diff_created_to_first_min|tostring), (if .diff_first_to_second_min==null then \"\" else (.diff_first_to_second_min|tostring) end) ]) | @csv" \
      "$ROWS_JSON" > "$OUT"
    echo "✔ CSV generado en: $OUT"
  else
    jq -r --slurp \
      "$CSV_HEADER, (.[] | [ .issue_key, .created_iso, .first_comment_iso, .first_comment_body, .second_comment_iso, .second_comment_body, (.diff_created_to_first_min|tostring), (if .diff_first_to_second_min==null then \"\" else (.diff_first_to_second_min|tostring) end) ]) | @csv" \
      "$ROWS_JSON"
  fi
  exit 0
fi

# ==========================
#  MODO JQL → CSV (si no hubo --keys)
# ==========================
ISSUES_TMP="$(mktemp)"
trap 'rm -f "${ISSUES_TMP:-}" "${ROWS_JSON:-}" "${CMT_TMP:-}"' EXIT

start_at=0
while : ; do
  resp="$(curl -sS -u "${JIRA_EMAIL}:${JIRA_TOKEN}" -H "Accept: application/json" \
    --get \
    --data-urlencode "jql=${JQL}" \
    --data-urlencode "fields=key,created" \
    --data-urlencode "maxResults=${PAGE_SIZE}" \
    --data-urlencode "startAt=${start_at}" \
    "https://${JIRA_SITE}/rest/api/3/search")"
  count="$(echo "$resp" | jq -r '.issues | length')"
  total="$(echo "$resp" | jq -r '.total')"
  [[ "$count" -eq 0 ]] && break
  echo "$resp" | jq -c '.issues[] | {key: .key, created: .fields.created}' >> "$ISSUES_TMP"
  (( start_at += count ))
  (( start_at >= total )) && break
done

CSV_HEADER='["issue_key","created_iso","first_comment_iso","first_comment_body","second_comment_iso","second_comment_body","diff_created_to_first_min","diff_first_to_second_min"]'
ROWS_JSON="$(mktemp)"; > "$ROWS_JSON"

if [[ -s "$ISSUES_TMP" ]]; then
  while IFS= read -r line; do
    key="$(echo "$line" | jq -r '.key')"
    row_json_for_issue "$key" >> "$ROWS_JSON"
  done < "$ISSUES_TMP"
fi

if [[ -n "$OUT" ]]; then
  jq -r --slurp \
    "$CSV_HEADER, (.[] | [ .issue_key, .created_iso, .first_comment_iso, .first_comment_body, .second_comment_iso, .second_comment_body, (.diff_created_to_first_min|tostring), (if .diff_first_to_second_min==null then \"\" else (.diff_first_to_second_min|tostring) end) ]) | @csv" \
    "$ROWS_JSON" > "$OUT"
  echo "✔ CSV generado en: $OUT"
else
  jq -r --slurp \
    "$CSV_HEADER, (.[] | [ .issue_key, .created_iso, .first_comment_iso, .first_comment_body, .second_comment_iso, .second_comment_body, (.diff_created_to_first_min|tostring), (if .diff_first_to_second_min==null then \"\" else (.diff_first_to_second_min|tostring) end) ]) | @csv" \
    "$ROWS_JSON"
fi


##Cómo usar ahora

Genera las claves (si tu proyecto es JSM):

1
2
3
4
./get_jsm_keys.sh             # o con tu .env:  PROJECT_KEY=ABK OUT_FILE=abk_keys.txt ./get_jsm_keys.sh ~/.config/jira-cli/.env

chmod +x jira_tool.sh
./jira_tool.sh --keys abk_keys.txt --out abk.csv

##Lo mejoramos pudiendo seleccionar tiempo:

get_jsm_keys.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#!/usr/bin/env bash
set -euo pipefail

# ==========================
# Config por defecto
# ==========================
JIRA_SITE="${JIRA_SITE:-armoniadelruido.atlassian.net}"
JIRA_EMAIL="${JIRA_EMAIL:-tu_email@dominio.com}"
JIRA_TOKEN="${JIRA_TOKEN:-PON_AQUI_TU_TOKEN}"
PROJECT_KEY="${PROJECT_KEY:-SOC}"          # clave del proyecto JSM
OUT_FILE="${OUT_FILE:-soc_keys.txt}"       # salida con issueKeys
MONTH_MODE="${MONTH_MODE:-}"               # "current" | "prev" | "" (sin filtro por mes)
FROM_DATE="${FROM_DATE:-}"                 # YYYY-MM-DD opcional
TO_DATE="${TO_DATE:-}"                     # YYYY-MM-DD opcional (exclusivo)
LIMIT="${LIMIT:-50}"                       # paginado

usage() {
  cat <<EOF
Uso:
  # Mes en curso
  MONTH_MODE=current PROJECT_KEY=SOC OUT_FILE=soc_keys.txt ./get_jsm_keys.sh ~/.config/jira-cli/.env

  # Mes anterior
  MONTH_MODE=prev PROJECT_KEY=SOC OUT_FILE=abk_keys_prev.txt ./get_jsm_keys.sh ~/.config/jira-cli/.env

  # Rango explícito (incluye FROM, excluye TO)
  FROM_DATE=2025-10-01 TO_DATE=2025-11-01 PROJECT_KEY=SOC ./get_jsm_keys.sh ~/.config/jira-cli/.env

Notas:
- Prioridad del filtro: FROM/TO > MONTH_MODE > sin filtro (todos).
- Después usa:  ./jira_tool.sh --keys soc_keys.txt --out soc.csv


Ejemplos:


MONTH_MODE=current PROJECT_KEY=SOC OUT_FILE=soc_keys.txt ./get_jsm_keys.sh ~/.config/jira-cli/.env
MONTH_MODE=prev PROJECT_KEY=SOC OUT_FILE=soc_keys_prev.txt ./get_jsm_keys.sh ~/.config/jira-cli/.env
FROM_DATE=2025-10-01 TO_DATE=2025-11-01 PROJECT_KEY=SOC OUT_FILE=soc_oct.txt ./get_jsm_keys.sh ~/.config/jira-cli/.env



EOF
  exit 1
}

# Cargar env desde fichero si se pasa como 1er arg
if [[ $# -ge 1 && -f "$1" ]]; then
  echo "Leyendo configuración desde: $1" >&2
  set -a
  # shellcheck disable=SC1090
  . <(grep -E '^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*=' "$1" | sed -E 's/^[[:space:]]+//')
  set +a
fi

need() { command -v "$1" >/dev/null 2>&1 || { echo "Falta: $1" >&2; exit 2; }; }
need curl; need jq; need date

# ==========================
# Calcular ventana temporal
# ==========================
# Normalizamos a epoch (UTC). TO es EXCLUSIVO.
epoch_of_day_start() { date -u -d "$1 00:00:00" +%s; }

if [[ -n "$FROM_DATE" || -n "$TO_DATE" ]]; then
  [[ -z "$FROM_DATE" || -z "$TO_DATE" ]] && usage
  START_EPOCH="$(epoch_of_day_start "$FROM_DATE")"
  END_EPOCH="$(epoch_of_day_start "$TO_DATE")"
elif [[ "$MONTH_MODE" == "current" ]]; then
  # [primer día del mes actual, primer día del mes siguiente)
  FIRST_THIS_MONTH="$(date -u +%Y-%m-01)"
  FIRST_NEXT_MONTH="$(date -u -d "$FIRST_THIS_MONTH +1 month" +%Y-%m-01)"
  START_EPOCH="$(epoch_of_day_start "$FIRST_THIS_MONTH")"
  END_EPOCH="$(epoch_of_day_start "$FIRST_NEXT_MONTH")"
elif [[ "$MONTH_MODE" == "prev" ]]; then
  # [primer día del mes anterior, primer día del mes actual)
  FIRST_THIS_MONTH="$(date -u +%Y-%m-01)"
  FIRST_PREV_MONTH="$(date -u -d "$FIRST_THIS_MONTH -1 month" +%Y-%m-01)"
  START_EPOCH="$(epoch_of_day_start "$FIRST_PREV_MONTH")"
  END_EPOCH="$(epoch_of_day_start "$FIRST_THIS_MONTH")"
else
  START_EPOCH=0
  END_EPOCH=9999999999
fi

echo "Filtro fechas (UTC epoch): $START_EPOCH .. $END_EPOCH (TO exclusivo)" >&2

# ==========================
# Encontrar serviceDeskId del proyecto
# ==========================
echo " Buscando serviceDeskId para ${PROJECT_KEY}..." >&2
SD_ID="$(
  curl -sS -u "${JIRA_EMAIL}:${JIRA_TOKEN}" -H "Accept: application/json" \
    "https://${JIRA_SITE}/rest/servicedeskapi/servicedesk" \
  | jq -r --arg PK "$PROJECT_KEY" '.values[] | select(.projectKey==$PK) | .id' | head -n1
)"
[[ -z "${SD_ID:-}" ]] && { echo " No se encontró serviceDeskId para ${PROJECT_KEY}." >&2; exit 1; }
echo " serviceDeskId=${SD_ID}" >&2

# ==========================
# Paginado y filtrado
# ==========================
: > "${OUT_FILE}"
START=0
FOUND=0
KEPT=0
echo "  Listando y filtrando por fechas…" >&2
while :; do
  R="$(
    curl -sS -u "${JIRA_EMAIL}:${JIRA_TOKEN}" -H "Accept: application/json" \
      "https://${JIRA_SITE}/rest/servicedeskapi/request?serviceDeskId=${SD_ID}&start=${START}&limit=${LIMIT}"
  )"

  CNT="$(echo "$R" | jq '.values | length')"
  (( CNT == 0 )) && break

  # Para cada request, miramos createdDate
  while IFS= read -r row; do
    issueKey="$(echo "$row" | jq -r '.issueKey')"
    created="$(echo "$row" | jq -r '.createdDate')"
    # Normalizar a formato con +00:00 para date -d
    norm="$(echo "$created" | sed -E 's/([0-9]{2})([0-9]{2})$/\1:\2/')"
    ce="$(date -u -d "$norm" +%s 2>/dev/null || echo 0)"
    (( ce >= START_EPOCH && ce < END_EPOCH )) && { echo "$issueKey" >> "${OUT_FILE}"; (( KEPT++ )); }
    (( FOUND++ ))
  done < <(echo "$R" | jq -c '.values[]')

  (( START += CNT ))
  echo "… procesadas ${FOUND}, guardadas ${KEPT}" >&2
done

sort -u "${OUT_FILE}" -o "${OUT_FILE}"
echo " Hecho. Claves guardadas: ${KEPT}. Archivo: ${OUT_FILE}"
This post is licensed under CC BY 4.0 by the author.