backlogops-cli 1.3__tar.gz → 1.4__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_cli-1.3 → backlogops_cli-1.4}/PKG-INFO +27 -12
  2. {backlogops_cli-1.3 → backlogops_cli-1.4}/README_pypi.md +24 -9
  3. {backlogops_cli-1.3 → backlogops_cli-1.4}/backlogops_cli.egg-info/PKG-INFO +27 -12
  4. {backlogops_cli-1.3 → backlogops_cli-1.4}/backlogops_cli.egg-info/requires.txt +2 -2
  5. {backlogops_cli-1.3 → backlogops_cli-1.4}/setup.py +3 -3
  6. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/_command_io.py +24 -5
  7. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/add_to_jira.py +3 -1
  8. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/estimate_ready_date.py +38 -12
  9. backlogops_cli-1.4/src/backlogops_cli/jira_fields.py +135 -0
  10. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/preset_wizard.py +3 -2
  11. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/update_backlog_in_jira.py +35 -12
  12. backlogops_cli-1.3/src/backlogops_cli/jira_fields.py +0 -83
  13. {backlogops_cli-1.3 → backlogops_cli-1.4}/LICENSE.txt +0 -0
  14. {backlogops_cli-1.3 → backlogops_cli-1.4}/backlogops_cli.egg-info/SOURCES.txt +0 -0
  15. {backlogops_cli-1.3 → backlogops_cli-1.4}/backlogops_cli.egg-info/dependency_links.txt +0 -0
  16. {backlogops_cli-1.3 → backlogops_cli-1.4}/backlogops_cli.egg-info/top_level.txt +0 -0
  17. {backlogops_cli-1.3 → backlogops_cli-1.4}/pyproject.toml +0 -0
  18. {backlogops_cli-1.3 → backlogops_cli-1.4}/setup.cfg +0 -0
  19. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/__init__.py +0 -0
  20. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/__main__.py +0 -0
  21. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/_migrate_warn.py +0 -0
  22. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/_wizard_io.py +0 -0
  23. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/add_releases_to_jira.py +0 -0
  24. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/adjust_release_content.py +0 -0
  25. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/bloc_version_reporter.py +0 -0
  26. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/config_edit.py +0 -0
  27. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/config_wizard.py +0 -0
  28. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/convert.py +0 -0
  29. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/demo_backlog.py +0 -0
  30. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/encrypt_token_file.py +0 -0
  31. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/extract_keys.py +0 -0
  32. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/list.py +0 -0
  33. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/migrate_cfg.py +0 -0
  34. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/order_by_deps.py +0 -0
  35. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/order_by_keys.py +0 -0
  36. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/order_by_release.py +0 -0
  37. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/order_releases.py +0 -0
  38. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/order_releases_in_jira.py +0 -0
  39. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/plan_release_dates.py +0 -0
  40. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/py.typed +0 -0
  41. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/rank_in_jira.py +0 -0
  42. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/read_jira.py +0 -0
  43. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/rename_releases_in_jira.py +0 -0
  44. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/update_releases_in_jira.py +0 -0
  45. {backlogops_cli-1.3 → backlogops_cli-1.4}/src/backlogops_cli/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: backlogops-cli
3
- Version: 1.3
3
+ Version: 1.4
4
4
  Summary: Command line interface for backlog operations.
5
5
  Author: Tom Björkholm
6
6
  Author-email: Tom Björkholm <klausuler_linnet0q@icloud.com>
@@ -24,9 +24,9 @@ Requires-Python: >=3.12
24
24
  Description-Content-Type: text/markdown
25
25
  License-File: LICENSE.txt
26
26
  Requires-Dist: argcomplete>=3.7.2
27
- Requires-Dist: backlogops>=1.3
27
+ Requires-Dist: backlogops>=1.4
28
28
  Requires-Dist: wizard-ui-bridge[textual]>=1.4
29
- Requires-Dist: edit-cfg-json-textual>=0.3.0
29
+ Requires-Dist: edit-cfg-json-textual>=0.4.0
30
30
  Requires-Dist: versionreporter>=0.4
31
31
  Dynamic: author
32
32
  Dynamic: license-file
@@ -76,6 +76,10 @@ The following functionality is available in all 3 packages:
76
76
  - Estimate ready date for the backlog items based on available teams, team
77
77
  velocity, vacation dates, periods with half time work, etc.
78
78
 
79
+ - Alternatively, where remaining time estimates are required, estimate ready
80
+ dates from the remaining time of the backlog items and the focus factor of the
81
+ teams (story points are recommended).
82
+
79
83
  - Configure what a backlog item that nobody has estimated counts as, per backlog
80
84
  item level, so that an unestimated item does not make the estimated dates too
81
85
  optimistic.
@@ -104,6 +108,9 @@ The following functionality is available in all 3 packages:
104
108
 
105
109
  - Rename columns when reading a file and when writing a file.
106
110
 
111
+ - Leave out columns that are empty on every row, when writing a file and when
112
+ showing the backlog in the GUI.
113
+
107
114
  - Map custom status names in input files to backlog item statuses.
108
115
 
109
116
  - Choose how backlog item levels are written: by number, by name, or both, and
@@ -115,8 +122,8 @@ The following functionality is available in all 3 packages:
115
122
 
116
123
  - A wizard to create a backlog-ops configuration file with the workforce, named
117
124
  input and output presets, level names, status name mapping, the guess for
118
- unestimated items, how the graphical user interface shows the data, and the
119
- Jira integration.
125
+ unestimated items, whether remaining time estimates are also used, how the
126
+ graphical user interface shows the data, and the Jira integration.
120
127
 
121
128
  - An editor showing a whole configuration file or a stand-alone preset file at
