diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 2cf613fa60..1e7cded33f 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -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) diff --git a/ui/app/(prowler)/scans/page.tsx b/ui/app/(prowler)/scans/page.tsx index 55dbf7eb8a..1b045721ef 100644 --- a/ui/app/(prowler)/scans/page.tsx +++ b/ui/app/(prowler)/scans/page.tsx @@ -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 ( - - - - ); - } - return ( <> - <> - {!hasManageScansPermission ? ( - + {!thereIsNoProviders && ( +
+ - ) : thereIsNoProvidersConnected ? ( - <> - - - ) : ( - - )} - -
- -
- +
+ +
+ }> + +
- }> - - -
+ )} ); diff --git a/ui/components/scans/launch-workflow/launch-scan-workflow-form.tsx b/ui/components/scans/launch-workflow/launch-scan-workflow-form.tsx index b0047445c7..6ee64724ba 100644 --- a/ui/components/scans/launch-workflow/launch-scan-workflow-form.tsx +++ b/ui/components/scans/launch-workflow/launch-scan-workflow-form.tsx @@ -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, diff --git a/ui/components/scans/launch-workflow/select-scan-provider.tsx b/ui/components/scans/launch-workflow/select-scan-provider.tsx index 999c65bea4..9d8be12e3e 100644 --- a/ui/components/scans/launch-workflow/select-scan-provider.tsx +++ b/ui/components/scans/launch-workflow/select-scan-provider.tsx @@ -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 = FieldPath, > { - providers: { - providerId: string; - alias: string; - providerType: string; - uid: string; - connected: boolean; - }[]; + providers: ScanProviderInfo[]; control: Control; name: TName; } diff --git a/ui/components/scans/no-providers-added.tsx b/ui/components/scans/no-providers-added.tsx index b5f7e08766..a108009c2d 100644 --- a/ui/components/scans/no-providers-added.tsx +++ b/ui/components/scans/no-providers-added.tsx @@ -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 ( - <> -
- - -
- -

- No Providers Configured -

-
-
-

- No providers have been configured. Start by setting up a - provider. -

-
+export const NoProvidersAdded = ({ onOpenWizard }: NoProvidersAddedProps) => ( +
+ + +
+ +

+ No Providers Configured +

+
+
+

+ No providers have been configured. Start by setting up a provider. +

+
- -
-
-
- - - ); -}; + +
+
+
+); diff --git a/ui/components/scans/scans-launch-section.test.tsx b/ui/components/scans/scans-launch-section.test.tsx new file mode 100644 index 0000000000..7abee44117 --- /dev/null +++ b/ui/components/scans/scans-launch-section.test.tsx @@ -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 ?
Provider wizard
: null, +})); + +vi.mock("@/components/scans/launch-workflow", () => ({ + LaunchScanWorkflow: () =>
Launch scan workflow
, +})); + +vi.mock("@/components/scans/no-providers-connected", () => ({ + NoProvidersConnected: () =>
No providers connected
, +})); + +vi.mock("@/components/ui/custom/custom-banner", () => ({ + CustomBanner: ({ title }: { title: string }) =>
{title}
, +})); + +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( + , + ); + + // When + await user.click( + screen.getByRole("button", { name: /open add provider modal/i }), + ); + rerender( + , + ); + + // Then + expect(screen.getByRole("dialog")).toHaveTextContent("Provider wizard"); + expect(screen.getByText("Launch scan workflow")).toBeInTheDocument(); + }); +}); diff --git a/ui/components/scans/scans-launch-section.tsx b/ui/components/scans/scans-launch-section.tsx new file mode 100644 index 0000000000..f683155a21 --- /dev/null +++ b/ui/components/scans/scans-launch-section.tsx @@ -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 ? ( + setIsProviderWizardOpen(true)} /> + ) : !hasManageScansPermission ? ( + + ) : thereIsNoProvidersConnected ? ( + + ) : ( + + )} + + + ); +} diff --git a/ui/types/scans.ts b/ui/types/scans.ts index 2cfabf009d..b90689440d 100644 --- a/ui/types/scans.ts +++ b/ui/types/scans.ts @@ -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 {