Some checks failed
Main Confidence / confidence (push) Failing after 49s
## Summary - unify provider-backed action starts behind the shared provider dispatch gate and shared start-result presenter - align tenant, onboarding, provider-connection, restore, directory, and monitoring surfaces with the same blocked, deduped, scope-busy, and accepted semantics - include the spec kit artifacts for spec 216 and the regression fixes that brought the full suite back to green ## Validation - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/RestoreRunIdempotencyTest.php tests/Feature/ExecuteRestoreRunJobTest.php tests/Feature/Restore/RestoreRunProviderStartTest.php tests/Feature/Hardening/ExecuteRestoreRunJobGateTest.php tests/Feature/Hardening/BlockedWriteAuditLogTest.php tests/Feature/Onboarding/OnboardingDraftLifecycleTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Browser/Spec177InventoryCoverageTruthSmokeTest.php` - `cd apps/platform && ./vendor/bin/sail artisan test --compact` ## Notes - branch: `216-provider-dispatch-gate` - commit: `34230be7` Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #255
211 lines
7.0 KiB
PHP
211 lines
7.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Filament\Widgets\Tenant;
|
|
|
|
use App\Filament\Resources\TenantResource\Pages\ViewTenant;
|
|
use App\Filament\Support\VerificationReportChangeIndicator;
|
|
use App\Filament\Support\VerificationReportViewer;
|
|
use App\Models\OperationRun;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use App\Services\Tenants\TenantOperabilityService;
|
|
use App\Services\Verification\StartVerification;
|
|
use App\Support\Auth\Capabilities;
|
|
use App\Support\Auth\UiTooltips;
|
|
use App\Support\OperationRunLinks;
|
|
use App\Support\OperationRunStatus;
|
|
use App\Support\OpsUx\OperationUxPresenter;
|
|
use App\Support\OpsUx\ProviderOperationStartResultPresenter;
|
|
use App\Support\OpsUx\OpsUxBrowserEvents;
|
|
use Filament\Actions\Action;
|
|
use Filament\Facades\Filament;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Widgets\Widget;
|
|
|
|
class TenantVerificationReport extends Widget
|
|
{
|
|
protected static bool $isLazy = false;
|
|
|
|
protected string $view = 'filament.widgets.tenant.tenant-verification-report';
|
|
|
|
public ?Tenant $record = null;
|
|
|
|
private function resolveTenant(): ?Tenant
|
|
{
|
|
$tenant = Filament::getTenant();
|
|
|
|
if ($tenant instanceof Tenant) {
|
|
return $tenant;
|
|
}
|
|
|
|
return $this->record instanceof Tenant ? $this->record : null;
|
|
}
|
|
|
|
public function startVerification(StartVerification $verification): void
|
|
{
|
|
$user = auth()->user();
|
|
|
|
if (! $user instanceof User) {
|
|
abort(403);
|
|
}
|
|
|
|
$tenant = $this->resolveTenant();
|
|
|
|
if (! $tenant instanceof Tenant) {
|
|
abort(404);
|
|
}
|
|
|
|
if (! $user->canAccessTenant($tenant)) {
|
|
abort(404);
|
|
}
|
|
|
|
$result = $verification->providerConnectionCheckForTenant(
|
|
tenant: $tenant,
|
|
initiator: $user,
|
|
extraContext: [
|
|
'surface' => [
|
|
'kind' => 'tenant_verification_widget',
|
|
],
|
|
],
|
|
);
|
|
|
|
$runUrl = OperationRunLinks::tenantlessView($result->run);
|
|
$notification = app(ProviderOperationStartResultPresenter::class)->notification(
|
|
result: $result,
|
|
blockedTitle: 'Verification blocked',
|
|
runUrl: $runUrl,
|
|
);
|
|
|
|
if ($result->status === 'scope_busy') {
|
|
OpsUxBrowserEvents::dispatchRunEnqueued($this);
|
|
|
|
$notification->send();
|
|
|
|
return;
|
|
}
|
|
|
|
if ($result->status === 'deduped') {
|
|
OpsUxBrowserEvents::dispatchRunEnqueued($this);
|
|
|
|
$notification->send();
|
|
|
|
return;
|
|
}
|
|
|
|
if ($result->status === 'blocked') {
|
|
$notification->send();
|
|
|
|
return;
|
|
}
|
|
|
|
OpsUxBrowserEvents::dispatchRunEnqueued($this);
|
|
|
|
$notification->send();
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
protected function getViewData(): array
|
|
{
|
|
$tenant = $this->resolveTenant();
|
|
|
|
if (! $tenant instanceof Tenant) {
|
|
return [
|
|
'tenant' => null,
|
|
'run' => null,
|
|
'runData' => null,
|
|
'runUrl' => null,
|
|
'report' => null,
|
|
'isInProgress' => false,
|
|
'canStart' => false,
|
|
'startTooltip' => null,
|
|
'rerunHint' => null,
|
|
];
|
|
}
|
|
|
|
$run = OperationRun::query()
|
|
->where('tenant_id', (int) $tenant->getKey())
|
|
->where('type', 'provider.connection.check')
|
|
->orderByDesc('id')
|
|
->first();
|
|
|
|
$report = $run instanceof OperationRun
|
|
? VerificationReportViewer::report($run)
|
|
: null;
|
|
$changeIndicator = $run instanceof OperationRun
|
|
? VerificationReportChangeIndicator::forRun($run)
|
|
: null;
|
|
$previousRunUrl = is_array($changeIndicator) && is_numeric($changeIndicator['previous_report_id'] ?? null)
|
|
? OperationRunLinks::tenantlessView((int) $changeIndicator['previous_report_id'])
|
|
: null;
|
|
|
|
$isInProgress = $run instanceof OperationRun
|
|
&& (string) $run->status !== OperationRunStatus::Completed->value;
|
|
|
|
$user = auth()->user();
|
|
$isTenantMember = $user instanceof User && $user->canAccessTenant($tenant);
|
|
$canOperate = app(TenantOperabilityService::class)->decisionFor($tenant)->canOperate;
|
|
$canStart = $isTenantMember
|
|
&& $canOperate
|
|
&& $user->can(Capabilities::PROVIDER_RUN, $tenant);
|
|
|
|
$lifecycleNotice = $isTenantMember && ! $canOperate
|
|
? 'Verification can be started from tenant management only while the tenant is active. Consent and connection configuration remain separate from this stored verification report.'
|
|
: null;
|
|
|
|
$runData = null;
|
|
|
|
if ($run instanceof OperationRun) {
|
|
$context = is_array($run->context ?? null) ? $run->context : [];
|
|
$targetScope = $context['target_scope'] ?? [];
|
|
$targetScope = is_array($targetScope) ? $targetScope : [];
|
|
|
|
$runData = [
|
|
'id' => (int) $run->getKey(),
|
|
'type' => (string) $run->type,
|
|
'status' => (string) $run->status,
|
|
'outcome' => (string) $run->outcome,
|
|
'initiator_name' => (string) $run->initiator_name,
|
|
'started_at' => $run->started_at?->toJSON(),
|
|
'completed_at' => $run->completed_at?->toJSON(),
|
|
'target_scope' => $targetScope,
|
|
'failures' => is_array($run->failure_summary ?? null) ? $run->failure_summary : [],
|
|
];
|
|
}
|
|
|
|
return [
|
|
'tenant' => $tenant,
|
|
'run' => $run,
|
|
'runData' => $runData,
|
|
'runUrl' => $run instanceof OperationRun ? OperationRunLinks::tenantlessView($run) : null,
|
|
'report' => $report,
|
|
'surface' => $run instanceof OperationRun
|
|
? VerificationReportViewer::surface($run, [], [
|
|
'hostKind' => 'tenant_widget',
|
|
'changeIndicator' => $changeIndicator,
|
|
'previousRunUrl' => $previousRunUrl,
|
|
'hostVariation' => [
|
|
'ownsNoRunState' => true,
|
|
'ownsActiveState' => true,
|
|
'supportsAssist' => false,
|
|
'supportsAcknowledge' => false,
|
|
'supportsTechnicalDetailsTrigger' => false,
|
|
],
|
|
])
|
|
: [],
|
|
'redactionNotes' => VerificationReportViewer::redactionNotes($report),
|
|
'isInProgress' => $isInProgress,
|
|
'showStartAction' => ! ($run instanceof OperationRun) && $isTenantMember && $canOperate,
|
|
'canStart' => $canStart,
|
|
'startTooltip' => $isTenantMember && $canOperate && ! $canStart ? UiTooltips::insufficientPermission() : null,
|
|
'lifecycleNotice' => $lifecycleNotice,
|
|
'rerunHint' => $run instanceof OperationRun && $canStart
|
|
? ViewTenant::verificationHeaderActionHint()
|
|
: null,
|
|
];
|
|
}
|
|
}
|