Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#136] Add 'recent activities' to tracking screen #208

Open
wants to merge 25 commits into
base: develop
Choose a base branch
from

Conversation

elbenfreund
Copy link
Collaborator

@elbenfreund elbenfreund commented Jul 8, 2017

This adds a 'recent activities' area to the tracking screen that can be used to take previous activities as templates for new raw fact strings or start tracking them right away. The entire functionality is optional (on by default right now) and can be configures via preferences.

The 'recent activities' box size can be adjusted by specifying the amount of activities it shall be able to show. All activities that not fit are available via scrolling.

Closes: #136

@elbenfreund elbenfreund added this to the 0.12.0 milestone Jul 8, 2017
@elbenfreund elbenfreund requested a review from jtojnar July 8, 2017 12:10
def _on_config_changed(self, sender):
"""Callback triggered when 'config-changed' event fired."""
if self._app.config['tracking_show_recent_activities']:
# We re-create it even if one existed before necause its parameters
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

necause

@@ -80,6 +80,11 @@ def __init__(self, parent, app, initial, *args, **kwargs):
('autocomplete_split_activity',
(_("Autocomplete activities and categories separately"),
HamsterSwitch())),
('tracking_show_recent_activities',
(_("Allow tracking based on recent activities."),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not sound clear to me. I would choose something like “Show recent activities for quickly starting tracking”

return OrderedSet(recent_activities)


def serialize_activity(activity):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not we have something like this somewhere already?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think earlier hamster-lib branches had something like this. Also the overview has its own "serialize activity" code which is different however:

  • it deals with legacy hamerest representation of activity.category=None. This is something that I would be willing to throw out of the window.
  • It uses - as seperator between activity.name and category. I think this is better looking for the overview. What do you think?

So this helper provides a generic serialization utilizing the standart @ seperator. As far as I can tell current versions of hamster-lib or hamster-gtk do not have such a function yet. But share the feeling that we had this around for a while in various incarnations...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Wait, do we intend to not support category-less facts?
  • I actually prefer the at sign for consistency or habit.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • No we do and want to support category less facts. I am not quite sure that the way legacy hamster did is really where we want to go..
  • Does that mean you would prefer @ in the overview as well? At this stage I do not care about this detail very much as we will have to revisit the general layout at some later stage anyway...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, having @ in overview is what I am accustomed to.

Copy link
Collaborator Author

@elbenfreund elbenfreund Sep 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alrighty. Will adjust this then :) Im cool either way tbh.

@@ -279,6 +285,8 @@ def _get_default_config(self):
# Frontend
'autocomplete_activities_range': 30,
'autocomplete_split_activity': False,
'tracking_show_recent_activities': True,
'tracking_recent_activities_items': 6,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use count or number. items is less clear.

# allocated to its children so they actually have a height that we can
# use.
grid.show_all()
# We fetch an arbitrary Button as height-reference
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is really ugly and crashes when there are no items:

$ env XDG_CONFIG_HOME=$HOME/.dev_config XDG_DATA_HOME=$HOME/.dev_local hamster-gtk
Hamster-GTK started.
Traceback (most recent call last):
  File "/nix/store/fzrxvdjas3icqb2n5akahvix758jnfvj-python3.6-hamster-gtk/lib/python3.6/site-packages/hamster_gtk/hamster_gtk.py", line 213, in _activate
    self.window = MainWindow(app)
  File "/nix/store/fzrxvdjas3icqb2n5akahvix758jnfvj-python3.6-hamster-gtk/lib/python3.6/site-packages/hamster_gtk/hamster_gtk.py", line 107, in __init__
    self.add(TrackingScreen(self.app))
  File "/nix/store/fzrxvdjas3icqb2n5akahvix758jnfvj-python3.6-hamster-gtk/lib/python3.6/site-packages/hamster_gtk/tracking/screens.py", line 49, in __init__
    self.start_tracking_view = StartTrackingBox(self._app)
  File "/nix/store/fzrxvdjas3icqb2n5akahvix758jnfvj-python3.6-hamster-gtk/lib/python3.6/site-packages/hamster_gtk/tracking/screens.py", line 195, in __init__
    self.recent_activities_widget = self._get_recent_activities_widget()
  File "/nix/store/fzrxvdjas3icqb2n5akahvix758jnfvj-python3.6-hamster-gtk/lib/python3.6/site-packages/hamster_gtk/tracking/screens.py", line 255, in _get_recent_activities_widget
    child = grid.get_children()[1]
IndexError: list index out of range
Traceback (most recent call last):
  File "/nix/store/fzrxvdjas3icqb2n5akahvix758jnfvj-python3.6-hamster-gtk/lib/python3.6/site-packages/hamster_gtk/hamster_gtk.py", line 218, in _activate
    app.add_window(self.window)
TypeError: Argument 1 does not allow None as a value
Hamster-GTK shut down.

Copy link
Collaborator Author

@elbenfreund elbenfreund Sep 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that the way we derive our min_height hint is far from elegant (but fixed value layouts are far worse even). I am absolutely open to better approaches! Empty children are definitely a nice catch and have been addressed in the latest version.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a todo comment and go with this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created #224 as I would like to avoid todo comments.


def _populate(self):
"""Fill the widget with rows per activity."""
def add_row_widgets(row_counter, activity):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

row_counterrow_index/row_number; counter implies an object that counts

def get_recent_activities(controller, start, end):
"""Return a list of all activities logged in facts within the given timeframe."""
recent_activities = [fact.activity for fact in controller.facts.get_all(start=start, end=end)]
return OrderedSet(recent_activities)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The activities are returned in a convoluted order. It should be reversed prior to removing duplicates.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I do understand. Could you elaborate please?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be OrderedSet(reversed(recent_activities)), otherwise you will get the activities in the order they were first used, not the last.

facts     1 2 1 3 2 1 3  output   reversed
current   x x   x       → 1 2 3  → 3 2 1
expected          x x x → 3 1 2  → 2 1 3

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the clarification. To be honest, right now FactManager.get_all() does not commit to any ordering at all. I created an issue with hamster-lib to improve this.
For now I will implement a workaround simple sorting call as part of the prelimery get_recent_activities helper which will hopefully address your concerns.


today = datetime.date.today()
start = today - datetime.timedelta(90)
activities = reversed(helpers.get_recent_activities(self._controller, start, today))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want all the activities and scroll instead of displaying the tracking_recent_activities_items?

Copy link
Collaborator Author

@elbenfreund elbenfreund Sep 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer such a more general solution. There is nothing preventing us from limiting the results to just tracking_recent_activities_items (via config) if we want, but especially given that we already have a solution I do not see the harm.

We need scrolling anyway (because we can not make any guarantee that tracking_recent_activities_items will fit in the space available. In light of that, it seems we get the extra activities for free if the user bothers scolling.

What would be the downside?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accidental scrolling to the other end and performance.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • "accidental scrolling": Im not sure I see the use case. especially as there is the posibility of a scrollbox even if if we just fetch the number of activities defined in the config.
  • by default we just fetch todays facts. this should net us no more than a couple of dozens facts at most I figure. I doubt there is any concievable performance impact on the DB or the GUI rendering. Unless I'm missing something this seems like a case of premature optimization.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • You scroll somewhere accidentally and since the list is long you do not have a simple way of returning back – you cannot just quickly scroll down and when you find out that you are on the wrong side quickly scroll to the correct one.
  • I see more than just today’s facts. In fact there does not seem to be any limit.

Copy link
Collaborator Author

@elbenfreund elbenfreund Sep 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right!

The idea was to make the timedelta adjustable via a config setting, but i forgot to actually do that :(
If done that way it is up to the user to figure what timeframe is viable to them. We probably should point out the performance consideration though...

It would probably be best to leave this additional customization to a seperate issue and use start=today for now...

This commits extends the 'start tracking' view with a list of recent
activities that can be used to euther copy the data to the entry field,
ready to be extended and then started manually by the user or to
start/continue tracking this activity right away.

Ref: #136
@codecov
Copy link

codecov bot commented Sep 22, 2017

Codecov Report

Merging #208 into develop will decrease coverage by 2.37%.
The diff coverage is 55.76%.

Impacted file tree graph

@@             Coverage Diff             @@
##           develop     #208      +/-   ##
===========================================
- Coverage    82.59%   80.21%   -2.38%     
===========================================
  Files           31       31              
  Lines         1356     1456     +100     
  Branches        78       86       +8     
===========================================
+ Hits          1120     1168      +48     
- Misses         213      260      +47     
- Partials        23       28       +5
Impacted Files Coverage Δ
hamster_gtk/preferences/preferences_dialog.py 100% <ø> (ø) ⬆️
hamster_gtk/overview/widgets/fact_grid.py 83.2% <100%> (+1.31%) ⬆️
hamster_gtk/tracking/screens.py 69.58% <46.66%> (-14.45%) ⬇️
hamster_gtk/helpers.py 80.28% <68.75%> (-3.36%) ⬇️
hamster_gtk/hamster_gtk.py 82.64% <90.9%> (-3.41%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6b5d106...1df269a. Read the comment docs.

@@ -45,7 +45,8 @@ def __init__(self, app, *args, **kwargs):
self.set_transition_type(Gtk.StackTransitionType.SLIDE_UP)
self.set_transition_duration(1000)
self.current_fact_view = CurrentFactBox(self._app.controller)
self.current_fact_view.connect('tracking-stopped', self.update)
self.current_fact_view.con275G
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wah

Until now, we returned a ``PathLocal`` instance as value for the
``tmpfile_path`` key. This has been changed to a proper string
representations instead.
Our ``app`` fixture (which is used extensively) used to instantiate the
default ``HamsterGTK`` class. This meant that the default config loading
was triggered and as a consequence the systems live config being used.
This is obviously an issue. Instead we monkey patch the class now to
skip the config loading machinery and use/return the ``config`` fixture.
Our previous test simply instantiated the class, hence tiggering the
config loading/creation machinery. This will directly interfere with the
actual user config! To avoid this we provide a new test that
monkeypatches the class accordingly. As a consequence the config loading
needs to be tested seperatly now though.
@elbenfreund
Copy link
Collaborator Author

@jtojnar I addressed most of your concerns and CI is passing again (finally). Do you think you will find some time to review it again?

The only big issue left open is this. I am absolutely open to improve the current hackish solution if you have better idea.

Copy link
Member

@jtojnar jtojnar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several typos, otherwise looks good from a shallow review (ignoring my bloat concerns)

app = hamster_gtk.HamsterGTK()
def test_instantiation(self, config):
"""
Make sure class instatiation works as intended.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instatiation → instantiation

separator (str, optional): ``string`` used to separate ``activity.name`` and
``category.name``. The separator will be omitted if ``none_categoty=''`` and
``activity.category=None``. Defaults to ``@``.
none_category (str, optional): ``string`` to represent the 'lack of a cactegory' for an
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cactegory

def get_recent_activities(controller, start, end):
"""Return a list of all activities logged in facts within the given timeframe."""
# [FIXME]
# This manual sorting within python is of cause less than optimal. We stick
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

of cause → of course

Args:
activity (Activity): ``Activity`` instance to serialize.
separator (str, optional): ``string`` used to separate ``activity.name`` and
``category.name``. The separator will be omitted if ``none_categoty=''`` and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

none_categoty → none_category


We actually test against a monkeypatched class in order to avoid the
config loading machinery as this would access the user data on fs.
The relevant skiped methods need to be tested separately.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

skipped

# allocated to its children so they actually have a height that we can
# use.
grid.show_all()
# We fetch an arbitrary Button as height-reference
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a todo comment and go with this.

return OrderedSet(recent_activities)


def serialize_activity(activity, separator='@', none_category='not categorized'):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably do not want to serialize uncategorized activities with the “not categorized” category.

screenshot from 2018-05-04 14-40-33

If you copy/start them, the fake category will become real.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch. 👍

When serializing facts for the 'continue tracking' functionality, we
need to make sure that the 'uncategorized' placeholder is omitted,
otherwise a category of that name would be created!

Ref.: #136
"""
def get_label(activity):
"""Label representing the activity/category combination."""
label = Gtk.Label(helpers.serialize_activity(activity, none_category=''))

This comment was marked as resolved.

@elbenfreund elbenfreund force-pushed the feature/136 branch 5 times, most recently from a2f4df4 to a33d84c Compare May 4, 2018 15:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants