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