122
129
  once, folded where it is deep, to change a single value without stepping
@@ -146,6 +153,10 @@ The following functionality is available in all 3 packages:
146
153
  Jira refused or what was skipped, so a value that was not written is visible
147
154
  without reading the whole listing.
148
155
 
156
+ - Show the custom fields of Jira, the fields an issue's edit screen accepts, and
157
+ the raw JSON Jira holds for a field of an issue, to find the correct Jira
158
+ field mapping.
159
+
149
160
  ## The operating model
150
161
 
151
162
  The operating model that most of the functionality is designed for is that the
@@ -154,8 +165,8 @@ are ordered by priority and dependencies to allow the teams to work in the
154
165
  backlog order. Each backlog item and each release may have a planned ready date,
155
166
  that records what has been communicated to the customer. Each backlog item and
156
167
  each release may have an estimated ready date, that is calculated from the
157
- current backlog state, the team velocity, and what we know about the
158
- availability of the team members.
168
+ current backlog state, the team velocity (or, for remaining time estimates, the
169
+ focus factor), and what we know about the availability of the team members.
159
170
 
160
171
  ## The backlog item fields
161
172
 
@@ -190,7 +201,11 @@ the library:
190
201
 
191
202
  - `team` The team responsible for the backlog item. Optional. Must not be empty
192
203
  string. Must be a valid team name. If None the item can be done by any team.
193
- If not None. the item can only be done by the specified team.
204
+ If not None, the item can only be done by the specified team.
205
+
206
+ - `remaining_time` The remaining time of the backlog item, as ideal focused work
207
+ time of one person. Optional. Only used by the alternative remaining time
208
+ estimates; story points are recommended. Must not be negative.
194
209
 
195
210
  - `depends_on_f2s` The list of keys of the backlog items that must have been
196
211
  finished before the current item can start. May be empty.
@@ -230,7 +245,7 @@ pip install --upgrade backlogops-cli
230
245
 
231
246
  For a better understanding of how to use the library, CLI or GUI, see the user
232
247
  documentation:
