9.9 KiB
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 shiftadmin open <date> [day|night]— mark a shift openadmin 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 optionalx<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: trueinslack-app-manifest.yaml;build_home_view()renders it andapp_home_openedre-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_submissionride the existing interactivity URL). - All mutations already have typed methods on the
Scheduleclass (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
Schedulemethods 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 addarg 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
Schedulemethods andget_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:
- Override modal — replaces/supplements
admin override.datepickerfor the date, a roster dropdown (static_selectbuilt fromSchedule.get_roster()so the extension/name can't be mistyped), and a day/nightradio_buttons/static_select.- A confirmation line in the submit response when the date is today (it will repoint 3CX).
- 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 parsedx<mult>token), and a label input.view_submissionvalidates (date not in the past, slots ≥ 1) and surfaces errors as Block Kit fielderrorsinstead 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 callclient.views_open(...)with the modal view (built by newbuild_*_modal()helpers insrc/shared/shared/blocks.py, mirroring the existingbuild_*block builders). - A new
@app.view(...)per modalcallback_idto readview.state.values, re-checkis_adminagainstget_admin_users()(modals can be opened from Home — never trust the surface), call the sameSchedulemethod the text command uses, run the same 3CX/announce side effects, andack()(with fielderrorson 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 viaget_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 onapp_home_opened; it needs the viewer's user id (it hasevent["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
interactivityrequest URL and the existingapp_homeconfig. No new OAuth scope and no reinstall are required (unlike thepins:writechange 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
Schedulemethods (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.