Merge lp:~justas.sadzevicius/gtimelog/recent_tasks into lp:~gtimelog-dev/gtimelog/trunk

Proposed by Marius Gedminas
Status: Work in progress
Proposed branch: lp:~justas.sadzevicius/gtimelog/recent_tasks
Merge into: lp:~gtimelog-dev/gtimelog/trunk
Diff against target: 242 lines (+189/-1)
1 file modified
src/gtimelog/main.py (+189/-1)
To merge this branch: bzr merge lp:~justas.sadzevicius/gtimelog/recent_tasks
Reviewer Review Type Date Requested Status
GTimeLog developers Pending
Review via email: mp+46582@code.launchpad.net

Description of the change

Cue 80s style commercial:

  Keep your time log activity list relevant and up to date!

  Act now and save (time)! *

  Only the best task lists brought to your doorstep by Justas'
Launchpad branch.

  * lp:~justas-pov/gtimelog/recent_tasks
  * unit tests sold separately

Cheers,
Justas

P.S.: my gtimelogrc config:

task_list_url =
recent_task_days = 14
max_recent_category_tasks = 6
max_total_recent_tasks = 14
max_top_duration = 7
max_top_recent = 7
max_top_days_worked = 6

(empty task_list_url and specified recent_task_days enable the task list)

P.P.S.:

"recent_task_days" - how many (calendar) days to aggregate

"max_recent_category_tasks" - max task entries per category

"max_total_recent_tasks" - max total of tasks in the list

Tasks are filtered by three criteria, then merged to a single list.

You can limit max amount of tasks for each filter:
"max_top_duration" - tasks you spent most time working on
"max_top_recent" - tasks you worked on most recently
"max_top_days_worked" - tasks you spend most days on

To post a comment you must log in.
Revision history for this message
Marius Gedminas (mgedmin) wrote :

I want something like this.

I'm not happy that it creates a new file, mirroring the information already loaded into memory from timelog.txt. Is that really necessary?

I'd like to make the sidebar capable of displaying multiple pieces of information. Tasks and Recent Tasks could be two alternatives, selectable from a dropdown that would replace the title label.

That's a lot of new settings. And I get the impression that a lot of them are 0 and therefore disable this feature. Can we get some reasonable defaults that make it work out of the box?

Revision history for this message
Justas Sadzevičius (justas.sadzevicius) wrote :

> I want something like this.
>
> I'm not happy that it creates a new file, mirroring the information already
> loaded into memory from timelog.txt. Is that really necessary?

No, it was a quick hack.

> I'd like to make the sidebar capable of displaying multiple pieces of
> information. Tasks and Recent Tasks could be two alternatives, selectable
> from a dropdown that would replace the title label.

Oh, that would be nice!

> That's a lot of new settings. And I get the impression that a lot of them are
> 0 and therefore disable this feature. Can we get some reasonable defaults
> that make it work out of the box?

+1 for reasonable defaults. And I agree on too many settings.

At this point I just wanted to fiddle with them all to keep the actual list as short as possible. When displaying, say, only 15 tasks it's quite difficult to decide what makes the cut and what does not.

For now, I'd like to do some "field testing" and see what happens.

Unmerged revisions

178. By Justas Sadzevičius

Reload task list if gtimelogrc changed.

177. By Justas Sadzevičius

More filtering options.

176. By Justas Sadzevičius

