mirror of
https://github.com/Sea-Haven-Industries/afterhours-shift-manager.git
synced 2026-10-07 08:08:59 +00:00
Refine night-row labeling and drop notifications
Weekday rows are night-only, so the moon glyph alone marks the after-hours shift; the verbose time-range label is kept only on weekend rows where day and night shifts coexist. Channel shift-change notifications now label the shift on every day for parity. Thread the caller's user_id through the regular-drop path instead of re-reading it off the employee record, and document the user-facing changes. Refs: #134
This commit is contained in:
parent
55ab07b864
commit
edfc6c0bb0
5 changed files with 87 additions and 20 deletions
17
CHANGELOG.md
17
CHANGELOG.md
|
|
@ -10,6 +10,23 @@ fine and still supported.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## v1.12.0 — June 26, 2026
|
||||||
|
|
||||||
|
**Clearer shift drops and an easier-to-read schedule.** Two small quality-of-life
|
||||||
|
improvements:
|
||||||
|
|
||||||
|
- **`/oncall drop` can now say which shift to let go.** If you hold more than one
|
||||||
|
shift on the same day — say a holiday day slot *and* that night's after-hours
|
||||||
|
shift, or a weekend day *and* night shift — dropping no longer guesses for you.
|
||||||
|
The bot lists what you hold and asks you to add `day`, `night`, or `holiday`
|
||||||
|
(for example `/oncall drop saturday night`), the same way picking one up
|
||||||
|
already works. Asking to drop a shift you don't actually hold is turned down
|
||||||
|
instead of quietly dropping the wrong one.
|
||||||
|
- **Night shifts stand out on the two-week post.** Day and night rows now carry
|
||||||
|
their own icons — a sun for the daytime shift and a moon for the after-hours
|
||||||
|
(night) shift — so the on-call row is easy to spot at a glance. Weekend rows,
|
||||||
|
which have both a day and a night shift, still spell out the full time range.
|
||||||
|
|
||||||
## v1.11.1 — June 17, 2026
|
## v1.11.1 — June 17, 2026
|
||||||
|
|
||||||
**Fix holiday call routing to 3CX.** The holiday router was calling the wrong 3CX
|
**Fix holiday call routing to 3CX.** The holiday router was calling the wrong 3CX
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,9 @@ SHIFT_LABELS = {
|
||||||
"night": "Night (5pm–8am)",
|
"night": "Night (5pm–8am)",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Per-shift glyphs so day vs. night rows are distinguishable at a glance — even
|
# Per-shift glyphs so day vs. night rows are distinguishable at a glance. On
|
||||||
# on weekdays, where only the night (after-hours) shift exists.
|
# weekdays only the night (after-hours) shift exists, so the moon glyph alone
|
||||||
|
# differentiates the row; the verbose time-range label is reserved for weekends.
|
||||||
SHIFT_GLYPHS = {
|
SHIFT_GLYPHS = {
|
||||||
"day": ":sunny:",
|
"day": ":sunny:",
|
||||||
"night": ":crescent_moon:",
|
"night": ":crescent_moon:",
|
||||||
|
|
@ -93,10 +94,15 @@ def _format_shift_line(
|
||||||
source: str,
|
source: str,
|
||||||
is_today: bool,
|
is_today: bool,
|
||||||
shift_type: str = "night",
|
shift_type: str = "night",
|
||||||
|
with_label: bool = True,
|
||||||
) -> str:
|
) -> str:
|
||||||
day_label = date.strftime("%a %b %-d")
|
day_label = date.strftime("%a %b %-d")
|
||||||
if shift_type in SHIFT_LABELS:
|
glyph = SHIFT_GLYPHS.get(shift_type)
|
||||||
day_label += f" {SHIFT_GLYPHS[shift_type]} {SHIFT_LABELS[shift_type]}"
|
if glyph:
|
||||||
|
if with_label and shift_type in SHIFT_LABELS:
|
||||||
|
day_label += f" {glyph} {SHIFT_LABELS[shift_type]}"
|
||||||
|
else:
|
||||||
|
day_label += f" {glyph}"
|
||||||
if is_today:
|
if is_today:
|
||||||
day_label = f"*{day_label} (today)*"
|
day_label = f"*{day_label} (today)*"
|
||||||
|
|
||||||
|
|
@ -182,9 +188,21 @@ def build_week_schedule(schedule, start_date: datetime | None = None) -> list[di
|
||||||
if source == "available" and not is_past:
|
if source == "available" and not is_past:
|
||||||
open_shifts.append((date_str, "day"))
|
open_shifts.append((date_str, "day"))
|
||||||
|
|
||||||
# After-hours (night) shift
|
# After-hours (night) shift. Weekdays are night-only, so the moon glyph
|
||||||
|
# carries the meaning; only weekends (where both shifts coexist) repeat
|
||||||
|
# the full time-range label.
|
||||||
ext, name, source = schedule.resolve_shift(date_str, day_name)
|
ext, name, source = schedule.resolve_shift(date_str, day_name)
|
||||||
lines.append(_format_shift_line(date, ext, name, source, is_today))
|
lines.append(
|
||||||
|
_format_shift_line(
|
||||||
|
date,
|
||||||
|
ext,
|
||||||
|
name,
|
||||||
|
source,
|
||||||
|
is_today,
|
||||||
|
"night",
|
||||||
|
with_label=day_name in WEEKEND_DAYS,
|
||||||
|
)
|
||||||
|
)
|
||||||
if source == "available" and not is_past:
|
if source == "available" and not is_past:
|
||||||
open_shifts.append((date_str, "night"))
|
open_shifts.append((date_str, "night"))
|
||||||
|
|
||||||
|
|
@ -252,11 +270,10 @@ def build_shift_change_message(
|
||||||
"""Build a channel notification for a shift change."""
|
"""Build a channel notification for a shift change."""
|
||||||
dt = datetime.strptime(date_str, "%Y-%m-%d")
|
dt = datetime.strptime(date_str, "%Y-%m-%d")
|
||||||
day_label = dt.strftime("%A, %b %-d")
|
day_label = dt.strftime("%A, %b %-d")
|
||||||
type_label = (
|
# Label the shift on every day, not just weekends: a single-line notification
|
||||||
f" ({SHIFT_LABELS.get(shift_type, shift_type)})"
|
# has room to spell out the after-hours (night) shift the static post conveys
|
||||||
if dt.strftime("%A") in WEEKEND_DAYS
|
# with the moon glyph.
|
||||||
else ""
|
type_label = f" ({SHIFT_LABELS.get(shift_type, shift_type)})"
|
||||||
)
|
|
||||||
|
|
||||||
if action == "picked_up":
|
if action == "picked_up":
|
||||||
text = f":white_check_mark: <@{user_id}> picked up the *{day_label}*{type_label} shift (Ext {ext})"
|
text = f":white_check_mark: <@{user_id}> picked up the *{day_label}*{type_label} shift (Ext {ext})"
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,23 @@ fine and still supported.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## v1.12.0 — June 26, 2026
|
||||||
|
|
||||||
|
**Clearer shift drops and an easier-to-read schedule.** Two small quality-of-life
|
||||||
|
improvements:
|
||||||
|
|
||||||
|
- **`/oncall drop` can now say which shift to let go.** If you hold more than one
|
||||||
|
shift on the same day — say a holiday day slot *and* that night's after-hours
|
||||||
|
shift, or a weekend day *and* night shift — dropping no longer guesses for you.
|
||||||
|
The bot lists what you hold and asks you to add `day`, `night`, or `holiday`
|
||||||
|
(for example `/oncall drop saturday night`), the same way picking one up
|
||||||
|
already works. Asking to drop a shift you don't actually hold is turned down
|
||||||
|
instead of quietly dropping the wrong one.
|
||||||
|
- **Night shifts stand out on the two-week post.** Day and night rows now carry
|
||||||
|
their own icons — a sun for the daytime shift and a moon for the after-hours
|
||||||
|
(night) shift — so the on-call row is easy to spot at a glance. Weekend rows,
|
||||||
|
which have both a day and a night shift, still spell out the full time range.
|
||||||
|
|
||||||
## v1.11.1 — June 17, 2026
|
## v1.11.1 — June 17, 2026
|
||||||
|
|
||||||
**Fix holiday call routing to 3CX.** The holiday router was calling the wrong 3CX
|
**Fix holiday call routing to 3CX.** The holiday router was calling the wrong 3CX
|
||||||
|
|
|
||||||
|
|
@ -1038,7 +1038,7 @@ def _handle_drop(
|
||||||
_drop_regular(
|
_drop_regular(
|
||||||
respond,
|
respond,
|
||||||
schedule,
|
schedule,
|
||||||
employee,
|
user_id,
|
||||||
date,
|
date,
|
||||||
date_str,
|
date_str,
|
||||||
day_name,
|
day_name,
|
||||||
|
|
@ -1052,7 +1052,7 @@ def _handle_drop(
|
||||||
def _drop_regular(
|
def _drop_regular(
|
||||||
respond,
|
respond,
|
||||||
schedule,
|
schedule,
|
||||||
employee,
|
user_id,
|
||||||
date,
|
date,
|
||||||
date_str,
|
date_str,
|
||||||
day_name,
|
day_name,
|
||||||
|
|
@ -1088,7 +1088,7 @@ def _drop_regular(
|
||||||
)
|
)
|
||||||
|
|
||||||
blocks = build_shift_change_message(
|
blocks = build_shift_change_message(
|
||||||
employee.get("slack_user_id", ""),
|
user_id,
|
||||||
date_str,
|
date_str,
|
||||||
"dropped",
|
"dropped",
|
||||||
ext,
|
ext,
|
||||||
|
|
|
||||||
|
|
@ -52,12 +52,28 @@ class TestBuildWeekSchedule:
|
||||||
assert "Alice (Ext 114)" in blocks[1]["text"]["text"]
|
assert "Alice (Ext 114)" in blocks[1]["text"]["text"]
|
||||||
|
|
||||||
@freeze_time("2026-06-01 12:00:00")
|
@freeze_time("2026-06-01 12:00:00")
|
||||||
def test_weekday_night_row_shows_night_glyph_and_label(self, schedule, seed):
|
def test_weekday_night_row_shows_glyph_without_time_label(self, schedule, seed):
|
||||||
# A weekday (after-hours) night row is now unmistakable: moon glyph + label.
|
# Weekdays are night-only, so the moon glyph alone differentiates the row;
|
||||||
|
# the verbose time-range label is reserved for weekends where both shifts
|
||||||
|
# coexist (and would otherwise repeat on ~10 weekday rows).
|
||||||
seed.weekly("Wednesday", "114", "Alice")
|
seed.weekly("Wednesday", "114", "Alice")
|
||||||
text = build_week_schedule(schedule)[1]["text"]["text"]
|
text = build_week_schedule(schedule)[1]["text"]["text"]
|
||||||
assert ":crescent_moon:" in text
|
wednesday_line = next(
|
||||||
assert "Night (5pm" in text
|
line for line in text.splitlines() if "Alice (Ext 114)" in line
|
||||||
|
)
|
||||||
|
assert ":crescent_moon:" in wednesday_line
|
||||||
|
assert "Night (5pm" not in wednesday_line
|
||||||
|
|
||||||
|
@freeze_time("2026-06-01 12:00:00")
|
||||||
|
def test_weekend_night_row_keeps_glyph_and_time_label(self, schedule, seed):
|
||||||
|
# Weekends carry both shifts, so the night row keeps the full label.
|
||||||
|
seed.weekly("Saturday", "200", "Alice")
|
||||||
|
text = build_week_schedule(schedule)[1]["text"]["text"]
|
||||||
|
saturday_line = next(
|
||||||
|
line for line in text.splitlines() if "Alice (Ext 200)" in line
|
||||||
|
)
|
||||||
|
assert ":crescent_moon:" in saturday_line
|
||||||
|
assert "Night (5pm" in saturday_line
|
||||||
|
|
||||||
@freeze_time("2026-06-01 12:00:00")
|
@freeze_time("2026-06-01 12:00:00")
|
||||||
def test_weekend_day_row_shows_day_glyph_and_label(self, schedule, seed):
|
def test_weekend_day_row_shows_day_glyph_and_label(self, schedule, seed):
|
||||||
|
|
@ -123,8 +139,8 @@ class TestBuildShiftChangeMessage:
|
||||||
)
|
)
|
||||||
text = blocks[0]["text"]["text"]
|
text = blocks[0]["text"]["text"]
|
||||||
assert "<@U1>" in text and "picked up" in text and "Ext 114" in text
|
assert "<@U1>" in text and "picked up" in text and "Ext 114" in text
|
||||||
# Weekday → no (Day/Night) label
|
# Weekday is night-only → the notification still labels the after-hours shift
|
||||||
assert "(Night" not in text
|
assert "Night (5pm" in text
|
||||||
|
|
||||||
def test_dropped_shows_available(self):
|
def test_dropped_shows_available(self):
|
||||||
blocks = build_shift_change_message(
|
blocks = build_shift_change_message(
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue