mirror of
https://github.com/Sea-Haven-Industries/afterhours-shift-manager.git
synced 2026-09-30 07:53:11 +00:00
* Add Slack admin modals + App Home admin section Replace the two most error-prone positional admin commands with Block Kit modals (override and holiday-add) opened from a new App Home admin section, while keeping the typed subcommands as a fallback. Validation and side effects are factored into shared helpers so the modal and command paths can't drift, and every action/view handler re-checks is_admin against get_admin_users() so a modal opened from Home can't bypass authorization. Adds Schedule.list_overrides for the upcoming-overrides overview. Refs: #136 * Update changelog date to July 02, 2026 * Add point-and-click admin actions in Slack for easier overrides and holidays * [#136] Add admin UI evaluation spike doc (#140) Co-authored-by: seahaven-openswe[bot] <296972425+seahaven-openswe[bot]@users.noreply.github.com> Co-authored-by: amoussa1229 <166072409+amoussa1229@users.noreply.github.com>
200 lines
9.9 KiB
Markdown
200 lines
9.9 KiB
Markdown
# Evaluation: admin web UI vs. clarifying/expanding in-Slack admin commands
|
|
|
|
_Spike for issue #136 — decide how to make administration easier: a separate
|
|
**admin web UI**, or **clarifying/expanding the in-Slack admin commands** (Block
|
|
Kit modals + an App Home admin section). This document records the options, a
|
|
recommendation, and the scope of the follow-up build. It is a "decide and
|
|
recommend" spike — no admin-surface code ships in this PR. The route is left for
|
|
sign-off (see "Decision needed" at the end)._
|
|
|
|
## Current admin surface
|
|
|
|
Every admin action is a positional-text subcommand of `/oncall admin …`,
|
|
authorized by `is_admin = user_id in schedule.get_admin_users()`
|
|
(`src/shared/shared/schedule.py`, `get_admin_users`) and handled in
|
|
`_handle_admin()` / `_handle_admin_holiday()` (`src/slack-bot/app.py`):
|
|
|
|
- `admin override <date> <ext> [day|night]` — assign a shift
|
|
- `admin open <date> [day|night]` — mark a shift open
|
|
- `admin clear <date> [day|night]` — remove an override (revert to weekly)
|
|
- `admin roster add|remove|rename <ext> [name]`
|
|
- `admin holiday add <date> <slots> [x<mult>] <label>` / `holiday remove <date>` /
|
|
`holiday list`
|
|
|
|
A same-day `override` / `open` / `clear` also repoints 3CX (`_update_3cx_routing`),
|
|
and `holiday add` provisions the 08:00/17:00 one-off schedules and may activate
|
|
inline — so these commands have real side effects, which makes a confirmation
|
|
preview valuable.
|
|
|
|
### Pain points (from the issue, confirmed in code)
|
|
|
|
- **Positional and error-prone.** Order-sensitive args — most acutely
|
|
`holiday add <date> <slots> [x<mult>] <label>`, which hand-parses an optional
|
|
`x<mult>` token out of the middle of the label (`_admin_holiday_add`). On any
|
|
mistake the only feedback is a usage string.
|
|
- **Discoverability.** The single source of documentation is the static admin
|
|
block in `build_help_blocks(is_admin=True)` (`src/shared/shared/blocks.py`).
|
|
No pickers, no validation hints, no previews.
|
|
- **No structured input.** No date picker, no roster dropdown, no
|
|
confirmation/preview before a change lands (and, same-day, repoints 3CX).
|
|
- **No overview.** No calendar/grid of upcoming overrides + holidays to scan.
|
|
|
|
### What already exists (lowers the cost of the Slack-native path)
|
|
|
|
- **App Home tab is live.** `home_tab_enabled: true` in
|
|
`slack-app-manifest.yaml`; `build_home_view()` renders it and
|
|
`app_home_opened` re-publishes it (`src/slack-bot/app.py`). It currently shows
|
|
only the non-admin help — there is a natural place to add an admin section.
|
|
- **Interactivity is wired.** `settings.interactivity.is_enabled: true`, and the
|
|
bot already registers `@app.action(...)` handlers (pickup/swap buttons). Adding
|
|
`@app.action(...)` for admin buttons and `@app.view(...)` for modal submits is
|
|
the same mechanism — **no new hosting, scope, or reinstall** is required for
|
|
modals (`views.open`/`view_submission` ride the existing interactivity URL).
|
|
- **All mutations already have typed methods** on the `Schedule` class
|
|
(`set_override`, `mark_open`, `remove_override`, `add_roster_entry`,
|
|
`remove_roster_entry`, `rename_roster_entry`, `create_holiday`,
|
|
`remove_holiday`). Modals and a web API would both call the same methods, so
|
|
the authorization and side-effect logic does not get duplicated.
|
|
|
|
## Options considered
|
|
|
|
### (a) Expand/clarify inside Slack — **recommended (phase 1)**
|
|
|
|
Add Block Kit **modals** (`views.open`) for the error-prone, high-value actions,
|
|
an **admin section in App Home**, and keep the text subcommands as a fallback.
|
|
|
|
- **Pros:** reuses existing interactivity + App Home; **no new infra, auth,
|
|
hosting, or OAuth scope**; native date pickers / dropdowns / numeric inputs
|
|
remove the positional-arg footguns; a confirmation preview before same-day
|
|
3CX repoints; one deploy target; the modal handlers call the same `Schedule`
|
|
methods the text commands do, so authorization stays in one place.
|
|
- **Cons:** still lives in Slack; modal layout is constrained (no true
|
|
calendar/grid — an upcoming list is the practical "overview"); a small amount
|
|
of new view-state plumbing.
|
|
|
|
### (b) Full admin web UI
|
|
|
|
A separate hosted app (following the internal-portal pattern: Google OAuth,
|
|
CDK-hosted, SHOC design system) talking to the schedule DynamoDB table through a
|
|
new API.
|
|
|
|
- **Pros:** full calendar/grid, bulk edits, the richest UX; not constrained by
|
|
Block Kit.
|
|
- **Cons:** significant effort and ongoing cost — a new auth path (Google OAuth),
|
|
hosting (CDK), an API surface in front of DynamoDB, a **second deploy target**,
|
|
and a security / cross-family review. It also **forks the authorization model**:
|
|
`get_admin_users()` holds Slack user IDs, so a Google-OAuth UI needs an
|
|
identity mapping (Google account → Slack admin) to keep one source of truth.
|
|
Likely overkill unless admin volume is high.
|
|
|
|
### (c) Status quo — keep text-only commands
|
|
|
|
- **Pros:** zero work.
|
|
- **Cons:** every pain point above persists; the `holiday add` arg order keeps
|
|
biting.
|
|
|
|
## Recommendation: phased — (a) now, (b) only if admin volume grows
|
|
|
|
Adopt **(a)** as phase 1 and treat **(b)** as a deferred phase 2 gated on real
|
|
demand. Rationale:
|
|
|
|
- **Admin actions are low-frequency and well-bounded.** Overrides, opens, clears,
|
|
roster edits, and a handful of holidays a year are individually quick. The cost
|
|
today is *correctness/discoverability per action* (getting the positional args
|
|
right), not throughput — which is exactly what modals fix, and which a web UI
|
|
fixes at far greater cost.
|
|
- **(a) reuses everything; (b) builds a new stack.** The Slack-native path needs
|
|
no new hosting, scope, auth, or review and lands a large UX win for the effort
|
|
of a few modals plus an App Home section. (b) is a new auth + hosting + API +
|
|
review surface for a marginal gain at current volume, and it splits the
|
|
authorization model.
|
|
- **(a) does not block (b).** If admin volume later justifies a calendar/grid or
|
|
bulk edits, the `Schedule` methods and `get_admin_users()` authorization are
|
|
already the clean seam a web API would sit on. Phase 1 is not throwaway.
|
|
|
|
Either way, **`get_admin_users()` stays the single authorization source of
|
|
truth**, and the **text commands stay as a fallback** so no current workflow
|
|
breaks.
|
|
|
|
## Scope of phase 1 (option a) — for the follow-up build
|
|
|
|
The phase-1 build is intentionally *not* in this PR. Scoped here so the
|
|
follow-up issue can be sized:
|
|
|
|
### Modals to build (`views.open` + `view_submission`)
|
|
|
|
Start with the two highest-pain actions, then optionally extend:
|
|
|
|
1. **Override modal** — replaces/supplements `admin override`.
|
|
- `datepicker` for the date, a roster **dropdown** (`static_select` built from
|
|
`Schedule.get_roster()` so the extension/name can't be mistyped), and a
|
|
day/night `radio_buttons`/`static_select`.
|
|
- A confirmation line in the submit response when the date is today (it will
|
|
repoint 3CX).
|
|
2. **Holiday-add modal** — replaces/supplements `admin holiday add` (the worst
|
|
positional offender).
|
|
- `datepicker` (date), `number_input`/plain-text **slots**, an explicit
|
|
**multiplier** input (its own field instead of a parsed `x<mult>` token),
|
|
and a **label** input.
|
|
- `view_submission` validates (date not in the past, slots ≥ 1) and surfaces
|
|
errors as Block Kit field `errors` instead of a usage string.
|
|
|
|
Optional follow-ons once the pattern is proven: **open**, **clear**, and
|
|
**roster add/remove/rename** modals. These are lower-pain (fewer args) so they
|
|
can stay text-only initially.
|
|
|
|
### Handlers / wiring
|
|
|
|
- A new `@app.action(...)` per admin button to call `client.views_open(...)` with
|
|
the modal view (built by new `build_*_modal()` helpers in
|
|
`src/shared/shared/blocks.py`, mirroring the existing `build_*` block builders).
|
|
- A new `@app.view(...)` per modal `callback_id` to read `view.state.values`,
|
|
**re-check `is_admin` against `get_admin_users()`** (modals can be opened from
|
|
Home — never trust the surface), call the same `Schedule` method the text
|
|
command uses, run the same 3CX/announce side effects, and `ack()` (with field
|
|
`errors` on validation failure).
|
|
- Reuse `_refresh_schedule_post(...)` after a successful mutation, exactly as the
|
|
text handlers do.
|
|
|
|
### App Home admin section
|
|
|
|
- In `build_home_view()`, when the viewer is an admin (look them up via
|
|
`get_admin_users()` before publishing), append an **Admin** section:
|
|
buttons that open the modals above, plus a compact **upcoming
|
|
overrides + holidays** list (the practical substitute for a calendar/grid).
|
|
- `publish_home(...)` already runs on `app_home_opened`; it needs the viewer's
|
|
user id (it has `event["user"]`) to decide whether to include the admin
|
|
section.
|
|
|
|
### Help / validation
|
|
|
|
- Keep `build_help_blocks(is_admin=True)` as the text fallback reference, and
|
|
point it at the new modals ("or open the Admin tab in Home").
|
|
|
|
### Manifest / scope impact
|
|
|
|
- **None.** Modals and App Home interactivity use the **already-enabled**
|
|
`interactivity` request URL and the existing `app_home` config. No new OAuth
|
|
scope and **no reinstall** are required (unlike the `pins:write` change in the
|
|
sibling #135 work).
|
|
|
|
## Scope of phase 2 (option b) — only if later justified
|
|
|
|
Recorded so the deferral is a decision, not an omission:
|
|
|
|
- **Auth:** Google OAuth following the internal-portal pattern, plus a Google →
|
|
Slack-admin identity mapping so `get_admin_users()` remains the source of
|
|
truth (do **not** introduce a parallel admin list).
|
|
- **Hosting:** CDK-hosted app on the SHOC design system, a second deploy target
|
|
alongside the SAM stack.
|
|
- **Data path:** a thin API in front of the schedule DynamoDB table that calls
|
|
the same `Schedule` methods (no direct table writes from the browser).
|
|
- **Reviews:** security / cross-family review for the new public surface and
|
|
auth path.
|
|
|
|
## Decision needed
|
|
|
|
Recommendation is **(a) now, (b) later if volume grows**. The build route — ship
|
|
phase 1 (Slack-native modals + App Home admin), commit to the full phased path,
|
|
or jump straight to a web UI — is left for sign-off on the issue/PR before any
|
|
admin-surface code is written.
|