backlogops-gui 1.2__tar.gz → 1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {backlogops_gui-1.2 → backlogops_gui-1.3}/PKG-INFO +45 -12
- {backlogops_gui-1.2 → backlogops_gui-1.3}/README_pypi.md +40 -7
- {backlogops_gui-1.2 → backlogops_gui-1.3}/backlogops_gui.egg-info/PKG-INFO +45 -12
- backlogops_gui-1.3/backlogops_gui.egg-info/requires.txt +6 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/setup.py +5 -5
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/application.py +10 -5
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/backlog_actions.py +13 -4
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/backlog_io.py +1 -1
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/backlog_window.py +37 -16
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/format_dialogs.py +1 -1
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/jira_dialogs.py +1 -1
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/jira_rank.py +12 -10
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/jira_update.py +7 -2
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/jira_write.py +4 -1
- backlogops_gui-1.3/src/backlogops_gui/report_windows.py +85 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/table_view.py +14 -3
- backlogops_gui-1.2/backlogops_gui.egg-info/requires.txt +0 -6
- backlogops_gui-1.2/src/backlogops_gui/report_windows.py +0 -61
- {backlogops_gui-1.2 → backlogops_gui-1.3}/LICENSE.txt +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/backlogops_gui.egg-info/SOURCES.txt +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/backlogops_gui.egg-info/dependency_links.txt +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/backlogops_gui.egg-info/entry_points.txt +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/backlogops_gui.egg-info/top_level.txt +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/pyproject.toml +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/setup.cfg +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/__init__.py +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/__main__.py +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/_migrate_warn.py +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/backlog_dialogs.py +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/blog_version_reporter.py +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/choice_dialogs.py +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/config_edit.py +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/file_choosers.py +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/jira_actions.py +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/jira_base.py +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/jira_order.py +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/jira_read.py +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/jira_rename.py +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/key_list_box.py +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/log_buffer.py +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/modal_dialog.py +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/py.typed +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/python_version.py +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/tcltk_version.py +0 -0
- {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/token_dialog.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: backlogops-gui
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3
|
|
4
4
|
Summary: Graphical user interface for backlog operations.
|
|
5
5
|
Author: Tom Björkholm
|
|
6
6
|
Author-email: Tom Björkholm <klausuler_linnet0q@icloud.com>
|
|
@@ -21,11 +21,11 @@ Classifier: Typing :: Typed
|
|
|
21
21
|
Requires-Python: >=3.12
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE.txt
|
|
24
|
-
Requires-Dist: backlogops>=1.
|
|
24
|
+
Requires-Dist: backlogops>=1.3
|
|
25
25
|
Requires-Dist: argcomplete>=3.7.2
|
|
26
|
-
Requires-Dist: wizard-ui-bridge>=1.
|
|
27
|
-
Requires-Dist: wizard-tk-bridge>=1.
|
|
28
|
-
Requires-Dist: edit-cfg-json-tk>=0.
|
|
26
|
+
Requires-Dist: wizard-ui-bridge>=1.4
|
|
27
|
+
Requires-Dist: wizard-tk-bridge>=1.4
|
|
28
|
+
Requires-Dist: edit-cfg-json-tk>=0.3.0
|
|
29
29
|
Requires-Dist: versionreporter>=0.4
|
|
30
30
|
Dynamic: author
|
|
31
31
|
Dynamic: license-file
|
|
@@ -75,6 +75,10 @@ The following functionality is available in all 3 packages:
|
|
|
75
75
|
- Estimate ready date for the backlog items based on available teams, team
|
|
76
76
|
velocity, vacation dates, periods with half time work, etc.
|
|
77
77
|
|
|
78
|
+
- Configure what a backlog item that nobody has estimated counts as, per backlog
|
|
79
|
+
item level, so that an unestimated item does not make the estimated dates too
|
|
80
|
+
optimistic.
|
|
81
|
+
|
|
78
82
|
- Extract backlog keys at given backlog item levels.
|
|
79
83
|
|
|
80
84
|
- Reorder the backlog so that the dependencies are satisfied.
|
|
@@ -109,7 +113,16 @@ The following functionality is available in all 3 packages:
|
|
|
109
113
|
- Migrate an older configuration or preset file to the current file format.
|
|
110
114
|
|
|
111
115
|
- A wizard to create a backlog-ops configuration file with the workforce, named
|
|
112
|
-
input and output presets, level names,
|
|
116
|
+
input and output presets, level names, status name mapping, the guess for
|
|
117
|
+
unestimated items, how the graphical user interface shows the data, and the
|
|
118
|
+
Jira integration.
|
|
119
|
+
|
|
120
|
+
- An editor showing a whole configuration file or a stand-alone preset file at
|
|
121
|
+
once, folded where it is deep, to change a single value without stepping
|
|
122
|
+
through the wizard.
|
|
123
|
+
|
|
124
|
+
- Encrypt the Jira API token to a pass-phrase-protected file, so that no
|
|
125
|
+
configuration file holds the token in clear text.
|
|
113
126
|
|
|
114
127
|
- Read a backlog and releases from Jira into a backlog and release table.
|
|
115
128
|
|
|
@@ -128,6 +141,10 @@ The following functionality is available in all 3 packages:
|
|
|
128
141
|
- Move backlog items to a chosen anchor in the Jira rank order, following a key
|
|
129
142
|
list.
|
|
130
143
|
|
|
144
|
+
- Report what a Jira operation did not do. Every Jira result opens with what
|
|
145
|
+
Jira refused or what was skipped, so a value that was not written is visible
|
|
146
|
+
without reading the whole listing.
|
|
147
|
+
|
|
131
148
|
## The operating model
|
|
132
149
|
|
|
133
150
|
The operating model that most of the functionality is designed for is that the
|
|
@@ -152,7 +169,10 @@ the library:
|
|
|
152
169
|
|
|
153
170
|
- `title` The title of the backlog item. Required.
|
|
154
171
|
|
|
155
|
-
- `story_points` The story points of the backlog item.
|
|
172
|
+
- `story_points` The story points of the backlog item. Optional. An item with no
|
|
173
|
+
story points is one that nobody has estimated yet, and what such an item is
|
|
174
|
+
worked with when a completion date is estimated is configured. May be a
|
|
175
|
+
fraction, such as 0.5.
|
|
156
176
|
|
|
157
177
|
- `status` The status of the backlog item. Required.
|
|
158
178
|
|
|
@@ -253,9 +273,11 @@ python -m backlogops_gui
|
|
|
253
273
|
python -m backlogops_gui -c config_file.cfg
|
|
254
274
|
````
|
|
255
275
|
|
|
256
|
-
At first startup you do not have any configuration file yet,
|
|
257
|
-
|
|
258
|
-
wizard.
|
|
276
|
+
At first startup you do not have any configuration file yet, so the
|
|
277
|
+
application shows a *No configuration* dialog offering to run the
|
|
278
|
+
configuration wizard, load an existing configuration file, or exit.
|
|
279
|
+
Cancelling the wizard or the file chooser returns to that dialog, so
|
|
280
|
+
the application starts only once a configuration is in place.
|
|
259
281
|
|
|
260
282
|
## Main window
|
|
261
283
|
|
|
@@ -323,6 +345,17 @@ is in the menus.
|
|
|
323
345
|
The backlog window shows 2 read-only tables: one with the backlog and
|
|
324
346
|
one with the list of releases. You will want to use the menus.
|
|
325
347
|
|
|
348
|
+
At the top of the window an information region records where the data
|
|
349
|
+
came from and when it was read, marks the window once you change the
|
|
350
|
+
backlog, and offers a **Read again** button that re-reads the same
|
|
351
|
+
source (the same file, or the same Jira preset and filter) in place. A
|
|
352
|
+
re-read with unsaved changes asks for confirmation first.
|
|
353
|
+
|
|
354
|
+
Each Jira action answers in a copy-pasteable pop-up that leads with what
|
|
355
|
+
did not happen: what Jira refused, then what was skipped, and last what
|
|
356
|
+
succeeded. When something was refused the pop-up title is marked
|
|
357
|
+
*NOT ALL SUCCEEDED*, so it is visible even behind another window.
|
|
358
|
+
|
|
326
359
|
- Backlog
|
|
327
360
|
|
|
328
361
|
- Order by keys...
|
|
@@ -365,10 +398,10 @@ one with the list of releases. You will want to use the menus.
|
|
|
365
398
|
|
|
366
399
|
## Test summary
|
|
367
400
|
|
|
368
|
-
- Test result:
|
|
401
|
+
- Test result: 2555 passed, 1 deselected in 58s
|
|
369
402
|
- No flake8 warnings.
|
|
370
403
|
- No mypy errors found.
|
|
371
404
|
- No pylint warnings.
|
|
372
405
|
- No python layout warnings.
|
|
373
|
-
- Built version(s): 1.
|
|
406
|
+
- Built version(s): 1.3
|
|
374
407
|
- Build and test using Python 3.14.7
|
|
@@ -41,6 +41,10 @@ The following functionality is available in all 3 packages:
|
|
|
41
41
|
- Estimate ready date for the backlog items based on available teams, team
|
|
42
42
|
velocity, vacation dates, periods with half time work, etc.
|
|
43
43
|
|
|
44
|
+
- Configure what a backlog item that nobody has estimated counts as, per backlog
|
|
45
|
+
item level, so that an unestimated item does not make the estimated dates too
|
|
46
|
+
optimistic.
|
|
47
|
+
|
|
44
48
|
- Extract backlog keys at given backlog item levels.
|
|
45
49
|
|
|
46
50
|
- Reorder the backlog so that the dependencies are satisfied.
|
|
@@ -75,7 +79,16 @@ The following functionality is available in all 3 packages:
|
|
|
75
79
|
- Migrate an older configuration or preset file to the current file format.
|
|
76
80
|
|
|
77
81
|
- A wizard to create a backlog-ops configuration file with the workforce, named
|
|
78
|
-
input and output presets, level names,
|
|
82
|
+
input and output presets, level names, status name mapping, the guess for
|
|
83
|
+
unestimated items, how the graphical user interface shows the data, and the
|
|
84
|
+
Jira integration.
|
|
85
|
+
|
|
86
|
+
- An editor showing a whole configuration file or a stand-alone preset file at
|
|
87
|
+
once, folded where it is deep, to change a single value without stepping
|
|
88
|
+
through the wizard.
|
|
89
|
+
|
|
90
|
+
- Encrypt the Jira API token to a pass-phrase-protected file, so that no
|
|
91
|
+
configuration file holds the token in clear text.
|
|
79
92
|
|
|
80
93
|
- Read a backlog and releases from Jira into a backlog and release table.
|
|
81
94
|
|
|
@@ -94,6 +107,10 @@ The following functionality is available in all 3 packages:
|
|
|
94
107
|
- Move backlog items to a chosen anchor in the Jira rank order, following a key
|
|
95
108
|
list.
|
|
96
109
|
|
|
110
|
+
- Report what a Jira operation did not do. Every Jira result opens with what
|
|
111
|
+
Jira refused or what was skipped, so a value that was not written is visible
|
|
112
|
+
without reading the whole listing.
|
|
113
|
+
|
|
97
114
|
## The operating model
|
|
98
115
|
|
|
99
116
|
The operating model that most of the functionality is designed for is that the
|
|
@@ -118,7 +135,10 @@ the library:
|
|
|
118
135
|
|
|
119
136
|
- `title` The title of the backlog item. Required.
|
|
120
137
|
|
|
121
|
-
- `story_points` The story points of the backlog item.
|
|
138
|
+
- `story_points` The story points of the backlog item. Optional. An item with no
|
|
139
|
+
story points is one that nobody has estimated yet, and what such an item is
|
|
140
|
+
worked with when a completion date is estimated is configured. May be a
|
|
141
|
+
fraction, such as 0.5.
|
|
122
142
|
|
|
123
143
|
- `status` The status of the backlog item. Required.
|
|
124
144
|
|
|
@@ -219,9 +239,11 @@ python -m backlogops_gui
|
|
|
219
239
|
python -m backlogops_gui -c config_file.cfg
|
|
220
240
|
````
|
|
221
241
|
|
|
222
|
-
At first startup you do not have any configuration file yet,
|
|
223
|
-
|
|
224
|
-
wizard.
|
|
242
|
+
At first startup you do not have any configuration file yet, so the
|
|
243
|
+
application shows a *No configuration* dialog offering to run the
|
|
244
|
+
configuration wizard, load an existing configuration file, or exit.
|
|
245
|
+
Cancelling the wizard or the file chooser returns to that dialog, so
|
|
246
|
+
the application starts only once a configuration is in place.
|
|
225
247
|
|
|
226
248
|
## Main window
|
|
227
249
|
|
|
@@ -289,6 +311,17 @@ is in the menus.
|
|
|
289
311
|
The backlog window shows 2 read-only tables: one with the backlog and
|
|
290
312
|
one with the list of releases. You will want to use the menus.
|
|
291
313
|
|
|
314
|
+
At the top of the window an information region records where the data
|
|
315
|
+
came from and when it was read, marks the window once you change the
|
|
316
|
+
backlog, and offers a **Read again** button that re-reads the same
|
|
317
|
+
source (the same file, or the same Jira preset and filter) in place. A
|
|
318
|
+
re-read with unsaved changes asks for confirmation first.
|
|
319
|
+
|
|
320
|
+
Each Jira action answers in a copy-pasteable pop-up that leads with what
|
|
321
|
+
did not happen: what Jira refused, then what was skipped, and last what
|
|
322
|
+
succeeded. When something was refused the pop-up title is marked
|
|
323
|
+
*NOT ALL SUCCEEDED*, so it is visible even behind another window.
|
|
324
|
+
|
|
292
325
|
- Backlog
|
|
293
326
|
|
|
294
327
|
- Order by keys...
|
|
@@ -331,10 +364,10 @@ one with the list of releases. You will want to use the menus.
|
|
|
331
364
|
|
|
332
365
|
## Test summary
|
|
333
366
|
|
|
334
|
-
- Test result:
|
|
367
|
+
- Test result: 2555 passed, 1 deselected in 58s
|
|
335
368
|
- No flake8 warnings.
|
|
336
369
|
- No mypy errors found.
|
|
337
370
|
- No pylint warnings.
|
|
338
371
|
- No python layout warnings.
|
|
339
|
-
- Built version(s): 1.
|
|
372
|
+
- Built version(s): 1.3
|
|
340
373
|
- Build and test using Python 3.14.7
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: backlogops-gui
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3
|
|
4
4
|
Summary: Graphical user interface for backlog operations.
|
|
5
5
|
Author: Tom Björkholm
|
|
6
6
|
Author-email: Tom Björkholm <klausuler_linnet0q@icloud.com>
|
|
@@ -21,11 +21,11 @@ Classifier: Typing :: Typed
|
|
|
21
21
|
Requires-Python: >=3.12
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE.txt
|
|
24
|
-
Requires-Dist: backlogops>=1.
|
|
24
|
+
Requires-Dist: backlogops>=1.3
|
|
25
25
|
Requires-Dist: argcomplete>=3.7.2
|
|
26
|
-
Requires-Dist: wizard-ui-bridge>=1.
|
|
27
|
-
Requires-Dist: wizard-tk-bridge>=1.
|
|
28
|
-
Requires-Dist: edit-cfg-json-tk>=0.
|
|
26
|
+
Requires-Dist: wizard-ui-bridge>=1.4
|
|
27
|
+
Requires-Dist: wizard-tk-bridge>=1.4
|
|
28
|
+
Requires-Dist: edit-cfg-json-tk>=0.3.0
|
|
29
29
|
Requires-Dist: versionreporter>=0.4
|
|
30
30
|
Dynamic: author
|
|
31
31
|
Dynamic: license-file
|
|
@@ -75,6 +75,10 @@ The following functionality is available in all 3 packages:
|
|
|
75
75
|
- Estimate ready date for the backlog items based on available teams, team
|
|
76
76
|
velocity, vacation dates, periods with half time work, etc.
|
|
77
77
|
|
|
78
|
+
- Configure what a backlog item that nobody has estimated counts as, per backlog
|
|
79
|
+
item level, so that an unestimated item does not make the estimated dates too
|
|
80
|
+
optimistic.
|
|
81
|
+
|
|
78
82
|
- Extract backlog keys at given backlog item levels.
|
|
79
83
|
|
|
80
84
|
- Reorder the backlog so that the dependencies are satisfied.
|
|
@@ -109,7 +113,16 @@ The following functionality is available in all 3 packages:
|
|
|
109
113
|
- Migrate an older configuration or preset file to the current file format.
|
|
110
114
|
|
|
111
115
|
- A wizard to create a backlog-ops configuration file with the workforce, named
|
|
112
|
-
input and output presets, level names,
|
|
116
|
+
input and output presets, level names, status name mapping, the guess for
|
|
117
|
+
unestimated items, how the graphical user interface shows the data, and the
|
|
118
|
+
Jira integration.
|
|
119
|
+
|
|
120
|
+
- An editor showing a whole configuration file or a stand-alone preset file at
|
|
121
|
+
once, folded where it is deep, to change a single value without stepping
|
|
122
|
+
through the wizard.
|
|
123
|
+
|
|
124
|
+
- Encrypt the Jira API token to a pass-phrase-protected file, so that no
|
|
125
|
+
configuration file holds the token in clear text.
|
|
113
126
|
|
|
114
127
|
- Read a backlog and releases from Jira into a backlog and release table.
|
|
115
128
|
|
|
@@ -128,6 +141,10 @@ The following functionality is available in all 3 packages:
|
|
|
128
141
|
- Move backlog items to a chosen anchor in the Jira rank order, following a key
|
|
129
142
|
list.
|
|
130
143
|
|
|
144
|
+
- Report what a Jira operation did not do. Every Jira result opens with what
|
|
145
|
+
Jira refused or what was skipped, so a value that was not written is visible
|
|
146
|
+
without reading the whole listing.
|
|
147
|
+
|
|
131
148
|
## The operating model
|
|
132
149
|
|
|
133
150
|
The operating model that most of the functionality is designed for is that the
|
|
@@ -152,7 +169,10 @@ the library:
|
|
|
152
169
|
|
|
153
170
|
- `title` The title of the backlog item. Required.
|
|
154
171
|
|
|
155
|
-
- `story_points` The story points of the backlog item.
|
|
172
|
+
- `story_points` The story points of the backlog item. Optional. An item with no
|
|
173
|
+
story points is one that nobody has estimated yet, and what such an item is
|
|
174
|
+
worked with when a completion date is estimated is configured. May be a
|
|
175
|
+
fraction, such as 0.5.
|
|
156
176
|
|
|
157
177
|
- `status` The status of the backlog item. Required.
|
|
158
178
|
|
|
@@ -253,9 +273,11 @@ python -m backlogops_gui
|
|
|
253
273
|
python -m backlogops_gui -c config_file.cfg
|
|
254
274
|
````
|
|
255
275
|
|
|
256
|
-
At first startup you do not have any configuration file yet,
|
|
257
|
-
|
|
258
|
-
wizard.
|
|
276
|
+
At first startup you do not have any configuration file yet, so the
|
|
277
|
+
application shows a *No configuration* dialog offering to run the
|
|
278
|
+
configuration wizard, load an existing configuration file, or exit.
|
|
279
|
+
Cancelling the wizard or the file chooser returns to that dialog, so
|
|
280
|
+
the application starts only once a configuration is in place.
|
|
259
281
|
|
|
260
282
|
## Main window
|
|
261
283
|
|
|
@@ -323,6 +345,17 @@ is in the menus.
|
|
|
323
345
|
The backlog window shows 2 read-only tables: one with the backlog and
|
|
324
346
|
one with the list of releases. You will want to use the menus.
|
|
325
347
|
|
|
348
|
+
At the top of the window an information region records where the data
|
|
349
|
+
came from and when it was read, marks the window once you change the
|
|
350
|
+
backlog, and offers a **Read again** button that re-reads the same
|
|
351
|
+
source (the same file, or the same Jira preset and filter) in place. A
|
|
352
|
+
re-read with unsaved changes asks for confirmation first.
|
|
353
|
+
|
|
354
|
+
Each Jira action answers in a copy-pasteable pop-up that leads with what
|
|
355
|
+
did not happen: what Jira refused, then what was skipped, and last what
|
|
356
|
+
succeeded. When something was refused the pop-up title is marked
|
|
357
|
+
*NOT ALL SUCCEEDED*, so it is visible even behind another window.
|
|
358
|
+
|
|
326
359
|
- Backlog
|
|
327
360
|
|
|
328
361
|
- Order by keys...
|
|
@@ -365,10 +398,10 @@ one with the list of releases. You will want to use the menus.
|
|
|
365
398
|
|
|
366
399
|
## Test summary
|
|
367
400
|
|
|
368
|
-
- Test result:
|
|
401
|
+
- Test result: 2555 passed, 1 deselected in 58s
|
|
369
402
|
- No flake8 warnings.
|
|
370
403
|
- No mypy errors found.
|
|
371
404
|
- No pylint warnings.
|
|
372
405
|
- No python layout warnings.
|
|
373
|
-
- Built version(s): 1.
|
|
406
|
+
- Built version(s): 1.3
|
|
374
407
|
- Build and test using Python 3.14.7
|
|
@@ -5,7 +5,7 @@ from setuptools import setup
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name='backlogops-gui',
|
|
8
|
-
version='1.
|
|
8
|
+
version='1.3',
|
|
9
9
|
description='Graphical user interface for backlog operations.',
|
|
10
10
|
author='Tom Björkholm',
|
|
11
11
|
author_email='klausuler_linnet0q@icloud.com',
|
|
@@ -19,11 +19,11 @@ setup(
|
|
|
19
19
|
]
|
|
20
20
|
},
|
|
21
21
|
install_requires=[
|
|
22
|
-
'backlogops >= 1.
|
|
22
|
+
'backlogops >= 1.3',
|
|
23
23
|
'argcomplete >= 3.7.2',
|
|
24
|
-
'wizard-ui-bridge >= 1.
|
|
25
|
-
'wizard-tk-bridge >= 1.
|
|
26
|
-
'edit-cfg-json-tk >= 0.
|
|
24
|
+
'wizard-ui-bridge >= 1.4',
|
|
25
|
+
'wizard-tk-bridge >= 1.4',
|
|
26
|
+
'edit-cfg-json-tk >= 0.3.0',
|
|
27
27
|
'versionreporter >= 0.4'
|
|
28
28
|
]
|
|
29
29
|
)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
The application opens a main window whose menu reads a backlog from a file
|
|
5
5
|
or from Jira, loads or replaces the active configuration from a file, runs
|
|
6
|
-
the
|
|
6
|
+
the backlog-ops configuration wizard, edits a configuration or a stand-alone
|
|
7
7
|
preset file in the folding editor of
|
|
8
8
|
:mod:`backlogops_gui.config_edit`, creates a stand-alone input or output
|
|
9
9
|
preset file, migrates a stand-alone preset file to the current format,
|
|
@@ -17,8 +17,8 @@ modified, and offers a "Read again" button that re-reads the same source.
|
|
|
17
17
|
On macOS the menu bar sits at the top of the display rather than in
|
|
18
18
|
the window, so the main window body shows a short description, the current
|
|
19
19
|
configuration status, and a log of the most recent diagnostic messages, to
|
|
20
|
-
make clear that the application is running. The
|
|
21
|
-
taken from the file given with ``-c`` or from the configured locations;
|
|
20
|
+
make clear that the application is running. The backlog-ops configuration
|
|
21
|
+
is taken from the file given with ``-c`` or from the configured locations;
|
|
22
22
|
when no configuration is found a startup dialog offers to run the wizard,
|
|
23
23
|
load a configuration file, or exit. Cancelling the wizard or a dialog
|
|
24
24
|
returns to that choice, so the application ends only when the user exits.
|
|
@@ -44,6 +44,7 @@ from config_as_json.file_extension import fix_file_extension
|
|
|
44
44
|
from wizard_tk_bridge import WizardUiBridgeTk
|
|
45
45
|
from backlogops import (
|
|
46
46
|
AvailableTeams, BacklogOpsConfig, BacklogReleases, CONFIG_EXTENSION,
|
|
47
|
+
DefaultStoryPoints,
|
|
47
48
|
GuiDisplayConfig, InputFormatConfig, Levels, OutputFormatConfig, Status,
|
|
48
49
|
get_demo_backlog, get_backlog_ops_config, backlog_ops_wizard,
|
|
49
50
|
preset_wizard, read_backlog_ops_config, read_io_preset, safe_write_config,
|
|
@@ -198,6 +199,10 @@ class BacklogApp:
|
|
|
198
199
|
"""Return the configured backlog item levels, or None when absent."""
|
|
199
200
|
return self.config.get_levels() if self.config else None
|
|
200
201
|
|
|
202
|
+
def def_points(self) -> Optional[DefaultStoryPoints]:
|
|
203
|
+
"""Return what an unestimated item is worked with, or None."""
|
|
204
|
+
return self.config.default_story_points if self.config else None
|
|
205
|
+
|
|
201
206
|
def status_map(self) -> Optional[dict[str, Status]]:
|
|
202
207
|
"""Return the library-wide status input map, or None when absent."""
|
|
203
208
|
return self.config.get_status_input_map() if self.config else None
|
|
@@ -614,8 +619,8 @@ class BacklogApp:
|
|
|
614
619
|
rename_releases=self.jira.renamer.rename_action())
|
|
615
620
|
BacklogWindow(self.root, data, title, self.out_presets,
|
|
616
621
|
self.available_teams, self.log, self.levels,
|
|
617
|
-
self.gui_display, warning, handlers,
|
|
618
|
-
reload=reload)
|
|
622
|
+
self.gui_display, warning, handlers,
|
|
623
|
+
def_points=self.def_points, source=source, reload=reload)
|
|
619
624
|
|
|
620
625
|
def report_versions(self) -> None:
|
|
621
626
|
"""Report version information into the log on a worker thread.
|
|
@@ -16,7 +16,8 @@ import tkinter as tk
|
|
|
16
16
|
from datetime import timedelta
|
|
17
17
|
from typing import Callable, Optional, TextIO
|
|
18
18
|
from backlogops import (
|
|
19
|
-
AddedToJira, AvailableTeams, BacklogReleases,
|
|
19
|
+
AddedToJira, AvailableTeams, BacklogReleases, DefaultStoryPoints, Levels,
|
|
20
|
+
OutputFormatConfig,
|
|
20
21
|
ReleaseChanges, ReleaseDateChanges, UpdatedBacklogInJira, allow_overwrite,
|
|
21
22
|
apply_jira_keys, format_add_result, format_backlog_updates,
|
|
22
23
|
format_content_changes, format_date_changes, get_keys_in_order,
|
|
@@ -248,11 +249,17 @@ def _run_change(parent: tk.Misc,
|
|
|
248
249
|
|
|
249
250
|
# pylint: disable-next=too-many-arguments,too-many-positional-arguments
|
|
250
251
|
def estimate_date(parent: tk.Misc, data: BacklogReleases,
|
|
251
|
-
teams: Optional[AvailableTeams],
|
|
252
|
+
teams: Optional[AvailableTeams],
|
|
253
|
+
points: Optional[DefaultStoryPoints], sink: TextIO,
|
|
252
254
|
refresh: Callable[[], None],
|
|
253
255
|
on_error: Callable[[str, str], None],
|
|
254
256
|
on_info: Callable[[str, str], None]) -> None:
|
|
255
|
-
"""Ask for the start date and estimate the ready dates.
|
|
257
|
+
"""Ask for the start date and estimate the ready dates.
|
|
258
|
+
|
|
259
|
+
An unestimated backlog item is worked with what the configuration
|
|
260
|
+
guesses for its level, so the guess of the loaded configuration is
|
|
261
|
+
passed on with the workforce.
|
|
262
|
+
"""
|
|
256
263
|
if teams is None:
|
|
257
264
|
on_error('No configuration',
|
|
258
265
|
'There is no teams configuration to estimate from.')
|
|
@@ -261,10 +268,12 @@ def estimate_date(parent: tk.Misc, data: BacklogReleases,
|
|
|
261
268
|
if choice is None:
|
|
262
269
|
return
|
|
263
270
|
ready_teams, start = teams, choice.start_date
|
|
271
|
+
guess = DefaultStoryPoints() if points is None else points
|
|
264
272
|
|
|
265
273
|
def change() -> tuple[str, Optional[Callable[[str], None]]]:
|
|
266
274
|
"""Estimate the dates and return the release date change report."""
|
|
267
|
-
changes = data.estimate_ready_date(ready_teams, start, sink
|
|
275
|
+
changes = data.estimate_ready_date(ready_teams, start, sink,
|
|
276
|
+
default_story_points=guess)
|
|
268
277
|
return _date_report(changes, sink)
|
|
269
278
|
_run_change(parent, change, refresh, on_error, on_info,
|
|
270
279
|
'Could not estimate ready date', 'Release date changes')
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
These helpers wrap the library read and write functions and resolve the
|
|
5
5
|
format the same way the command line does: an empty value infers the
|
|
6
6
|
format from the file name, a value of only letters and digits is a preset
|
|
7
|
-
name looked up in the presets
|
|
7
|
+
name looked up in the presets of the backlog-ops configuration, and any other
|
|
8
8
|
value is the path of a stand-alone format configuration file. Diagnostics
|
|
9
9
|
go to the given sink, because a graphical application shows them in a log
|
|
10
10
|
view rather than on a console.
|
|
@@ -26,10 +26,12 @@ from wizard_tk_bridge.auto_scroll import auto_hide
|
|
|
26
26
|
from wizard_tk_bridge.close_binding import CLOSE_ACCELERATOR, bind_close
|
|
27
27
|
from backlogops import (
|
|
28
28
|
AddedReleasesToJira, AddedToJira, AvailableTeams, BacklogReleases,
|
|
29
|
+
DefaultStoryPoints,
|
|
29
30
|
GuiDisplayConfig, Levels, OrderedReleasesInJira, OutputFormatConfig,
|
|
30
31
|
RankedInJira, RenamedReleasesInJira, UpdatedBacklogInJira,
|
|
31
32
|
UpdatedReleasesInJira, format_order_result, format_rank_result,
|
|
32
|
-
format_release_result, format_release_updates, format_rename_result
|
|
33
|
+
format_release_result, format_release_updates, format_rename_result,
|
|
34
|
+
report_has_problems)
|
|
33
35
|
from backlogops_gui.backlog_actions import (
|
|
34
36
|
adjust_content, apply_add_result, apply_update_result, estimate_date,
|
|
35
37
|
extract_keys, order_by_deps, order_by_keys, order_by_release, order_dates,
|
|
@@ -41,6 +43,8 @@ from backlogops_gui.table_view import (
|
|
|
41
43
|
RELEASE_COLUMN_WIDTH = 110
|
|
42
44
|
WARNING_WRAP = 760
|
|
43
45
|
MODIFIED_MARK = ' — Modified'
|
|
46
|
+
PROBLEM_MARK = ' — NOT ALL SUCCEEDED'
|
|
47
|
+
"""Added to a Jira pop-up title when the listing reports a problem."""
|
|
44
48
|
|
|
45
49
|
|
|
46
50
|
def current_time() -> datetime:
|
|
@@ -112,6 +116,8 @@ class BacklogWindow:
|
|
|
112
116
|
[], GuiDisplayConfig] = GuiDisplayConfig,
|
|
113
117
|
warning: Optional[str] = None,
|
|
114
118
|
jira: Optional[JiraHandlers] = None, *,
|
|
119
|
+
def_points: Callable[
|
|
120
|
+
[], Optional[DefaultStoryPoints]] = lambda: None,
|
|
115
121
|
source: Optional[BacklogSource] = None,
|
|
116
122
|
reload: Optional[Callable[
|
|
117
123
|
[Callable[[BacklogReleases, Optional[str]], None]],
|
|
@@ -123,7 +129,8 @@ class BacklogWindow:
|
|
|
123
129
|
data: The backlog and releases to show.
|
|
124
130
|
title: The window title, typically the source file name.
|
|
125
131
|
presets: Callable returning the current output presets.
|
|
126
|
-
teams: Callable returning the loaded
|
|
132
|
+
teams: Callable returning the loaded workforce, or None when
|
|
133
|
+
no configuration is loaded.
|
|
127
134
|
sink: Stream that receives low-level write diagnostics.
|
|
128
135
|
levels: Callable returning the configured levels, or None for
|
|
129
136
|
the default levels.
|
|
@@ -135,6 +142,8 @@ class BacklogWindow:
|
|
|
135
142
|
jira: The Jira menu handlers to offer, or None for none. Each
|
|
136
143
|
handler is None when its operation is unavailable, which
|
|
137
144
|
disables its menu item.
|
|
145
|
+
def_points: Callable returning what the configuration works an
|
|
146
|
+
unestimated backlog item with, or None for none.
|
|
138
147
|
source: Where the data came from and when it was read. When
|
|
139
148
|
given, an information region is shown at the top of the
|
|
140
149
|
window; when None no information region is shown.
|
|
@@ -146,6 +155,7 @@ class BacklogWindow:
|
|
|
146
155
|
self._data = data
|
|
147
156
|
self._presets = presets
|
|
148
157
|
self._teams = teams
|
|
158
|
+
self._def_points = def_points
|
|
149
159
|
self._sink = sink
|
|
150
160
|
self._levels = levels
|
|
151
161
|
self._gui_display = gui_display
|
|
@@ -461,8 +471,8 @@ class BacklogWindow:
|
|
|
461
471
|
|
|
462
472
|
def _estimate_date(self) -> None:
|
|
463
473
|
"""Estimate the ready dates and refresh the tables."""
|
|
464
|
-
estimate_date(self._win, self._data, self._teams(), self.
|
|
465
|
-
self._changed_refresh, self._report_error,
|
|
474
|
+
estimate_date(self._win, self._data, self._teams(), self._def_points(),
|
|
475
|
+
self._sink, self._changed_refresh, self._report_error,
|
|
466
476
|
self._report_info)
|
|
467
477
|
|
|
468
478
|
def _set_plan(self) -> None:
|
|
@@ -503,7 +513,18 @@ class BacklogWindow:
|
|
|
503
513
|
|
|
504
514
|
def _show_add_report(self, text: str) -> None:
|
|
505
515
|
"""Show the add result text in a copy-pasteable pop-up."""
|
|
506
|
-
|
|
516
|
+
self._show_jira_report('Added to Jira', text)
|
|
517
|
+
|
|
518
|
+
def _show_jira_report(self, title: str, text: str) -> None:
|
|
519
|
+
"""Show a Jira result listing, marking a title with problems.
|
|
520
|
+
|
|
521
|
+
A listing reporting something that did not happen gets a marked
|
|
522
|
+
title, so the user sees that not everything succeeded even when
|
|
523
|
+
the pop-up shows only its first lines or the log is hidden behind
|
|
524
|
+
another window.
|
|
525
|
+
"""
|
|
526
|
+
mark = PROBLEM_MARK if report_has_problems(text) else ''
|
|
527
|
+
show_text_report(self._win, f'{title}{mark}', text)
|
|
507
528
|
|
|
508
529
|
def _releases_add(self) -> None:
|
|
509
530
|
"""Add the shown releases to Jira and show the result lists."""
|
|
@@ -516,8 +537,8 @@ class BacklogWindow:
|
|
|
516
537
|
A release name never changes, so the shown releases already match
|
|
517
538
|
what was added and no rebuild of the tables is needed.
|
|
518
539
|
"""
|
|
519
|
-
|
|
520
|
-
|
|
540
|
+
self._show_jira_report('Add releases to Jira',
|
|
541
|
+
format_release_result(result))
|
|
521
542
|
|
|
522
543
|
def _releases_update(self) -> None:
|
|
523
544
|
"""Update the shown releases in Jira and show the result lists."""
|
|
@@ -531,8 +552,8 @@ class BacklogWindow:
|
|
|
531
552
|
failed releases. An update changes only the Jira versions, not the
|
|
532
553
|
shown releases, so no rebuild of the tables is needed.
|
|
533
554
|
"""
|
|
534
|
-
|
|
535
|
-
|
|
555
|
+
self._show_jira_report('Update releases in Jira',
|
|
556
|
+
format_release_updates(result))
|
|
536
557
|
|
|
537
558
|
def _backlog_update(self) -> None:
|
|
538
559
|
"""Update the shown backlog in Jira and show the result lists."""
|
|
@@ -551,7 +572,7 @@ class BacklogWindow:
|
|
|
551
572
|
|
|
552
573
|
def _show_update_report(self, text: str) -> None:
|
|
553
574
|
"""Show the backlog update result text in a copy-pasteable pop-up."""
|
|
554
|
-
|
|
575
|
+
self._show_jira_report('Update backlog in Jira', text)
|
|
555
576
|
|
|
556
577
|
def _rank_jira(self) -> None:
|
|
557
578
|
"""Move chosen issues in the Jira rank order and show the result."""
|
|
@@ -564,8 +585,8 @@ class BacklogWindow:
|
|
|
564
585
|
Ranking changes only the Jira rank of issues, not the shown
|
|
565
586
|
backlog, so no rebuild of the tables is needed.
|
|
566
587
|
"""
|
|
567
|
-
|
|
568
|
-
|
|
588
|
+
self._show_jira_report('Rank items in Jira',
|
|
589
|
+
format_rank_result(result))
|
|
569
590
|
|
|
570
591
|
def _releases_order(self) -> None:
|
|
571
592
|
"""Order the releases in Jira and show the result lists."""
|
|
@@ -578,8 +599,8 @@ class BacklogWindow:
|
|
|
578
599
|
Ordering changes only the Jira version order, not the shown
|
|
579
600
|
releases, so no rebuild of the tables is needed.
|
|
580
601
|
"""
|
|
581
|
-
|
|
582
|
-
|
|
602
|
+
self._show_jira_report('Order releases in Jira',
|
|
603
|
+
format_order_result(result))
|
|
583
604
|
|
|
584
605
|
def _releases_rename(self) -> None:
|
|
585
606
|
"""Rename the shown releases in Jira and show the result lists."""
|
|
@@ -592,5 +613,5 @@ class BacklogWindow:
|
|
|
592
613
|
Renaming changes only the Jira version names, not the shown
|
|
593
614
|
releases, so no rebuild of the tables is needed.
|
|
594
615
|
"""
|
|
595
|
-
|
|
596
|
-
|
|
616
|
+
self._show_jira_report('Rename releases in Jira',
|
|
617
|
+
format_rename_result(result))
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"""File-format option dialogs for reading and writing backlog files.
|
|
3
3
|
|
|
4
4
|
The format options mirror the command line: the format is either inferred
|
|
5
|
-
from the file name, taken from a named preset stored in the
|
|
5
|
+
from the file name, taken from a named preset stored in the backlog-ops
|
|
6
6
|
configuration, or read from a stand-alone configuration file. Writing also
|
|
7
7
|
offers to put the releases before the backlog. The chosen format is
|
|
8
8
|
returned as a single value understood by the resolver in
|
|
@@ -560,7 +560,7 @@ class JiraRankDialog(ModalDialog):
|
|
|
560
560
|
load_keys_into(self._win, self._text, self._sink)
|
|
561
561
|
|
|
562
562
|
def _confirm(self) -> None:
|
|
563
|
-
"""Store the preset, filter, keys and
|
|
563
|
+
"""Store the preset, filter, keys and anchor, needing preset+keys."""
|
|
564
564
|
name = self._preset.get()
|
|
565
565
|
if not name:
|
|
566
566
|
messagebox.showerror('No Jira preset', 'Select a Jira preset.',
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#! /usr/local/bin/python3
|
|
2
|
-
"""Move issues to
|
|
3
|
-
|
|
4
|
-
The ranker offers a handler that asks for a preset,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
"""Move issues to a chosen anchor of a Jira backlog by rank.
|
|
3
|
+
|
|
4
|
+
The ranker offers a handler that asks for a preset, an issue filter, the
|
|
5
|
+
keys to move, the anchor to move them to and whether to honour relations,
|
|
6
|
+
then ranks them in Jira on a worker thread and hands the result back to the
|
|
7
|
+
GUI thread. The anchor is either end of the backlog or the first or last of
|
|
8
|
+
the listed keys. It is available only when a configuration with Jira presets
|
|
9
|
+
is loaded. The backlog and the current ranking come from Jira through the
|
|
10
|
+
preset, not from the shown backlog, so the handler does not need the shown
|
|
11
|
+
data.
|
|
10
12
|
"""
|
|
11
13
|
|
|
12
14
|
# Copyright (c) 2026, Tom Björkholm
|
|
@@ -20,7 +22,7 @@ from backlogops_gui.jira_dialogs import JiraRankOptions, ask_jira_rank
|
|
|
20
22
|
|
|
21
23
|
# pylint: disable-next=too-few-public-methods
|
|
22
24
|
class JiraRanker(JiraAction):
|
|
23
|
-
"""Moves issues to
|
|
25
|
+
"""Moves issues to a chosen anchor of a Jira backlog by rank."""
|
|
24
26
|
|
|
25
27
|
def rank_action(self) -> Optional[Callable[
|
|
26
28
|
[Callable[[RankedInJira], None]], None]]:
|
|
@@ -28,7 +30,7 @@ class JiraRanker(JiraAction):
|
|
|
28
30
|
return self._rank if self._available() else None
|
|
29
31
|
|
|
30
32
|
def _rank(self, on_done: Callable[[RankedInJira], None]) -> None:
|
|
31
|
-
"""Ask for a preset, filter, keys and
|
|
33
|
+
"""Ask for a preset, filter, keys and anchor, then rank in Jira."""
|
|
32
34
|
options = ask_jira_rank(self._app.root, self._preset_filters(),
|
|
33
35
|
self._app.log)
|
|
34
36
|
if options is None:
|
|
@@ -110,11 +110,16 @@ class JiraUpdater(JiraAction):
|
|
|
110
110
|
|
|
111
111
|
def done(result: UpdatedBacklogInJira) -> None:
|
|
112
112
|
"""Hand the result to the window and log the completed update."""
|
|
113
|
+
added = result.added
|
|
113
114
|
summary = (
|
|
114
115
|
f"Updated {len(result.updated)} backlog items in Jira; "
|
|
115
116
|
f"{len(result.already_correct)} already correct; "
|
|
116
117
|
f"{len(result.ignored)} ignored; "
|
|
117
|
-
f"{len(
|
|
118
|
-
f"(
|
|
118
|
+
f"{len(added.stored)} added; "
|
|
119
|
+
f"{len(added.failed)} not added; "
|
|
120
|
+
f"{len(result.failed_fields) + len(added.failed_fields)} "
|
|
121
|
+
"fields not set; "
|
|
122
|
+
f"{len(result.failed_links) + len(added.failed_links)} "
|
|
123
|
+
f"links not written (preset '{name}').\n")
|
|
119
124
|
self._finish(on_done, result, summary)
|
|
120
125
|
self._dispatch('Could not update backlog in Jira', name, call, done)
|
|
@@ -70,7 +70,10 @@ class JiraWriter(JiraAction):
|
|
|
70
70
|
def done(result: AddedToJira) -> None:
|
|
71
71
|
"""Hand the result to the window and log the completed write."""
|
|
72
72
|
summary = (f"Added {len(result.stored)} backlog items to Jira; "
|
|
73
|
-
f"{len(result.already_present)} already present "
|
|
73
|
+
f"{len(result.already_present)} already present; "
|
|
74
|
+
f"{len(result.failed)} failed; "
|
|
75
|
+
f"{len(result.failed_fields)} fields not set; "
|
|
76
|
+
f"{len(result.failed_links)} links not written "
|
|
74
77
|
f"(preset '{name}').\n")
|
|
75
78
|
self._finish(on_done, result, summary)
|
|
76
79
|
self._dispatch('Could not add to Jira', name, call, done)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#! /usr/local/bin/python3
|
|
2
|
+
"""Read-only text pop-ups for change listings and text reports.
|
|
3
|
+
|
|
4
|
+
A change listing is shown with a Save-to-file and a Dismiss button, so the
|
|
5
|
+
user can keep a record of what an action changed. A text report is shown
|
|
6
|
+
read-only but copy-pasteable, with only a Dismiss button. Both show their
|
|
7
|
+
text in a non-wrapping box with a vertical and a horizontal scrollbar, so
|
|
8
|
+
the user sees that the text continues past the visible part of the window.
|
|
9
|
+
Both return the created window so a caller or a test can drive or close it.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
# Copyright (c) 2026, Tom Björkholm
|
|
13
|
+
# MIT License
|
|
14
|
+
|
|
15
|
+
import tkinter as tk
|
|
16
|
+
from typing import Callable
|
|
17
|
+
from wizard_tk_bridge.close_binding import bind_close
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _add_scrollbars(frame: tk.Frame, box: tk.Text) -> None:
|
|
21
|
+
"""Add the vertical and horizontal scrollbars driving the text box."""
|
|
22
|
+
vertical = tk.Scrollbar(frame, orient='vertical', command=box.yview)
|
|
23
|
+
horizontal = tk.Scrollbar(frame, orient='horizontal', command=box.xview)
|
|
24
|
+
box.configure(yscrollcommand=vertical.set, xscrollcommand=horizontal.set)
|
|
25
|
+
vertical.grid(row=0, column=1, sticky='ns')
|
|
26
|
+
horizontal.grid(row=1, column=0, sticky='ew')
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _scrolled_text(parent: tk.Misc, text: str, width: int,
|
|
30
|
+
height: int) -> None:
|
|
31
|
+
"""Pack a read-only scrollable box showing the text in the parent.
|
|
32
|
+
|
|
33
|
+
The text is shown in a disabled box, which still lets the user select
|
|
34
|
+
and copy it. The box does not wrap, so a line longer than the window
|
|
35
|
+
is reached with the horizontal scrollbar.
|
|
36
|
+
"""
|
|
37
|
+
frame = tk.Frame(parent)
|
|
38
|
+
frame.pack(padx=12, pady=(10, 4), fill='both', expand=True)
|
|
39
|
+
frame.rowconfigure(0, weight=1)
|
|
40
|
+
frame.columnconfigure(0, weight=1)
|
|
41
|
+
box = tk.Text(frame, width=width, height=height, wrap='none')
|
|
42
|
+
box.insert('1.0', text)
|
|
43
|
+
box.configure(state='disabled')
|
|
44
|
+
box.grid(row=0, column=0, sticky='nsew')
|
|
45
|
+
_add_scrollbars(frame, box)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def show_change_list(parent: tk.Misc, title: str, text: str,
|
|
49
|
+
on_save: Callable[[], None]) -> tk.Toplevel:
|
|
50
|
+
"""Show a change listing with Save-to-file and Dismiss buttons.
|
|
51
|
+
|
|
52
|
+
The listing is shown read-only in a scrollable box. The Save button
|
|
53
|
+
calls ``on_save`` and the Dismiss button closes the window. The created
|
|
54
|
+
window is returned so a caller (or a test) can drive or close it.
|
|
55
|
+
"""
|
|
56
|
+
win = tk.Toplevel(parent)
|
|
57
|
+
win.title(title)
|
|
58
|
+
if isinstance(parent, tk.Wm):
|
|
59
|
+
win.transient(parent)
|
|
60
|
+
_scrolled_text(win, text, 50, 12)
|
|
61
|
+
button_bar = tk.Frame(win)
|
|
62
|
+
button_bar.pack(padx=12, pady=10, fill='x')
|
|
63
|
+
tk.Button(button_bar, text='Save to file…',
|
|
64
|
+
command=on_save).pack(side='left')
|
|
65
|
+
tk.Button(button_bar, text='Dismiss',
|
|
66
|
+
command=win.destroy).pack(side='right')
|
|
67
|
+
bind_close(win)
|
|
68
|
+
return win
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def show_text_report(parent: tk.Misc, title: str, text: str) -> tk.Toplevel:
|
|
72
|
+
"""Show read-only, copy-pasteable text with a Dismiss button.
|
|
73
|
+
|
|
74
|
+
The report is shown in a scrollable box tall enough to show a listing
|
|
75
|
+
with a few entries per section without scrolling. The created window
|
|
76
|
+
is returned so a caller or a test can drive or close it.
|
|
77
|
+
"""
|
|
78
|
+
win = tk.Toplevel(parent)
|
|
79
|
+
win.title(title)
|
|
80
|
+
if isinstance(parent, tk.Wm):
|
|
81
|
+
win.transient(parent)
|
|
82
|
+
_scrolled_text(win, text, 72, 24)
|
|
83
|
+
tk.Button(win, text='Dismiss', command=win.destroy).pack(padx=12, pady=10)
|
|
84
|
+
bind_close(win)
|
|
85
|
+
return win
|
|
@@ -7,8 +7,10 @@ so the on-screen colors match a written spreadsheet: the status cell and the
|
|
|
7
7
|
estimated-ready-date cell are highlighted by the format rules, and the other
|
|
8
8
|
cells are left plain. The columns are the union of the field names met in the
|
|
9
9
|
rows, kept in first-seen order, and every cell is rendered as text so the
|
|
10
|
-
table can show any value type
|
|
11
|
-
|
|
10
|
+
table can show any value type; a decimal number loses the trailing zeros
|
|
11
|
+
it does not need, so story points read as ``1`` and ``0.5``. A per-table
|
|
12
|
+
column-name map can rename a column or drop it from the display, as the GUI
|
|
13
|
+
display configuration decides.
|
|
12
14
|
"""
|
|
13
15
|
|
|
14
16
|
# Copyright (c) 2026, Tom Björkholm
|
|
@@ -42,7 +44,16 @@ def _columns(rows: Sequence[dict[str, ValueFmt]]) -> list[str]:
|
|
|
42
44
|
|
|
43
45
|
|
|
44
46
|
def _cell_text(value: Value) -> str:
|
|
45
|
-
"""Return one cell value rendered as display text.
|
|
47
|
+
"""Return one cell value rendered as display text.
|
|
48
|
+
|
|
49
|
+
A decimal number is shown without the trailing zeros it does not
|
|
50
|
+
need, so half a story point reads as ``0.5`` and a whole one as
|
|
51
|
+
``1`` rather than ``1.0``. A whole number is already written
|
|
52
|
+
without a decimal point, and a boolean is not a decimal number in
|
|
53
|
+
Python, so both keep their own text.
|
|
54
|
+
"""
|
|
55
|
+
if isinstance(value, float):
|
|
56
|
+
return f'{value:g}'
|
|
46
57
|
return '' if value is None else str(value)
|
|
47
58
|
|
|
48
59
|
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
#! /usr/local/bin/python3
|
|
2
|
-
"""Read-only text pop-ups for change listings and text reports.
|
|
3
|
-
|
|
4
|
-
A change listing is shown with a Save-to-file and a Dismiss button, so the
|
|
5
|
-
user can keep a record of what an action changed. A text report is shown
|
|
6
|
-
read-only but copy-pasteable, with only a Dismiss button. Both return the
|
|
7
|
-
created window so a caller or a test can drive or close it.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
# Copyright (c) 2026, Tom Björkholm
|
|
11
|
-
# MIT License
|
|
12
|
-
|
|
13
|
-
import tkinter as tk
|
|
14
|
-
from typing import Callable
|
|
15
|
-
from wizard_tk_bridge.close_binding import bind_close
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def show_change_list(parent: tk.Misc, title: str, text: str,
|
|
19
|
-
on_save: Callable[[], None]) -> tk.Toplevel:
|
|
20
|
-
"""Show a change listing with Save-to-file and Dismiss buttons.
|
|
21
|
-
|
|
22
|
-
The listing is shown read-only. The Save button calls ``on_save`` and
|
|
23
|
-
the Dismiss button closes the window. The created window is returned
|
|
24
|
-
so a caller (or a test) can drive or close it.
|
|
25
|
-
"""
|
|
26
|
-
win = tk.Toplevel(parent)
|
|
27
|
-
win.title(title)
|
|
28
|
-
if isinstance(parent, tk.Wm):
|
|
29
|
-
win.transient(parent)
|
|
30
|
-
box = tk.Text(win, width=50, height=12, wrap='none')
|
|
31
|
-
box.insert('1.0', text)
|
|
32
|
-
box.configure(state='disabled')
|
|
33
|
-
box.pack(padx=12, pady=(10, 4), fill='both', expand=True)
|
|
34
|
-
button_bar = tk.Frame(win)
|
|
35
|
-
button_bar.pack(padx=12, pady=10, fill='x')
|
|
36
|
-
tk.Button(button_bar, text='Save to file…',
|
|
37
|
-
command=on_save).pack(side='left')
|
|
38
|
-
tk.Button(button_bar, text='Dismiss',
|
|
39
|
-
command=win.destroy).pack(side='right')
|
|
40
|
-
bind_close(win)
|
|
41
|
-
return win
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def show_text_report(parent: tk.Misc, title: str, text: str) -> tk.Toplevel:
|
|
45
|
-
"""Show read-only, copy-pasteable text with a Dismiss button.
|
|
46
|
-
|
|
47
|
-
The text is shown in a disabled text box, which still lets the user
|
|
48
|
-
select and copy it. The created window is returned so a caller or a
|
|
49
|
-
test can drive or close it.
|
|
50
|
-
"""
|
|
51
|
-
win = tk.Toplevel(parent)
|
|
52
|
-
win.title(title)
|
|
53
|
-
if isinstance(parent, tk.Wm):
|
|
54
|
-
win.transient(parent)
|
|
55
|
-
box = tk.Text(win, width=50, height=14, wrap='none')
|
|
56
|
-
box.insert('1.0', text)
|
|
57
|
-
box.configure(state='disabled')
|
|
58
|
-
box.pack(padx=12, pady=(10, 4), fill='both', expand=True)
|
|
59
|
-
tk.Button(win, text='Dismiss', command=win.destroy).pack(padx=12, pady=10)
|
|
60
|
-
bind_close(win)
|
|
61
|
-
return win
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|