Skip to content

Commit 6a25581

Browse files
author
CI
committed
Move the off option to the top of the manual override dropdowns
1 parent 6e73d4e commit 6a25581

3 files changed

Lines changed: 81 additions & 23 deletions

File tree

‎apps/predbat/tests/test_manual_select.py‎

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,77 @@
99
# pylint: disable=attribute-defined-outside-init
1010

1111

12+
def first_future_option(my_predbat, item):
13+
"""
14+
Pick the first selectable time option from a manual dropdown, preferring a non-today day label
15+
16+
A today label can parse as a past slot at day boundaries, so one is only used as a fallback.
17+
"""
18+
today_label = my_predbat.now_utc.strftime("%a")
19+
fallback = None
20+
for option in item.get("options", []):
21+
if not option or option == "off" or option.startswith("+") or option.startswith("["):
22+
continue
23+
if not option.startswith(today_label + " "):
24+
return option
25+
if fallback is None:
26+
fallback = option
27+
return fallback
28+
29+
30+
def test_off_option_first(my_predbat):
31+
"""
32+
Test that off is the first option in each manual override dropdown (#5105)
33+
34+
The dropdowns are up to 48 hours long, so off at the end of the list means scrolling
35+
through every slot just to cancel the overrides.
36+
"""
37+
failed = 0
38+
print("Test manual dropdown off option ordering")
39+
40+
# One config item per dropdown builder: manual_times(), manual_rates() and api_select_update()
41+
for config_item, select in (("manual_charge", my_predbat.manual_select), ("manual_import_rates", my_predbat.manual_select), ("manual_api", my_predbat.api_select)):
42+
select(config_item, "off")
43+
options = my_predbat.config_index.get(config_item, {}).get("options", [])
44+
if not options:
45+
print(f"ERROR: T9 No options found for {config_item}")
46+
failed = 1
47+
elif options[0] != "off":
48+
print(f"ERROR: T10 Expected off to be the first option of {config_item}, got {options[0]} from {options[:3]}")
49+
failed = 1
50+
elif options.count("off") != 1:
51+
print(f"ERROR: T11 Expected exactly one off option in {config_item}, got {options.count('off')}")
52+
failed = 1
53+
54+
# Off stays first once a slot is selected, and still cancels the selection
55+
charge_item = my_predbat.config_index.get("manual_charge")
56+
future_time = first_future_option(my_predbat, charge_item)
57+
if not future_time:
58+
print("ERROR: T12 No future time options found for manual_charge")
59+
return 1
60+
61+
my_predbat.manual_select("manual_charge", future_time)
62+
options = charge_item.get("options", [])
63+
if options[0] != "off":
64+
print(f"ERROR: T13 Expected off to remain the first option after selecting {future_time}, got {options[0]}")
65+
failed = 1
66+
if "[" + future_time + "]" not in options:
67+
print(f"ERROR: T14 Expected {future_time} to be marked as selected in the options, got {options[:3]}")
68+
failed = 1
69+
70+
my_predbat.manual_select("manual_charge", "off")
71+
charge_value = charge_item.get("value", "")
72+
if charge_value != "off":
73+
print(f"ERROR: T15 Expected off to clear the manual_charge selection, got {charge_value}")
74+
failed = 1
75+
76+
# Clean up
77+
my_predbat.manual_select("manual_import_rates", "off")
78+
my_predbat.api_select("manual_api", "off")
79+
80+
return failed
81+
82+
1283
def run_test_manual_select(my_predbat):
1384
"""
1485
Test manual select dropdowns for force charge, export, demand
@@ -32,19 +103,7 @@ def run_test_manual_select(my_predbat):
32103
return 1
33104

34105
# Find first selectable option, preferring a non-today day label to avoid past-slot parsing at day boundaries
35-
future_time_label = None
36-
today_label = my_predbat.now_utc.strftime("%a")
37-
for option in charge_item["options"]:
38-
if option != "off" and not option.startswith("+") and not option.startswith("[") and not option.startswith(today_label + " "):
39-
future_time_label = option
40-
break
41-
42-
# Fallback to any selectable option if all options are for today
43-
if not future_time_label:
44-
for option in charge_item["options"]:
45-
if option != "off" and not option.startswith("+") and not option.startswith("["):
46-
future_time_label = option
47-
break
106+
future_time_label = first_future_option(my_predbat, charge_item)
48107

49108
if not future_time_label:
50109
print("ERROR: T2 No future time options found")
@@ -102,6 +161,8 @@ def run_test_manual_select(my_predbat):
102161
my_predbat.manual_select("manual_export", "off")
103162
my_predbat.manual_select("manual_demand", "off")
104163

164+
failed |= test_off_option_first(my_predbat)
165+
105166
if failed:
106167
print("Manual select tests FAILED")
107168
else:

‎apps/predbat/userinterface.py‎

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1469,15 +1469,14 @@ def api_select_update(self, config_item, new_value=None):
14691469
if values:
14701470
values = "+" + values
14711471

1472-
# Create the new dropdown
1473-
time_values = []
1472+
# Create the new dropdown, off first so cancelling everything doesn't mean scrolling the whole list (#5105)
1473+
time_values = ["off"]
14741474
for minute_str in time_overrides:
14751475
minute_str = "[" + minute_str + "]"
14761476
time_values.append(minute_str)
14771477

14781478
if values not in time_values:
14791479
time_values.append(values)
1480-
time_values.append("off")
14811480
item["options"] = time_values
14821481
if not values:
14831482
values = "off"
@@ -1565,9 +1564,9 @@ def manual_rates(self, config_item, exclude=None, new_value=None, default_rate=0
15651564
if values:
15661565
values = "+" + values
15671566

1568-
# Create the new dropdown
1567+
# Create the new dropdown, off first so cancelling everything doesn't mean scrolling the whole list (#5105)
15691568
if update:
1570-
time_values = []
1569+
time_values = ["off"]
15711570
for minute in range(minutes_now, minutes_now + manual_rate_max, plan_interval):
15721571
minute_str = (midnight_utc + timedelta(minutes=minute)).strftime("%a %H:%M")
15731572
if minute in rate_overrides_minutes:
@@ -1578,7 +1577,6 @@ def manual_rates(self, config_item, exclude=None, new_value=None, default_rate=0
15781577

15791578
if values not in time_values:
15801579
time_values.append(values)
1581-
time_values.append("off")
15821580
item["options"] = time_values
15831581
if not values:
15841582
values = "off"
@@ -1645,9 +1643,9 @@ def manual_times(self, config_item, exclude=None, new_value=None, update=True):
16451643
if values:
16461644
values = "+" + values
16471645

1648-
# Create the new dropdown
1646+
# Create the new dropdown, off first so cancelling everything doesn't mean scrolling the whole list (#5105)
16491647
if update:
1650-
time_values = []
1648+
time_values = ["off"]
16511649
for minute in range(minutes_now, minutes_now + manual_time_max, plan_interval):
16521650
minute_str = (midnight_utc + timedelta(minutes=minute)).strftime("%a %H:%M")
16531651
if minute in time_overrides:
@@ -1656,7 +1654,6 @@ def manual_times(self, config_item, exclude=None, new_value=None, update=True):
16561654

16571655
if values not in time_values:
16581656
time_values.append(values)
1659-
time_values.append("off")
16601657
item["options"] = time_values
16611658
if not values:
16621659
values = "off"

‎docs/customisation.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,7 @@ Selected slots will be shown in the list in square brackets, and you can cancel
752752

753753
When you use the Manual Control features you can select the day and time from the next 48 hours, the overrides will be removed once their time slot expires (they do not repeat).
754754

755-
The **off** option at the bottom of the list will cancel all selected force charges.
755+
The **off** option at the top of the list will cancel all selected force charges.
756756

757757
![image](images/manual_select.png)
758758

0 commit comments

Comments
 (0)