mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-04 02:04:06 +00:00
fix(ui): hide the Registry tab in Add Provider when Registry is unavailable (#12837)
This commit is contained in:
@@ -9,8 +9,10 @@ import type { AddProviderFormValues } from "@/types/formSchemas";
|
||||
import { RadioGroupProvider } from "./radio-group-provider";
|
||||
|
||||
function Selector({
|
||||
registryAvailable = true,
|
||||
registryOptions = [{ type: "acme", label: "Acme Cloud" }],
|
||||
}: {
|
||||
registryAvailable?: boolean;
|
||||
registryOptions?: RegistryProviderOption[];
|
||||
}) {
|
||||
const form = useForm<AddProviderFormValues>();
|
||||
@@ -18,6 +20,7 @@ function Selector({
|
||||
<RadioGroupProvider
|
||||
control={form.control}
|
||||
isInvalid={false}
|
||||
registryAvailable={registryAvailable}
|
||||
registryOptions={registryOptions}
|
||||
/>
|
||||
);
|
||||
@@ -131,4 +134,53 @@ describe("provider selector", () => {
|
||||
screen.getByRole("option", { name: /Amazon Web Services/ }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
it("hides the source tabs when Registry is unavailable in the deployment", () => {
|
||||
// Given: Local (OSS) or Registry-flag-off deployments deny discovery.
|
||||
render(<Selector registryAvailable={false} registryOptions={[]} />);
|
||||
|
||||
// Then: only the built-in providers are offered, without a Registry tab.
|
||||
expect(screen.queryByRole("tablist")).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole("tab", { name: "Registry" }),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("option", { name: /Amazon Web Services/ }),
|
||||
).toBeVisible();
|
||||
expect(
|
||||
screen.queryByText("No Registry providers available."),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("textbox", { name: "Search providers" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
it("falls back to all providers when Registry access is revoked on the Registry tab", async () => {
|
||||
// Given
|
||||
const user = userEvent.setup();
|
||||
const { rerender } = render(<Selector />);
|
||||
await user.click(screen.getByRole("tab", { name: "Registry" }));
|
||||
expect(
|
||||
screen.queryByRole("option", { name: /Amazon Web Services/ }),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
// When: a discovery refresh reports the deployment no longer offers Registry.
|
||||
rerender(<Selector registryAvailable={false} registryOptions={[]} />);
|
||||
|
||||
// Then
|
||||
expect(screen.queryByRole("tablist")).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("option", { name: /Amazon Web Services/ }),
|
||||
).toBeVisible();
|
||||
|
||||
// When / Then: access restored keeps the previous Registry selection.
|
||||
rerender(<Selector />);
|
||||
expect(screen.getByRole("tab", { name: "Registry" })).toHaveAttribute(
|
||||
"aria-selected",
|
||||
"true",
|
||||
);
|
||||
expect(
|
||||
screen.getByRole("option", { name: /Acme Cloud Registry/ }),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,6 +33,12 @@ type ProviderTab = (typeof PROVIDER_TAB)[keyof typeof PROVIDER_TAB];
|
||||
|
||||
interface RadioGroupProviderProps {
|
||||
control: Control<AddProviderFormValues>;
|
||||
/**
|
||||
* Whether this deployment offers Registry providers (Cloud or Private Cloud
|
||||
* with the Registry flag on and a user allowed to manage providers). Off in
|
||||
* Local (OSS) and flag-off deployments, where the source tabs are hidden.
|
||||
*/
|
||||
registryAvailable?: boolean;
|
||||
registryOptions?: RegistryProviderOption[];
|
||||
isInvalid: boolean;
|
||||
errorMessage?: string;
|
||||
@@ -42,10 +48,14 @@ export const RadioGroupProvider: FC<RadioGroupProviderProps> = ({
|
||||
control,
|
||||
isInvalid,
|
||||
errorMessage,
|
||||
registryAvailable = false,
|
||||
registryOptions = [],
|
||||
}) => {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [activeTab, setActiveTab] = useState<ProviderTab>(PROVIDER_TAB.ALL);
|
||||
const [selectedTab, setSelectedTab] = useState<ProviderTab>(PROVIDER_TAB.ALL);
|
||||
// Fall back to the full list if Registry access is revoked while the
|
||||
// Registry tab is selected, so the selector never shows an empty tab.
|
||||
const activeTab = registryAvailable ? selectedTab : PROVIDER_TAB.ALL;
|
||||
|
||||
const options = [
|
||||
...PROVIDERS.map((provider) => ({
|
||||
@@ -82,12 +92,14 @@ export const RadioGroupProvider: FC<RadioGroupProviderProps> = ({
|
||||
<Tabs
|
||||
className="flex flex-col px-4"
|
||||
value={activeTab}
|
||||
onValueChange={(value) => setActiveTab(value as ProviderTab)}
|
||||
onValueChange={(value) => setSelectedTab(value as ProviderTab)}
|
||||
>
|
||||
<TabsList aria-label="Provider source">
|
||||
<TabsTrigger value={PROVIDER_TAB.ALL}>All providers</TabsTrigger>
|
||||
<TabsTrigger value={PROVIDER_TAB.REGISTRY}>Registry</TabsTrigger>
|
||||
</TabsList>
|
||||
{registryAvailable && (
|
||||
<TabsList aria-label="Provider source">
|
||||
<TabsTrigger value={PROVIDER_TAB.ALL}>All providers</TabsTrigger>
|
||||
<TabsTrigger value={PROVIDER_TAB.REGISTRY}>Registry</TabsTrigger>
|
||||
</TabsList>
|
||||
)}
|
||||
<div className="relative z-10 shrink-0 py-4">
|
||||
<SearchInput
|
||||
aria-label="Search providers"
|
||||
|
||||
@@ -212,9 +212,12 @@ describe("provider wizard account creation", () => {
|
||||
screen.getByRole("option", { name: /Amazon Web Services/ }),
|
||||
).toBeVisible();
|
||||
|
||||
// Then: an unanswered discovery cannot vouch for Registry availability.
|
||||
expect(
|
||||
screen.queryByRole("tab", { name: "Registry" }),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
// When
|
||||
await user.click(screen.getByRole("tab", { name: "Registry" }));
|
||||
expect(screen.getByText("No Registry providers available.")).toBeVisible();
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "Retry Registry providers" }),
|
||||
);
|
||||
@@ -223,8 +226,25 @@ describe("provider wizard account creation", () => {
|
||||
expect(
|
||||
await screen.findByRole("option", { name: "Acme Cloud Registry" }),
|
||||
).toBeVisible();
|
||||
expect(screen.getByRole("tab", { name: "Registry" })).toBeVisible();
|
||||
expect(
|
||||
screen.queryByText("Registry providers could not be loaded"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps the Registry tab with a retry when eligible discovery fails", async () => {
|
||||
// Given: Cloud with Registry enabled, but the catalog read failed.
|
||||
getInstalledRegistryProviderOptions.mockResolvedValueOnce({
|
||||
status: "error",
|
||||
});
|
||||
const user = userEvent.setup();
|
||||
render(<ProviderWizardModal open onOpenChange={vi.fn()} />);
|
||||
await screen.findByText("Registry providers could not be loaded");
|
||||
|
||||
// When
|
||||
await user.click(screen.getByRole("tab", { name: "Registry" }));
|
||||
|
||||
// Then
|
||||
expect(screen.getByText("No Registry providers available.")).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -82,3 +82,51 @@ describe("provider account aliases", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Registry provider source tabs", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("hides the Registry tab when discovery denies access (Local or flag off)", async () => {
|
||||
// Given
|
||||
getInstalledRegistryProviderOptions.mockResolvedValue({
|
||||
status: "access_denied",
|
||||
});
|
||||
|
||||
// When
|
||||
render(<ConnectAccountForm onSuccess={vi.fn()} />);
|
||||
await waitFor(() =>
|
||||
expect(getInstalledRegistryProviderOptions).toHaveBeenCalled(),
|
||||
);
|
||||
|
||||
// Then
|
||||
expect(
|
||||
screen.getByRole("option", { name: /Amazon Web Services/ }),
|
||||
).toBeVisible();
|
||||
expect(
|
||||
screen.queryByRole("tab", { name: "Registry" }),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole("tab", { name: "All providers" }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows the Registry tab once discovery confirms the deployment offers it", async () => {
|
||||
// Given: Cloud or Private Cloud with Registry enabled and no artifacts yet.
|
||||
getInstalledRegistryProviderOptions.mockResolvedValue({
|
||||
status: "ready",
|
||||
options: [],
|
||||
});
|
||||
|
||||
// When
|
||||
render(<ConnectAccountForm onSuccess={vi.fn()} />);
|
||||
|
||||
// Then
|
||||
expect(await screen.findByRole("tab", { name: "Registry" })).toBeVisible();
|
||||
expect(screen.getByRole("tab", { name: "All providers" })).toHaveAttribute(
|
||||
"aria-selected",
|
||||
"true",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -218,6 +218,10 @@ export const ConnectAccountForm = ({
|
||||
const [registryOptions, setRegistryOptions] = useState<
|
||||
RegistryProviderOption[]
|
||||
>([]);
|
||||
// Only Cloud and Private Cloud deployments with the Registry flag on answer
|
||||
// discovery with "ready" or "error"; Local (OSS) and flag-off deployments
|
||||
// are denied and never show the Registry tab.
|
||||
const [registryAvailable, setRegistryAvailable] = useState(false);
|
||||
const [registryError, setRegistryError] = useState(false);
|
||||
const [providerError, setProviderError] = useState<string | null>(null);
|
||||
const [discoveryAttempt, setDiscoveryAttempt] = useState(0);
|
||||
@@ -231,10 +235,12 @@ export const ConnectAccountForm = ({
|
||||
const result = await getInstalledRegistryProviderOptions();
|
||||
if (!active) return;
|
||||
setRegistryOptions(result.status === "ready" ? result.options : []);
|
||||
setRegistryAvailable(result.status !== "access_denied");
|
||||
setRegistryError(result.status === "error");
|
||||
} catch {
|
||||
if (active) {
|
||||
setRegistryOptions([]);
|
||||
setRegistryAvailable(false);
|
||||
setRegistryError(true);
|
||||
}
|
||||
}
|
||||
@@ -467,6 +473,7 @@ export const ConnectAccountForm = ({
|
||||
</Alert>
|
||||
)}
|
||||
<RadioGroupProvider
|
||||
registryAvailable={registryAvailable}
|
||||
registryOptions={registryOptions}
|
||||
control={form.control}
|
||||
isInvalid={!!form.formState.errors.providerType}
|
||||
|
||||
@@ -79,6 +79,24 @@ export class RegistryPage extends BasePage {
|
||||
await expect(this.registryLink).toBeHidden();
|
||||
}
|
||||
|
||||
async verifyProviderSelectorWithoutRegistry(): Promise<void> {
|
||||
await this.page.goto("/providers");
|
||||
await this.dismissWelcomeDialog();
|
||||
await this.page.getByRole("button", { name: /Add (a )?Provider/i }).click();
|
||||
await expect(
|
||||
this.page.getByRole("option", {
|
||||
name: "Amazon Web Services",
|
||||
exact: true,
|
||||
}),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
this.page.getByRole("tab", { name: "Registry", exact: true }),
|
||||
).toBeHidden();
|
||||
await expect(
|
||||
this.page.getByRole("tab", { name: "All providers", exact: true }),
|
||||
).toBeHidden();
|
||||
}
|
||||
|
||||
async verifyOnboarding(): Promise<void> {
|
||||
await expect(this.connectButton).toBeVisible();
|
||||
await expect(
|
||||
|
||||
@@ -94,6 +94,7 @@ test.describe.serial("Registry", () => {
|
||||
await registryPage.verifyRegistryNavigationHidden();
|
||||
await registryPage.goto();
|
||||
await registryPage.verifyDirectRouteDenied();
|
||||
await registryPage.verifyProviderSelectorWithoutRegistry();
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user