blob: 51959f6e7cae7bc61856470a40c76aeb782c435b [file] [log] [blame]
#!/bin/bash
# -----------------------------------------------------------------------
# Copyright 2023-2024 Open Networking Foundation Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http:#www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------------
# SPDX-FileCopyrightText: 2023-2024 Open Networking Foundation Contributors
# SPDX-License-Identifier: Apache-2.0
## --------------------------------------------------------------------
## Intent: Construct a jira ticket query with attributes
## --------------------------------------------------------------------
{ # loader
declare pgm=''
pgm="$(realpath --canonicalize-existing "$0")"
# stack-trace-on-error
# interrupt handler
# mkdir with auto-cleanup at exit
declare root=''
root="${pgm%%/jira/bin/jira-search.sh}"
source "$root/lf/onf-common/common.sh" '--common-args-begin--'
pgm_lib="${root}/jira/jira-search"
readonly pgm_lib
pgm_bin="${root}/bin"
readonly pgm_bin
pgm_help="${root}/jira/jira-search/help"
readonly pgm_help
}
# set -euo pipefail
#source ~/.sandbox/trainlab-common/common.sh '--common-args-begin--'
##-------------------##
##---] GLOBALS [---##
##-------------------##
declare -g -a text=()
declare -g -a text_and=()
declare -g -a text_or=()
declare -g -a urls_raw=()
declare -g -a urls_filt=()
declare -g -a labels_incl=()
declare -g -a labels_excl=()
declare -g -a projects=()
path="$(realpath $0 --canonicalize-existing)"
# source "${path%\.sh}/utils.sh"
source "${pgm_lib}/utils.sh"
#source "$pgmlib/fixversion.sh"
#source "$pgmlib/resolved.sh"
source "${pgm_lib}/fixversion.sh"
source "${pgm_lib}/resolved.sh"
source "${pgm_lib}/help/utils.sh"
## --------------------------------------------------------------------
## --------------------------------------------------------------------
function error()
{
cat <<ERROR
** -----------------------------------------------------------------------
** IAM: ${FUNCNAME[1]}
** ERROR: $@
** -----------------------------------------------------------------------
ERROR
echo
exit 1
}
## -----------------------------------------------------------------------
## -----------------------------------------------------------------------
function html_encode()
{
local -n ref=$1; shift
local tmp="$ref"
tmp="${tmp//[[:space:]]/%20}"
tmp="${tmp//\"/%22}"
tmp="${tmp//\'/%27}"
ref="$tmp"
return
}
## -----------------------------------------------------------------------
## Intent: Insert a conjunction into the stream when prior statements exist
## -----------------------------------------------------------------------
function conjunction()
{
return
local -n ref=$1; shift
[[ $# -gt 0 ]] && { local literal="$1"; shift; }
## -------------------------------
## Conjunction if prior statements
## -------------------------------
if [ ${#ref[@]} -gt 0 ]; then
if [[ -v literal ]]; then
ref+=("$literal")
elif [[ -v bool_and ]]; then
ref+=('AND')
else
ref+=('OR')
fi
fi
return
}
## -----------------------------------------------------------------------
## Intent: Helper method
## -----------------------------------------------------------------------
## Usage : local path="$(join_by '/' 'lib' "${fields[@]}")"
## -----------------------------------------------------------------------
function join_by()
{
local d=${1-} f=${2-}; if shift 2; then printf %s "$f" "${@/#/$d}"; fi;
}
## --------------------------------------------------------------------
## Intent: Query by component name filter
## --------------------------------------------------------------------
## Value: helm-charts
## --------------------------------------------------------------------
function do_components()
{
declare -n args=$1; shift
declare -n ans=$1; shift
# [ -z ${args+word} ] && { args=(); }
if [[ ${#args[@]} -gt 0 ]]; then
local modifier
if [[ -v bool_not ]]; then
modifier='NOT IN'
else
modifier='IN'
fi
ans+=("component ${modifier} (${args[@]})")
# alt: comp='foo' OR comp='bar'
fi
return
}
## --------------------------------------------------------------------
## Intent: Query filter by labels assigned to a ticket:
## o pods, failing, testing
## --------------------------------------------------------------------
# "project in (UKSCR, COMPRG) AND issuetype = Bug AND labels in (BAT)" and
## --------------------------------------------------------------------
function do_labels()
{
declare -n incl=$1; shift # was args=
declare -n excl=$1; shift
declare -n ans=$1; shift
## --------------------------------
## Conjunction if stream tokens > 0
## --------------------------------
conjunction ans
declare -a tokens=()
## -----------------------------
## -----------------------------
if [[ ${#incl[@]} -gt 0 ]]; then
local modifier
if [[ -v bool_not ]]; then
modifier='NOT IN'
else
modifier='IN'
fi
local labels=$(join_by ',' "${incl[@]}")
local -a tmp=(\
'('\
'label IS EMPTY' \
'OR' \
"labels ${modifier} ($labels)" \
')'\
)
tokens+=("${tmp[@]}")
fi
conjunction tokens 'AND'
## -----------------------------
## -----------------------------
if [[ ${#excl[@]} -gt 0 ]]; then
local labels=$(join_by ',' "${excl[@]}")
tokens+=('(' "labels NOT IN ($labels)" ')')
fi
ans+=("${tokens[@]}")
return
}
## --------------------------------------------------------------------
## Intent: Modify search query by project type (SEBA, VOL)
## --------------------------------------------------------------------
function do_projects()
{
declare -n ref=$1; shift
[[ ${#projects[@]} -eq 0 ]] && { return; }
local terms="$(join_by ',' "${projects[@]}")"
# local -a buffer=('(' 'project' 'IN' "($terms)" ')')
# ref+=("$(join_by '%20' "${buffer[@]}")")
ref+=("(project IN ($terms))")
return
}
## --------------------------------------------------------------------
## Intent: Query by compound text filters
## --------------------------------------------------------------------
function do_text()
{
local -n ref=$1; shift
local -n ans=$1; shift
local val
## Accumulate
if [[ ${#ref[@]} -gt 0 ]]; then
if [[ -v bool_and ]]; then
text_and+=("${ref[@]}")
else
text_or+=("${ref[@]}")
fi
fi
## Append terms: AND
if [[ ${#text_and[@]} -gt 0 ]]; then
declare -a term=()
for val in "${text_and[@]}";
do
term+=("text ~ \"$val\"")
done
val=$(join_by ' AND ' "${term[@]}")
ans+=("($val)")
fi
## Append terms: OR
if [[ ${#text_or[@]} -gt 0 ]]; then
declare -a term=()
for val in "${text_or[@]}";
do
term+=("text ~ \"$val\"")
done
val=$(join_by ' OR ' "${term[@]}")
ans+=("($val)")
fi
return
}
## --------------------------------------------------------------------
## Intent: Query by assigned or requestor
## --------------------------------------------------------------------
## Note: Simple for now but support query by a list of suers
## --------------------------------------------------------------------
function do_user()
{
declare -n ans=$1; shift
[[ -v argv_nobody ]] && return
local user='currentUser()'
if [[ -v argv_user ]]; then
user="$argv_user"
fi
if [[ -v argv_assigned ]]; then
ans+=("assignee=${user}")
fi
if [[ -v argv_reported ]]; then
ans+=("reporter=${user}")
fi
return
}
## --------------------------------------------------------------------
## Intent: Combine filter arguments into a search query
## --------------------------------------------------------------------
function gen_filter()
{
declare -n ans=$1; shift
declare -n args=$1; shift
## -----------------------------------
## Begin by joining major search terms
## -----------------------------------
declare -a _tmp=()
local val
for val in "${args[@]}";
do
_tmp+=("$val" 'AND')
done
if [[ ${#_tmp[@]} -gt 0 ]]; then
unset _tmp[-1]
fi
## -----------------------
## Massage with html codes
## -----------------------
ans="$(join_by '%20' "${_tmp[@]}")"
return
}
## --------------------------------------------------------------------
## Intent: Combine filter arguments into a search query
## --------------------------------------------------------------------
function gen_url()
{
declare -n ans=$1; shift
declare -n args=$1; shift
## Which jira server to query (?)
[[ ! -v server ]] && declare -g server='jira.opennetworking.org'
tmp_url="https://${server}/issues/?jql="
tmp="${tmp_url}${args}"
ans="${tmp// /%20}"
return
}
## --------------------------------------------------------------------
## Intent: Dispaly command usage
## --------------------------------------------------------------------
function usage()
{
cat <<EOH
Usage: $0 VOL-xxxx
--debug Enable script debug mode
--dry-run Simulate
--todo Display future enhancements
VOL-{xxxx} View a jira ticket by ID
[SERVER]
--server {cord,onf}
--onf jira.opennetworking.org
--opencord jira.opencord.org
[WHAT]
--component Search by component name assigned to ticket
--label Search by label name assigned to ticket.
--text Search string(s)
EOH
# declare -a topics=()
# topics+=('fixversion.switches')
# topics+=('resolved.switches')
#
# help_switch_show "${topics[@]}"#
#
#[USER(s)]
# --me Tickets assigned to or reported by me.
# --user [u] Tickets assigned to this user.
# --nobody Raw query, no filtering by user
cat <<EOH
[BOOL]
--and Join terms using 'AND'
--or Join terms using 'OR'
[MEMBER]
--in (default) Items belong (--component IN)
--not-in Negate item set (--component NOT IN)
[Contains]
--text [t] (join modifer: --and, --or)
--text-and [t] All of these terms
--text-or [t] Any of these terms
[RANGE]
--newer [d] Search for tickets created < [n] days ago.
--older [d] Search for tickets created > [n] days ago.
[ALIASES]
--all Query for all unresolved tickets
[TOPIC]
--fixversion Query by field: fixedversion
--resolved Query by field: resolved
--user Query by owner, requestor or 'my' jira tickets.
[HELP]
--help This message
--help-{topic} Display switch help and use case. (--help-resolved)
--usage-{topic} Display use cases for a given switch (--usage-user)
[USAGE]
$0 --opencord --assigned --unresolved
o Display all tickets assigned to my login
$0 --reported --or --text 'bbsim' --text 'release'
o Search for tickets that contain strings bbsim or release
$0 --cord --text-and 'release' --text-and 'voltctl'
o Search jira.opencord for tickets that contain release and voltctl
$0 --text 'bitergia' --text 'Jira' -and
o Search jira.opennetworking for tickets containing string bitergia and Jira
$0 --cord --label failing --label pod
o Search jira.opencord for tests failing due to pod/hardware issuses.
$0 --proj VOL --fixversion "VOLTHA v2.12" --resolved-is-empty
o Query for unresolved release tickets
EOH
return
}
## --------------------------------------------------------------------
# classpath=$(join_by ':' "${mypath[@]}")
## --------------------------------------------------------------------
function join_by()
{
local d=${1-} f=${2-}; if shift 2; then printf %s "$f" "${@/#/$d}"; fi;
}
##----------------##
##---] MAIN [---##
##----------------##
declare -a suffix0=()
# declare -g -i debug=1
while [ $# -gt 0 ]; do
if [ ${#suffix0[@]} -gt 0 ]; then
suffix0+=('AND')
fi
arg="$1"; shift
[[ -v debug ]] && echo "** argv=[$arg] [$*]"
case "$arg" in
'--help') usage; exit 0 ;;
'--help-'*) help_with "${arg/--help-/}" ;;
'--usage-'*) help_usage_show "${arg/--usage-/}"
;;
##-----------------##
##---] MODES [---##
##-----------------##
-*debug) declare -g -i debug=1 ;;
--dry-run) declare -g -i dry_run=1 ;;
##------------------------##
##---] SWITCH ALIAS [---##
##------------------------##
--unresolved)
declare -a args=()
args+=('--resolved-is-empty')
[[ $# -gt 0 ]] && { args+=("$@"); }
set -- "${args[@]}"
;;
##-------------------##
##---] BY USER [---##
##-------------------##
--assigned) declare -g -i argv_assigned=1 ;;
--reported) declare -g -i argv_reported=1 ;;
--me) declare -g -i argv_me=1 ;;
--nobody) declare -g -i argv_nobody=1 ;;
--user)
arg="$1"; shift
declare -g argv_user="$arg"
;;
##------------------##
##---] SERVER [---##
##------------------##
--serv*)
arg="$1"; shift
case "$arg" in
*cord*) server='jira.opencord.org' ;;
*onf*) server='jira.opennetworking.org' ;;
*) error "--server [$arg] expected opencord or onf" ;;
esac
;;
--onf) declare server='jira.opennetworking.org' ;;
--opencord) declare server='jira.opencord.org' ;;
##---------------------##
##---] SEARCH-BY [---##
##---------------------##
--component|--comp*)
arg="$1"; shift
[[ ! -v components ]] && declare -g -a components=()
components+=("$arg")
;;
--label-excl)
arg="$1"; shift
labels_excl+=("$arg")
;;
--label|--label-incl)
arg="$1"; shift
labels_incl+=("$arg")
;;
##-----------------------##
##---] Text Search [---##
##-----------------------##
# jsearch.sh --text-and bbsim --text-and release
-*text-and) text_and+=("$1"); shift ;;
-*text-or) text_or+=("$1"); shift ;;
# % js --and --text jenkins --text cord
# text ~ "Jira Software"
 # [WORDs]
# text ~ "\"Jira Software\""
 # [STRING]
-*text)
arg="$1"; shift
if [[ -v bool_and ]]; then
text_and+=("$arg")
elif [[ -v bool_or ]]; then
text_or+=("$arg")
else
text+=("$arg")
fi
;;
--all) set -- '--resolved-is-none' "$@" ;; # alias: --[un-]resolved
--todo) source "${pgm_lib}/todo.sh" ;;
--proj*) projects+=("$1"); shift ;;
--fixversion-*)
# function get_jql_fixversion()
case "$arg" in
*excl)
[[ ! -v fixversion_excl ]] && { declare -g -a fixversion_excl=(); }
val="\"$1\""; shift
html_encode val
fixversion_excl+=("$val");
;;
*incl)
[[ ! -v fixversion_incl ]] && { declare -g -a fixversion_incl=(); }
val="\"$1\""; shift
html_encode val
fixversion_incl+=("$val");
;;
*not-empty) declare -g -i fixversion_not_empty=1 ;;
*is-empty) declare -g -i fixversion_is_empty=1 ;;
*) error "Detected invalid --fixversion-* modifier" ;;
esac
;;
'--resolved-'*)
# function get_jql_reasons()
case "$arg" in
*start) declare -g resolved_start="$1"; shift ;;
*end) declare -g resolved_end="$1"; shift ;;
*not-empty) declare -g resolved_not_empty="$1" ;;
*empty) declare -g resolved_is_empty="$1" ;;
*excl)
[[ ! -v resolved_excl ]] && { declare -g -a resolved_excl=(); }
val="\"$1\""; shift
html_encode val
resolved_excl+=("$val");
;;
*incl)
[[ ! -v resolved_incl ]] && { declare -g -a resolved_incl=(); }
val="\"$1\""; shift
html_encode val
resolved_incl+=("$val");
;;
*) ;;
*) error "Detected invalid --resolved-* modifier" ;;
esac
;;
-*newer)
arg="$1"; shift
suffix0+=("created <= '-${arg}d'") ;;
-*older)
arg="$1"; shift
suffix0+=("created >= '-${arg}d'") ;;
##----------------##
##---] BOOL [---##
##----------------##
--[aA][nN][dD]) declare -g -i bool_and=1 ;;
--[oO][rR]) declare -g -i bool_or=1 ;;
##------------------##
##---] MEMBER [---##
##------------------##
--[iI][nN]) declare -g -i bool_in=1 ;;
--[nN][oO][tT]) declare -g -i bool_not=1 ;;
[A-Z][A-Z][A-Z]-[0-9]*)
case "$arg" in
CORD-[0-9]*)
url="https://jira.opencord.org/browse/${arg}"
urls_raw+=('--new-window' "$url")
;;
INF-[0-9]*)
url="https://jira.opennetworking.org/browse/${arg}"
urls_raw+=('--new-window' "$url")
;;
VOL-[0-9]*)
url="https://jira.opencord.org/browse/${arg}"
urls_raw+=('--new-window' "$url")
;;
*) error "Detected invalid ticket [$arg]" ;;
esac
;;
# -----------------------------------------------------------------------
# https://support.atlassian.com/jira-software-cloud/docs/search-syntax-for-text-fields/
# -----------------------------------------------------------------------
# +jira atlassian -- must contain jira, atlassian is optional
# -japan -- exclude term
# [STEM] summary ~ "customize" -- finds stem 'custom' in the Summary field
*)
declare -p text_and
error "Detected unknown argument $arg"
;;
esac
done
## --------------
## Required check
## --------------
[[ ! -v server ]] && { error "--server={cord,onf} is required"; }
## ----------------------
## Construct query filter
## ----------------------
do_user suffix0
do_projects suffix0
[[ -v components ]] && { do_components components suffix0; }
do_labels labels_incl labels_excl suffix0
do_text text suffix0
do_resolved suffix0
do_fixversion suffix0
filter=''
gen_filter filter suffix0
if [[ ! -v urls_raw ]]; then
url=''
gen_url url filter
urls_filt+=("$url")
elif [ ${#urls_raw} -eq 0 ]; then
url=''
gen_url url filter
urls_filt+=("$url")
fi
[[ -v debug ]] && [[ -v url ]] && echo "URL: $url"
browser="${BROWSER:-firefox}"
echo "$browser ${urls_filt[@]} ${urls_raw[@]}"
if [[ ! -v dry_run ]]; then
"$browser" "${urls_filt[@]}" "${urls_raw[@]}" >/dev/null 2>/dev/null &
fi
# [SEE ALSO]
# o https://support.atlassian.com/jira-software-cloud/docs/advanced-search-reference-jql-fields/
# [EOF]