blob: 31c22cea3a25aec4849821e350ad2fd8f95fbee1 [file] [log] [blame]
Joey Armstrong3134bfd2024-02-10 20:51:25 -05001#!/bin/bash
2## --------------------------------------------------------------------
3## Intent: Construct a jira ticket query with attributes
4## --------------------------------------------------------------------
5
6# set -euo pipefail
7#source ~/.sandbox/trainlab-common/common.sh '--common-args-begin--'
8
9##-------------------##
10##---] GLOBALS [---##
11##-------------------##
12declare -g -a text=()
13declare -g -a text_and=()
14declare -g -a text_or=()
15
16declare -g -a urls_raw=()
17declare -g -a urls_filt=()
18
19declare -g -a labels_incl=()
20declare -g -a labels_excl=()
21
22declare -g -a projects=()
23
24path="$(realpath $0 --canonicalize-existing)"
25source "${path%\.sh}/utils.sh"
26source "$pgmlib/fixversion.sh"
27source "$pgmlib/resolved.sh"
28
29## --------------------------------------------------------------------
30## --------------------------------------------------------------------
31function error()
32{
33 echo "ERROR ${FUNCNAME[1]}: $@"
34 exit 1
35}
36
37## -----------------------------------------------------------------------
38## -----------------------------------------------------------------------
39function html_encode()
40{
41 local -n ref=$1; shift
42 local tmp="$ref"
43
44 tmp="${tmp//[[:space:]]/%20}"
45 tmp="${tmp//\"/%22}"
46 tmp="${tmp//\'/%27}"
47
48 ref="$tmp"
49 return
50}
51
52## -----------------------------------------------------------------------
53## Intent: Insert a conjunction into the stream when prior statements exist
54## -----------------------------------------------------------------------
55function conjunction()
56{
57 return
58
59 local -n ref=$1; shift
60 [[ $# -gt 0 ]] && { local literal="$1"; shift; }
61
62 ## -------------------------------
63 ## Conjunction if prior statements
64 ## -------------------------------
65 if [ ${#ref[@]} -gt 0 ]; then
66 if [[ -v literal ]]; then
67 ref+=("$literal")
68 elif [[ -v bool_and ]]; then
69 ref+=('AND')
70 else
71 ref+=('OR')
72 fi
73 fi
74
75 return
76}
77
78## -----------------------------------------------------------------------
79## Intent: Helper method
80## -----------------------------------------------------------------------
81## Usage : local path="$(join_by '/' 'lib' "${fields[@]}")"
82## -----------------------------------------------------------------------
83function join_by()
84{
85 local d=${1-} f=${2-}; if shift 2; then printf %s "$f" "${@/#/$d}"; fi;
86}
87
88## --------------------------------------------------------------------
89## Intent: Query by component name filter
90## --------------------------------------------------------------------
91## Value: helm-charts
92## --------------------------------------------------------------------
93function do_components()
94{
95 declare -n args=$1; shift
96 declare -n ans=$1; shift
97
98 # [ -z ${args+word} ] && { args=(); }
99
100 if [[ ${#args[@]} -gt 0 ]]; then
101
102 local modifier
103 if [[ -v bool_not ]]; then
104 modifier='NOT IN'
105 else
106 modifier='IN'
107 fi
108 ans+=("component ${modifier} (${args[@]})")
109 # alt: comp='foo' OR comp='bar'
110 fi
111
112 return
113}
114
115## --------------------------------------------------------------------
116## Intent: Query filter by labels assigned to a ticket:
117## o pods, failing, testing
118## --------------------------------------------------------------------
119# "project in (UKSCR, COMPRG) AND issuetype = Bug AND labels in (BAT)" and
120## --------------------------------------------------------------------
121function do_labels()
122{
123 declare -n incl=$1; shift # was args=
124 declare -n excl=$1; shift
125 declare -n ans=$1; shift
126
127 ## --------------------------------
128 ## Conjunction if stream tokens > 0
129 ## --------------------------------
130 conjunction ans
131
132 declare -a tokens=()
133
134 ## -----------------------------
135 ## -----------------------------
136 if [[ ${#incl[@]} -gt 0 ]]; then
137
138 local modifier
139 if [[ -v bool_not ]]; then
140 modifier='NOT IN'
141 else
142 modifier='IN'
143 fi
144
145 local labels=$(join_by ',' "${incl[@]}")
146 local -a tmp=(\
147 '('\
148 'label IS EMPTY' \
149 'OR' \
150 "labels ${modifier} ($labels)" \
151 ')'\
152 )
153 tokens+=("${tmp[@]}")
154 fi
155
156 conjunction tokens 'AND'
157
158 ## -----------------------------
159 ## -----------------------------
160 if [[ ${#excl[@]} -gt 0 ]]; then
161 local labels=$(join_by ',' "${excl[@]}")
162 tokens+=('(' "labels NOT IN ($labels)" ')')
163 fi
164
165 ans+=("${tokens[@]}")
166 return
167}
168
169## --------------------------------------------------------------------
170## Intent: Modify search query by project type (SEBA, VOL)
171## --------------------------------------------------------------------
172function do_projects()
173{
174 declare -n ref=$1; shift
175
176 [[ ${#projects[@]} -eq 0 ]] && { return; }
177
178 local terms="$(join_by ',' "${projects[@]}")"
179# local -a buffer=('(' 'project' 'IN' "($terms)" ')')
180# ref+=("$(join_by '%20' "${buffer[@]}")")
181 ref+=("(project IN ($terms))")
182 return
183}
184
185## --------------------------------------------------------------------
186## Intent: Query by compound text filters
187## --------------------------------------------------------------------
188function do_text()
189{
190 local -n ref=$1; shift
191 local -n ans=$1; shift
192 local val
193
194 ## Accumulate
195 if [[ ${#ref[@]} -gt 0 ]]; then
196
197 if [[ -v bool_and ]]; then
198 text_and+=("${ref[@]}")
199 else
200 text_or+=("${ref[@]}")
201 fi
202 fi
203
204 ## Append terms: AND
205 if [[ ${#text_and[@]} -gt 0 ]]; then
206 declare -a term=()
207 for val in "${text_and[@]}";
208 do
209 term+=("text ~ \"$val\"")
210 done
211 val=$(join_by ' AND ' "${term[@]}")
212 ans+=("($val)")
213 fi
214
215 ## Append terms: OR
216 if [[ ${#text_or[@]} -gt 0 ]]; then
217 declare -a term=()
218 for val in "${text_or[@]}";
219 do
220 term+=("text ~ \"$val\"")
221 done
222 val=$(join_by ' OR ' "${term[@]}")
223 ans+=("($val)")
224 fi
225
226 return
227}
228
229## --------------------------------------------------------------------
230## Intent: Query by assigned or requestor
231## --------------------------------------------------------------------
232## Note: Simple for now but support query by a list of suers
233## --------------------------------------------------------------------
234function do_user()
235{
236 declare -n ans=$1; shift
237
238 [[ -v argv_nobody ]] && return
239
240 local user='currentUser()'
241 if [[ -v argv_user ]]; then
242 user="$argv_user"
243 fi
244
245 if [[ -v argv_assigned ]]; then
246 ans+=("assignee=${user}")
247 fi
248
249 if [[ -v argv_reported ]]; then
250 ans+=("reporter=${user}")
251 fi
252
253 return
254}
255
256## --------------------------------------------------------------------
257## Intent: Combine filter arguments into a search query
258## --------------------------------------------------------------------
259function gen_filter()
260{
261 declare -n ans=$1; shift
262 declare -n args=$1; shift
263
264 ## -----------------------------------
265 ## Begin by joining major search terms
266 ## -----------------------------------
267 declare -a _tmp=()
268 local val
269 for val in "${args[@]}";
270 do
271 _tmp+=("$val" 'AND')
272 done
273 unset _tmp[-1]
274
275 ## -----------------------
276 ## Massage with html codes
277 ## -----------------------
278 ans="$(join_by '%20' "${_tmp[@]}")"
279 return
280}
281
282## --------------------------------------------------------------------
283## Intent: Combine filter arguments into a search query
284## --------------------------------------------------------------------
285function gen_url()
286{
287 declare -n ans=$1; shift
288 declare -n args=$1; shift
289
290 ## Which jira server to query (?)
291 [[ ! -v server ]] && declare -g server='jira.opennetworking.org'
292 tmp_url="https://${server}/issues/?jql="
293 tmp="${tmp_url}${args}"
294 ans="${tmp// /%20}"
295 return
296}
297
298## --------------------------------------------------------------------
299## Intent: Dispaly command usage
300## --------------------------------------------------------------------
301function usage()
302{
303 cat <<EOH
304Usage: $0 VOL-xxxx
305 --debug Enable script debug mode
306 --dry-run Simulate
307
308 VOL-{xxxx} View a jira ticket by ID
309
310[SERVER]
311 --onf jira.opennetworking.org (default)
312 --opencord jira.opencord.org
313
314[WHAT]
315 --component Search by component name assigned to ticket
316 --label Search by label name assigned to ticket.
317 --text Search string(s)
318
319[FIXVERSION] - Voltha-v2.12
320 --fixversion-incl
321 --fixversion-excl
322 --fixversion-is-empty
323 --fixversion-not-empty
324
325[RESOLVED] - tokens={Declined, Won't Fix}
326 --resolved-start ccyy-mm-dd
327 --resolved-end ccyy-mm-dd
328 --resolved-incl {token(s)}
329 --resolved-excl {token(s)}
330 --resolved-is-empty Query for open tickets
331 --resolved-not-empty
332
333[USER(s)]
334 --me Tickets assigned to or reported by me.
335 --user [u] Tickets assigned to this user.
336 --nobody Raw query, no filtering by user
337
338[BY-USER]
339 --assigned Tickets assided to user
340 --reported Tickets created by user
341
342[BOOL]
343 --and Join terms using 'AND'
344 --or Join terms using 'OR'
345
346[MEMBER]
347 --in (default) Items belong (--component IN)
348 --not-in Negate item set (--component NOT IN)
349
350[Contains]
351 --text [t] (join modifer: --and, --or)
352 --text-and [t] All of these terms
353 --text-or [t] Any of these terms
354
355[RANGE]
356 --newer [d] Search for tickets created < [n] days ago.
357 --older [d] Search for tickets created > [n] days ago.
358
359[ALIASES]
360 --all Query for all unresolved tickets
361
362[USAGE]
363 $0 --assigned
364 o Display all tickets assigned to my login
365 $0 --requested --user tux
366 o Display all tickets requested by user tux
367 $0 --reported --or --text 'bbsim' --text 'release'
368 o Search for tickets that contain strings bbsim or release
369 $0 --cord --text-and 'release' --text-and 'voltctl'
370 o Search jira.opencord for tickets that contain release and voltctl
371 $0 --text 'bitergia' --text 'Jira' -and
372 o Search jira.opennetworking for tickets containing string bitergia and Jira
373
374 $0 --cord --label failing --label pod
375 o Search jira.opencord for tests failing due to pod/hardware issuses.
376
377 $0 --proj VOL --fixversion "VOLTHA v2.12" --resolved-is-empty
378 o Query for unresolved release tickets
379EOH
380
381 return
382}
383
384## --------------------------------------------------------------------
385# classpath=$(join_by ':' "${mypath[@]}")
386## --------------------------------------------------------------------
387function join_by()
388{
389 local d=${1-} f=${2-}; if shift 2; then printf %s "$f" "${@/#/$d}"; fi;
390}
391
392##----------------##
393##---] MAIN [---##
394##----------------##
395declare -a suffix0=()
396
397# declare -g -i debug=1
398
399while [ $# -gt 0 ]; do
400
401 if [ ${#suffix0[@]} -gt 0 ]; then
402 suffix0+=('AND')
403 fi
404
405 arg="$1"; shift
406 [[ -v debug ]] && echo "** argv=[$arg] [$*]"
407
408 case "$arg" in
409
410 -*help) usage; exit 0 ;;
411
412 ##-----------------##
413 ##---] MODES [---##
414 ##-----------------##
415 -*debug) declare -g -i debug=1 ;;
416 --dry-run) declare -g -i dry_run=1 ;;
417
418 ##-------------------##
419 ##---] BY USER [---##
420 ##-------------------##
421 --assigned) declare -g -i argv_assigned=1 ;;
422 --reported) declare -g -i argv_reported=1 ;;
423 --me) declare -g -i argv_me=1 ;;
424 --nobody) declare -g -i argv_nobody=1 ;;
425 --user)
426 arg="$1"; shift
427 declare -g argv_user="$arg"
428 ;;
429
430 ##------------------##
431 ##---] SERVER [---##
432 ##------------------##
433 -*onf) declare server='jira.opennetworking.org'; error "FOUND --onf" ;;
434 -*cord) declare server='jira.opencord.org' ;;
435
436 ##---------------------##
437 ##---] SEARCH-BY [---##
438 ##---------------------##
439 --component|--comp*)
440 arg="$1"; shift
441 [[ ! -v components ]] && declare -g -a components=()
442 components+=("$arg")
443 ;;
444
445 --label-excl)
446 arg="$1"; shift
447 labels_excl+=("$arg")
448 ;;
449
450 --label|--label-incl)
451 arg="$1"; shift
452 labels_incl+=("$arg")
453 ;;
454
455 ##-----------------------##
456 ##---] Text Search [---##
457 ##-----------------------##
458 # jsearch.sh --text-and bbsim --text-and release
459 -*text-and) text_and+=("$1"); shift ;;
460 -*text-or) text_or+=("$1"); shift ;;
461
462 # % js --and --text jenkins --text cord
463 # text ~ "Jira Software"
 # [WORDs]
464 # text ~ "\"Jira Software\""
 # [STRING]
465 -*text)
466 arg="$1"; shift
467 if [[ -v bool_and ]]; then
468 text_and+=("$arg")
469 elif [[ -v bool_or ]]; then
470 text_or+=("$arg")
471 else
472 text+=("$arg")
473 fi
474 ;;
475
476 --all) set -- '--resolved-is-none' "$@" ;; # alias: --[un-]resolved
477
478 --proj*) projects+=("$1"); shift ;;
479
480 --fixversion-*)
481 # function get_jql_fixversion()
482 case "$arg" in
483 *excl)
484 [[ ! -v fixversion_excl ]] && { declare -g -a fixversion_excl=(); }
485 val="\"$1\""; shift
486 html_encode val
487 fixversion_excl+=("$val");
488 ;;
489
490 *incl)
491 [[ ! -v fixversion_incl ]] && { declare -g -a fixversion_incl=(); }
492 val="\"$1\""; shift
493 html_encode val
494 fixversion_incl+=("$val");
495 ;;
496
497 *not-empty) declare -g -i fixversion_not_empty=1 ;;
498 *is-empty) declare -g -i fixversion_is_empty=1 ;;
499
500 *) error "Detected invalid --fixversion-* modifier" ;;
501 esac
502 ;;
503
504 --resolved-*)
505 # function get_jql_reasons()
506 case "$arg" in
507
508 *start) declare -g resolved_start="$1"; shift ;;
509 *end) declare -g resolved_end="$1"; shift ;;
510
511 *not-empty) declare -g resolved_not_empty="$1" ;;
512 *empty) declare -g resolved_is_empty="$1" ;;
513
514 *excl)
515 [[ ! -v resolved_excl ]] && { declare -g -a resolved_excl=(); }
516 val="\"$1\""; shift
517 html_encode val
518 resolved_excl+=("$val");
519 ;;
520 *incl)
521 [[ ! -v resolved_incl ]] && { declare -g -a resolved_incl=(); }
522 val="\"$1\""; shift
523 html_encode val
524 resolved_incl+=("$val");
525 ;;
526 *) ;;
527 *) error "Detected invalid --resolved-* modifier" ;;
528 esac
529 ;;
530
531 -*newer)
532 arg="$1"; shift
533 suffix0+=("created <= '-${arg}d'") ;;
534
535 -*older)
536 arg="$1"; shift
537 suffix0+=("created >= '-${arg}d'") ;;
538
539 ##----------------##
540 ##---] BOOL [---##
541 ##----------------##
542 --[aA][nN][dD]) declare -g -i bool_and=1 ;;
543 --[oO][rR]) declare -g -i bool_or=1 ;;
544
545 ##------------------##
546 ##---] MEMBER [---##
547 ##------------------##
548 --[iI][nN]) declare -g -i bool_in=1 ;;
549 --[nN][oO][tT]) declare -g -i bool_not=1 ;;
550
551 [A-Z][A-Z][A-Z]-[0-9]*)
552 case "$arg" in
553 CORD-[0-9]*)
554 url="https://jira.opencord.org/browse/${arg}"
555 urls_raw+=('--new-window' "$url")
556 ;;
557
558 INF-[0-9]*)
559 url="https://jira.opennetworking.org/browse/${arg}"
560 urls_raw+=('--new-window' "$url")
561 ;;
562
563 VOL-[0-9]*)
564 url="https://jira.opencord.org/browse/${arg}"
565 urls_raw+=('--new-window' "$url")
566 ;;
567
568 *) error "Detected invalid ticket [$arg]" ;;
569
570 esac
571 ;;
572
573 # -----------------------------------------------------------------------
574 # https://support.atlassian.com/jira-software-cloud/docs/search-syntax-for-text-fields/
575 # -----------------------------------------------------------------------
576 # +jira atlassian -- must contain jira, atlassian is optional
577 # -japan -- exclude term
578 # [STEM] summary ~ "customize" -- finds stem 'custom' in the Summary field
579 *)
580 declare -p text_and
581 error "Detected unknown argument $arg"
582 ;;
583 esac
584done
585
586## ----------------------
587## Construct query filter
588## ----------------------
589do_user suffix0
590do_projects suffix0
591[[ -v components ]] && { do_components components suffix0; }
592do_labels labels_incl labels_excl suffix0
593do_text text suffix0
594do_resolved suffix0
595do_fixversion suffix0
596
597filter=''
598gen_filter filter suffix0
599
600if [[ ! -v urls_raw ]]; then
601 url=''
602 gen_url url filter
603 urls_filt+=("$url")
604elif [ ${#urls_raw} -eq 0 ]; then
605 url=''
606 gen_url url filter
607 urls_filt+=("$url")
608fi
609
610[[ -v debug ]] && [[ -v url ]] && echo "URL: $url"
611# browser="${BROWSER:-/snap/bin/firefox}"
612# browser="${BROWSER:-/opt/firefox/current/firefox}"
613browser="${BROWSER:-opera}"
614echo "$browser ${urls_filt[@]} ${urls_raw[@]}"
615
616if [[ ! -v dry_run ]]; then
617 "$browser" "${urls_filt[@]}" "${urls_raw[@]}" >/dev/null 2>/dev/null &
618fi
619
620# [SEE ALSO]
621# o https://support.atlassian.com/jira-software-cloud/docs/advanced-search-reference-jql-fields/
622
623# [EOF]