Skip to content

Commit 423ce41

Browse files
committed
Add command to list spent times
1 parent 9c771ab commit 423ce41

File tree

4 files changed

+72
-10
lines changed

4 files changed

+72
-10
lines changed

redmine/cli/main.py

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from redmine.project import Project
1616
from redmine.query import Query
1717
from redmine.redmine import Redmine
18+
from redmine.time import Time
1819
from redmine.tracker import Tracker
1920
from redmine.user import User
2021
from redmine.version import Version
@@ -121,15 +122,28 @@ def show(redmine, issue_id, journals, pager):
121122

122123

123124
@cli.command()
124-
@click.option(OPTIONS["subject"]["long"], OPTIONS["subject"]["short"], default=None, required=True)
125+
@click.option(
126+
OPTIONS["subject"]["long"], OPTIONS["subject"]["short"], default=None, required=True
127+
)
125128
@click.option(
126129
OPTIONS["description"]["long"], OPTIONS["description"]["short"], default=None
127130
)
128131
@click.option(OPTIONS["edit"]["long"], OPTIONS["edit"]["short"], default=False)
129-
@click.option(OPTIONS["project"]["long"], OPTIONS["project"]["short"], default=None, required=True)
130-
@click.option(OPTIONS["status"]["long"], OPTIONS["status"]["short"], default=None, required=True)
131-
@click.option(OPTIONS["tracker"]["long"], OPTIONS["tracker"]["short"], default=None, required=True)
132-
@click.option(OPTIONS["priority"]["long"], OPTIONS["priority"]["short"], default=None, required=True)
132+
@click.option(
133+
OPTIONS["project"]["long"], OPTIONS["project"]["short"], default=None, required=True
134+
)
135+
@click.option(
136+
OPTIONS["status"]["long"], OPTIONS["status"]["short"], default=None, required=True
137+
)
138+
@click.option(
139+
OPTIONS["tracker"]["long"], OPTIONS["tracker"]["short"], default=None, required=True
140+
)
141+
@click.option(
142+
OPTIONS["priority"]["long"],
143+
OPTIONS["priority"]["short"],
144+
default=None,
145+
required=True,
146+
)
133147
@click.option(OPTIONS["assignee"]["long"], OPTIONS["assignee"]["short"], default=None)
134148
@click.option(OPTIONS["start"]["long"], OPTIONS["start"]["short"], default=None)
135149
@click.option(OPTIONS["due"]["long"], OPTIONS["due"]["short"], default=None)
@@ -360,3 +374,35 @@ def open(redmine, issue_id):
360374

361375
url = urljoin(redmine.url, "/issues/{}".format(issue_id))
362376
click.launch(url)
377+
378+
379+
@cli.command()
380+
@click.option(OPTIONS["user"]["long"], OPTIONS["user"]["short"], default=None)
381+
@click.option(OPTIONS["project"]["long"], OPTIONS["project"]["short"], default=None)
382+
@click.option(OPTIONS["from"]["long"], default=None)
383+
@click.option(OPTIONS["to"]["long"], default=None)
384+
@click.option(OPTIONS["on"]["long"], default=None)
385+
@click.pass_obj
386+
def times(redmine, **kwargs):
387+
""" List spent times """
388+
389+
on = kwargs.get("on")
390+
if on is not None:
391+
kwargs.update({"from": on, "to": on})
392+
393+
try:
394+
entries = redmine.get(
395+
"time_entries",
396+
cache=False,
397+
**{
398+
"user_id": kwargs.get("user"),
399+
"project_id": kwargs.get("project"),
400+
"from": kwargs.get("from"),
401+
"to": kwargs.get("to"),
402+
},
403+
)
404+
except HTTPError as e:
405+
return click.echo(click.style(f"Fatal: {e}", fg="red"))
406+
407+
for entry in entries:
408+
click.echo(Time(**entry))

redmine/cli/options.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,8 @@
2727
"json": {"long": "--json/--no-json"},
2828
"account": {"long": "--account", "help": "Account name to use"},
2929
"pager": {"long": "--pager/--no-pager"},
30+
"user": {"long": "--user", "short": "-u"},
31+
"from": {"long": "--from"},
32+
"to": {"long": "--to"},
33+
"on": {"long": "--on"},
3034
}

redmine/redmine.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ def __repr__(self):
3636
def __str__(self):
3737
return repr(self)
3838

39-
def fetch(self, resource):
39+
def fetch(self, resource, **kwargs):
4040
resp = requests.get(
4141
urljoin(self.url, "{}.json".format(resource)),
42-
params={"limit": 100},
42+
params={"limit": 100, **kwargs},
4343
headers=self.auth_header,
4444
verify=self.ssl_verify,
4545
)
@@ -52,16 +52,16 @@ def set_cache(self, cache_file, data):
5252
with open(cache_file, "w+") as cf:
5353
cf.write(json.dumps(data))
5454

55-
def get(self, resource):
55+
def get(self, resource, cache=True, **kwargs):
5656
# Some resources (i.e issue_priorities) have paths that contain "/"
5757
rname = resource.split("/")[-1]
5858
cache_file = os.path.join(self.cache_dir, "{}.json".format(rname))
5959
if os.path.exists(cache_file):
6060
with open(cache_file, "r") as cf:
6161
data = json.loads(cf.read())
6262
else:
63-
data = self.fetch(resource)
64-
if resource in data:
63+
data = self.fetch(resource, **kwargs)
64+
if resource in data and cache:
6565
self.set_cache(cache_file, data)
6666

6767
return data[rname]

redmine/time.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class Time:
2+
def __init__(self, *args, **kwargs):
3+
self.project = kwargs.get("project")
4+
self.issue = kwargs.get("issue")
5+
self.user = kwargs.get("user")
6+
self.hours = kwargs.get("hours")
7+
self.activity = kwargs.get("activity")
8+
self.comments = kwargs.get("comments")
9+
self.spent_on = kwargs.get("spent_on")
10+
11+
def __str__(self):
12+
return f"{self.project['name']:<21.20} {self.issue['id']:>6} {self.user['name']:<21.20} {self.activity['name']:<15.14} {self.spent_on:<11} {self.hours:>6} hours"

0 commit comments

Comments
 (0)