ahitrin / siebenapp Goto Github PK
View Code? Open in Web Editor NEWExperimental dependency-aware goal manager
Home Page: https://ahitrin.github.io/SiebenApp/
License: GNU General Public License v3.0
Experimental dependency-aware goal manager
Home Page: https://ahitrin.github.io/SiebenApp/
License: GNU General Public License v3.0
When we add, insert or rename a goal there should be a possibility to deny any changes and keep goal tree unchanged. But currently there is no possibility to cancel edit as it's started.
We could add a new "Cancel" button at the right of edit field. Also it would be nice to add "Save" button too.
Currently, there is impossible to undo changes made to the goaltree (even by mistake). In order to fix this, we need to save changes history in the project file.
Current state of rendering of large goal trees is far from perfection. Let's see an example fragment:
Here, we have a dependency between goals 25 and 28 (selected with a purple color):
Looks like a green path should look better. Alas, the rendering engine cannot find it. I guess, we could try to use force-based algorithms to tune edge positions after the initial rendering.
Current rendering mechanism has serious disadvantages:
dot
program)work.dot
and work.png
)We should try to use libgraphviz
to calculate goal positions and draw goals using Qt instead.
There a frequently used case when we could avoid redrawing of all goals on GUI. It's a Select
event. It affects neither goal positions, nor goal sizes, nor edges between goals (except when a deselected goal disappears).
If we track this situation, overall performance should be significantly improved, especially on large graphs. And it doesn't look like a challenging issue. We could just track user actions and cache previous rendering results.
It would be useful to have a possibility to open several goal trees inside one SiebenApp window.
Motivation: current DB model is quite performant. All the data is read once on load, and then is only updated. Nevertheless, DB files could be updated from external sources (e.g. by external synchronization processes like Dropbox/Git/etc). Since changes in a DB file are not currently monitored, such scenario may lead to data loss.
Solution: we should implement a mechanism of checking "freshness" of DB files and provide some kind of "refreshing" when changed occur.
Goals:
We need to finish work that was started in the domain.py
. It seems better to implement classic OOP inheritance than dynamic dispatch using "overriden" attributes. The code should became less murky and more supportable by IDEs when we're done
It would be useful to distinguish two types of links:
Ideas how to work with different kind of links:
L
to create subgoal link, and l
to create dependency link.How to reproduce:
Expected behavior: a message "This goal can't be closed because it have open subgoals". And nothing more.
Actual behavior: unzoom
We don't need an unzoom here!
Currently we have only 1 goal to zoom into. It's not useful when working with large goal trees. You constantly switch back to the global view when unzoom, and have to visually search for the next goal.
A -> B -> C -> D
(zoom on B)
B -> C -> D
(zoom on C)
C -> D
(unzoom on C)
A -> B -> C -> D
The simplest solution is to have stacked zoom levels. Like this:
A -> B -> C -> D
(zoom on B)
B -> C -> D
(zoom on C)
C -> D
(unzoom on C)
B -> C -> D
(unzoom on B)
A -> B -> C -> D
When unzooming, you should return back to the previous zoom root, not to the global tree root.
Application should be installable as binary package on all standard platforms: Windows, Linux, MacOS X.
Sometimes it would be useful to join two or more goaltrees into a single one. This should be made in sieben-manage
script.
Planned usage:
sieben-manage merge first.db second.db [third.db ...]
This command should take all goals and relations from second.db
(and other DBs when listed) and put them as subgoals of the root goal in first.db
. Only the first.db
file will be changed. When command is executed several times it adds several subtrees to the first.db
(no check on "already merged" is done).
Current goal ordering in clieben
is too primitive. It would be better to use topological sorting instead. This would make all open goals grouping together so they become easier to manage.
It's often useful to quickly understand how much progress has been made for the given subtree. Currently, this value could only be estimated manually, and requires switching into "Open & Closed goals" view.
In order to track progress more easily, we should show 'goal progress' counter on the goal widget. The rules are following:
1.Goal progress is a ratio of closed/all subgoals (including the goal itself).
2. For a single goal, progress ratio could be either 0% (open) or 100% (closed)
3. For a complex goal with subgoals, the ratio lies between 0 (all subgoals are open) and 100 (all subgoals and the goal itself are closed).
4. Blockers do not affect progress ratio
Currently, it's not clear how the ratio should be displayed. Maybe, percents would be enough. Or maybe it's better to include both percents and open/total counters.
Toggle view switching exists here from the very first versions of the applictation. But it seems not much useful for now. Instead of switch loop, we should try to implement independent view switches. This should bring us more mainstreamly UX.
Sometimes I'd like to overview a whole goaltree at once (or even print it). The easiest way to do it is restoring "export to dot" functionality that existed in the system before #5.
This should be done in a separate application.
Problem: in large goaltrees, it's hard to find subgoals even when we know their name. We need a mechanism that provides at least simple search facilities
Idea: Add a new layer for filter. When pressing f
hotkey, the user is asked for the search string (regex?). When it's entered, this layer shows only goals matching this string/regex (together with selected goals and, possibly, edges). When it's cleared, this layer filters nothing.
How to reproduce:
Expected behavior: a message should appear - "Goal can't be linked to itself"
Actual behavior: nothing happens
After some update of Hypothesis dependency property test may run for too long. Looks like a bug in Hypothesis library. It may be related to HypothesisWorks/hypothesis#2340 or HypothesisWorks/hypothesis#2308.
This causes CI builds to fail. When I've caught this bug locally, it was possible to take a profile. Seems that it hangs somewhere inside shrinking function.
When working with large goal trees it would be nice to have an ability of quick search for goals.
Current vision:
Zoom
and Enumeration
f
to toggle enable/disable filterGoal: split app tutorial and meta-info (current status, etc).
README
and move it into /doc
README
https://travis-ci.org/ahitrin/SiebenApp/jobs/636474599
=================================== FAILURES ===================================
________________________ TestGoalTreeRandomWalk.runTest ________________________
self = <hypothesis.stateful.GoaltreeRandomWalk.TestCase testMethod=runTest>
def runTest(self):
> run_state_machine_as_test(state_machine_class)
../../../virtualenv/python3.8.0/lib/python3.8/site-packages/hypothesis/stateful.py:283:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../../virtualenv/python3.8.0/lib/python3.8/site-packages/hypothesis/stateful.py:178: in run_state_machine_as_test
run_state_machine(state_machine_factory)
../../../virtualenv/python3.8.0/lib/python3.8/site-packages/hypothesis/stateful.py:91: in run_state_machine
@given(st.data())
siebenapp/tests/test_properties.py:106: in goaltree_is_always_valid
assert self.goaltree.verify()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <siebenapp.zoom.Zoom object at 0x7fb9eaf729d0>
def verify(self) -> bool:
ok = self.goaltree.verify()
if len(self.zoom_root) == 1:
return ok
visible_goals = self._build_visible_goals()
assert (
self.settings["selection"] in visible_goals
), "Selected goal must be within visible area"
> assert (
self.settings["previous_selection"] in visible_goals
), "Prev-selected goal must be within visible area"
E AssertionError: Prev-selected goal must be within visible area
siebenapp/zoom.py:113: AssertionError
---------------------------------- Hypothesis ----------------------------------
Falsifying example:
state = GoaltreeRandomWalk()
state.open_db_connection()
Goals: [(1, 'Root', 1)], Edges: [], Selection: []
state.add_goal(b=False)
Goals: [(1, 'Root', 1), (2, 'a', 1)], Edges: [(1, 2, 1)], Selection: []
Draw 1: 2
state.select_random_goal(d=data(...))
Goals: [(1, 'Root', 1), (2, 'a', 1)], Edges: [(1, 2, 1)], Selection: [('selection', 2)]
state.zoom()
Goals: [(1, 'Root', 1), (2, 'a', 1)], Edges: [(1, 2, 1)], Selection: [('selection', 2), ('previous_selection', 2)]
state.add_goal(b=False)
Goals: [(1, 'Root', 1), (2, 'a', 1), (3, 'a', 1)], Edges: [(1, 2, 1), (2, 3, 1)], Selection: [('selection', 2), ('previous_selection', 2)]
state.add_goal(b=False)
Goals: [(1, 'Root', 1), (2, 'a', 1), (3, 'a', 1), (4, 'a', 1)], Edges: [(1, 2, 1), (2, 3, 1), (2, 4, 1)], Selection: [('selection', 2), ('previous_selection', 2)]
state.add_goal(b=False)
Goals: [(1, 'Root', 1), (2, 'a', 1), (3, 'a', 1), (4, 'a', 1), (5, 'a', 1)], Edges: [(1, 2, 1), (2, 3, 1), (2, 4, 1), (2, 5, 1)], Selection: [('selection', 2), ('previous_selection', 2)]
Draw 2: 5
state.select_random_goal(d=data(...))
Goals: [(1, 'Root', 1), (2, 'a', 1), (3, 'a', 1), (4, 'a', 1), (5, 'a', 1)], Edges: [(1, 2, 1), (2, 3, 1), (2, 4, 1), (2, 5, 1)], Selection: [('previous_selection', 2), ('selection', 5)]
state.zoom()
Goals: [(1, 'Root', 1), (2, 'a', 1), (3, 'a', 1), (4, 'a', 1), (5, 'a', 1)], Edges: [(1, 2, 1), (2, 3, 1), (2, 4, 1), (2, 5, 1)], Selection: [('selection', 5), ('previous_selection', 5)]
state.add_goal(b=False)
Goals: [(1, 'Root', 1), (2, 'a', 1), (3, 'a', 1), (4, 'a', 1), (5, 'a', 1), (6, 'a', 1)], Edges: [(1, 2, 1), (2, 3, 1), (2, 4, 1), (2, 5, 1), (5, 6, 1)], Selection: [('selection', 5), ('previous_selection', 5)]
Draw 3: 6
state.select_random_goal(d=data(...))
Goals: [(1, 'Root', 1), (2, 'a', 1), (3, 'a', 1), (4, 'a', 1), (5, 'a', 1), (6, 'a', 1)], Edges: [(1, 2, 1), (2, 3, 1), (2, 4, 1), (2, 5, 1), (5, 6, 1)], Selection: [('previous_selection', 5), ('selection', 6)]
state.zoom()
Goals: [(1, 'Root', 1), (2, 'a', 1), (3, 'a', 1), (4, 'a', 1), (5, 'a', 1), (6, 'a', 1)], Edges: [(1, 2, 1), (2, 3, 1), (2, 4, 1), (2, 5, 1), (5, 6, 1)], Selection: [('selection', 6), ('previous_selection', 6)]
state.zoom()
Goals: [(1, 'Root', 1), (2, 'a', 1), (3, 'a', 1), (4, 'a', 1), (5, 'a', 1), (6, 'a', 1)], Edges: [(1, 2, 1), (2, 3, 1), (2, 4, 1), (2, 5, 1), (5, 6, 1)], Selection: [('selection', 6), ('previous_selection', 6)]
Draw 4: 5
state.select_random_goal(d=data(...))
Goals: [(1, 'Root', 1), (2, 'a', 1), (3, 'a', 1), (4, 'a', 1), (5, 'a', 1), (6, 'a', 1)], Edges: [(1, 2, 1), (2, 3, 1), (2, 4, 1), (2, 5, 1), (5, 6, 1)], Selection: [('previous_selection', 6), ('selection', 5)]
state.zoom()
state.teardown()
================== 1 failed, 123 passed in 166.95s (0:02:46) ===================
Motivation: more readable tests. We could use declarative DSL to build trees instead of constructing it manually.
DSL example:
1so Root -> 2, 3, 4 2-c Top 1 -> 3-o Middle -> 4 4po Top 2 ->
There is one row per goal and all its subgoals. A format is simple:
s
for current selection, p
for previous selection, -
for otherso
for open goals, c
for closed ones->
(delimiter)Currently goaltrees are constructed in tests in non-visual way:
def test_closed_leaf_goal_could_not_be_reopened(self):
self.goals.add('A')
self.goals.select(2)
self.goals.add('B')
self.goals.select(3)
self.goals.toggle_close()
self.goals.select(2)
self.goals.toggle_close()
assert self.goals.all(keys='open,top') == {
1: {'open': True, 'top': True},
2: {'open': False, 'top': False},
3: {'open': False, 'top': False}}
...
With declarative DSL, goaltree construction should become much more concise and readable:
def test_closed_leaf_goal_could_not_be_reopened(self):
self.goals = build_goaltree('''
1so Root -> 2
2-c A -> 3
3-c B ->
''')
assert self.goals.all(keys='open,top') == {
1: {'open': True, 'top': True},
2: {'open': False, 'top': False},
3: {'open': False, 'top': False}}
...
It's sometimes too easy to accidentally hit d
button in GUI and unintentionally remove a goal. This action cannot be reverted. That's pity.
We should allow delete goals only with a confirmation. It could be done in the same way as renaming, via the prompt.
I've completely lost interest on it. It's difficult to make it less ugly, it's difficult to beat performance issues. Also, DPG library itself (for 1.x version) was put into maintenance-only mode recently.
That's why it doesn't seem reasonable to continue work on DPG version anymore. #255 was an interesting experiment that brought improvements into rendering process. But now we should put a comma.
With using 'plain text' for dot output, not all symbols are allowed in node names. For example, names containing quote marks could not be rendered.
Possible solution: we should try to use html.escape
to cleanup such symbols before rendering work.dot
.
Motivation: not all goal actions are possible in some situations. For example, the last link between the leaf goal and its parent could not be removed. User should be notified about reasons why some action could not be performed.
Of course, this notification must not be intrusive. Currently, some message in status line at the bottom of the screen could be enough. More changes in interface are to come in #5, and notification mechanism could also be modified after this change.
Currently, information about selected subgoals is distributed between several places:
RenderRow.select
fieldGraph.selections()
methodGraph.settings("selection")
methodThis seems overcomplicated. It would be better to keep this information in a single place. Mostly probably, the better place for it should be a new field in RenderResult
class. It should be filled from arguments in RenderResult.__init__
. All clients should use it instead of old methods. Old ways to obtain selections should be removed.
Currently, sieben-manage dot
exports a whole goal tree from the given file. It's not useful in many situations.
To make its output closer to what user may see in Sieben UI, we should add an opportunity to consider all existing layers and optionally enable/disable existing views.
E.g.:
# by default, shows only open goals (or selected)
sieben-manage dot my.db
# show both open and closed goals (or selected)
sieben-manage dot -n my.db
# show both open and closed goals, but only switchable (or selected)
sieben-manage dot -nt my.db
# show only open (or selected) goals with progress counters
sieben-manage dot -p my.db
Feature drop: this improvement will make sieben-manage
unable to mitigate zoom roots. That's OK for now. There is a workaround: to export a full goal tree we could unzoom it in Sieben UI, perform an export, and zoom it back.
A weak-typed Graph.q(keys: str)->Dict[int, Any]
lives in code from the very first versions of the application. At the beginning, it provided great flexibility and allowed to write simpler tests. But now, it mostly brakes the development process. We should replace it with a modern pythonic data structure.
A 'keys' argument of 'q' method should also go away during this cleanup. In production code, we always use the same keys list.
What do we expect from this change:
mypy
(completion, type checks)if
s)With QML, user interface should become more attractive and nice comparing to native widgets.
There are several attributes that are added into each goal on some stages and seems not applicable to be RenderRow
fields. Examples are x
, y
, row
, col
that are added in Renderer
.
In addition, there are potential new properties that could be added dynamically to some RenderRow
instances: filter properties, autolink properties, zoom root attribute, progress status, and so on. We could use these attributes instead of current "fake goals". This will reduce total amount of nodes and edges shown, increasing understandability of rendered graph. Also, we could finally get rid of RenderResult.graph()
method, collecting everything within rows.
Python goes forward, so we should do the same. Set Python 3.9 as the minimal supported version. Improve code style using new syntax.
Edit field may lose focus on many events including language switching (using Super
+Space
). This makes it impossible to create goals in another language.
Sometimes it would be helpful to export a goaltree from Sieben database to an external system that support nothing more than formatted text (a kind of wiki or GitHub). The most popular structured plain text format nowadays is Markdown.
So, let's implement a markdown export in sieben-manage
. It should work like existing sieben-manage dot
, with new enhancements planned to implement in #310.
Problem: sometimes it would be useful to auto-assign another parent goal when adding a new goal.
An example: I work on system that contains of two modules, foo
and bar
. While working on foo
, I understand that it requires a new feature in bar
. To reflect this, I add a new subgoal: "bar: implement feature". But I'm currently zoomed into the "foo" subtree, so the new subgoal belongs to it. To make this goal visible in the "bar" subtree I have to unzoom and manually add a new parent link between "bar" root and my new subgoal. This workflow is far from perfect.
Idea: allow to assign special regexes for any goal. When a new goal is being added, it's name is checked against all regexes we have. For all matching regexes a new blocker link should be created, and one of the regexed goals become a new parent of the given subgoal.
With the given example, I would add "bar.*"
regex to the "bar" root goal, and it would become the parent of any new (or renamed) goal which name starts with "bar.."
We try to split different pieces of functionality into separate classes (Goals
, Zoom
, Enumeration
, yet more to come in #67). But the main challenge here grows from the dependencies between different methods. The less each layer knows about others the better.
A good way to achieve this is usually replacing direct method calls with event/command handling. Let's do it
While working on #329, I've discovered that there's the same pattern in many places:
RenderResult
instance via q()
methodTo make things simple, we should try to add roots: Set[GoalId]
field into RenderResult
and calculate it right in RenderResult.__init__
. Then, when we use this instance obtained from q()
, start iterating right from its values. Calls to settings("root")
should also be replaced with calls to render_result.roots
.
There could be several root goals, for example, when we switch on "Only switchable goals" toggle.
I'd like to use SiebenApp in GUI-less environment (e.g., smartphone + Termux). In order to do this, we need to finish and merge changes from the cli
branch.
The planned EOL of Python 3.5 should happen at Sep 2020. New versions are wide available. So, it seems reasonable to drop support for the old version of the language.
In addition, this removal will unblock possibility to use Python 3.6 features which could improve readability and/or performance of the application.
Motivation: currently, SiebenApp depends on two external libraries that must be installed beforehand: Python and Qt 5. This makes installation and running a bit difficult. Modern Python UI is not restricted by Qt only. It also includes few interesting alternatives. We should take a look at them, so they could make installation and running simpler.
We're looking for the following benefits:
Currently, we have the following list of possible alternatives:
In order to reduce amount of visible goals it would be useful to zoom view to the selected goal. This goal should be shown as new root. All goals that do not depend on the selected one should be hidden.
Unzooming currently should just reset view back to the very root goal.
Currently, only one root goal is allowed. It's a legacy of my previous experiments (e.g., Clokado) which were aimed to solve a single big goal a time. SiebenApp allow to work with more complex goals, and there is no actual need to stick all of them to the single root goal.
Let's try to allow addition of new root goals. This could simplify overall goaltree height and reduce its complexity.
Initial structure - like this:
open_(1, Root, [2, 3]),
open_(2, Zoom root, [4])
open_(3, New parent)
open_(4, Top)
Actions:
Expected result: both selections belongs to the selected scope (only 2)
Actual result: goal 4 still remains selected (out of scope)
Current state of goal tree is kept in file sieben.db
. Despite of its extension, it's nothing more than raw dump of app's inner state made with pickle module. Therefore it's impossible to keep several goal trees (like several projects and so on) in one file. Also, migrations are difficult to implement.
We should add support for embedded DB instead. I guess, Sqlite would be a good choice.
Currently all actions are available only via keyboard. And it's impossible to see full list of actions from the application. We should add a simple dialog window containing such list.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.