From 0256282a843c3f246239e4aa8d2e7b0151df6e80 Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Dec 01 2020 12:02:25 +0000 Subject: [PATCH 1/2] cli: list-task --after/--before/--all allow querying for closed tasks Fixes: https://pagure.io/koji/issue/2565 --- diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py index e50e904..0925212 100644 --- a/cli/koji_cli/commands.py +++ b/cli/koji_cli/commands.py @@ -6391,10 +6391,22 @@ def handle_list_tasks(goptions, session, args): parser.add_option("--host", help=_("Only tasks for this host")) parser.add_option("--quiet", action="store_true", default=goptions.quiet, help=_("Do not display the column headers")) + parser.add_option("--before", + help=_("List builds built before this time, " + "time is specified as timestamp or date/time in any " + "format which can be parsed by dateutil.parser. e.g. " + "\"2020-12-31 12:35\" or \"December 31st 12:35\"")) + parser.add_option("--after", + help=_("List builds built after this time (same format as for --before")) + parser.add_option("--all", action="store_true", + help=_("List also finished tasks (valid only with --after)")) (options, args) = parser.parse_args(args) if len(args) != 0: parser.error(_("This command takes no arguments")) + if options.all and not options.after: + parser.error(_("--all must be used with --after")) + activate_session(session, goptions) tasklist = _list_tasks(options, session) if not tasklist: diff --git a/cli/koji_cli/lib.py b/cli/koji_cli/lib.py index 0e2a724..472bbe9 100644 --- a/cli/koji_cli/lib.py +++ b/cli/koji_cli/lib.py @@ -722,9 +722,14 @@ def _list_tasks(options, session): "Retrieve a list of tasks" callopts = { - 'state': [koji.TASK_STATES[s] for s in ('FREE', 'OPEN', 'ASSIGNED')], 'decode': True, } + if not options.all: + callopts['state'] = [koji.TASK_STATES[s] for s in ('FREE', 'OPEN', 'ASSIGNED')] + if options.after: + callopts['startedAfter'] = options.after + if options.before: + callopts['startedBefore'] = options.before if getattr(options, 'mine', False): if getattr(options, 'user', None): diff --git a/tests/test_cli/test_list_tasks.py b/tests/test_cli/test_list_tasks.py index ac45f29..a701219 100644 --- a/tests/test_cli/test_list_tasks.py +++ b/tests/test_cli/test_list_tasks.py @@ -22,6 +22,9 @@ class TestListTasks(unittest.TestCase): options.method = None options.channel = None options.host = None + options.before = None + options.after = None + options.all = False session = mock.MagicMock(name='session') session.getLoggedInUser.return_value = {'id': 1, 'username': 'name'} session.listTasks.return_value = [] @@ -259,4 +262,11 @@ Options: --channel=CHANNEL Only tasks in this channel --host=HOST Only tasks for this host --quiet Do not display the column headers + --before=BEFORE List builds built before this time, time is specified as + timestamp or date/time in any format which can be parsed + by dateutil.parser. e.g. "2020-12-31 12:35" or "December + 31st 12:35" + --after=AFTER List builds built after this time (same format as for + --before + --all List also finished tasks (valid only with --after) """ % self.progname) From 254c766b1fcd5fa4f814da8ca567b01df0753691 Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Dec 01 2020 13:40:51 +0000 Subject: [PATCH 2/2] cli: create after/before OptionParser helper --- diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py index 0925212..3a819e1 100644 --- a/cli/koji_cli/commands.py +++ b/cli/koji_cli/commands.py @@ -17,7 +17,6 @@ import traceback from collections import OrderedDict, defaultdict from optparse import SUPPRESS_HELP, OptionParser -import dateutil.parser import six import six.moves.xmlrpc_client from six.moves import filter, map, range, zip @@ -25,6 +24,7 @@ from six.moves import filter, map, range, zip import koji from koji.util import base64encode, md5_constructor, to_list from koji_cli.lib import ( + TimeOption, _, _list_tasks, _progress_callback, @@ -3017,15 +3017,12 @@ def anon_handle_list_pkgs(goptions, session, args): def anon_handle_list_builds(goptions, session, args): "[info] Print the build listing" usage = _("usage: %prog list-builds [options]") - parser = OptionParser(usage=get_usage_str(usage)) + parser = OptionParser(usage=get_usage_str(usage), option_class=TimeOption) parser.add_option("--package", help=_("List builds for this package")) parser.add_option("--buildid", help=_("List specific build from ID or nvr")) - parser.add_option("--before", - help=_("List builds built before this time, " - "time is specified as timestamp or date/time in any " - "format which can be parsed by dateutil.parser. e.g. " - "\"2020-12-31 12:35\" or \"December 31st 12:35\"")) - parser.add_option("--after", + parser.add_option("--before", type="time", + help=_("List builds built before this time, ") + TimeOption.get_help()) + parser.add_option("--after", type="time", help=_("List builds built after this time (same format as for --before")) parser.add_option("--state", help=_("List builds in this state")) parser.add_option("--task", help=_("List builds for this task")) @@ -3090,26 +3087,10 @@ def anon_handle_list_builds(goptions, session, args): opts['state'] = koji.BUILD_STATES[options.state] except KeyError: parser.error(_("Invalid state")) - for opt in ('before', 'after'): - val = getattr(options, opt) - if not val: - continue - try: - ts = float(val) - setattr(options, opt, ts) - continue - except ValueError: - pass - try: - dt = dateutil.parser.parse(val) - ts = time.mktime(dt.timetuple()) - setattr(options, opt, ts) - except Exception: - parser.error(_("Invalid time specification: %s") % val) if options.before: - opts['completeBefore'] = getattr(options, 'before') + opts['completeBefore'] = options.before if options.after: - opts['completeAfter'] = getattr(options, 'after') + opts['completeAfter'] = options.after if options.task: try: opts['taskID'] = int(options.task) @@ -4475,7 +4456,7 @@ _table_keys = { def anon_handle_list_history(goptions, session, args): "[info] Display historical data" usage = _("usage: %prog list-history [options]") - parser = OptionParser(usage=get_usage_str(usage)) + parser = OptionParser(usage=get_usage_str(usage), option_class=TimeOption) # Don't use local debug option, this one stays here for backward compatibility # https://pagure.io/koji/issue/2084 parser.add_option("--debug", action="store_true", default=goptions.debug, help=SUPPRESS_HELP) @@ -4495,12 +4476,9 @@ def anon_handle_list_history(goptions, session, args): parser.add_option("--group", help=_("Only show entries relating to a given group")) parser.add_option("--host", help=_("Only show entries related to given host")) parser.add_option("--channel", help=_("Only show entries related to given channel")) - parser.add_option("--before", - help=_("Only show entries before this time, " - "time is specified as timestamp or date/time in any " - "format which can be parsed by dateutil.parser. e.g. " - "\"2020-12-31 12:35\" or \"December 31st 12:35\"")) - parser.add_option("--after", + parser.add_option("--before", type="time", + help=_("Only show entries before this time, ") + TimeOption.get_help()) + parser.add_option("--after", type="time", help=_("Only show entries after timestamp (same format as for --before)")) parser.add_option("--before-event", metavar="EVENT_ID", type='int', help=_("Only show entries before event")) @@ -4522,22 +4500,6 @@ def anon_handle_list_history(goptions, session, args): parser.error(_("This command takes no arguments")) kwargs = {} limited = False - for opt in ('before', 'after'): - val = getattr(options, opt) - if not val: - continue - try: - ts = float(val) - setattr(options, opt, ts) - continue - except ValueError: - pass - try: - dt = dateutil.parser.parse(val) - ts = time.mktime(dt.timetuple()) - setattr(options, opt, ts) - except Exception: - parser.error(_("Invalid time specification: %s") % val) for opt in ('package', 'tag', 'build', 'editor', 'user', 'permission', 'cg', 'external_repo', 'build_target', 'group', 'before', 'after', 'host', 'channel'): @@ -6382,7 +6344,7 @@ def handle_set_task_priority(goptions, session, args): def handle_list_tasks(goptions, session, args): "[info] Print the list of tasks" usage = _("usage: %prog list-tasks [options]") - parser = OptionParser(usage=get_usage_str(usage)) + parser = OptionParser(usage=get_usage_str(usage), option_class=TimeOption) parser.add_option("--mine", action="store_true", help=_("Just print your tasks")) parser.add_option("--user", help=_("Only tasks for this user")) parser.add_option("--arch", help=_("Only tasks for this architecture")) @@ -6391,13 +6353,10 @@ def handle_list_tasks(goptions, session, args): parser.add_option("--host", help=_("Only tasks for this host")) parser.add_option("--quiet", action="store_true", default=goptions.quiet, help=_("Do not display the column headers")) - parser.add_option("--before", - help=_("List builds built before this time, " - "time is specified as timestamp or date/time in any " - "format which can be parsed by dateutil.parser. e.g. " - "\"2020-12-31 12:35\" or \"December 31st 12:35\"")) - parser.add_option("--after", - help=_("List builds built after this time (same format as for --before")) + parser.add_option("--before", type="time", + help=_("List tasks completed before this time, ") + TimeOption.get_help()) + parser.add_option("--after", type="time", + help=_("List tasks completed after this time (same format as for --before")) parser.add_option("--all", action="store_true", help=_("List also finished tasks (valid only with --after)")) (options, args) = parser.parse_args(args) diff --git a/cli/koji_cli/lib.py b/cli/koji_cli/lib.py index 472bbe9..6234234 100644 --- a/cli/koji_cli/lib.py +++ b/cli/koji_cli/lib.py @@ -10,9 +10,11 @@ import string import sys import time from contextlib import closing +from copy import copy import requests import six +import dateutil.parser from six.moves import range import koji @@ -23,8 +25,39 @@ from koji.util import md5_constructor, to_list # for compatibility with plugins based on older version of lib # Use optparse imports directly in new code. +# Nevertheless, TimeOption can be used from here. OptionParser = optparse.OptionParser + +def _check_time_option(option, opt, value): + """Converts str timestamp or date/time to float timestamp""" + try: + ts = float(value) + return ts + except ValueError: + pass + try: + dt = dateutil.parser.parse(value) + ts = time.mktime(dt.timetuple()) + return ts + except Exception: + raise optparse.OptionValueError( + _("option %s: invalid time specification: %r") % (opt, value)) + + +class TimeOption(optparse.Option): + """OptionParser extension for timestamp/datetime values""" + TYPES = optparse.Option.TYPES + ("time",) + TYPE_CHECKER = copy(optparse.Option.TYPE_CHECKER) + TYPE_CHECKER['time'] = _check_time_option + + @classmethod + def get_help(self): + return _("time is specified as timestamp or date/time in any " + "format which can be parsed by dateutil.parser. e.g. " + "\"2020-12-31 12:35\" or \"December 31st 12:35\"") + + greetings = ('hello', 'hi', 'yo', "what's up", "g'day", 'back to work', 'bonjour', 'hallo', @@ -726,6 +759,7 @@ def _list_tasks(options, session): } if not options.all: callopts['state'] = [koji.TASK_STATES[s] for s in ('FREE', 'OPEN', 'ASSIGNED')] + if options.after: callopts['startedAfter'] = options.after if options.before: diff --git a/tests/test_cli/test_list_tasks.py b/tests/test_cli/test_list_tasks.py index a701219..28c1f6b 100644 --- a/tests/test_cli/test_list_tasks.py +++ b/tests/test_cli/test_list_tasks.py @@ -262,11 +262,11 @@ Options: --channel=CHANNEL Only tasks in this channel --host=HOST Only tasks for this host --quiet Do not display the column headers - --before=BEFORE List builds built before this time, time is specified as - timestamp or date/time in any format which can be parsed - by dateutil.parser. e.g. "2020-12-31 12:35" or "December - 31st 12:35" - --after=AFTER List builds built after this time (same format as for + --before=BEFORE List tasks completed before this time, time is specified + as timestamp or date/time in any format which can be + parsed by dateutil.parser. e.g. "2020-12-31 12:35" or + "December 31st 12:35" + --after=AFTER List tasks completed after this time (same format as for --before --all List also finished tasks (valid only with --after) """ % self.progname)