Tenant Switch implemented Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local> Reviewed-on: #32
90 lines
5.5 KiB
Markdown
90 lines
5.5 KiB
Markdown
# Feature Specification: Tenant Portfolio & Context Switch (031)
|
|
|
|
**Feature Branch**: `feat/031-tenant-portfolio-context-switch`
|
|
**Created**: 2026-01-04
|
|
**Status**: Implemented (ready to merge)
|
|
**Risk**: Medium
|
|
**Priority**: P1
|
|
|
|
## Context
|
|
Today TenantPilot behaves like a single-tenant app:
|
|
- The “current tenant” is global (`tenants.is_current` + `Tenant::current()`), not per user.
|
|
- Most tenant-scoped screens implicitly use `Tenant::current()`.
|
|
|
|
This is limiting and potentially unsafe for:
|
|
- Customers running multiple tenants (PROD/DEV/STAGING).
|
|
- MSPs managing many customer tenants.
|
|
|
|
We need a tenant-agnostic **Portfolio** view plus an explicit, always-visible **Current Tenant** context for all tenant-scoped areas (Policies, Backups, Restore Runs, etc.).
|
|
|
|
## Design Considerations (Best Practice)
|
|
- Prefer an explicit tenant context (route parameter or session) over hidden global state.
|
|
- Avoid storing Current Tenant in the `users` table as the source of truth (cross-tab risk). If persistence is needed, store **“last used”** separately and treat it as a default for new sessions.
|
|
- Keep console/automation behavior stable: `INTUNE_TENANT_ID` can remain a console override, but tenant-scoped UI must not depend on it.
|
|
|
|
## User Scenarios & Testing
|
|
|
|
### User Story 1 — Portfolio overview (P1)
|
|
As a user with access to multiple tenants, I can see a portfolio overview with health/status and key counts.
|
|
|
|
**Acceptance Scenarios**
|
|
1. Tenants list shows only tenants the user can access.
|
|
2. Portfolio shows environment badge (PROD/DEV/STAGING/OTHER) and connection/health indicators.
|
|
3. Portfolio columns can be filtered by environment and connection status.
|
|
|
|
### User Story 2 — Safe tenant context switching (P1)
|
|
As a user, I can switch the Current Tenant via a topbar switcher or by clicking “Open” in the portfolio, and all tenant-scoped screens reflect that tenant.
|
|
|
|
**Acceptance Scenarios**
|
|
1. Switching tenant updates the visible Current Tenant badge and redirects to a default tenant-scoped landing page (e.g. Policies).
|
|
2. Policies, Backups, Restore Runs, and Policy Versions are scoped to the selected tenant.
|
|
3. Restore flows always show the target tenant and environment prominently and require tenant-aware type-to-confirm.
|
|
|
|
### User Story 3 — Multi-tenant bulk actions (P2)
|
|
As an operator, I can select multiple tenants in the portfolio and run safe bulk actions (initially Sync).
|
|
|
|
**Acceptance Scenarios**
|
|
1. Bulk “Sync selected” dispatches a sync job per tenant (batch) and shows progress.
|
|
2. Readonly users cannot trigger bulk sync.
|
|
|
|
### User Story 4 — Authorization hardening (P1)
|
|
As a user, I cannot access tenants or tenant-scoped data I am not authorized for.
|
|
|
|
**Acceptance Scenarios**
|
|
1. Attempting to open a tenant without access is denied (403) and does not change Current Tenant.
|
|
2. Direct URL access to tenant-scoped pages for an unauthorized tenant returns 403/404.
|
|
|
|
## Requirements
|
|
|
|
### Functional Requirements
|
|
- **FR-001**: Introduce a per-user Current Tenant context for all tenant-scoped screens.
|
|
- **FR-002**: Current Tenant context must be always visible in the UI (topbar) to reduce “wrong tenant” operations.
|
|
- **FR-003**: Add an “Open” action from the portfolio to set Current Tenant and redirect into the tenant-scoped area.
|
|
- **FR-004**: Portfolio view is tenant-agnostic and supports filtering, search, and safe bulk actions.
|
|
- **FR-005**: Tenant access is enforced centrally (single `canAccessTenant(...)` gate/policy used by UI + routes + services).
|
|
- **FR-006**: Restore remains single-tenant; restore actions must include explicit tenant/environment confirmations and never rely on hidden global context.
|
|
- **FR-007**: Bulk Sync is tenant-safe: per-tenant authorization, per-tenant job execution, and audit logs for each tenant sync trigger.
|
|
|
|
### UX / UI Requirements
|
|
- **UX-001**: Topbar shows “Tenant: <name>” with an environment badge (PROD/DEV/STAGING/OTHER) and is accessible from all tenant-scoped pages.
|
|
- **UX-002**: Tenant switcher is searchable (typeahead); favorites (if enabled) appear at the top.
|
|
- **UX-003**: Portfolio table includes (at minimum): Name, Tenant ID (short/copy), Environment, Connection/App status, RBAC/Health indicator, Last Sync (time), Policies count; optional Restore runs (last 30d).
|
|
- **UX-004**: Portfolio “Open” action makes the tenant context explicit and navigates into the tenant-scoped area.
|
|
- **UX-005**: Restore screens show “Target Tenant” prominently (name + environment badge) and require tenant-aware type-to-confirm (e.g. `RESTORE PROD`).
|
|
|
|
### Data Model Requirements
|
|
- **DM-001**: Introduce tenant access/membership mapping (user ↔ tenant) with a role (`owner|manager|operator|readonly`).
|
|
- **DM-002**: Add tenant environment classification (`prod|dev|staging|other`) as a first-class attribute (column or indexed JSONB).
|
|
- **DM-003 (Optional)**: Persist per-user tenant preferences (favorites + last used) without coupling it to cross-tab safety.
|
|
- **DM-004 (Optional)**: Support grouping tenants by customer (MSP use case) via a lightweight “customer label” or a dedicated Customer model (future).
|
|
|
|
## Non-Goals
|
|
- No multi-tenant policy detail view in one screen.
|
|
- No multi-tenant restore; restore run always targets exactly one tenant.
|
|
- No cross-tenant diff/promotion (separate feature).
|
|
|
|
## Success Criteria
|
|
- **SC-001**: A user can switch Current Tenant quickly and always understands which tenant they are operating on.
|
|
- **SC-002**: All tenant-scoped data is strictly filtered and authorization-safe.
|
|
- **SC-003**: Bulk Sync works across selected tenants with clear feedback and role gating.
|