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.
Files changed (45) hide show
  1. {backlogops_gui-1.2 → backlogops_gui-1.3}/PKG-INFO +45 -12
  2. {backlogops_gui-1.2 → backlogops_gui-1.3}/README_pypi.md +40 -7
  3. {backlogops_gui-1.2 → backlogops_gui-1.3}/backlogops_gui.egg-info/PKG-INFO +45 -12
  4. backlogops_gui-1.3/backlogops_gui.egg-info/requires.txt +6 -0
  5. {backlogops_gui-1.2 → backlogops_gui-1.3}/setup.py +5 -5
  6. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/application.py +10 -5
  7. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/backlog_actions.py +13 -4
  8. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/backlog_io.py +1 -1
  9. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/backlog_window.py +37 -16
  10. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/format_dialogs.py +1 -1
  11. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/jira_dialogs.py +1 -1
  12. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/jira_rank.py +12 -10
  13. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/jira_update.py +7 -2
  14. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/jira_write.py +4 -1
  15. backlogops_gui-1.3/src/backlogops_gui/report_windows.py +85 -0
  16. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/table_view.py +14 -3
  17. backlogops_gui-1.2/backlogops_gui.egg-info/requires.txt +0 -6
  18. backlogops_gui-1.2/src/backlogops_gui/report_windows.py +0 -61
  19. {backlogops_gui-1.2 → backlogops_gui-1.3}/LICENSE.txt +0 -0
  20. {backlogops_gui-1.2 → backlogops_gui-1.3}/backlogops_gui.egg-info/SOURCES.txt +0 -0
  21. {backlogops_gui-1.2 → backlogops_gui-1.3}/backlogops_gui.egg-info/dependency_links.txt +0 -0
  22. {backlogops_gui-1.2 → backlogops_gui-1.3}/backlogops_gui.egg-info/entry_points.txt +0 -0
  23. {backlogops_gui-1.2 → backlogops_gui-1.3}/backlogops_gui.egg-info/top_level.txt +0 -0
  24. {backlogops_gui-1.2 → backlogops_gui-1.3}/pyproject.toml +0 -0
  25. {backlogops_gui-1.2 → backlogops_gui-1.3}/setup.cfg +0 -0
  26. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/__init__.py +0 -0
  27. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/__main__.py +0 -0
  28. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/_migrate_warn.py +0 -0
  29. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/backlog_dialogs.py +0 -0
  30. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/blog_version_reporter.py +0 -0
  31. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/choice_dialogs.py +0 -0
  32. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/config_edit.py +0 -0
  33. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/file_choosers.py +0 -0
  34. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/jira_actions.py +0 -0
  35. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/jira_base.py +0 -0
  36. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/jira_order.py +0 -0
  37. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/jira_read.py +0 -0
  38. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/jira_rename.py +0 -0
  39. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/key_list_box.py +0 -0
  40. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/log_buffer.py +0 -0
  41. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/modal_dialog.py +0 -0
  42. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/py.typed +0 -0
  43. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/python_version.py +0 -0
  44. {backlogops_gui-1.2 → backlogops_gui-1.3}/src/backlogops_gui/tcltk_version.py +0 -0
  45. {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.2
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.2
24
+ Requires-Dist: backlogops>=1.3
25
25
  Requires-Dist: argcomplete>=3.7.2
26
- Requires-Dist: wizard-ui-bridge>=1.3
27
- Requires-Dist: wizard-tk-bridge>=1.3
28
- Requires-Dist: edit-cfg-json-tk>=0.2.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, and status name mapping.
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. Required.
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
- so when you start the application it will start the configuration
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: 2309 passed, 1 deselected in 58s
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.2
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, and status name mapping.
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. Required.
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
- so when you start the application it will start the configuration
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: 2309 passed, 1 deselected in 58s
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.2
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.2
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.2
24
+ Requires-Dist: backlogops>=1.3
25
25
  Requires-Dist: argcomplete>=3.7.2
26
- Requires-Dist: wizard-ui-bridge>=1.3
27
- Requires-Dist: wizard-tk-bridge>=1.3
28
- Requires-Dist: edit-cfg-json-tk>=0.2.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, and status name mapping.
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. Required.
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
- so when you start the application it will start the configuration
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: 2309 passed, 1 deselected in 58s
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.2
406
+ - Built version(s): 1.3
374
407
  - Build and test using Python 3.14.7
@@ -0,0 +1,6 @@
1
+ backlogops>=1.3
2
+ argcomplete>=3.7.2
3
+ wizard-ui-bridge>=1.4
4
+ wizard-tk-bridge>=1.4
5
+ edit-cfg-json-tk>=0.3.0
6
+ versionreporter>=0.4
@@ -5,7 +5,7 @@ from setuptools import setup
5
5
 
6
6
  setup(
7
7
  name='backlogops-gui',
8
- version='1.2',
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.2',
22
+ 'backlogops >= 1.3',
23
23
  'argcomplete >= 3.7.2',
24
- 'wizard-ui-bridge >= 1.3',
25
- 'wizard-tk-bridge >= 1.3',
26
- 'edit-cfg-json-tk >= 0.2.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 teams configuration wizard, edits a configuration or a stand-alone
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 teams configuration is
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, source=source,
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, Levels, OutputFormatConfig,
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], sink: TextIO,
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 from the teams configuration, and any other
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 teams configuration.
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._sink,
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
- show_text_report(self._win, 'Added to Jira', text)
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
- show_text_report(self._win, 'Add releases to Jira',
520
- format_release_result(result))
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
- show_text_report(self._win, 'Update releases in Jira',
535
- format_release_updates(result))
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
- show_text_report(self._win, 'Update backlog in Jira', text)
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
- show_text_report(self._win, 'Rank items in Jira',
568
- format_rank_result(result))
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
- show_text_report(self._win, 'Order releases in Jira',
582
- format_order_result(result))
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
- show_text_report(self._win, 'Rename releases in Jira',
596
- format_rename_result(result))
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 teams
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 end, requiring preset+keys."""
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 the front or end of a Jira backlog by rank.
3
-
4
- The ranker offers a handler that asks for a preset, the keys to move and
5
- which end to move them to, then ranks them in Jira on a worker thread and
6
- hands the result back to the GUI thread. It is available only when a
7
- configuration with Jira presets is loaded. The backlog and the current
8
- ranking come from Jira through the preset, not from the shown backlog, so
9
- the handler does not need the shown data.
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 the front or end of a Jira backlog by rank."""
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 end, then rank in Jira."""
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(result.added.stored)} added "
118
- f"(preset '{name}').\n")
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. A per-table column-name map can rename a
11
- column or drop it from the display, as the GUI display configuration decides.
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,6 +0,0 @@
1
- backlogops>=1.2
2
- argcomplete>=3.7.2
3
- wizard-ui-bridge>=1.3
4
- wizard-tk-bridge>=1.3
5
- edit-cfg-json-tk>=0.2.0
6
- versionreporter>=0.4
@@ -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