backlogops 0.4__tar.gz → 0.6__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-0.4 → backlogops-0.6}/PKG-INFO +9 -12
- {backlogops-0.4 → backlogops-0.6}/README_pypi.md +5 -8
- {backlogops-0.4 → backlogops-0.6}/backlogops.egg-info/PKG-INFO +9 -12
- {backlogops-0.4 → backlogops-0.6}/backlogops.egg-info/requires.txt +3 -2
- {backlogops-0.4 → backlogops-0.6}/pyproject.toml +0 -1
- {backlogops-0.4 → backlogops-0.6}/setup.py +4 -3
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/backlog_ops_wizard.py +144 -22
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/blo_version_reporter.py +2 -2
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/io_preset_wizard.py +1 -1
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/jira_wizard.py +39 -3
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/wizard_forms.py +107 -117
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/wizard_helpers.py +10 -178
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/wizard_navigator.py +13 -30
- {backlogops-0.4 → backlogops-0.6}/LICENSE.txt +0 -0
- {backlogops-0.4 → backlogops-0.6}/backlogops.egg-info/SOURCES.txt +0 -0
- {backlogops-0.4 → backlogops-0.6}/backlogops.egg-info/dependency_links.txt +0 -0
- {backlogops-0.4 → backlogops-0.6}/backlogops.egg-info/top_level.txt +0 -0
- {backlogops-0.4 → backlogops-0.6}/setup.cfg +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/__init__.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/apply_format_rules.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/available_teams.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/available_teams_config.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/backlog.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/backlog_helpers.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/backlog_in_release_order.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/backlog_ops_config.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/backlog_releases.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/backlog_releases_io.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/config_file_io.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/date_ranges.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/demo_backlog.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/estimate_ready_date.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/format_rules.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/io_config.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/jira_connect.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/jira_io_config.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/jira_order_releases.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/jira_rank_backlog.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/jira_rank_by_keys.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/jira_rank_move_keys.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/jira_read.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/jira_rename_releases.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/jira_search.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/jira_token.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/jira_update_backlog.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/jira_update_releases.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/jira_write.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/jira_write_fields.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/jira_write_format.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/jira_write_releases.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/key_list_io.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/levels.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/move_keys_first.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/name_list_io.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/no_text_io.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/order_by_dependencies.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/person.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/py.typed +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/release_backlog_updates.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/release_change_io.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/releases.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/rename_list_io.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/table_create.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/table_rows.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/team.py +0 -0
- {backlogops-0.4 → backlogops-0.6}/src/backlogops/work_hours.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: backlogops
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6
|
|
4
4
|
Summary: Library with backlog operations.
|
|
5
5
|
Author: Tom Björkholm
|
|
6
6
|
Author-email: Tom Björkholm <klausuler_linnet0q@icloud.com>
|
|
@@ -14,7 +14,6 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.14
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
|
-
Classifier: Development Status :: 4 - Beta
|
|
18
17
|
Classifier: Intended Audience :: Developers
|
|
19
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
19
|
Classifier: Topic :: Office/Business :: Scheduling
|
|
@@ -23,10 +22,11 @@ Requires-Python: >=3.12
|
|
|
23
22
|
Description-Content-Type: text/markdown
|
|
24
23
|
License-File: LICENSE.txt
|
|
25
24
|
Requires-Dist: config-as-json>=1.4
|
|
26
|
-
Requires-Dist: cryptography>=
|
|
25
|
+
Requires-Dist: cryptography>=50.0.0
|
|
27
26
|
Requires-Dist: jira[cli,opt]>=3.10.5
|
|
28
27
|
Requires-Dist: tableio>=1.1
|
|
29
|
-
Requires-Dist: tableio-cfg-json>=
|
|
28
|
+
Requires-Dist: tableio-cfg-json>=1.1
|
|
29
|
+
Requires-Dist: wizard-ui-bridge[textual]>=1.1
|
|
30
30
|
Requires-Dist: versionreporter>=0.4
|
|
31
31
|
Dynamic: author
|
|
32
32
|
Dynamic: license-file
|
|
@@ -51,15 +51,11 @@ There are 3 related packages for backlog operations:
|
|
|
51
51
|
|
|
52
52
|
## Project status
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
place, so the main work from here is on the user experience rather than on new
|
|
56
|
-
core functionality. The three packages share a version number and are released
|
|
57
|
-
together.
|
|
54
|
+
The three packages share a version number and are released together.
|
|
58
55
|
|
|
59
56
|
The library is not meant to ever be "finished": using it for real planning keeps
|
|
60
57
|
surfacing new ideas. Some of those will be added to backlogops itself, and
|
|
61
|
-
others will fit better in separate packages built on top of backlogops.
|
|
62
|
-
adopters are very welcome, and their feedback shapes what comes next.
|
|
58
|
+
others will fit better in separate packages built on top of backlogops.
|
|
63
59
|
|
|
64
60
|
## Available functionality
|
|
65
61
|
|
|
@@ -316,9 +312,10 @@ For the full set of public names see the API documentation linked above.
|
|
|
316
312
|
|
|
317
313
|
## Test summary
|
|
318
314
|
|
|
319
|
-
- Test result:
|
|
315
|
+
- Test result: 2450 passed, 9 deselected in 39s
|
|
320
316
|
- No flake8 warnings.
|
|
321
317
|
- No mypy errors found.
|
|
318
|
+
- No pylint warnings.
|
|
322
319
|
- No python layout warnings.
|
|
323
|
-
- Built version(s): 0.
|
|
320
|
+
- Built version(s): 0.6
|
|
324
321
|
- Build and test using Python 3.14.6
|
|
@@ -16,15 +16,11 @@ There are 3 related packages for backlog operations:
|
|
|
16
16
|
|
|
17
17
|
## Project status
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
place, so the main work from here is on the user experience rather than on new
|
|
21
|
-
core functionality. The three packages share a version number and are released
|
|
22
|
-
together.
|
|
19
|
+
The three packages share a version number and are released together.
|
|
23
20
|
|
|
24
21
|
The library is not meant to ever be "finished": using it for real planning keeps
|
|
25
22
|
surfacing new ideas. Some of those will be added to backlogops itself, and
|
|
26
|
-
others will fit better in separate packages built on top of backlogops.
|
|
27
|
-
adopters are very welcome, and their feedback shapes what comes next.
|
|
23
|
+
others will fit better in separate packages built on top of backlogops.
|
|
28
24
|
|
|
29
25
|
## Available functionality
|
|
30
26
|
|
|
@@ -281,9 +277,10 @@ For the full set of public names see the API documentation linked above.
|
|
|
281
277
|
|
|
282
278
|
## Test summary
|
|
283
279
|
|
|
284
|
-
- Test result:
|
|
280
|
+
- Test result: 2450 passed, 9 deselected in 39s
|
|
285
281
|
- No flake8 warnings.
|
|
286
282
|
- No mypy errors found.
|
|
283
|
+
- No pylint warnings.
|
|
287
284
|
- No python layout warnings.
|
|
288
|
-
- Built version(s): 0.
|
|
285
|
+
- Built version(s): 0.6
|
|
289
286
|
- Build and test using Python 3.14.6
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: backlogops
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6
|
|
4
4
|
Summary: Library with backlog operations.
|
|
5
5
|
Author: Tom Björkholm
|
|
6
6
|
Author-email: Tom Björkholm <klausuler_linnet0q@icloud.com>
|
|
@@ -14,7 +14,6 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.14
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
|
-
Classifier: Development Status :: 4 - Beta
|
|
18
17
|
Classifier: Intended Audience :: Developers
|
|
19
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
19
|
Classifier: Topic :: Office/Business :: Scheduling
|
|
@@ -23,10 +22,11 @@ Requires-Python: >=3.12
|
|
|
23
22
|
Description-Content-Type: text/markdown
|
|
24
23
|
License-File: LICENSE.txt
|
|
25
24
|
Requires-Dist: config-as-json>=1.4
|
|
26
|
-
Requires-Dist: cryptography>=
|
|
25
|
+
Requires-Dist: cryptography>=50.0.0
|
|
27
26
|
Requires-Dist: jira[cli,opt]>=3.10.5
|
|
28
27
|
Requires-Dist: tableio>=1.1
|
|
29
|
-
Requires-Dist: tableio-cfg-json>=
|
|
28
|
+
Requires-Dist: tableio-cfg-json>=1.1
|
|
29
|
+
Requires-Dist: wizard-ui-bridge[textual]>=1.1
|
|
30
30
|
Requires-Dist: versionreporter>=0.4
|
|
31
31
|
Dynamic: author
|
|
32
32
|
Dynamic: license-file
|
|
@@ -51,15 +51,11 @@ There are 3 related packages for backlog operations:
|
|
|
51
51
|
|
|
52
52
|
## Project status
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
place, so the main work from here is on the user experience rather than on new
|
|
56
|
-
core functionality. The three packages share a version number and are released
|
|
57
|
-
together.
|
|
54
|
+
The three packages share a version number and are released together.
|
|
58
55
|
|
|
59
56
|
The library is not meant to ever be "finished": using it for real planning keeps
|
|
60
57
|
surfacing new ideas. Some of those will be added to backlogops itself, and
|
|
61
|
-
others will fit better in separate packages built on top of backlogops.
|
|
62
|
-
adopters are very welcome, and their feedback shapes what comes next.
|
|
58
|
+
others will fit better in separate packages built on top of backlogops.
|
|
63
59
|
|
|
64
60
|
## Available functionality
|
|
65
61
|
|
|
@@ -316,9 +312,10 @@ For the full set of public names see the API documentation linked above.
|
|
|
316
312
|
|
|
317
313
|
## Test summary
|
|
318
314
|
|
|
319
|
-
- Test result:
|
|
315
|
+
- Test result: 2450 passed, 9 deselected in 39s
|
|
320
316
|
- No flake8 warnings.
|
|
321
317
|
- No mypy errors found.
|
|
318
|
+
- No pylint warnings.
|
|
322
319
|
- No python layout warnings.
|
|
323
|
-
- Built version(s): 0.
|
|
320
|
+
- Built version(s): 0.6
|
|
324
321
|
- Build and test using Python 3.14.6
|
|
@@ -21,7 +21,6 @@ classifiers = [
|
|
|
21
21
|
"Programming Language :: Python :: 3.13",
|
|
22
22
|
"Programming Language :: Python :: 3.14",
|
|
23
23
|
"Operating System :: OS Independent",
|
|
24
|
-
"Development Status :: 4 - Beta",
|
|
25
24
|
"Intended Audience :: Developers",
|
|
26
25
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
26
|
"Topic :: Office/Business :: Scheduling",
|
|
@@ -5,7 +5,7 @@ from setuptools import setup
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name='backlogops',
|
|
8
|
-
version='0.
|
|
8
|
+
version='0.6',
|
|
9
9
|
description='Library with backlog operations.',
|
|
10
10
|
author='Tom Björkholm',
|
|
11
11
|
author_email='klausuler_linnet0q@icloud.com',
|
|
@@ -15,10 +15,11 @@ setup(
|
|
|
15
15
|
package_data={'backlogops': ['src/py.typed']},
|
|
16
16
|
install_requires=[
|
|
17
17
|
'config-as-json >= 1.4',
|
|
18
|
-
'cryptography >=
|
|
18
|
+
'cryptography >= 50.0.0',
|
|
19
19
|
'jira[cli,opt] >= 3.10.5',
|
|
20
20
|
'tableio >= 1.1',
|
|
21
|
-
'tableio-cfg-json >=
|
|
21
|
+
'tableio-cfg-json >= 1.1',
|
|
22
|
+
'wizard-ui-bridge[textual] >= 1.1',
|
|
22
23
|
'versionreporter >= 0.4'
|
|
23
24
|
]
|
|
24
25
|
)
|
|
@@ -6,9 +6,9 @@ The public helpers :func:`available_teams_wizard` and
|
|
|
6
6
|
persons and their personal work-hour exceptions, the teams with their
|
|
7
7
|
members, and, for the full configuration, the named TableIO presets, the
|
|
8
8
|
backlog item levels, the status-name map, the GUI display and the Jira
|
|
9
|
-
integration. They drive any ``WizardUiBridge``
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
integration. They drive any ``WizardUiBridge`` of ``wizard_ui_bridge``,
|
|
10
|
+
so the same wizard logic runs on a console text interface, a Textual
|
|
11
|
+
full-screen interface or a graphical user interface.
|
|
12
12
|
|
|
13
13
|
Each repeated part is asked by first requesting a count and then collecting
|
|
14
14
|
exactly that many items, so there are no open-ended "add another?" prompts.
|
|
@@ -22,7 +22,7 @@ in :mod:`backlogops.io_preset_wizard`.
|
|
|
22
22
|
|
|
23
23
|
from functools import partial
|
|
24
24
|
from typing import Optional
|
|
25
|
-
from
|
|
25
|
+
from wizard_ui_bridge import WizardAbort, WizardUiBridge
|
|
26
26
|
from backlogops.available_teams import AvailableTeams
|
|
27
27
|
from backlogops.backlog_ops_config import BacklogOpsConfig, \
|
|
28
28
|
DEF_STATUS_INPUT_MAP
|
|
@@ -31,7 +31,8 @@ from backlogops.levels import DEFAULT_LEVELS, Level
|
|
|
31
31
|
from backlogops.person import Person
|
|
32
32
|
from backlogops.table_rows import RELEASE_FIELDS
|
|
33
33
|
from backlogops.team import FteException, Membership, Team
|
|
34
|
-
from backlogops.work_hours import CompanyWorkHours,
|
|
34
|
+
from backlogops.work_hours import CompanyWorkHours, DEFAULT_WORK_WEEK, \
|
|
35
|
+
ExceptionWorkHours, ScheduleWorkHours, WeekDay
|
|
35
36
|
from backlogops.wizard_helpers import _backlog_map_fields
|
|
36
37
|
from backlogops.wizard_navigator import _Navigator, _ask_level_display
|
|
37
38
|
from backlogops.wizard_forms import FormField, FormResult, choice_field, \
|
|
@@ -220,29 +221,145 @@ def _levels_or_none(levels: list[Level]) -> Optional[list[Level]]:
|
|
|
220
221
|
return levels
|
|
221
222
|
|
|
222
223
|
|
|
224
|
+
_COMPANY_QUESTION = (
|
|
225
|
+
'Configure the company work hours.\n'
|
|
226
|
+
'Set the work hours for each week day, then, if there are any, the '
|
|
227
|
+
'first holiday, closure or special-work period and how many more '
|
|
228
|
+
'there are.')
|
|
229
|
+
"""Instruction shown above the combined company work-hours form."""
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
_MAX_EXCEPTIONS = 60
|
|
233
|
+
"""Upper bound on the number of extra company periods the wizard adds."""
|
|
234
|
+
|
|
235
|
+
|
|
223
236
|
def _build_company(nav: _Navigator,
|
|
224
237
|
default: Optional[AvailableTeams]) -> CompanyWorkHours:
|
|
225
238
|
"""Ask for the company weekly schedule and holiday periods.
|
|
226
239
|
|
|
227
|
-
The weekly
|
|
228
|
-
|
|
229
|
-
|
|
240
|
+
The weekly work hours and the first holiday, closure or special-work
|
|
241
|
+
period are asked together on one form; any further periods each get
|
|
242
|
+
their own form whose question lists the periods entered so far.
|
|
230
243
|
"""
|
|
231
244
|
company = default.company_work_hours if default else None
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
245
|
+
values = nav.ask_form(_COMPANY_QUESTION, _company_fields(), _company_rule,
|
|
246
|
+
seed=_company_seed(company))
|
|
247
|
+
stored = company.exceptions if company else []
|
|
248
|
+
return CompanyWorkHours(work_hours=_schedule_from(values),
|
|
249
|
+
exceptions=_build_exceptions(nav, values, stored))
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _schedule_fields() -> list[FormField]:
|
|
253
|
+
"""Return one work-hours field per week day, Monday first.
|
|
254
|
+
|
|
255
|
+
A day has between zero and twenty-four work hours, so the float field
|
|
256
|
+
enforces that inclusive range.
|
|
257
|
+
"""
|
|
258
|
+
return [number_field(day.name.lower(), day.name.capitalize(),
|
|
259
|
+
default=DEFAULT_WORK_WEEK[day], minimum=0.0,
|
|
260
|
+
maximum=24.0)
|
|
261
|
+
for day in WeekDay]
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _company_fields() -> list[FormField]:
|
|
265
|
+
"""Return the combined company schedule and first-period fields."""
|
|
266
|
+
periods_q = 'Any holiday, closure or special-work periods?'
|
|
267
|
+
more_q = 'Number of additional such periods'
|
|
268
|
+
return (_schedule_fields()
|
|
269
|
+
+ [yes_no_field('has_periods', periods_q, False)]
|
|
270
|
+
+ _exception_fields()
|
|
271
|
+
+ [int_field('more', more_q, default=0, minimum=0,
|
|
272
|
+
maximum=_MAX_EXCEPTIONS)])
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _company_rule(values: FormResult) -> tuple[Optional[str], set[str]]:
|
|
276
|
+
"""Disable the period fields with no periods, else order-check them."""
|
|
277
|
+
if not values.flag('has_periods'):
|
|
278
|
+
return None, {'start', 'end', 'hours', 'new_days', 'more'}
|
|
279
|
+
return _period_rule(values)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _company_seed(company: Optional[CompanyWorkHours]) -> Optional[FormResult]:
|
|
283
|
+
"""Return the combined company form values from stored work hours."""
|
|
284
|
+
if company is None:
|
|
285
|
+
return None
|
|
286
|
+
values: dict[str, object] = {
|
|
287
|
+
day.name.lower(): company.work_hours.get(day, DEFAULT_WORK_WEEK[day])
|
|
288
|
+
for day in WeekDay}
|
|
289
|
+
periods = company.exceptions
|
|
290
|
+
values['has_periods'] = bool(periods)
|
|
291
|
+
values['more'] = max(len(periods) - 1, 0)
|
|
292
|
+
if periods:
|
|
293
|
+
values.update(_period_values(periods[0]))
|
|
294
|
+
return FormResult(values)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _schedule_from(values: FormResult) -> ScheduleWorkHours:
|
|
298
|
+
"""Return the weekly schedule read from the combined company form."""
|
|
299
|
+
return {day: values.number(day.name.lower()) for day in WeekDay}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _build_exceptions(nav: _Navigator, values: FormResult,
|
|
303
|
+
stored: list[ExceptionWorkHours]
|
|
304
|
+
) -> list[ExceptionWorkHours]:
|
|
305
|
+
"""Return the company periods, the first from the combined form."""
|
|
306
|
+
if not values.flag('has_periods'):
|
|
307
|
+
return []
|
|
308
|
+
first = _period_from(values)
|
|
309
|
+
rest = stored[1:] if len(stored) > 1 else []
|
|
310
|
+
return [first] + _more_exceptions(nav, values.whole('more'), first, rest)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _more_exceptions(nav: _Navigator, count: int, first: ExceptionWorkHours,
|
|
314
|
+
seeds: list[ExceptionWorkHours]
|
|
315
|
+
) -> list[ExceptionWorkHours]:
|
|
316
|
+
"""Collect the additional company periods after the first one.
|
|
317
|
+
|
|
318
|
+
Each period is asked on its own form whose question lists the periods
|
|
319
|
+
entered so far. The running list is reset on the first item, so a
|
|
320
|
+
replayed sub-level after a back or cancel request does not double it.
|
|
321
|
+
"""
|
|
322
|
+
shown = [first]
|
|
323
|
+
|
|
324
|
+
def build(item: Optional[tuple[int, Optional[ExceptionWorkHours]]]
|
|
325
|
+
) -> ExceptionWorkHours:
|
|
326
|
+
"""Ask one additional period, shown after the earlier ones."""
|
|
327
|
+
assert item is not None
|
|
328
|
+
index, seed = item
|
|
329
|
+
if index == 0:
|
|
330
|
+
del shown[1:]
|
|
331
|
+
values = nav.ask_form(_more_question(shown), _exception_fields(),
|
|
332
|
+
_period_rule, seed=_exc_seed(seed))
|
|
333
|
+
shown.append(_period_from(values))
|
|
334
|
+
return shown[-1]
|
|
335
|
+
indexed: list[tuple[int, Optional[ExceptionWorkHours]]] = [
|
|
336
|
+
(k, seeds[k] if k < len(seeds) else None) for k in range(count)]
|
|
337
|
+
return nav.counted(count, indexed, build)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _more_question(shown: list[ExceptionWorkHours]) -> str:
|
|
341
|
+
"""Return the next-period question listing the periods entered so far."""
|
|
342
|
+
lines = ['Configure the next holiday, closure or special-work period.',
|
|
343
|
+
'Periods entered so far:']
|
|
344
|
+
return '\n'.join(lines + [_period_line(period) for period in shown])
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _period_line(period: ExceptionWorkHours) -> str:
|
|
348
|
+
"""Return a one-line summary of one exception period."""
|
|
349
|
+
extra = ' (adds free-day work)' if period.new_work_days else ''
|
|
350
|
+
return (f' {period.start_date} to {period.end_date}: '
|
|
351
|
+
f'{period.hours_per_day:g} h/day{extra}')
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _period_values(period: ExceptionWorkHours) -> dict[str, object]:
|
|
355
|
+
"""Return the form values for one work-hour exception period."""
|
|
356
|
+
return {'start': period.start_date, 'end': period.end_date,
|
|
357
|
+
'hours': period.hours_per_day, 'new_days': period.new_work_days}
|
|
237
358
|
|
|
238
359
|
|
|
239
360
|
def _exc_seed(exc: Optional[ExceptionWorkHours]) -> Optional[FormResult]:
|
|
240
361
|
"""Return the work-hour exception form values from an exception."""
|
|
241
|
-
if exc is None
|
|
242
|
-
return None
|
|
243
|
-
return FormResult({'start': exc.start_date, 'end': exc.end_date,
|
|
244
|
-
'hours': exc.hours_per_day,
|
|
245
|
-
'new_days': exc.new_work_days})
|
|
362
|
+
return None if exc is None else FormResult(_period_values(exc))
|
|
246
363
|
|
|
247
364
|
|
|
248
365
|
def _period_rule(values: FormResult) -> tuple[Optional[str], set[str]]:
|
|
@@ -265,16 +382,21 @@ def _exception_fields() -> list[FormField]:
|
|
|
265
382
|
'normally free?', False)]
|
|
266
383
|
|
|
267
384
|
|
|
385
|
+
def _period_from(values: FormResult) -> ExceptionWorkHours:
|
|
386
|
+
"""Return one exception period from a form's period fields."""
|
|
387
|
+
return ExceptionWorkHours(start_date=values.day('start'),
|
|
388
|
+
end_date=values.day('end'),
|
|
389
|
+
hours_per_day=values.number('hours'),
|
|
390
|
+
new_work_days=values.flag('new_days'))
|
|
391
|
+
|
|
392
|
+
|
|
268
393
|
def _ask_exception(nav: _Navigator, seed: Optional[ExceptionWorkHours] = None
|
|
269
394
|
) -> ExceptionWorkHours:
|
|
270
395
|
"""Ask for one work-hour exception period on a single form."""
|
|
271
396
|
values = nav.ask_form('Configure the work-hour exception period.',
|
|
272
397
|
_exception_fields(), _period_rule,
|
|
273
398
|
seed=_exc_seed(seed))
|
|
274
|
-
return
|
|
275
|
-
end_date=values.day('end'),
|
|
276
|
-
hours_per_day=values.number('hours'),
|
|
277
|
-
new_work_days=values.flag('new_days'))
|
|
399
|
+
return _period_from(values)
|
|
278
400
|
|
|
279
401
|
|
|
280
402
|
def _build_persons(nav: _Navigator,
|
|
@@ -16,8 +16,8 @@ class BloVersionReporter(VersionReporter):
|
|
|
16
16
|
@override
|
|
17
17
|
def package_names(self) -> list[str]:
|
|
18
18
|
"""Return the package names that this package reports."""
|
|
19
|
-
ret = ['backlogops', 'tableio', 'tableio-cfg-json', '
|
|
20
|
-
'mformat']
|
|
19
|
+
ret = ['backlogops', 'tableio', 'tableio-cfg-json', 'wizard-ui-bridge',
|
|
20
|
+
'config-as-json', 'mformat']
|
|
21
21
|
ret += super().package_names()
|
|
22
22
|
return ret
|
|
23
23
|
|
|
@@ -19,7 +19,7 @@ wizard, where each preset additionally has a name.
|
|
|
19
19
|
from functools import partial
|
|
20
20
|
from typing import Optional
|
|
21
21
|
from tableio import FileAccess
|
|
22
|
-
from
|
|
22
|
+
from wizard_ui_bridge import WizardAbort, WizardUiBridge
|
|
23
23
|
from backlogops.io_config import InputFormatConfig, OutputFormatConfig, \
|
|
24
24
|
_FormatConfig, make_input_config, make_output_config
|
|
25
25
|
from backlogops.table_rows import RELEASE_FIELDS
|
|
@@ -25,7 +25,7 @@ token is visible while typed; the pass phrases are masked.
|
|
|
25
25
|
from dataclasses import dataclass
|
|
26
26
|
from functools import partial
|
|
27
27
|
from typing import Callable, Optional, TypeVar
|
|
28
|
-
from
|
|
28
|
+
from wizard_ui_bridge import PrefillValueType, WizardPathKind
|
|
29
29
|
from backlogops.jira_io_config import DEF_BACKLOG_COLUMN_MAP, \
|
|
30
30
|
DEF_RELEASE_COLUMN_MAP, JiraColumnMap, JiraConnectConfig, JiraIOConfig, \
|
|
31
31
|
JiraIssueTypeMap, JiraPreset, JiraType, TokenStorage, _FILE_MODES, \
|
|
@@ -364,8 +364,9 @@ _ISSUE_MAP_Q = ('Use a level-to-issue-type map when writing (otherwise the '
|
|
|
364
364
|
|
|
365
365
|
_FILTER_EG = default_jira_filter('<project key>')
|
|
366
366
|
"""Illustrative blank-filter default shown in the filter help text."""
|
|
367
|
-
_FILTER_HELP = ('Leave blank to select the
|
|
368
|
-
f'
|
|
367
|
+
_FILTER_HELP = ('Leave blank to select the whole project in rank order; '
|
|
368
|
+
f'the default {_FILTER_EG} is offered once the project key '
|
|
369
|
+
'is set.')
|
|
369
370
|
"""Help shown for the blank-defaulting issue filter field."""
|
|
370
371
|
|
|
371
372
|
|
|
@@ -417,6 +418,40 @@ def _preset_rule(has_issue_type: bool
|
|
|
417
418
|
return rule
|
|
418
419
|
|
|
419
420
|
|
|
421
|
+
# pylint: disable-next=too-few-public-methods
|
|
422
|
+
class _FilterPrefill:
|
|
423
|
+
"""Offer the default rank filter while the preset filter stays blank.
|
|
424
|
+
|
|
425
|
+
While the user has not written their own issue filter, every change to
|
|
426
|
+
the project key re-derives the default filter and offers it to the
|
|
427
|
+
filter field, so the filter tracks the project as it is typed. The
|
|
428
|
+
moment the filter holds text the user placed there, tracking stops and
|
|
429
|
+
the filter is left alone. The last value offered is remembered so the
|
|
430
|
+
bridge writing that value back does not look like the user typing it.
|
|
431
|
+
"""
|
|
432
|
+
|
|
433
|
+
def __init__(self) -> None:
|
|
434
|
+
"""Start with nothing offered and the filter not yet user-owned."""
|
|
435
|
+
self._taken_over = False
|
|
436
|
+
self._last_fill: Optional[str] = None
|
|
437
|
+
|
|
438
|
+
def __call__(self, values: FormResult,
|
|
439
|
+
changed: str) -> list[tuple[str, PrefillValueType]]:
|
|
440
|
+
"""Return a filter prefill for a project change, or nothing."""
|
|
441
|
+
self._note_user_filter(values)
|
|
442
|
+
project = values.opt_text('def_project')
|
|
443
|
+
if self._taken_over or changed != 'def_project' or not project:
|
|
444
|
+
return []
|
|
445
|
+
self._last_fill = default_jira_filter(project)
|
|
446
|
+
return [('def_filter', self._last_fill)]
|
|
447
|
+
|
|
448
|
+
def _note_user_filter(self, values: FormResult) -> None:
|
|
449
|
+
"""Mark the filter user-owned once it holds text we did not offer."""
|
|
450
|
+
current = values.opt_text('def_filter')
|
|
451
|
+
if current and current != self._last_fill:
|
|
452
|
+
self._taken_over = True
|
|
453
|
+
|
|
454
|
+
|
|
420
455
|
def _preset_seed(name: Optional[str], preset: Optional[JiraPreset],
|
|
421
456
|
choices: _PresetChoices) -> Optional[FormResult]:
|
|
422
457
|
"""Return the preset form values from a stored preset."""
|
|
@@ -444,6 +479,7 @@ def _ask_preset(nav: _Navigator, choices: _PresetChoices, used: set[str],
|
|
|
444
479
|
values = nav.ask_form('Configure the Jira preset.',
|
|
445
480
|
_preset_fields(used, choices),
|
|
446
481
|
_preset_rule(bool(choices.issue_type_maps)),
|
|
482
|
+
prefill=_FilterPrefill(),
|
|
447
483
|
seed=_preset_seed(seed_name, seed_preset, choices))
|
|
448
484
|
return _preset_from(nav, values, choices)
|
|
449
485
|
|