From 375a8a5c558e4b99a09c074e01ec39b566bf09b3 Mon Sep 17 00:00:00 2001 From: "Pablo F.G" Date: Thu, 16 Jul 2026 14:26:50 +0200 Subject: [PATCH] fix(ui): always show Scans page so imported scans are visible - Render the scans table regardless of provider connection status - Show no-provider / not-connected onboarding help as a non-blocking hint - Soften "connection required" copy; drop the forced onboarding redirect --- ui/app/(prowler)/scans/page.test.ts | 15 +++- ui/app/(prowler)/scans/page.tsx | 76 ++++++------------- .../display-imported-scans.fixed.md | 1 + .../scans/no-providers-connected.tsx | 8 +- ui/components/scans/scans-page-shell.test.tsx | 60 +++++++++++++++ ui/components/scans/scans-page-shell.tsx | 11 +++ .../scans/scans-providers-empty-state.tsx | 19 +++-- 7 files changed, 120 insertions(+), 70 deletions(-) create mode 100644 ui/changelog.d/display-imported-scans.fixed.md diff --git a/ui/app/(prowler)/scans/page.test.ts b/ui/app/(prowler)/scans/page.test.ts index 08c36aaf46..1f90105920 100644 --- a/ui/app/(prowler)/scans/page.test.ts +++ b/ui/app/(prowler)/scans/page.test.ts @@ -9,11 +9,18 @@ describe("scans page onboarding", () => { const pagePath = path.join(currentDir, "page.tsx"); const source = readFileSync(pagePath, "utf8"); - it("redirects the scan tour replay to add-provider when providers are missing or disconnected", () => { - expect(source).toContain('redirect("/providers?onboarding=add-provider")'); - expect(source).toContain( - 'resolvedSearchParams.onboarding === "view-first-scan"', + it("never blocks or redirects away from Scans when providers are missing or disconnected", () => { + // Imported scans belong to a never-connected provider, so the page must render the + // table regardless of provider connection status instead of gating/redirecting. + expect(source).not.toContain( + 'redirect("/providers?onboarding=add-provider")', ); + expect(source).not.toContain("missingScanPrerequisite"); + }); + + it("always renders the scans table shell", () => { + expect(source).toContain(" { diff --git a/ui/app/(prowler)/scans/page.tsx b/ui/app/(prowler)/scans/page.tsx index f307920e52..5bbfbdf213 100644 --- a/ui/app/(prowler)/scans/page.tsx +++ b/ui/app/(prowler)/scans/page.tsx @@ -1,4 +1,3 @@ -import { redirect } from "next/navigation"; import { Suspense } from "react"; import { getAllProviderGroups } from "@/actions/manage-groups/manage-groups"; @@ -10,7 +9,6 @@ import { } from "@/actions/scans/scans-filters"; import { getSchedules, getSchedulesPage } from "@/actions/schedules"; import { auth } from "@/auth.config"; -import { PageReady } from "@/components/onboarding"; import { appendPendingScheduleRowsToPage, buildScheduledTabRows, @@ -21,7 +19,6 @@ import { pickScheduleProviderFilters, } from "@/components/scans/scans.utils"; import { ScansPageShell } from "@/components/scans/scans-page-shell"; -import { ScansProvidersEmptyState } from "@/components/scans/scans-providers-empty-state"; import { SkeletonTableScans } from "@/components/scans/table"; import { ScanJobsTable } from "@/components/scans/table/scan-jobs-table"; import { ContentLayout } from "@/components/shadcn/content-layout"; @@ -188,36 +185,22 @@ export default async function Scans({ const providers = providersData?.data ?? []; const providerGroups = providerGroupsData?.data ?? []; - const connectedProviders = providers.filter( + const hasConnectedProvider = providers.some( (provider: ProviderProps) => provider.attributes.connection.connected === true, ); - const thereIsNoProviders = providers.length === 0; - const thereIsNoProvidersConnected = - !thereIsNoProviders && connectedProviders.length === 0; - const missingScanPrerequisite = - thereIsNoProviders || thereIsNoProvidersConnected; - - if ( - missingScanPrerequisite && - resolvedSearchParams.onboarding === "view-first-scan" - ) { - redirect("/providers?onboarding=add-provider"); - } const hasManageScansPermission = Boolean( session?.user?.permissions?.manage_scans, ); - const activeScanCount = missingScanPrerequisite - ? 0 - : await getActiveScanCount(resolvedSearchParams); - const onboardingAction = missingScanPrerequisite - ? { + const activeScanCount = await getActiveScanCount(resolvedSearchParams); + const onboardingAction = hasConnectedProvider + ? { flowId: "view-first-scan" } + : { flowId: "view-first-scan", fallbackFlowId: "add-provider", useFallback: true, - } - : { flowId: "view-first-scan" }; + }; return ( - {missingScanPrerequisite ? ( - <> - {/* The populated branch mounts inside ScansPageShell to - enable the navbar tour icon. The empty branch must mark the route - ready too, otherwise the icon (which falls back to the add-provider - flow here) stays hidden for users with no connected provider. */} - - - - ) : ( - - - } - > - + - - - )} + } + > + + + ); } diff --git a/ui/changelog.d/display-imported-scans.fixed.md b/ui/changelog.d/display-imported-scans.fixed.md new file mode 100644 index 0000000000..9b8e01fdb0 --- /dev/null +++ b/ui/changelog.d/display-imported-scans.fixed.md @@ -0,0 +1 @@ +Imported scans now appear on the Scans page even when no provider is connected diff --git a/ui/components/scans/no-providers-connected.tsx b/ui/components/scans/no-providers-connected.tsx index d2cd428987..b2e85d19ab 100644 --- a/ui/components/scans/no-providers-connected.tsx +++ b/ui/components/scans/no-providers-connected.tsx @@ -18,12 +18,8 @@ export const NoProvidersConnected = () => {

- No providers are currently connected. Connecting a provider is - required to launch on-demand scans. -

-

- Once the providers are correctly configured, this message will - disappear, and on-demand scans can be launched. + None of your providers are connected yet. Connect one to launch + on-demand scans — imported scans still appear below.

diff --git a/ui/components/scans/scans-page-shell.test.tsx b/ui/components/scans/scans-page-shell.test.tsx index c142c2e3ac..689fe51846 100644 --- a/ui/components/scans/scans-page-shell.test.tsx +++ b/ui/components/scans/scans-page-shell.test.tsx @@ -144,6 +144,19 @@ const providers: ProviderProps[] = [ }, ]; +const disconnectedProviders: ProviderProps[] = [ + { + ...providers[0], + attributes: { + ...providers[0].attributes, + connection: { + ...providers[0].attributes.connection, + connected: false, + }, + }, + }, +]; + describe("ScansPageShell", () => { afterEach(() => { vi.unstubAllEnvs(); @@ -416,4 +429,51 @@ describe("ScansPageShell", () => { expect(calledUrl).toContain("tab=scheduled"); expect(calledUrl).not.toContain("filter%5Btrigger%5D"); }); + + it("shows a non-blocking hint when no provider is connected, while still rendering the table", () => { + vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false"); + + render( + +
Scans table
+
, + ); + + expect(screen.getByText("No Connected Providers")).toBeInTheDocument(); + // The table (and therefore imported scans) must still render below the hint. + expect(screen.getByText("Scans table")).toBeInTheDocument(); + }); + + it("shows the no-providers hint when there are no providers, while still rendering the table", () => { + vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false"); + + render( + +
Scans table
+
, + ); + + expect(screen.getByText("No Providers Configured")).toBeInTheDocument(); + expect(screen.getByText("Scans table")).toBeInTheDocument(); + }); + + it("does not show the providers hint when a provider is connected", () => { + vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false"); + + render( + +
Scans table
+
, + ); + + expect( + screen.queryByText("No Connected Providers"), + ).not.toBeInTheDocument(); + expect( + screen.queryByText("No Providers Configured"), + ).not.toBeInTheDocument(); + }); }); diff --git a/ui/components/scans/scans-page-shell.tsx b/ui/components/scans/scans-page-shell.tsx index b553d224b5..739a96753f 100644 --- a/ui/components/scans/scans-page-shell.tsx +++ b/ui/components/scans/scans-page-shell.tsx @@ -30,6 +30,7 @@ const viewFirstScanFlow = getFlowById("view-first-scan")!; import { CliImportBanner } from "./cli-import-banner"; import { LaunchScanModal } from "./launch-scan-modal"; import { ScansFilterBar } from "./scans-filter-bar"; +import { ScansProvidersEmptyState } from "./scans-providers-empty-state"; import { useScansFilters } from "./use-scans-filters"; interface ScansPageShellProps { @@ -68,6 +69,10 @@ export function ScansPageShell({ const hasConnectedProviders = providers.some( (provider) => provider.attributes.connection.connected === true, ); + const thereAreNoProviders = providers.length === 0; + // Non-blocking onboarding hint: shown when scans can't be launched (no provider, or + // none connected). The table still renders below, so imported scans stay visible. + const showProvidersHint = thereAreNoProviders || !hasConnectedProviders; const isCloudEnvironment = isCloud(); const launchDisabled = !hasManageScansPermission || !hasConnectedProviders; const launchOpen = isLaunchScanModalOpen || urlLaunchOpen; @@ -113,6 +118,12 @@ export function ScansPageShell({ {/* Signals the navbar that this route's data has loaded (enables the replay icon). */} + {showProvidersHint && ( + + )}
- {thereIsNoProviders ? ( - - ) : ( - - )} - + return thereIsNoProviders ? ( + + ) : ( + ); }