233
- [Using backlogops](https://github.com/tom-bjorkholm/backlog-ops/blob/master/doc//using_backlogops/README.md)
248
+ [Using backlogops](https://github.com/tom-bjorkholm/backlog-ops/blob/master/doc/using_backlogops/README.md)
234
249
 
235
250
  ## API documentation
236
251
 
@@ -291,7 +306,7 @@ backlogops_cli serves 2 purposes:
291
306
  Extract backlog keys at the given levels to a key list
292
307
 
293
308
  python3 -m backlogops_cli.jira_fields
294
- Print Jira custom fields and an issue's editable fields
309
+ Print Jira custom fields, an issue's editable fields and JSON
295
310
 
296
311
  python3 -m backlogops_cli.list
297
312
  List all commands available in backlogops_cli
@@ -342,10 +357,10 @@ backlogops_cli serves 2 purposes:
342
357
 
343
358
  ## Test summary
344
359
 
345
- - Test result: 2555 passed, 1 deselected in 58s
360
+ - Test result: 2902 passed, 1 deselected in 68s (0:01:08)
346
361
  - No flake8 warnings.
347
362
  - No mypy errors found.
348
363
  - No pylint warnings.
349
364
  - No python layout warnings.
350
- - Built version(s): 1.3
365
+ - Built version(s): 1.4
351
366
  - 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
+ - Alternatively, where remaining time estimates are required, estimate ready
45
+ dates from the remaining time of the backlog items and the focus factor of the
46
+ teams (story points are recommended).
47
+
44
48
  - Configure what a backlog item that nobody has estimated counts as, per backlog
45
49
  item level, so that an unestimated item does not make the estimated dates too
46
50
  optimistic.
@@ -69,6 +73,9 @@ The following functionality is available in all 3 packages:
69
73
 
70
74
  - Rename columns when reading a file and when writing a file.
71
75
 
76
+ - Leave out columns that are empty on every row, when writing a file and when
77
+ showing the backlog in the GUI.
78
+
72
79
  - Map custom status names in input files to backlog item statuses.
73
80
 
74
81
  - Choose how backlog item levels are written: by number, by name, or both, and
@@ -80,8 +87,8 @@ The following functionality is available in all 3 packages:
80
87
 
81
88
  - A wizard to create a backlog-ops configuration file with the workforce, named
82
89
  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.
90
+ unestimated items, whether remaining time estimates are also used, how the
91
+ graphical user interface shows the data, and the Jira integration.
85
92
 
86
93
  - An editor showing a whole configuration file or a stand-alone preset file at
87
94
  once, folded where it is deep, to change a single value without stepping
@@ -111,6 +118,10 @@ The following functionality is available in all 3 packages:
111
118
  Jira refused or what was skipped, so a value that was not written is visible
112
119
  without reading the whole listing.
113
120
 
121
+ - Show the custom fields of Jira, the fields an issue's edit screen accepts, and
122
+ the raw JSON Jira holds for a field of an issue, to find the correct Jira
123
+ field mapping.
124
+
114
125
  ## The operating model
115
126
 
116
127
  The operating model that most of the functionality is designed for is that the
@@ -119,8 +130,8 @@ are ordered by priority and dependencies to allow the teams to work in the
119
130
  backlog order. Each backlog item and each release may have a planned ready date,
120
131
  that records what has been communicated to the customer. Each backlog item and
121
132
  each release may have an estimated ready date, that is calculated from the
122
- current backlog state, the team velocity, and what we know about the
123
- availability of the team members.
133
+ current backlog state, the team velocity (or, for remaining time estimates, the
134
+ focus factor), and what we know about the availability of the team members.
124
135
 
125
136
  ## The backlog item fields
126
137
 
@@ -155,7 +166,11 @@ the library:
155
166
 
156
167
  - `team` The team responsible for the backlog item. Optional. Must not be empty
157
168
  string. Must be a valid team name. If None the item can be done by any team.
158
- If not None. the item can only be done by the specified team.
169
+ If not None, the item can only be done by the specified team.
170
+
171
+ - `remaining_time` The remaining time of the backlog item, as ideal focused work
172
+ time of one person. Optional. Only used by the alternative remaining time
173
+ estimates; story points are recommended. Must not be negative.
159
174
 
160
175
  - `depends_on_f2s` The list of keys of the backlog items that must have been
161
176
  finished before the current item can start. May be empty.
@@ -195,7 +210,7 @@ pip install --upgrade backlogops-cli
195
210
 
196
211
  For a better understanding of how to use the library, CLI or GUI, see the user
197
212
  documentation:
198
- [Using backlogops](https://github.com/tom-bjorkholm/backlog-ops/blob/master/doc//using_backlogops/README.md)
213
+ [Using backlogops](https://github.com/tom-bjorkholm/backlog-ops/blob/master/doc/using_backlogops/README.md)
199
214
 
200
215
  ## API documentation
201
216
 
@@ -256,7 +271,7 @@ backlogops_cli serves 2 purposes:
256
271
  Extract backlog keys at the given levels to a key list
257
272
 
258
273
  python3 -m backlogops_cli.jira_fields
259
- Print Jira custom fields and an issue's editable fields
274
+ Print Jira custom fields, an issue's editable fields and JSON
260
275
 
261
276
  python3 -m backlogops_cli.list
262
277
  List all commands available in backlogops_cli
@@ -307,10 +322,10 @@ backlogops_cli serves 2 purposes:
307
322
 
308
323
  ## Test summary
309
324
 
310
- - Test result: 2555 passed, 1 deselected in 58s
325
+ - Test result: 2902 passed, 1 deselected in 68s (0:01:08)
311
326
  - No flake8 warnings.
312
327
  - No mypy errors found.
313
328
  - No pylint warnings.
314
329
  - No python layout warnings.
315
- - Built version(s): 1.3
330
+ - Built version(s): 1.4
316
331
  - Build and test using Python 3.14.7
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: backlogops-cli
3
- Version: 1.3
3
+ Version: 1.4
4
4
  Summary: Command line interface for backlog operations.
5
5
  Author: Tom Björkholm
6
6
  Author-email: Tom Björkholm <klausuler_linnet0q@icloud.com>
@@ -24,9 +24,9 @@ Requires-Python: >=3.12
24
24
  Description-Content-Type: text/markdown
25
25
  License-File: LICENSE.txt
26
26
  Requires-Dist: argcomplete>=3.7.2
27
- Requires-Dist: backlogops>=1.3
27
+ Requires-Dist: backlogops>=1.4
28
28
  Requires-Dist: wizard-ui-bridge[textual]>=1.4
29
- Requires-Dist: edit-cfg-json-textual>=0.3.0
29
+ Requires-Dist: edit-cfg-json-textual>=0.4.0
30
30
  Requires-Dist: versionreporter>=0.4
31
31
  Dynamic: author
32
32
  Dynamic: license-file
@@ -76,6 +76,10 @@ The following functionality is available in all 3 packages:
76
76
  - Estimate ready date for the backlog items based on available teams, team
77
77
  velocity, vacation dates, periods with half time work, etc.
78
78
 
79
+ - Alternatively, where remaining time estimates are required, estimate ready
80
+ dates from the remaining time of the backlog items and the focus factor of the
81
+ teams (story points are recommended).
82
+
79
83
  - Configure what a backlog item that nobody has estimated counts as, per backlog
80
84
  item level, so that an unestimated item does not make the estimated dates too
81
85
  optimistic.
@@ -104,6 +108,9 @@ The following functionality is available in all 3 packages:
104
108
 
105
109
  - Rename columns when reading a file and when writing a file.
106
110
 
111
+ - Leave out columns that are empty on every row, when writing a file and when
112
+ showing the backlog in the GUI.
113
+
107
114
  - Map custom status names in input files to backlog item statuses.
108
115
 
109
116
  - Choose how backlog item levels are written: by number, by name, or both, and
@@ -115,8 +122,8 @@ The following functionality is available in all 3 packages:
115
122
 
116
123
  - A wizard to create a backlog-ops configuration file with the workforce, named
117
124
  input and output presets, level names, status name mapping, the guess for
118
- unestimated items, how the graphical user interface shows the data, and the
119
- Jira integration.
125
+ unestimated items, whether remaining time estimates are also used, how the
126
+ graphical user interface shows the data, and the Jira integration.
120
127
 
121
128
  - An editor showing a whole configuration file or a stand-alone preset file at
122
129
  once, folded where it is deep, to change a single value without stepping
@@ -146,6 +153,10 @@ The following functionality is available in all 3 packages:
146
153
  Jira refused or what was skipped, so a value that was not written is visible
147
154
  without reading the whole listing.
148
155
 
156
+ - Show the custom fields of Jira, the fields an issue's edit screen accepts, and
157
+ the raw JSON Jira holds for a field of an issue, to find the correct Jira
158
+ field mapping.
159
+
149
160
  ## The operating model
150
161
 
151
162
  The operating model that most of the functionality is designed for is that the
@@ -154,8 +165,8 @@ are ordered by priority and dependencies to allow the teams to work in the
154
165
  backlog order. Each backlog item and each release may have a planned ready date,
155
166
  that records what has been communicated to the customer. Each backlog item and
156
167
  each release may have an estimated ready date, that is calculated from the
157
- current backlog state, the team velocity, and what we know about the
158
- availability of the team members.
168
+ current backlog state, the team velocity (or, for remaining time estimates, the
169
+ focus factor), and what we know about the availability of the team members.
159
170
 
160
171
  ## The backlog item fields
161
172
 
@@ -190,7 +201,11 @@ the library:
190
201
 
191
202
  - `team` The team responsible for the backlog item. Optional. Must not be empty
192
203
  string. Must be a valid team name. If None the item can be done by any team.
193
- If not None. the item can only be done by the specified team.
204
+ If not None, the item can only be done by the specified team.
205
+
206
+ - `remaining_time` The remaining time of the backlog item, as ideal focused work
207
+ time of one person. Optional. Only used by the alternative remaining time
208
+ estimates; story points are recommended. Must not be negative.
194
209
 
195
210
  - `depends_on_f2s` The list of keys of the backlog items that must have been
196
211
  finished before the current item can start. May be empty.
@@ -230,7 +245,7 @@ pip install --upgrade backlogops-cli
230
245
 
231
246
  For a better understanding of how to use the library, CLI or GUI, see the user
232
247
  documentation:
233
- [Using backlogops](https://github.com/tom-bjorkholm/backlog-ops/blob/master/doc//using_backlogops/README.md)
248
+ [Using backlogops](https://github.com/tom-bjorkholm/backlog-ops/blob/master/doc/using_backlogops/README.md)
234
249
 
235
250
  ## API documentation
236
251
 
@@ -291,7 +306,7 @@ backlogops_cli serves 2 purposes:
291
306
  Extract backlog keys at the given levels to a key list
292
307
 
293
308
  python3 -m backlogops_cli.jira_fields
294
- Print Jira custom fields and an issue's editable fields
309
+ Print Jira custom fields, an issue's editable fields and JSON
295
310
 
296
311
  python3 -m backlogops_cli.list
297
312
  List all commands available in backlogops_cli
@@ -342,10 +357,10 @@ backlogops_cli serves 2 purposes:
342
357
 
343
358
  ## Test summary
344
359
 
345
- - Test result: 2555 passed, 1 deselected in 58s
360
+ - Test result: 2902 passed, 1 deselected in 68s (0:01:08)
346
361
  - No flake8 warnings.
347
362
  - No mypy errors found.
348
363
  - No pylint warnings.
349
364
  - No python layout warnings.
350
- - Built version(s): 1.3
365
+ - Built version(s): 1.4
351
366
  - Build and test using Python 3.14.7
@@ -1,5 +1,5 @@
1
1
  argcomplete>=3.7.2
2
- backlogops>=1.3
2
+ backlogops>=1.4
3
3
  wizard-ui-bridge[textual]>=1.4
4
- edit-cfg-json-textual>=0.3.0
4
+ edit-cfg-json-textual>=0.4.0
5
5
  versionreporter>=0.4
@@ -5,7 +5,7 @@ from setuptools import setup
5
5
 
6
6
  setup(
7
7
  name='backlogops-cli',
8
- version='1.3',
8
+ version='1.4',
9
9
  description='Command line interface for backlog operations.',
10
10
  author='Tom Björkholm',
11
11
  author_email='klausuler_linnet0q@icloud.com',
@@ -15,9 +15,9 @@ setup(
15
15
  package_data={'backlogops_cli': ['src/py.typed']},
16
16
  install_requires=[
17
17
  'argcomplete >= 3.7.2',
18
- 'backlogops >= 1.3',
18
+ 'backlogops >= 1.4',
19
19
  'wizard-ui-bridge[textual] >= 1.4',
20
- 'edit-cfg-json-textual >= 0.3.0',
20
+ 'edit-cfg-json-textual >= 0.4.0',
21
21
  'versionreporter >= 0.4'
22
22
  ]
23
23
  )
@@ -146,13 +146,15 @@ def add_config_arg(parser: argparse.ArgumentParser) -> None:
146
146
 
147
147
  The configuration file holds the workforce, the named input and output
148
148
  presets, the levels, the global status map, the guess for unestimated
149
- items, the GUI display settings and the Jira configuration. Without
149
+ items, the remaining time settings, the GUI display settings and the
150
+ Jira configuration. Without
150
151
  ``-c`` the file is discovered the same way as the GUI.
151
152
  """
152
153
  parser.add_argument('-c', '--config', dest='config',
153
154
  help='Backlog-ops configuration file (workforce, '
154
155
  'named presets, levels, status map, default story '
155
- 'points, Jira configuration). Without -c the '
156
+ 'points, remaining time settings, Jira '
157
+ 'configuration). Without -c the '
156
158
  'file is found from $BACKLOGOPS_CFG, else '
157
159
  'backlogops.cfg in $BACKLOGOPS_DIR, else '
158
160
  '$HOME/.backlogops.cfg.')
@@ -233,6 +235,20 @@ def io_levels(config: Optional[BacklogOpsConfig]) -> Optional[Levels]:
233
235
  return config.get_levels() if config is not None else None
234
236
 
235
237
 
238
+ def io_remaining_time(config: Optional[BacklogOpsConfig]) -> bool:
239
+ """Return whether ``config`` enables remaining time estimates.
240
+
241
+ Args:
242
+ config: The resolved backlog-ops configuration, or None for the
243
+ defaults, which do not use remaining time estimates.
244
+
245
+ Returns:
246
+ True when remaining time estimates are enabled in ``config``.
247
+ """
248
+ return config is not None and \
249
+ config.remaining_time.enable_remaining_time
250
+
251
+
236
252
  def read_input(parsed: argparse.Namespace,
237
253
  config: Optional[BacklogOpsConfig]) -> BacklogReleases:
238
254
  """Read and validate the backlog and releases from the input file.
@@ -345,7 +361,8 @@ def _write_output(parsed: argparse.Namespace,
345
361
  write_backlog_releases(data, parsed.output, out_config, rules,
346
362
  levels=io_levels(config),
347
363
  file_exists_callback=overwrite_callback(
348
- parsed.force))
364
+ parsed.force),
365
+ use_remaining_time=io_remaining_time(config))
349
366
 
350
367
 
351
368
  def run_write(parsed: argparse.Namespace,
@@ -407,7 +424,8 @@ def write_result_file(config: BacklogOpsConfig, path: str,
407
424
  auto_ch_hook=CliPresetMigrateWarnHook())
408
425
  write_backlog_releases(data, path, out_config, FormatRules(),
409
426
  levels=config.get_levels(),
410
- file_exists_callback=overwrite_callback(force))
427
+ file_exists_callback=overwrite_callback(force),
428
+ use_remaining_time=io_remaining_time(config))
411
429
  print(f'Wrote {path}')
412
430
 
413
431
 
@@ -420,7 +438,8 @@ def run_added_to_jira(
420
438
  """Read the input, add it to Jira, and print the added lists.
421
439
 
422
440
  ``add`` receives the parsed args, the config and the input data, adds
423
- to Jira, writes any ``--added``/``--existing`` files, and returns the
441
+ to Jira, writes any ``--added-file``/``--existing-file`` files, and
442
+ returns the
424
443
  result. ``report`` formats that result for stdout, printed unless
425
444
  ``-q``/``--quiet`` was given. ``exists_error`` is the key-exists error
426
445
  meaning nothing was added; other value or OS errors are reported with
@@ -66,7 +66,9 @@ def _add(parsed: argparse.Namespace, config: BacklogOpsConfig,
66
66
  on_existing_key=mode,
67
67
  rank_anchor=rank_anchor(parsed.rank),
68
68
  levels=config.get_levels(),
69
- status_map=config.get_status_input_map())
69
+ status_map=config.get_status_input_map(),
70
+ use_remaining_time=config.remaining_time
71
+ .enable_remaining_time)
70
72
  print(f'Added {len(result.stored)} items to Jira; '
71
73
  f'{len(result.already_present)} already present; '
72
74
  f'{len(result.failed)} failed; '
@@ -7,10 +7,16 @@ as documented for :func:`backlogops.estimate_ready_date`. The teams
7
7
  configuration (velocity, work hours, vacations and so on) is taken from
8
8
  the file given by ``--config`` or, when that is absent, from the
9
9
  configured backlog-ops file, and so is what a backlog item that nobody
10
- has estimated is worked with. The backlog with the estimated dates and the
11
- releases are written to the output file. The input and output formats are
12
- inferred from the file name extensions, but can be overridden by a
13
- configuration file or by a named preset.
10
+ has estimated is worked with. With ``--remaining-time`` the ready dates
11
+ are instead estimated from the remaining time of the items and the focus
12
+ factor of the teams (or the configured default focus factor), as
13
+ documented for
14
+ :func:`backlogops.estimate_rt_ready_date`; that needs remaining time
15
+ estimates to be enabled in the configuration, and the command fails
16
+ without writing anything when they are not. The backlog with the
17
+ estimated dates and the releases are written to the output file. The
18
+ input and output formats are inferred from the file name extensions, but
19
+ can be overridden by a configuration file or by a named preset.
14
20
  """
15
21
 
16
22
  # PYTHON_ARGCOMPLETE_OK
@@ -36,6 +42,16 @@ def build_parser() -> argparse.ArgumentParser:
36
42
  parser.add_argument('-d', '--start-date', dest='start_date',
37
43
  metavar='ISO_DATE',
38
44
  help='Day the teams start working (default today).')
45
+ parser.add_argument('--remaining-time', dest='remaining_time',
46
+ action='store_true',
47
+ help='Estimate from the remaining time of the items '
48
+ 'and the focus factor of the teams (or the default '
49
+ 'focus factor) instead of from '
50
+ 'story points and velocity. Requires remaining time '
51
+ 'estimates to be enabled in the configuration '
52
+ '("enable_remaining_time": true in its '
53
+ '"remaining_time" section); otherwise the command '
54
+ 'fails and writes nothing.')
39
55
  parser.add_argument('--set-plan', dest='set_plan', action='store_true',
40
56
  help='Also copy each estimated date to the planned '
41
57
  'date.')
@@ -55,13 +71,20 @@ def _estimate(parsed: argparse.Namespace, config: Optional[BacklogOpsConfig],
55
71
  ) -> tuple[str, Optional[Callable[[str], None]]]:
56
72
  """Estimate the dates and return the release date change report.
57
73
 
58
- The configuration is required for this command, so ``config`` is never
59
- None here; the assertion makes that explicit for the type checker.
74
+ The dates are estimated from remaining time when ``--remaining-time``
75
+ is given, and from story points otherwise. The configuration is
76
+ required for this command, so ``config`` is never None here; the
77
+ assertion makes that explicit for the type checker.
60
78
  """
61
79
  assert isinstance(config, BacklogOpsConfig)
62
- changes = data.estimate_ready_date(
63
- config.available_teams, _start_date(parsed),
64
- default_story_points=config.default_story_points)
80
+ start = _start_date(parsed)
81
+ if parsed.remaining_time:
82
+ changes = data.estimate_rt_ready_date(config.available_teams,
83
+ config.remaining_time, start)
84
+ else:
85
+ changes = data.estimate_ready_date(
86
+ config.available_teams, start,
87
+ default_story_points=config.default_story_points)
65
88
  if parsed.set_plan:
66
89
  data.set_plan_from_estimate()
67
90
  return date_report(changes, overwrite_callback(parsed.force))
@@ -71,9 +94,12 @@ def main(args: Optional[list[str]] = None) -> int:
71
94
  """Estimate the ready dates and write the output file.
72
95
 
73
96
  The backlog with the estimated dates and the releases are written to
74
- the output file. The estimated release dates are updated as well, and
75
- the list of release date changes is printed to stdout, or also saved
76
- to a file when ``--changes-file`` is given.
97
+ the output file. The ready dates are estimated from story points, or
98
+ from remaining time with ``--remaining-time``, which fails when the
99
+ configuration does not enable remaining time estimates. The estimated
100
+ release dates are updated as well, and the list of release date
101
+ changes is printed to stdout, or also saved to a file when
102
+ ``--changes-file`` is given.
77
103
 
78
104
  Args:
79
105
  args: Optional replacement for ``sys.argv[1:]``, mainly for tests.
@@ -0,0 +1,135 @@
1
+ #! /usr/local/bin/python3
2
+ """Print Jira field information for a preset, to diagnose write mappings.
3
+
4
+ The command prints the custom field id to display name map that the reader
5
+ fetches from Jira, so a column-map name such as 'Story point estimate' can
6
+ be matched to its field id. With ``--issue`` it also prints the fields the
7
+ given issue's edit screen offers, which explains why a mapped field cannot
8
+ be set on that issue's type: a field missing from the edit screen cannot be
9
+ set through the issue edit REST endpoint. Each ``--field`` (which needs
10
+ ``--issue``) prints the raw Jira JSON at a field id or dotted path of that
11
+ issue, such as ``timetracking.remainingEstimateSeconds``, and the field's
12
+ edit screen entry, so a column map path can be checked against what Jira
13
+ really holds and how it may be written.
14
+
15
+ An encrypted Jira token is unlocked by a pass phrase asked on the terminal
16
+ only when it is needed.
17
+ """
18
+
19
+ # PYTHON_ARGCOMPLETE_OK
20
+ # Copyright (c) 2026, Tom Björkholm
21
+ # MIT License
22
+
23
+ import argparse
24
+ import json
25
+ import sys
26
+ from typing import Optional
27
+ from jira import JIRAError
28
+ from backlogops import (
29
+ JiraConnections, JiraFieldJson, jira_custom_fields, jira_editable_fields,
30
+ jira_field_json)
31
+ from backlogops_cli._command_io import (
32
+ build_jira_parser, jira_passphrase, parsed_args, required_config)
33
+
34
+ DESCRIPTION = "Print Jira custom fields, an issue's editable fields and JSON"
35
+
36
+
37
+ def build_parser() -> argparse.ArgumentParser:
38
+ """Build the command line parser for the field diagnostic command."""
39
+ parser = build_jira_parser(DESCRIPTION, with_input=False)
40
+ parser.add_argument('--issue', dest='issue', metavar='KEY',
41
+ help="Also print the fields this issue's edit "
42
+ 'screen offers (for example SCRUM-15).')
43
+ parser.add_argument('--field', dest='fields', action='append',
44
+ metavar='NAME', default=[],
45
+ help='Also print the raw Jira JSON of this field of '
46
+ 'the --issue: a field id, a custom field name, or a '
47
+ 'dotted path such as '
48
+ 'timetracking.remainingEstimateSeconds. Repeatable.')
49
+ return parser
50
+
51
+
52
+ def _print_pairs(heading: str, pairs: list[tuple[str, str]]) -> None:
53
+ """Print a heading and each field id and display name pair."""
54
+ print(heading)
55
+ if not pairs:
56
+ print(' (none)')
57
+ for field_id, name in pairs:
58
+ print(f' {field_id} {name}')
59
+
60
+
61
+ def _print_json(heading: str, value: object) -> None:
62
+ """Print a heading and the value as indented JSON below it."""
63
+ print(f' {heading}:')
64
+ text = json.dumps(value, indent=2, ensure_ascii=False, default=str)
65
+ for line in text.splitlines():
66
+ print(f' {line}')
67
+
68
+
69
+ def _print_field(issue: str, found: JiraFieldJson) -> None:
70
+ """Print one field path's JSON value and its edit screen entry."""
71
+ print(f"\nField '{found.name}' of {issue} (Jira field {found.field_id}):")
72
+ if found.present:
73
+ _print_json('value', found.value)
74
+ else:
75
+ print(' value: (no such path in the issue)')
76
+ if found.edit_entry is None:
77
+ print(' edit screen: (not offered, so not writable by an edit)')
78
+ else:
79
+ _print_json('edit screen', found.edit_entry)
80
+
81
+
82
+ def _read_all(parsed: argparse.Namespace) -> tuple[
83
+ list[tuple[str, str]], Optional[list[tuple[str, str]]],
84
+ list[JiraFieldJson]]:
85
+ """Read the custom fields, edit screen fields and field JSON asked for."""
86
+ config = required_config(parsed)
87
+ connections = JiraConnections(config.get_jira_config(), jira_passphrase)
88
+ customs = jira_custom_fields(connections, parsed.preset)
89
+ if parsed.issue is None:
90
+ return customs, None, []
91
+ editable = jira_editable_fields(connections, parsed.preset, parsed.issue)
92
+ found = jira_field_json(connections, parsed.preset, parsed.issue,
93
+ parsed.fields)
94
+ return customs, editable, found
95
+
96
+
97
+ def _run(parsed: argparse.Namespace) -> int:
98
+ """Print the custom field map and, optionally, the issue's fields."""
99
+ try:
100
+ customs, editable, found = _read_all(parsed)
101
+ except (ValueError, TypeError, KeyError, OSError, JIRAError) as error:
102
+ print(f'Could not read Jira fields: {error}', file=sys.stderr)
103
+ return 1
104
+ _print_pairs(f"Custom fields for preset '{parsed.preset}':", customs)
105
+ if editable is not None:
106
+ print()
107
+ _print_pairs(f'Fields settable on the edit screen of '
108
+ f'{parsed.issue}:', editable)
109
+ for field in found:
110
+ _print_field(parsed.issue, field)
111
+ return 0
112
+
113
+
114
+ def main(args: Optional[list[str]] = None) -> int:
115
+ """Print Jira field information for a preset.
116
+
117
+ Args:
118
+ args: Optional replacement for ``sys.argv[1:]``, mainly for tests.
119
+
120
+ Returns:
121
+ ``0`` on success, ``1`` when the fields cannot be read.
122
+
123
+ Raises:
124
+ SystemExit: With status ``2`` when ``--field`` is given without
125
+ ``--issue``, as for any other command line usage error.
126
+ """
127
+ parser = build_parser()
128
+ parsed = parsed_args(parser, args)
129
+ if parsed.fields and parsed.issue is None:
130
+ parser.error('--field needs --issue')
131
+ return _run(parsed)
132
+
133
+
134
+ if __name__ == '__main__': # pragma: no cover
135
+ sys.exit(main())
@@ -2,8 +2,9 @@
2
2
  """Run the IO preset wizard and store the created preset file.
3
3
 
4
4
  The created file holds a single input or output TableIO preset (a format
5
- configuration with its column-name maps, and a level display for an output
6
- preset). Such a stand-alone file is used wherever an input or output
5
+ configuration with its column-name maps, and a level display and whether
6
+ to leave out empty columns for an output preset). Such a stand-alone file
7
+ is used wherever an input or output
7
8
  configuration is taken, by giving its file name.
8
9
  """
9
10
 
@@ -7,11 +7,13 @@ backlog-ops configuration, changing only a chosen subset of the mapped
7
7
  fields. The subset is chosen with exactly one of two flags: ``-s``/``--store``
8
8
  lists the columns to update (or the single word ``all`` for every mapped
9
9
  writable column), while ``-e``/``--exclude`` updates every mapped writable
10
- column except the listed ones.
10
+ column except the listed ones. The remaining time is a writable column only
11
+ while remaining time estimates are enabled.
11
12
 
12
13
  ``--on-missing`` chooses what to do with an item whose key is not present in
13
14
  Jira: ``raise`` (the default) stops with an error, ``ignore`` leaves it
14
- alone, and ``add`` creates it with all of its fields. ``--links`` chooses how
15
+ alone, and ``add`` creates it with all of its mapped fields (the remaining
16
+ time only while remaining time estimates are enabled). ``--links`` chooses how
15
17
  the parent and dependency links are updated: ``reconcile`` (the default) makes
16
18
  the Jira links match the backlog exactly, removing a Jira link the backlog no
17
19
  longer has and clearing a dropped parent, while ``add`` only adds the missing
@@ -49,6 +51,9 @@ _MISSING_MODES = {'raise': OnMissingKey.RAISE, 'ignore': OnMissingKey.IGNORE,
49
51
  _LINK_MODES = {'reconcile': LinkUpdate.RECONCILE,
50
52
  'add': LinkUpdate.ADD_MISSING}
51
53
  _STORE_ALL = 'all'
54
+ _RT_WRITABLE = ('remaining_time is writable only while '
55
+ '"enable_remaining_time" is true in the configuration.')
56
+ """Help and message note on when the remaining time can be updated."""
52
57
 
53
58
 
54
59
  def build_parser() -> argparse.ArgumentParser:
@@ -76,40 +81,57 @@ def _add_column_flags(parser: argparse.ArgumentParser) -> None:
76
81
  group.add_argument('-s', '--store', dest='store', nargs='+',
77
82
  metavar='COLUMN',
78
83
  help='Columns to update, or the single word "all" for '
79
- 'every mapped writable column.')
84
+ 'every mapped writable column. ' + _RT_WRITABLE)
80
85
  group.add_argument('-e', '--exclude', dest='exclude', nargs='+',
81
86
  metavar='COLUMN',
82
- help='Update every mapped writable column but these.')
87
+ help='Update every mapped writable column but these. '
88
+ + _RT_WRITABLE)
83
89
 
84
90
 
85
- def _resolve_fields(parsed: argparse.Namespace,
86
- connections: JiraConnections) -> list[str]:
91
+ def _resolve_fields(parsed: argparse.Namespace, connections: JiraConnections,
92
+ use_remaining_time: bool) -> list[str]:
87
93
  """Return the internal field names to update from the -s/-e flags.
88
94
 
89
95
  ``-s all`` and ``-e`` are resolved against the preset's updatable
90
96
  columns. A ``-s`` name that is not an updatable column is reported and
91
- dropped, so a typo does not silently update nothing.
97
+ dropped, so a typo does not silently update nothing. The remaining
98
+ time is an updatable column only while remaining time estimates are
99
+ used.
92
100
  """
93
- updatable = updatable_backlog_fields(connections, parsed.preset)
101
+ updatable = updatable_backlog_fields(connections, parsed.preset,
102
+ use_remaining_time=use_remaining_time)
94
103
  if parsed.store is not None:
95
104
  if parsed.store == [_STORE_ALL]:
96
105
  return updatable
97
106
  unknown = [name for name in parsed.store if name not in updatable]
98
107
  if unknown:
99
- print('Ignoring columns not updatable in this preset: '
100
- + ', '.join(unknown), file=sys.stderr)
108
+ _report_ignored(unknown, use_remaining_time)
101
109
  return [name for name in parsed.store if name in updatable]
102
110
  excluded = set(parsed.exclude)
103
111
  return [name for name in updatable if name not in excluded]
104
112
 
105
113
 
114
+ def _report_ignored(unknown: list[str], use_remaining_time: bool) -> None:
115
+ """Report the ``-s`` columns dropped as not updatable to stderr.
116
+
117
+ A dropped ``remaining_time`` while remaining time estimates are not
118
+ used gets a note saying why, because the preset may well map it.
119
+ """
120
+ note = ''
121
+ if 'remaining_time' in unknown and not use_remaining_time:
122
+ note = ' (' + _RT_WRITABLE + ')'
123
+ print('Ignoring columns not updatable in this preset: '
124
+ + ', '.join(unknown) + note, file=sys.stderr)
125
+
126
+
106
127
  def _update(parsed: argparse.Namespace, config: BacklogOpsConfig,
107
128
  data_backlog: object) -> UpdatedBacklogInJira:
108
129
  """Update the input backlog in Jira using the named preset."""
109
130
  print(f"Updating backlog in Jira using preset '{parsed.preset}'...",
110
131
  file=sys.stderr)
111
132
  connections = JiraConnections(config.get_jira_config(), jira_passphrase)
112
- fields = _resolve_fields(parsed, connections)
133
+ use_rt = config.remaining_time.enable_remaining_time
134
+ fields = _resolve_fields(parsed, connections, use_rt)
113
135
  mode = _MISSING_MODES[parsed.on_missing]
114
136
  link_update = _LINK_MODES[parsed.links]
115
137
  assert isinstance(data_backlog, list)
@@ -119,7 +141,8 @@ def _update(parsed: argparse.Namespace, config: BacklogOpsConfig,
119
141
  link_update=link_update,
120
142
  rank_anchor=rank_anchor(parsed.rank),
121
143
  levels=config.get_levels(),
122
- status_map=config.get_status_input_map())
144
+ status_map=config.get_status_input_map(),
145
+ use_remaining_time=use_rt)
123
146
  _report_summary(result)
124
147
  return result
125
148
 
@@ -1,83 +0,0 @@
1
- #! /usr/local/bin/python3
2
- """Print Jira field information for a preset, to diagnose write mappings.
3
-
4
- The command prints the custom field id to display name map that the reader
5
- fetches from Jira, so a column-map name such as 'Story point estimate' can
6
- be matched to its field id. With ``--issue`` it also prints the fields the
7
- given issue's edit screen offers, which explains why a mapped field cannot
8
- be set on that issue's type: a field missing from the edit screen cannot be
9
- set through the issue edit REST endpoint.
10
-
11
- An encrypted Jira token is unlocked by a pass phrase asked on the terminal
12
- only when it is needed.
13
- """
14
-
15
- # PYTHON_ARGCOMPLETE_OK
16
- # Copyright (c) 2026, Tom Björkholm
17
- # MIT License
18
-
19
- import argparse
20
- import sys
21
- from typing import Optional
22
- from jira import JIRAError
23
- from backlogops import (
24
- JiraConnections, jira_custom_fields, jira_editable_fields)
25
- from backlogops_cli._command_io import (
26
- build_jira_parser, jira_passphrase, parsed_args, required_config)
27
-
28
- DESCRIPTION = "Print Jira custom fields and an issue's editable fields"
29
-
30
-
31
- def build_parser() -> argparse.ArgumentParser:
32
- """Build the command line parser for the field diagnostic command."""
33
- parser = build_jira_parser(DESCRIPTION, with_input=False)
34
- parser.add_argument('--issue', dest='issue', metavar='KEY',
35
- help="Also print the fields this issue's edit "
36
- 'screen offers (for example SCRUM-15).')
37
- return parser
38
-
39
-
40
- def _print_pairs(heading: str, pairs: list[tuple[str, str]]) -> None:
41
- """Print a heading and each field id and display name pair."""
42
- print(heading)
43
- if not pairs:
44
- print(' (none)')
45
- for field_id, name in pairs:
46
- print(f' {field_id} {name}')
47
-
48
-
49
- def _run(parsed: argparse.Namespace) -> int:
50
- """Print the custom field map and, optionally, editable fields."""
51
- try:
52
- config = required_config(parsed)
53
- connections = JiraConnections(config.get_jira_config(),
54
- jira_passphrase)
55
- customs = jira_custom_fields(connections, parsed.preset)
56
- editable = (jira_editable_fields(connections, parsed.preset,
57
- parsed.issue)
58
- if parsed.issue is not None else None)
59
- except (ValueError, TypeError, KeyError, OSError, JIRAError) as error:
60
- print(f'Could not read Jira fields: {error}', file=sys.stderr)
61
- return 1
62
- _print_pairs(f"Custom fields for preset '{parsed.preset}':", customs)
63
- if editable is not None:
64
- print()
65
- _print_pairs(f'Fields settable on the edit screen of '
66
- f'{parsed.issue}:', editable)
67
- return 0
68
-
69
-
70
- def main(args: Optional[list[str]] = None) -> int:
71
- """Print Jira field information for a preset.
72
-
73
- Args:
74
- args: Optional replacement for ``sys.argv[1:]``, mainly for tests.
75
-
76
- Returns:
77
- ``0`` on success, ``1`` when the fields cannot be read.
78
- """
79
- return _run(parsed_args(build_parser(), args))
80
-
81
-
82
- if __name__ == '__main__': # pragma: no cover
83
- sys.exit(main())
File without changes
File without changes