Hello community, here is the log from the commit of package redminecli for openSUSE:Factory checked in at 2020-01-12 23:23:59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/redminecli (Old) and /work/SRC/openSUSE:Factory/.redminecli.new.6675 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "redminecli" Sun Jan 12 23:23:59 2020 rev:2 rq:763174 version:1.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/redminecli/redminecli.changes 2020-01-02 14:44:57.860997699 +0100 +++ /work/SRC/openSUSE:Factory/.redminecli.new.6675/redminecli.changes 2020-01-12 23:25:52.610849252 +0100 @@ -1,0 +2,11 @@ +Fri Jan 10 19:49:52 UTC 2020 - Martin Hauke <[email protected]> + +- Update to version 1.3.0 + Features: + * New command to list time entries (redmine times) + * New command to create time entries (redmine spent) + Fixes: + * Fix regression on issue create (see #33 ) + * Fix creating issue with parent issue id + +------------------------------------------------------------------- Old: ---- redminecli-1.2.0.tar.gz New: ---- redminecli-1.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ redminecli.spec ++++++ --- /var/tmp/diff_new_pack.m7gg2N/_old 2020-01-12 23:25:53.750849673 +0100 +++ /var/tmp/diff_new_pack.m7gg2N/_new 2020-01-12 23:25:53.786849686 +0100 @@ -1,6 +1,7 @@ # -# spec file for package python-redminecli +# spec file for package redminecli # +# Copyright (c) 2020 SUSE LINUX GmbH, Nuernberg, Germany. # Copyright (c) 2019, Martin Hauke <[email protected]> # # All modifications and additions to the file contributed by third parties @@ -12,15 +13,16 @@ # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. -# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# Please submit bugfixes or comments via https://bugs.opensuse.org/ +# Name: redminecli -Version: 1.2.0 +Version: 1.3.0 Release: 0 Summary: Command line interface for Redmine -Group: Development/Tools/Other License: CECILL-B +Group: Development/Tools/Other URL: https://github.com/egegunes/redmine-cli Source: https://github.com/egegunes/redmine-cli/archive/%{version}.tar.gz#/%{name}-%{version}.tar.gz BuildRequires: fdupes @@ -40,6 +42,7 @@ %package -n redminecli-bash-completion Summary: Bash completion for redminecli +Group: Development/Tools/Other BuildRequires: bash-completion Requires: bash-completion Requires: redminecli ++++++ redminecli-1.2.0.tar.gz -> redminecli-1.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redmine-cli-1.2.0/redmine/activity.py new/redmine-cli-1.3.0/redmine/activity.py --- old/redmine-cli-1.2.0/redmine/activity.py 1970-01-01 01:00:00.000000000 +0100 +++ new/redmine-cli-1.3.0/redmine/activity.py 2020-01-07 20:46:54.000000000 +0100 @@ -0,0 +1,7 @@ +class Activity: + def __init__(self, **kwargs): + self.id = kwargs.get("id") + self.name = kwargs.get("name") + + def __str__(self): + return f"{self.id:<4} {self.name:<20}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redmine-cli-1.2.0/redmine/cli/alias.py new/redmine-cli-1.3.0/redmine/cli/alias.py --- old/redmine-cli-1.2.0/redmine/cli/alias.py 2020-01-02 07:20:57.000000000 +0100 +++ new/redmine-cli-1.3.0/redmine/cli/alias.py 2020-01-07 20:46:54.000000000 +0100 @@ -1,5 +1,4 @@ import click - from redmine.cli.config import Config diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redmine-cli-1.2.0/redmine/cli/main.py new/redmine-cli-1.3.0/redmine/cli/main.py --- old/redmine-cli-1.2.0/redmine/cli/main.py 2020-01-02 07:20:57.000000000 +0100 +++ new/redmine-cli-1.3.0/redmine/cli/main.py 2020-01-07 20:46:54.000000000 +0100 @@ -3,9 +3,10 @@ import sys from collections import OrderedDict -import click from requests.exceptions import HTTPError +import click +from redmine.activity import Activity from redmine.cli.alias import AliasedGroup from redmine.cli.config import Config, pass_config from redmine.cli.helpers import get_description, get_note @@ -15,6 +16,7 @@ from redmine.project import Project from redmine.query import Query from redmine.redmine import Redmine +from redmine.time import Time from redmine.tracker import Tracker from redmine.user import User from redmine.version import Version @@ -121,15 +123,28 @@ @cli.command() [email protected](OPTIONS["subject"]["long"], OPTIONS["subject"]["short"], default=None) [email protected]( + OPTIONS["subject"]["long"], OPTIONS["subject"]["short"], default=None, required=True +) @click.option( OPTIONS["description"]["long"], OPTIONS["description"]["short"], default=None ) @click.option(OPTIONS["edit"]["long"], OPTIONS["edit"]["short"], default=False) [email protected](OPTIONS["project"]["long"], OPTIONS["project"]["short"], default=None) [email protected](OPTIONS["status"]["long"], OPTIONS["status"]["short"], default=None) [email protected](OPTIONS["tracker"]["long"], OPTIONS["tracker"]["short"], default=None) [email protected](OPTIONS["priority"]["long"], OPTIONS["priority"]["short"], default=None) [email protected]( + OPTIONS["project"]["long"], OPTIONS["project"]["short"], default=None, required=True +) [email protected]( + OPTIONS["status"]["long"], OPTIONS["status"]["short"], default=None, required=True +) [email protected]( + OPTIONS["tracker"]["long"], OPTIONS["tracker"]["short"], default=None, required=True +) [email protected]( + OPTIONS["priority"]["long"], + OPTIONS["priority"]["short"], + default=None, + required=True, +) @click.option(OPTIONS["assignee"]["long"], OPTIONS["assignee"]["short"], default=None) @click.option(OPTIONS["start"]["long"], OPTIONS["start"]["short"], default=None) @click.option(OPTIONS["due"]["long"], OPTIONS["due"]["short"], default=None) @@ -285,6 +300,22 @@ @list.command() @click.pass_obj +def activity(redmine): + """ List time tracking activities """ + + try: + activities = sorted( + redmine.get("enumerations/time_entry_activities"), key=lambda x: x["id"] + ) + except HTTPError as e: + return click.echo(click.style(f"Fatal: {e}", fg="red")) + + for activity in activities: + click.echo(Activity(**activity)) + + [email protected]() [email protected]_obj def user(redmine): """ List users """ @@ -360,3 +391,53 @@ url = urljoin(redmine.url, "/issues/{}".format(issue_id)) click.launch(url) + + [email protected]() [email protected](OPTIONS["user"]["long"], OPTIONS["user"]["short"], default=None) [email protected](OPTIONS["project"]["long"], OPTIONS["project"]["short"], default=None) [email protected](OPTIONS["from"]["long"], default=None) [email protected](OPTIONS["to"]["long"], default=None) [email protected](OPTIONS["on"]["long"], default=None) [email protected]_obj +def times(redmine, **kwargs): + """ List spent times """ + + on = kwargs.get("on") + if on is not None: + kwargs.update({"from": on, "to": on}) + + try: + entries = redmine.get( + "time_entries", + cache=False, + **{ + "user_id": kwargs.get("user"), + "project_id": kwargs.get("project"), + "from": kwargs.get("from"), + "to": kwargs.get("to"), + }, + ) + except HTTPError as e: + return click.echo(click.style(f"Fatal: {e}", fg="red")) + + for entry in entries: + click.echo(Time(**entry)) + + [email protected]() [email protected]("issue_id") [email protected]("hours") [email protected](OPTIONS["on"]["long"], default=None) [email protected](OPTIONS["activity"]["long"], OPTIONS["activity"]["short"], default=None) [email protected](OPTIONS["comment"]["long"], OPTIONS["comment"]["short"], default=None) [email protected]_obj +def spent(redmine, issue_id, hours, **kwargs): + """ Create new time entry """ + + try: + redmine.create_time_entry(issue_id, hours, **kwargs) + except HTTPError as e: + return click.echo(click.style(f"Fatal: {e}", fg="red")) + + click.echo(click.style("Time logged", fg="green"), err=True) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redmine-cli-1.2.0/redmine/cli/options.py new/redmine-cli-1.3.0/redmine/cli/options.py --- old/redmine-cli-1.2.0/redmine/cli/options.py 2020-01-02 07:20:57.000000000 +0100 +++ new/redmine-cli-1.3.0/redmine/cli/options.py 2020-01-07 20:46:54.000000000 +0100 @@ -27,4 +27,10 @@ "json": {"long": "--json/--no-json"}, "account": {"long": "--account", "help": "Account name to use"}, "pager": {"long": "--pager/--no-pager"}, + "user": {"long": "--user", "short": "-u"}, + "from": {"long": "--from"}, + "to": {"long": "--to"}, + "on": {"long": "--on"}, + "activity": {"long": "--activity", "short": "-A"}, + "comment": {"long": "--comment", "short": "-C"}, } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redmine-cli-1.2.0/redmine/issue.py new/redmine-cli-1.3.0/redmine/issue.py --- old/redmine-cli-1.2.0/redmine/issue.py 2020-01-02 07:20:57.000000000 +0100 +++ new/redmine-cli-1.3.0/redmine/issue.py 2020-01-07 20:46:54.000000000 +0100 @@ -42,7 +42,7 @@ created_on = datetime.strptime(self.created_on, "%Y-%m-%dT%H:%M:%SZ") header += ( - f"Reported by {self.author['name']} on" + f"Reported by {self.author['name']} on " f"{created_on.date()} {created_on.time()}\n\n" ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redmine-cli-1.2.0/redmine/redmine.py new/redmine-cli-1.3.0/redmine/redmine.py --- old/redmine-cli-1.2.0/redmine/redmine.py 2020-01-02 07:20:57.000000000 +0100 +++ new/redmine-cli-1.3.0/redmine/redmine.py 2020-01-07 20:46:54.000000000 +0100 @@ -2,9 +2,10 @@ import os from urllib.parse import urljoin -import click import requests +import click + class Redmine: def __init__( @@ -36,10 +37,10 @@ def __str__(self): return repr(self) - def fetch(self, resource): + def fetch(self, resource, **kwargs): resp = requests.get( urljoin(self.url, "{}.json".format(resource)), - params={"limit": 100}, + params={"limit": 100, **kwargs}, headers=self.auth_header, verify=self.ssl_verify, ) @@ -52,7 +53,7 @@ with open(cache_file, "w+") as cf: cf.write(json.dumps(data)) - def get(self, resource): + def get(self, resource, cache=True, **kwargs): # Some resources (i.e issue_priorities) have paths that contain "/" rname = resource.split("/")[-1] cache_file = os.path.join(self.cache_dir, "{}.json".format(rname)) @@ -60,8 +61,8 @@ with open(cache_file, "r") as cf: data = json.loads(cf.read()) else: - data = self.fetch(resource) - if resource in data: + data = self.fetch(resource, **kwargs) + if resource in data and cache: self.set_cache(cache_file, data) return data[rname] @@ -82,7 +83,7 @@ memberships.extend(response["memberships"]) users = {} - membership_types = ['user', 'group', 'group_anonymous'] + membership_types = ["user", "group", "group_anonymous"] for m in memberships: for t in membership_types: @@ -205,7 +206,7 @@ "description": kwargs.get("description"), "priority_id": kwargs.get("priority"), "assigned_to_id": kwargs.get("assignee"), - "parent_issue_id": kwargs.get("parent_issue"), + "parent_issue_id": kwargs.get("parent"), "start_date": kwargs.get("start"), "due_date": kwargs.get("due"), "done_ratio": kwargs.get("done"), @@ -221,3 +222,29 @@ resp.raise_for_status() return resp.json()["issue"] + + def create_time_entry(self, issue_id, hours, **kwargs): + fields = { + "time_entry": { + "issue_id": issue_id, + "hours": hours, + "comments": kwargs.get("comment"), + } + } + + if kwargs.get("activity"): + fields["time_entry"].update({"activity_id": kwargs.get("activity")}) + + if kwargs.get("on"): + fields["time_entry"].update({"spent_on": kwargs.get("on")}) + + resp = requests.post( + f"{self.url}/time_entries.json", + json=fields, + headers=self.auth_header, + verify=self.ssl_verify, + ) + + resp.raise_for_status() + + return resp.json() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redmine-cli-1.2.0/redmine/time.py new/redmine-cli-1.3.0/redmine/time.py --- old/redmine-cli-1.2.0/redmine/time.py 1970-01-01 01:00:00.000000000 +0100 +++ new/redmine-cli-1.3.0/redmine/time.py 2020-01-07 20:46:54.000000000 +0100 @@ -0,0 +1,19 @@ +class Time: + def __init__(self, *args, **kwargs): + self.project = kwargs.get("project") + self.issue = kwargs.get("issue") + self.user = kwargs.get("user") + self.hours = kwargs.get("hours") + self.activity = kwargs.get("activity") + self.comments = kwargs.get("comments") + self.spent_on = kwargs.get("spent_on") + + def __str__(self): + time = f"{self.project['name']:<21.20} " + time += f"{self.issue['id']:>6} " + time += f"{self.user['name']:<21.20} " + time += f"{self.activity['name']:<15.14} " + time += f"{self.spent_on:<11} " + time += f"{self.hours:>6} hours" + + return time diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/redmine-cli-1.2.0/setup.py new/redmine-cli-1.3.0/setup.py --- old/redmine-cli-1.2.0/setup.py 2020-01-02 07:20:57.000000000 +0100 +++ new/redmine-cli-1.3.0/setup.py 2020-01-07 20:46:54.000000000 +0100 @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- -from setuptools import setup, find_packages - +from setuptools import find_packages, setup with open("README.md") as f: readme = f.read() setup( name="redminecli", - version="1.2.0", + version="1.3.0", description="Command line interface for Redmine", long_description=readme, long_description_content_type="text/markdown",