Fast-and-dirty "recent" task list to pick from most recent, most worked on and most often worked on tasks.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/gtimelog/main.py'
--- src/gtimelog/main.py 2011-01-13 22:15:15 +0000
+++ src/gtimelog/main.py 2011-01-18 11:06:11 +0000
@@ -12,6 +12,7 @@
12import urllib12import urllib
13import datetime13import datetime
14import tempfile14import tempfile
15import itertools
15import ConfigParser16import ConfigParser
1617
17import pygtk18import pygtk
@@ -694,7 +695,7 @@
694 """695 """
695 mtime = self.get_mtime()696 mtime = self.get_mtime()
696 if mtime != self.last_mtime:697 if mtime != self.last_mtime:
697 self.load()698 self.reload()
698 return True699 return True
699 else:700 else:
700 return False701 return False
@@ -794,6 +795,13 @@
794 task_list_url = ''795 task_list_url = ''
795 edit_task_list_cmd = ''796 edit_task_list_cmd = ''
796797
798 recent_task_days = 0
799 max_recent_category_tasks = 15
800 max_total_recent_tasks = 50
801 max_top_recent_tasks = 0
802 max_top_duration_tasks = 0
803 max_top_days_worked_tasks = 0
804
797 show_office_hours = True805 show_office_hours = True
798 show_tray_icon = True806 show_tray_icon = True
799 prefer_app_indicator = True807 prefer_app_indicator = True
@@ -816,6 +824,17 @@
816 self.virtual_midnight.strftime('%H:%M'))824 self.virtual_midnight.strftime('%H:%M'))
817 config.set('gtimelog', 'task_list_url', self.task_list_url)825 config.set('gtimelog', 'task_list_url', self.task_list_url)
818 config.set('gtimelog', 'edit_task_list_cmd', self.edit_task_list_cmd)826 config.set('gtimelog', 'edit_task_list_cmd', self.edit_task_list_cmd)
827 config.set('gtimelog', 'recent_task_days', self.recent_task_days)
828 config.set('gtimelog', 'max_recent_category_tasks',
829 self.max_recent_category_tasks)
830 config.set('gtimelog', 'max_total_recent_tasks',
831 self.max_total_recent_tasks)
832 config.set('gtimelog', 'max_top_recent_tasks',
833 self.max_top_recent_tasks)
834 config.set('gtimelog', 'max_top_duration_tasks',
835 self.max_top_duration_tasks)
836 config.set('gtimelog', 'max_top_days_worked_tasks',
837 self.max_top_days_worked_tasks)
819 config.set('gtimelog', 'show_office_hours',838 config.set('gtimelog', 'show_office_hours',
820 str(self.show_office_hours))839 str(self.show_office_hours))
821 config.set('gtimelog', 'show_tray_icon', str(self.show_tray_icon))840 config.set('gtimelog', 'show_tray_icon', str(self.show_tray_icon))
@@ -840,6 +859,18 @@
840 'virtual_midnight'))859 'virtual_midnight'))
841 self.task_list_url = config.get('gtimelog', 'task_list_url')860 self.task_list_url = config.get('gtimelog', 'task_list_url')
842 self.edit_task_list_cmd = config.get('gtimelog', 'edit_task_list_cmd')861 self.edit_task_list_cmd = config.get('gtimelog', 'edit_task_list_cmd')
862 self.recent_task_days = config.getint('gtimelog',
863 'recent_task_days')
864 self.max_recent_category_tasks = config.getint('gtimelog',
865 'max_recent_category_tasks')
866 self.max_total_recent_tasks = config.getint('gtimelog',
867 'max_total_recent_tasks')
868 self.max_top_recent_tasks = config.getint('gtimelog',
869 'max_top_recent_tasks')
870 self.max_top_duration_tasks = config.getint('gtimelog',
871 'max_top_duration_tasks')
872 self.max_top_days_worked_tasks = config.getint('gtimelog',
873 'max_top_days_worked_tasks')
843 self.show_office_hours = config.getboolean('gtimelog',874 self.show_office_hours = config.getboolean('gtimelog',
844 'show_office_hours')875 'show_office_hours')
845 self.show_tray_icon = config.getboolean('gtimelog', 'show_tray_icon')876 self.show_tray_icon = config.getboolean('gtimelog', 'show_tray_icon')
@@ -858,6 +889,146 @@
858 f.close()889 f.close()
859890
860891
892class RecentTaskList(TaskList):
893 """Task list picked out from the time log.
894
895 Keeps a cached copy of the list in a local file, so you can use it offline.
896 """
897
898 def __init__(self, cache_filename, timelog_filename,
899 recent_days=0,
900 max_category_tasks=0,
901 max_total_tasks=0,
902 max_top_recent=0,
903 max_top_duration=0,
904 max_top_days_worked=0,
905 virtual_midnight=datetime.time(0, 0)):
906 self.timelog_filename = timelog_filename
907 self.max_category_tasks=max_category_tasks
908 self.max_total_tasks=max_total_tasks
909 self.max_top_recent=max_top_recent
910 self.max_top_duration=max_top_duration
911 self.max_top_days_worked=max_top_days_worked
912 self.recent_days=recent_days
913 self.virtual_midnight=virtual_midnight
914 TaskList.__init__(self, cache_filename)
915 self.first_time = True
916
917 def get_mtime(self):
918 """Return mtime tuple of (self.filename, self.timelog_filename),
919 or None if the one of them doesn't exist.
920 """
921 try:
922 return max(os.stat(self.filename).st_mtime,
923 os.stat(self.timelog_filename).st_mtime)
924 except OSError:
925 return None
926
927 def check_reload(self):
928 if self.first_time:
929 self.first_time = False
930 if not os.path.exists(self.filename):
931 self.generate()
932 self.load()
933 return True
934 return TaskList.check_reload(self)
935
936 def top_recent(self, entries):
937 result = []
938 for start, stop, duration, entry in entries:
939 if entry in result:
940 result.remove(entry)
941 result.insert(0, entry)
942 return result
943
944 def top_duration(self, entries):
945 entry_duration = {}
946 for start, stop, duration, entry in entries:
947 total_duration = entry_duration.get(entry, datetime.timedelta(0))
948 total_duration += duration
949 entry_duration[entry] = total_duration
950
951 sorted_entries = sorted(entry_duration.items(),
952 key=lambda e: e[1],
953 reverse=True)
954 result = [e[0] for e in sorted_entries]
955 return result
956
957 def top_days_worked(self, entries):
958 entry_days = {}
959 for start, stop, duration, entry in entries:
960 day = virtual_day(start, self.virtual_midnight)
961 entry_days.setdefault(entry, set()).add(day)
962 sorted_entries = sorted(entry_days.items(),
963 key=lambda e: len(e[1]),
964 reverse=True)
965 result = [e[0] for e in sorted_entries]
966 return result
967
968 def generate(self):
969 """Generate the task list from time log."""
970 virtual_tomorrow = virtual_day(
971 datetime.datetime.now() + datetime.timedelta(1),
972 self.virtual_midnight)
973
974 dt_max = datetime.datetime.combine(virtual_tomorrow, self.virtual_midnight)
975 dt_min = dt_max - datetime.timedelta(self.recent_days)
976
977 time_window = TimeWindow(
978 self.timelog_filename, dt_min, dt_max,
979 self.virtual_midnight)
980
981 # XXX: load to memory... ouch.
982 all_entries = list(time_window.all_entries())
983
984 top_recent = self.top_recent(all_entries)
985 if self.max_top_recent:
986 top_recent = top_recent[:self.max_top_recent]
987 top_duration = self.top_duration(all_entries)
988 if self.max_top_duration:
989 top_duration = top_duration[:self.max_top_duration]
990 top_days_worked = self.top_days_worked(all_entries)
991 if self.max_top_days_worked:
992 top_days_worked = top_days_worked[:self.max_top_days_worked]
993
994 recent = []
995 for entries in itertools.izip_longest(top_recent,
996 top_duration,
997 top_days_worked):
998 for entry in filter(None, entries):
999 if ': ' in entry:
1000 cat_task = tuple([s.strip() for s in entry.split(': ', 1)])
1001 else:
1002 cat_task = ('Other', entry.strip())
1003 if cat_task not in recent:
1004 recent.append(cat_task)
1005
1006 recent = recent[:self.max_total_tasks]
1007
1008 by_category = {}
1009 for category, task in recent:
1010 by_category.setdefault(category, []).append(task)
1011 for category in by_category:
1012 tasks = by_category[category]
1013 tasks = tasks[:self.max_category_tasks]
1014 by_category[category] = tasks
1015
1016 separator = '#..%s..#' % '...x...'.join([str(d) for d in range(1, 7)])
1017 f = open(self.filename, "w")
1018 for category in sorted(by_category):
1019 tasks = by_category[category]
1020 print >> f, separator
1021 print >> f
1022 for task in tasks:
1023 print >> f, '%s: %s' % (category, task)
1024 print >> f
1025 f.close()
1026
1027 def reload(self):
1028 self.generate()
1029 self.load()
1030
1031
861class IconChooser(object):1032class IconChooser(object):
8621033
863 @property1034 @property
@@ -1796,6 +1967,23 @@
1796 if settings.task_list_url:1967 if settings.task_list_url:
1797 tasks = RemoteTaskList(settings.task_list_url,1968 tasks = RemoteTaskList(settings.task_list_url,
1798 os.path.join(configdir, 'remote-tasks.txt'))1969 os.path.join(configdir, 'remote-tasks.txt'))
1970 elif settings.recent_task_days:
1971 tasks = RecentTaskList(
1972 os.path.join(configdir, 'recent-tasks.txt'),
1973 os.path.join(configdir, 'timelog.txt'),
1974 recent_days=settings.recent_task_days,
1975 max_category_tasks=settings.max_recent_category_tasks,
1976 max_total_tasks=settings.max_total_recent_tasks,
1977 max_top_recent=settings.max_top_recent_tasks,
1978 max_top_duration=settings.max_top_duration_tasks,
1979 max_top_days_worked=settings.max_top_days_worked_tasks,
1980 virtual_midnight=settings.virtual_midnight)
1981 try:
1982 if (os.stat(tasks.filename).st_mtime <
1983 os.stat(settings_file).st_mtime):
1984 tasks.reload()
1985 except OSError:
1986 pass
1799 else:1987 else:
1800 tasks = TaskList(os.path.join(configdir, 'tasks.txt'))1988 tasks = TaskList(os.path.join(configdir, 'tasks.txt'))
1801 main_window = MainWindow(timelog, settings, tasks)1989 main_window = MainWindow(timelog, settings, tasks)

Subscribers

People subscribed via source and target branches