94 lines
5.3 KiB
Markdown
94 lines
5.3 KiB
Markdown
# Research: Operations Tenantless Canonical Migration
|
|
|
|
**Feature**: 078-operations-tenantless-canonical
|
|
**Date**: 2026-02-06
|
|
|
|
---
|
|
|
|
## R-001 — Filament v5 Infolist Reuse on Standalone Pages
|
|
|
|
**Decision**: Use the native `InteractsWithSchemas` mechanism (already on every `Page`). No need for `InteractsWithInfolists` or `HasInfolists`.
|
|
|
|
**Rationale**: In Filament v5, the schema system is unified — forms, infolists, and tables all go through `InteractsWithSchemas`. The `InteractsWithInfolists` trait is fully deprecated (every method proxies to schema equivalents). `HasInfolists` is an empty marker interface. Every `Filament\Pages\Page` already extends `BasePage` which uses `InteractsWithSchemas` and implements `HasSchemas`.
|
|
|
|
**How it works**:
|
|
1. Define `public function infolist(Schema $schema): Schema` on the Page
|
|
2. `InteractsWithSchemas` auto-discovers it via reflection
|
|
3. Render via `{{ $this->infolist }}` (magic property) or `EmbeddedSchema::make('infolist')`
|
|
|
|
**Alternatives considered**:
|
|
- Adding `InteractsWithInfolists` trait → Rejected: deprecated shim, adds no functionality
|
|
- Extracting a static builder from `OperationRunResource` → Not needed: `infolist()` is already `static`
|
|
|
|
**Source files verified**:
|
|
- `vendor/filament/infolists/src/Concerns/InteractsWithInfolists.php` — all methods `@deprecated`
|
|
- `vendor/filament/infolists/src/Contracts/HasInfolists.php` — empty interface
|
|
- `vendor/filament/support/src/Pages/BasePage.php` — `implements HasSchemas`, uses `InteractsWithSchemas`
|
|
- `vendor/filament/support/src/Concerns/InteractsWithSchemas.php` — auto-discovers methods by name
|
|
|
|
---
|
|
|
|
## R-002 — OperationRunResource::infolist() Compatibility
|
|
|
|
**Decision**: Call `OperationRunResource::infolist($schema)` directly from the standalone Page.
|
|
|
|
**Rationale**: The method is `public static`, uses no `$this` references, and already handles tenantless context gracefully (`Filament::getTenant()` returns null → falls back to `OperationRunLinks::tenantlessView()`).
|
|
|
|
**Requirements for the standalone Page**:
|
|
1. `public function infolist(Schema $schema): Schema` → delegates to `OperationRunResource::infolist($schema)`
|
|
2. `public function defaultInfolist(Schema $schema): Schema` → sets `->record($this->run)->columns(2)`
|
|
3. `public bool $opsUxIsTabHidden = false` property → needed for polling callback in infolist
|
|
4. Override `content()` or render via Blade `{{ $this->infolist }}`
|
|
|
|
**Alternatives considered**:
|
|
- Extracting infolist into a shared trait → Overengineered for one consumer
|
|
- Keeping hand-coded Blade → Defeats the single-source goal
|
|
|
|
---
|
|
|
|
## R-003 — Headless Resource Pattern (Empty getPages)
|
|
|
|
**Decision**: Return `[]` from `OperationRunResource::getPages()` to eliminate all auto-generated routes.
|
|
|
|
**Rationale**: `Resource::routes()` iterates `getPages()` in a `foreach` — empty array means zero route registrations. The resource class is retained as a static utility providing `::table()` and `::infolist()` schema builders.
|
|
|
|
**Impact**: The following route names will no longer exist:
|
|
- `filament.admin.resources.operations.index`
|
|
- `filament.admin.resources.operations.view`
|
|
|
|
**Files that reference these routes** (need updating):
|
|
- `tests/Feature/Verification/VerificationAuthorizationTest.php` (L38, L74) — `OperationRunResource::getUrl('view', ...)`
|
|
- `tests/Feature/OpsUx/FailureSanitizationTest.php` (L58) — `OperationRunResource::getUrl('view', ...)`
|
|
- `tests/Feature/OpsUx/CanonicalViewRunLinksTest.php` (L25) — guard test scanning for stale references (update regex)
|
|
- `tests/Feature/Verification/VerificationDbOnlyTest.php` — `ViewOperationRun` Livewire test
|
|
- `tests/Feature/OpsUx/FailureSanitizationTest.php` — `ViewOperationRun` Livewire test
|
|
- `tests/Feature/Verification/VerificationReportRenderingTest.php` — `ViewOperationRun` Livewire test
|
|
- `tests/Feature/Monitoring/OperationsCanonicalUrlsTest.php` (L121) — `ListOperationRuns` Livewire test
|
|
- `tests/Feature/Monitoring/OperationsTenantScopeTest.php` (L119) — `ListOperationRuns` Livewire test
|
|
|
|
**Alternatives considered**:
|
|
- Keeping a dummy page that redirects → Adds complexity, spec says natural 404
|
|
- Excluding resource from discovery → More fragile than empty `getPages()`
|
|
|
|
---
|
|
|
|
## R-004 — KPI Header Tenantless Behavior
|
|
|
|
**Decision**: Hide `OperationsKpiHeader` when no tenant context is available (Phase 1).
|
|
|
|
**Rationale**: The widget runs 6 queries all scoped by `tenant_id`. Without tenant context, all queries return 0. Showing zeros is misleading. Workspace-scoped queries are a larger refactor deferred to a separate spec.
|
|
|
|
**Implementation**: Check `Filament::getTenant()` in the widget's `getStats()` — return empty array if null. Or conditionally register the widget on the page based on tenant presence.
|
|
|
|
**Source**: `app/Filament/Widgets/Operations/OperationsKpiHeader.php` — 131 lines, 4 stat cards, `$isLazy = false`
|
|
|
|
---
|
|
|
|
## R-005 — Legacy List Redirect (FR-078-012)
|
|
|
|
**Decision**: Add a 302 redirect from `/admin/t/{tenant}/operations` to `/admin/operations`.
|
|
|
|
**Rationale**: Unlike detail URLs which naturally 404 after page decommission, the list URL pattern may still be reached through Filament's navigation system during the transition period. A simple redirect avoids confusion.
|
|
|
|
**Note**: This is the only redirect in the spec. All detail-level legacy URLs naturally 404.
|