fix(ui): preserve scans provider wizard flow

This commit is contained in:
Alan Buscaglia
2026-05-18 13:34:32 +02:00
committed by GitHub
parent 964548a677
commit 21df5c58b1
8 changed files with 186 additions and 100 deletions
+4
View File
@@ -33,6 +33,10 @@ All notable changes to the **Prowler UI** are documented in this file.
- Finding drawer no longer renders literal backticks around inline code in Risk, Description and Remediation sections [(#11142)](https://github.com/prowler-cloud/prowler/pull/11142)
### 🐞 Fixed
- Launch Scan first-provider wizard continues after provider creation instead of resetting the Scans page [(#11136)](https://github.com/prowler-cloud/prowler/pull/11136)
---
## [1.26.1] (Prowler 5.26.1)
+28 -43
View File
@@ -4,16 +4,11 @@ import { getAllProviders } from "@/actions/providers";
import { getScans } from "@/actions/scans";
import { auth } from "@/auth.config";
import { MutedFindingsConfigButton } from "@/components/providers";
import {
NoProvidersAdded,
NoProvidersConnected,
ScansFilters,
} from "@/components/scans";
import { LaunchScanWorkflow } from "@/components/scans/launch-workflow";
import { ScansFilters } from "@/components/scans";
import { ScansLaunchSection } from "@/components/scans/scans-launch-section";
import { SkeletonTableScans } from "@/components/scans/table";
import { ScansTableWithPolling } from "@/components/scans/table/scans";
import { ContentLayout } from "@/components/ui";
import { CustomBanner } from "@/components/ui/custom/custom-banner";
import {
createProviderDetailsMapping,
extractProviderUIDs,
@@ -81,11 +76,15 @@ export default async function Scans({
const thereIsNoProviders =
!providersData?.data || providersData.data.length === 0;
const thereIsNoProvidersConnected = providersData?.data?.every(
(provider: ProviderProps) => !provider.attributes.connection.connected,
const thereIsNoProvidersConnected = Boolean(
providersData?.data?.every(
(provider: ProviderProps) => !provider.attributes.connection.connected,
),
);
const hasManageScansPermission = session?.user?.permissions?.manage_scans;
const hasManageScansPermission = Boolean(
session?.user?.permissions?.manage_scans,
);
// Extract provider UIDs and create provider details mapping for filtering
const providerUIDs = providersData ? extractProviderUIDs(providersData) : [];
@@ -93,44 +92,30 @@ export default async function Scans({
? createProviderDetailsMapping(providerUIDs, providersData)
: [];
if (thereIsNoProviders) {
return (
<ContentLayout title="Scans" icon="lucide:timer">
<NoProvidersAdded />
</ContentLayout>
);
}
return (
<ContentLayout title="Scans" icon="lucide:timer">
<>
<>
{!hasManageScansPermission ? (
<CustomBanner
title={"Access Denied"}
message={"You don't have permission to launch the scan."}
<ScansLaunchSection
providers={providerInfo}
hasManageScansPermission={hasManageScansPermission}
thereIsNoProviders={thereIsNoProviders}
thereIsNoProvidersConnected={thereIsNoProvidersConnected}
/>
{!thereIsNoProviders && (
<div className="flex flex-col gap-6">
<ScansFilters
providerUIDs={providerUIDs}
providerDetails={providerDetails}
completedScans={completedScans}
/>
) : thereIsNoProvidersConnected ? (
<>
<NoProvidersConnected />
</>
) : (
<LaunchScanWorkflow providers={providerInfo} />
)}
</>
<div className="flex flex-col gap-6">
<ScansFilters
providerUIDs={providerUIDs}
providerDetails={providerDetails}
completedScans={completedScans}
/>
<div className="flex items-center justify-end">
<MutedFindingsConfigButton />
<div className="flex items-center justify-end">
<MutedFindingsConfigButton />
</div>
<Suspense fallback={<SkeletonTableScans />}>
<SSRDataTableScans searchParams={resolvedSearchParams} />
</Suspense>
</div>
<Suspense fallback={<SkeletonTableScans />}>
<SSRDataTableScans searchParams={resolvedSearchParams} />
</Suspense>
</div>
)}
</>
</ContentLayout>
);
@@ -11,23 +11,15 @@ import { Button } from "@/components/shadcn";
import { CustomInput } from "@/components/ui/custom";
import { Form } from "@/components/ui/form";
import { toast } from "@/components/ui/toast";
import { onDemandScanFormSchema } from "@/types";
import { onDemandScanFormSchema, ScanProviderInfo } from "@/types";
import { SCAN_LAUNCHED_EVENT } from "../table/scans/scans-table-with-polling";
import { SelectScanProvider } from "./select-scan-provider";
type ProviderInfo = {
providerId: string;
alias: string;
providerType: string;
uid: string;
connected: boolean;
};
export const LaunchScanWorkflow = ({
providers,
}: {
providers: ProviderInfo[];
providers: ScanProviderInfo[];
}) => {
const formSchema = z.object({
...onDemandScanFormSchema().shape,
@@ -11,18 +11,13 @@ import {
} from "@/components/shadcn";
import { EntityInfo } from "@/components/ui/entities";
import { FormControl, FormField, FormMessage } from "@/components/ui/form";
import { ScanProviderInfo } from "@/types";
interface SelectScanProviderProps<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> {
providers: {
providerId: string;
alias: string;
providerType: string;
uid: string;
connected: boolean;
}[];
providers: ScanProviderInfo[];
control: Control<TFieldValues>;
name: TName;
}
+30 -37
View File
@@ -1,45 +1,38 @@
"use client";
import { useState } from "react";
import { ProviderWizardModal } from "@/components/providers/wizard";
import { Button, Card, CardContent } from "@/components/shadcn";
import { InfoIcon } from "../icons/Icons";
export const NoProvidersAdded = () => {
const [open, setOpen] = useState(false);
interface NoProvidersAddedProps {
onOpenWizard: () => void;
}
return (
<>
<div className="flex min-h-screen items-center justify-center">
<Card variant="base" className="mx-auto w-full max-w-3xl">
<CardContent className="flex flex-col items-center gap-4 p-6 text-center sm:p-8">
<div className="flex flex-col items-center gap-4">
<InfoIcon className="h-10 w-10 text-gray-800 dark:text-white" />
<h2 className="text-2xl font-bold text-gray-800 dark:text-white">
No Providers Configured
</h2>
</div>
<div className="flex flex-col items-center gap-3">
<p className="text-md leading-relaxed text-gray-600 dark:text-gray-300">
No providers have been configured. Start by setting up a
provider.
</p>
</div>
export const NoProvidersAdded = ({ onOpenWizard }: NoProvidersAddedProps) => (
<div className="flex min-h-screen items-center justify-center">
<Card variant="base" className="mx-auto w-full max-w-3xl">
<CardContent className="flex flex-col items-center gap-4 p-6 text-center sm:p-8">
<div className="flex flex-col items-center gap-4">
<InfoIcon className="h-10 w-10 text-gray-800 dark:text-white" />
<h2 className="text-2xl font-bold text-gray-800 dark:text-white">
No Providers Configured
</h2>
</div>
<div className="flex flex-col items-center gap-3">
<p className="text-md leading-relaxed text-gray-600 dark:text-gray-300">
No providers have been configured. Start by setting up a provider.
</p>
</div>
<Button
aria-label="Open Add Provider modal"
className="w-full max-w-xs justify-center"
size="lg"
onClick={() => setOpen(true)}
>
Get Started
</Button>
</CardContent>
</Card>
</div>
<ProviderWizardModal open={open} onOpenChange={setOpen} />
</>
);
};
<Button
aria-label="Open Add Provider modal"
className="w-full max-w-xs justify-center"
size="lg"
onClick={onOpenWizard}
>
Get Started
</Button>
</CardContent>
</Card>
</div>
);
@@ -0,0 +1,62 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { ScansLaunchSection } from "./scans-launch-section";
vi.mock("@/components/providers/wizard", () => ({
ProviderWizardModal: ({ open }: { open: boolean }) =>
open ? <div role="dialog">Provider wizard</div> : null,
}));
vi.mock("@/components/scans/launch-workflow", () => ({
LaunchScanWorkflow: () => <div>Launch scan workflow</div>,
}));
vi.mock("@/components/scans/no-providers-connected", () => ({
NoProvidersConnected: () => <div>No providers connected</div>,
}));
vi.mock("@/components/ui/custom/custom-banner", () => ({
CustomBanner: ({ title }: { title: string }) => <div>{title}</div>,
}));
const connectedProvider = {
providerId: "provider-1",
alias: "Production",
providerType: "aws",
uid: "123456789012",
connected: true,
};
describe("ScansLaunchSection", () => {
it("should keep the provider wizard open when providers data refreshes after adding the first provider", async () => {
// Given
const user = userEvent.setup();
const { rerender } = render(
<ScansLaunchSection
providers={[]}
hasManageScansPermission
thereIsNoProviders
thereIsNoProvidersConnected
/>,
);
// When
await user.click(
screen.getByRole("button", { name: /open add provider modal/i }),
);
rerender(
<ScansLaunchSection
providers={[connectedProvider]}
hasManageScansPermission
thereIsNoProviders={false}
thereIsNoProvidersConnected={false}
/>,
);
// Then
expect(screen.getByRole("dialog")).toHaveTextContent("Provider wizard");
expect(screen.getByText("Launch scan workflow")).toBeInTheDocument();
});
});
@@ -0,0 +1,47 @@
"use client";
import { useState } from "react";
import { ProviderWizardModal } from "@/components/providers/wizard";
import { LaunchScanWorkflow } from "@/components/scans/launch-workflow";
import { NoProvidersAdded } from "@/components/scans/no-providers-added";
import { NoProvidersConnected } from "@/components/scans/no-providers-connected";
import { CustomBanner } from "@/components/ui/custom/custom-banner";
import { ScanProviderInfo } from "@/types";
interface ScansLaunchSectionProps {
providers: ScanProviderInfo[];
hasManageScansPermission: boolean;
thereIsNoProviders: boolean;
thereIsNoProvidersConnected: boolean;
}
export function ScansLaunchSection({
providers,
hasManageScansPermission,
thereIsNoProviders,
thereIsNoProvidersConnected,
}: ScansLaunchSectionProps) {
const [isProviderWizardOpen, setIsProviderWizardOpen] = useState(false);
return (
<>
{thereIsNoProviders ? (
<NoProvidersAdded onOpenWizard={() => setIsProviderWizardOpen(true)} />
) : !hasManageScansPermission ? (
<CustomBanner
title={"Access Denied"}
message={"You don't have permission to launch the scan."}
/>
) : thereIsNoProvidersConnected ? (
<NoProvidersConnected />
) : (
<LaunchScanWorkflow providers={providers} />
)}
<ProviderWizardModal
open={isProviderWizardOpen}
onOpenChange={setIsProviderWizardOpen}
/>
</>
);
}
+11 -3
View File
@@ -45,18 +45,26 @@ export interface ScanRelationships {
task: RelationshipWrapper;
}
export interface ScanProviderInfo {
export interface ScanResultProviderInfo {
provider: ProviderType;
uid: string;
alias: string;
}
export interface ScanProviderInfo {
providerId: string;
alias: string;
providerType: string;
uid: string;
connected: boolean;
}
export interface ScanProps {
type: "scans";
id: string;
attributes: ScanAttributes;
relationships: ScanRelationships;
providerInfo?: ScanProviderInfo;
providerInfo?: ScanResultProviderInfo;
}
export interface ScanEntityProviderInfo {
@@ -77,7 +85,7 @@ export interface ScanEntity {
}
export interface ExpandedScanData extends ScanProps {
providerInfo: ScanProviderInfo;
providerInfo: ScanResultProviderInfo;
}
export interface IncludedResource {