From e6f47a9bd4fbb9aa52260d925d51b5de1a00c313 Mon Sep 17 00:00:00 2001 From: "Hugo P.Brito" Date: Fri, 17 Jul 2026 12:48:56 +0100 Subject: [PATCH] fix(ui): address grouped Jira follow-up feedback --- ui/actions/integrations/jira-dispatch.test.ts | 17 +++--- ui/actions/integrations/jira-dispatch.ts | 28 ++++++++- ui/app/(prowler)/findings/page.tsx | 8 +-- ui/components/findings/findings-filters.tsx | 2 + .../findings/send-to-jira-modal.test.tsx | 34 +++++------ ui/components/findings/send-to-jira-modal.tsx | 19 +++--- .../table/column-finding-resources.tsx | 4 +- .../findings/table/data-table-row-actions.tsx | 8 ++- .../table/findings-group-drill-down.tsx | 4 +- .../table/findings-group-table.test.tsx | 6 -- .../findings/table/findings-group-table.tsx | 30 +++++++--- ui/hooks/use-filter-batch.test.ts | 42 +++++++++++++ ui/hooks/use-filter-batch.ts | 59 ++++++++++++++++--- ui/lib/deployment.test.ts | 48 --------------- ui/lib/deployment.ts | 18 +----- 15 files changed, 196 insertions(+), 131 deletions(-) diff --git a/ui/actions/integrations/jira-dispatch.test.ts b/ui/actions/integrations/jira-dispatch.test.ts index 901d472a43..aa54907a5a 100644 --- a/ui/actions/integrations/jira-dispatch.test.ts +++ b/ui/actions/integrations/jira-dispatch.test.ts @@ -86,7 +86,7 @@ describe("sendJiraDispatch", () => { }); }); - it("should fail completed task polling when Jira dispatch has partial failures", async () => { + it("should preserve partial success when Jira dispatch has created and failed issues", async () => { // Given pollTaskUntilSettledMock.mockResolvedValue({ ok: true, @@ -99,8 +99,9 @@ describe("sendJiraDispatch", () => { // Then expect(result).toEqual({ - success: false, - error: + success: true, + message: "2 Jira issues were created or updated successfully.", + warning: "Jira dispatch completed with 1 failed and 2 created/updated issues.", }); }); @@ -118,8 +119,9 @@ describe("sendJiraDispatch", () => { // Then expect(result).toEqual({ - success: false, - error: + success: true, + message: "2 Jira issues were created or updated successfully.", + warning: "Jira dispatch completed with 1 failed and 2 created/updated issues.", }); }); @@ -137,8 +139,9 @@ describe("sendJiraDispatch", () => { // Then expect(result).toEqual({ - success: false, - error: + success: true, + message: "Finding successfully sent to Jira!", + warning: "Jira dispatch completed with 1 failed and 1 created/updated issue.", }); }); diff --git a/ui/actions/integrations/jira-dispatch.ts b/ui/actions/integrations/jira-dispatch.ts index f320c2ec2b..7dc72c4c9e 100644 --- a/ui/actions/integrations/jira-dispatch.ts +++ b/ui/actions/integrations/jira-dispatch.ts @@ -61,6 +61,17 @@ const buildJiraDispatchFailureMessage = ( return `Jira dispatch completed with ${failedCount} failed and ${successCount} created/updated issue${successCount === 1 ? "" : "s"}.`; }; +const buildJiraDispatchSuccessMessage = ( + result: JiraTaskResult | undefined, +) => { + const successCount = getJiraDispatchSuccessCount(result); + if (successCount > 1) { + return `${successCount} Jira issues were created or updated successfully.`; + } + + return "Finding successfully sent to Jira!"; +}; + export const getJiraIssueTypes = async ( integrationId: string, projectKey: string, @@ -221,7 +232,8 @@ export const sendJiraDispatch = async ({ export const pollJiraDispatchTask = async ( taskId: string, ): Promise< - { success: true; message: string } | { success: false; error: string } + | { success: true; message: string; warning?: string } + | { success: false; error: string } > => { const res = await pollTaskUntilSettled(taskId, { maxAttempts: 5, @@ -236,6 +248,15 @@ export const pollJiraDispatchTask = async ( if (state === "completed") { const failedCount = getJiraDispatchFailedCount(jiraResult); if (failedCount > 0) { + const successCount = getJiraDispatchSuccessCount(jiraResult); + if (successCount > 0) { + return { + success: true, + message: buildJiraDispatchSuccessMessage(jiraResult), + warning: buildJiraDispatchFailureMessage(jiraResult, failedCount), + }; + } + return { success: false, error: buildJiraDispatchFailureMessage(jiraResult, failedCount), @@ -257,7 +278,10 @@ export const pollJiraDispatchTask = async ( }; } - return { success: true, message: "Finding successfully sent to Jira!" }; + return { + success: true, + message: buildJiraDispatchSuccessMessage(jiraResult), + }; } if (state === "failed") { diff --git a/ui/app/(prowler)/findings/page.tsx b/ui/app/(prowler)/findings/page.tsx index 39e95ecb2b..4e2595d432 100644 --- a/ui/app/(prowler)/findings/page.tsx +++ b/ui/app/(prowler)/findings/page.tsx @@ -60,9 +60,8 @@ async function getFindingGroupFilterOptions({ const optionFilters = excludeFindingGroupOwnFilters(filters); const options = new Map(); let page = 1; - let totalPages = 1; - do { + while (true) { const response = await fetchFindingGroups({ filters: optionFilters, page, @@ -76,9 +75,10 @@ async function getFindingGroupFilterOptions({ }); } - totalPages = response?.meta?.pagination?.pages ?? page; + const totalPages = response?.meta?.pagination?.pages ?? page; + if (page >= totalPages) break; page += 1; - } while (page <= totalPages); + } return Array.from(options.values()); } diff --git a/ui/components/findings/findings-filters.tsx b/ui/components/findings/findings-filters.tsx index 7a7ad20c6b..fc444bb32e 100644 --- a/ui/components/findings/findings-filters.tsx +++ b/ui/components/findings/findings-filters.tsx @@ -74,6 +74,7 @@ const countVisibleFilterKeys = (filters: Record): number => const FILTER_CONTROL_COLUMN_CLASS = "min-w-0 flex-none basis-full sm:basis-[calc((100%_-_0.75rem)/2)] lg:basis-[calc((100%_-_1.5rem)/3)] xl:basis-[calc((100%_-_2.25rem)/4)] 2xl:basis-[calc((100%_-_3rem)/5)]"; const FILTER_GRID_ITEM_CLASS = "min-w-0"; +const FINDING_GROUP_FILTER_KEYS = ["filter[check_id]", "filter[check_id__in]"]; export const FindingsFilterBatchControls = ({ providers, @@ -367,6 +368,7 @@ export const FindingsFilters = (props: FindingsFiltersProps) => { getFilterValue, } = useFilterBatch({ defaultParams: { "filter[muted]": "false" }, + exclusiveFilterGroups: [FINDING_GROUP_FILTER_KEYS], }); return ( diff --git a/ui/components/findings/send-to-jira-modal.test.tsx b/ui/components/findings/send-to-jira-modal.test.tsx index a064ddae77..835d4a1277 100644 --- a/ui/components/findings/send-to-jira-modal.test.tsx +++ b/ui/components/findings/send-to-jira-modal.test.tsx @@ -2,6 +2,8 @@ import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { beforeEach, describe, expect, it, vi } from "vitest"; +import { JIRA_DISPATCH_MODE, JIRA_DISPATCH_TARGET } from "@/types/integrations"; + import { SendToJiraModal } from "./send-to-jira-modal"; const { @@ -104,20 +106,18 @@ describe("SendToJiraModal", () => { onOpenChange={vi.fn()} findingId="check-a" findingTitle="Check A" - targetIds={["check-a"]} - targetType="check_id" targetBatches={[ { targetIds: ["check-a"], - targetType: "check_id", - dispatchMode: "grouped", + targetType: JIRA_DISPATCH_TARGET.CHECK_ID, + dispatchMode: JIRA_DISPATCH_MODE.GROUPED, }, { targetIds: ["finding-1", "finding-2"], - targetType: "finding_id", + targetType: JIRA_DISPATCH_TARGET.FINDING_ID, }, ]} - defaultDispatchMode="grouped" + defaultDispatchMode={JIRA_DISPATCH_MODE.GROUPED} selectedResourceCount={1} description="Create Jira issues for 1 Group and 2 Findings." />, @@ -189,20 +189,18 @@ describe("SendToJiraModal", () => { onOpenChange={onOpenChange} findingId="check-a" findingTitle="Check A" - targetIds={["check-a"]} - targetType="check_id" targetBatches={[ { targetIds: ["check-a"], - targetType: "check_id", - dispatchMode: "grouped", + targetType: JIRA_DISPATCH_TARGET.CHECK_ID, + dispatchMode: JIRA_DISPATCH_MODE.GROUPED, }, { targetIds: ["finding-1", "finding-2"], - targetType: "finding_id", + targetType: JIRA_DISPATCH_TARGET.FINDING_ID, }, ]} - defaultDispatchMode="grouped" + defaultDispatchMode={JIRA_DISPATCH_MODE.GROUPED} selectedResourceCount={1} description="Create Jira issues for 1 Group and 2 Findings." />, @@ -442,13 +440,14 @@ describe("SendToJiraModal", () => { expect(toastMock).toHaveBeenCalledTimes(1); }); - it("shows an error toast when Jira dispatch polling reports partial failures", async () => { + it("shows a partial success toast when a task reports created and failed issues", async () => { // Given const user = userEvent.setup(); const onOpenChange = vi.fn(); pollJiraDispatchTaskMock.mockResolvedValue({ - success: false, - error: "Jira dispatch completed with 1 failed and 2 created issues.", + success: true, + message: "2 Jira issues were created or updated successfully.", + warning: "Jira dispatch completed with 1 failed and 2 created issues.", }); render( { // Then await waitFor(() => expect(toastMock).toHaveBeenCalledWith({ - variant: "destructive", - title: "Error", + title: "Partial success", description: - "Jira dispatch completed with 1 failed and 2 created issues.", + "2 Jira issues were created or updated successfully. Some Jira dispatches failed: Jira dispatch completed with 1 failed and 2 created issues.", }), ); expect(toastMock).not.toHaveBeenCalledWith( diff --git a/ui/components/findings/send-to-jira-modal.tsx b/ui/components/findings/send-to-jira-modal.tsx index 397d8d2d73..246ddb7cf0 100644 --- a/ui/components/findings/send-to-jira-modal.tsx +++ b/ui/components/findings/send-to-jira-modal.tsx @@ -62,15 +62,15 @@ interface SendToJiraSingleTargetProps extends SendToJiraModalBaseProps { } interface SendToJiraTargetListProps extends SendToJiraModalBaseProps { - targetIds?: string[]; - targetType?: JiraDispatchTarget; + targetIds: string[]; + targetType: JiraDispatchTarget; targetBatches?: never; } interface SendToJiraBatchProps extends SendToJiraModalBaseProps { - targetIds?: string[]; - targetType?: JiraDispatchTarget; - targetBatches?: JiraDispatchTargetBatch[]; + targetIds?: never; + targetType?: never; + targetBatches: JiraDispatchTargetBatch[]; } type SendToJiraModalProps = @@ -293,15 +293,20 @@ export const SendToJiraModal = ({ ), ...launchErrors, ]; + const warnings = taskResults.flatMap((taskResult) => { + if (!taskResult.success || !taskResult.warning) return []; + return [taskResult.warning]; + }); - if (errors.length > 0) { + if (errors.length > 0 || warnings.length > 0) { const successfulTask = taskResults.find( (taskResult) => taskResult.success, ); if (successfulTask) { + const failedMessages = [...warnings, ...errors].join(" "); toast({ title: "Partial success", - description: `${successfulTask.message || "Some Jira issues were created successfully."} Some Jira dispatches failed: ${errors.join(" ")}`, + description: `${successfulTask.message || "Some Jira issues were created successfully."} Some Jira dispatches failed: ${failedMessages}`, }); return; } diff --git a/ui/components/findings/table/column-finding-resources.tsx b/ui/components/findings/table/column-finding-resources.tsx index e93becb25f..2abe560386 100644 --- a/ui/components/findings/table/column-finding-resources.tsx +++ b/ui/components/findings/table/column-finding-resources.tsx @@ -28,7 +28,7 @@ import type { FindingTriageLoadedNote, FindingTriageSummary, } from "@/types/findings-triage"; -import { JIRA_DISPATCH_MODE } from "@/types/integrations"; +import { JIRA_DISPATCH_MODE, JIRA_DISPATCH_TARGET } from "@/types/integrations"; import { canMuteFindingResource } from "./finding-resource-selection"; import { @@ -133,7 +133,7 @@ const ResourceRowActions = ({ findingId={resource.findingId} findingTitle={resource.checkId} targetIds={displayIds} - targetType="finding_id" + targetType={JIRA_DISPATCH_TARGET.FINDING_ID} defaultDispatchMode={ displayIds.length > 1 ? JIRA_DISPATCH_MODE.GROUPED diff --git a/ui/components/findings/table/data-table-row-actions.tsx b/ui/components/findings/table/data-table-row-actions.tsx index e2f92c04f9..55b3c6e7fe 100644 --- a/ui/components/findings/table/data-table-row-actions.tsx +++ b/ui/components/findings/table/data-table-row-actions.tsx @@ -23,7 +23,7 @@ import type { FindingTriageLoadedNote, FindingTriageSummary, } from "@/types/findings-triage"; -import { JIRA_DISPATCH_MODE } from "@/types/integrations"; +import { JIRA_DISPATCH_MODE, JIRA_DISPATCH_TARGET } from "@/types/integrations"; import type { ProviderType } from "@/types/providers"; import { canMuteFindingGroup } from "./finding-group-selection"; @@ -279,7 +279,11 @@ export function DataTableRowActions({ findingId={finding.id} findingTitle={findingTitle} targetIds={jiraTargetIds} - targetType={isGroup ? "check_id" : "finding_id"} + targetType={ + isGroup + ? JIRA_DISPATCH_TARGET.CHECK_ID + : JIRA_DISPATCH_TARGET.FINDING_ID + } defaultDispatchMode={jiraDefaultDispatchMode} canChooseGroupedDispatch={canChooseGroupedJiraDispatch} selectedResourceCount={selectedJiraResourceCount} diff --git a/ui/components/findings/table/findings-group-drill-down.tsx b/ui/components/findings/table/findings-group-drill-down.tsx index 751a361121..f10bb10c30 100644 --- a/ui/components/findings/table/findings-group-drill-down.tsx +++ b/ui/components/findings/table/findings-group-drill-down.tsx @@ -36,7 +36,7 @@ import { isFindingGroupMuted, } from "@/lib/findings-groups"; import { FindingGroupRow } from "@/types"; -import { JIRA_DISPATCH_MODE } from "@/types/integrations"; +import { JIRA_DISPATCH_MODE, JIRA_DISPATCH_TARGET } from "@/types/integrations"; import { FloatingMuteButton } from "../floating-mute-button"; import { getColumnFindingResources } from "./column-finding-resources"; @@ -268,7 +268,7 @@ export function FindingsGroupDrillDown({ findingId={selectedFindingIds[0] ?? ""} findingTitle={group.checkTitle} targetIds={selectedFindingIds} - targetType="finding_id" + targetType={JIRA_DISPATCH_TARGET.FINDING_ID} defaultDispatchMode={ selectedFindingIds.length > 1 ? JIRA_DISPATCH_MODE.GROUPED diff --git a/ui/components/findings/table/findings-group-table.test.tsx b/ui/components/findings/table/findings-group-table.test.tsx index f43b57b662..48daebdb7f 100644 --- a/ui/components/findings/table/findings-group-table.test.tsx +++ b/ui/components/findings/table/findings-group-table.test.tsx @@ -573,8 +573,6 @@ describe("FindingsGroupTable", () => { const lastCall = SendToJiraModalMock.mock.calls.at(-1)?.[0]; expect(lastCall).toMatchObject({ isOpen: true, - targetIds: ["check-a"], - targetType: "check_id", targetBatches: [ { targetIds: ["check-a"], @@ -641,8 +639,6 @@ describe("FindingsGroupTable", () => { const lastCall = SendToJiraModalMock.mock.calls.at(-1)?.[0]; expect(lastCall).toMatchObject({ isOpen: true, - targetIds: ["check-b"], - targetType: "check_id", targetBatches: [ { targetIds: ["check-b"], @@ -711,8 +707,6 @@ describe("FindingsGroupTable", () => { }; expect(lastCall).toMatchObject({ isOpen: true, - targetIds: ["check-b"], - targetType: "check_id", targetBatches: [ { targetIds: ["check-b"], diff --git a/ui/components/findings/table/findings-group-table.tsx b/ui/components/findings/table/findings-group-table.tsx index 9c93fb99ed..71c3105b0b 100644 --- a/ui/components/findings/table/findings-group-table.tsx +++ b/ui/components/findings/table/findings-group-table.tsx @@ -17,7 +17,7 @@ import { canDrillDownFindingGroup } from "@/lib/findings-groups"; import { getFlowById } from "@/lib/onboarding"; import { createExploreFindingsTourStepHandlers } from "@/lib/tours/explore-findings.tour"; import { FindingGroupRow, MetaDataProps } from "@/types"; -import { JIRA_DISPATCH_MODE } from "@/types/integrations"; +import { JIRA_DISPATCH_MODE, JIRA_DISPATCH_TARGET } from "@/types/integrations"; import { FloatingMuteButton } from "../floating-mute-button"; import { getColumnFindingGroups } from "./column-finding-groups"; @@ -171,8 +171,8 @@ export function FindingsGroupTable({ ? selectedCheckIds : resourceSelection; const jiraTargetType = jiraGroupSelectionTakesPrecedence - ? "check_id" - : "finding_id"; + ? JIRA_DISPATCH_TARGET.CHECK_ID + : JIRA_DISPATCH_TARGET.FINDING_ID; const singleSelectedGroup = selectedCheckIds.length === 1 ? selectedFindings.find( @@ -203,12 +203,12 @@ export function FindingsGroupTable({ ? [ { targetIds: selectedCheckIds, - targetType: "check_id" as const, + targetType: JIRA_DISPATCH_TARGET.CHECK_ID, dispatchMode: JIRA_DISPATCH_MODE.GROUPED, }, { targetIds: resourceSelection, - targetType: "finding_id" as const, + targetType: JIRA_DISPATCH_TARGET.FINDING_ID, ...(resourceSelection.length > 1 ? {} : { dispatchMode: JIRA_DISPATCH_MODE.INDIVIDUAL }), @@ -438,7 +438,24 @@ export function FindingsGroupTable({ /> )} - {canSendToJira && ( + {canSendToJira && jiraBatches && ( + + )} + + {canSendToJira && !jiraBatches && ( { // Then expect(result.current.pendingFilters["filter[severity__in]"]).toEqual([]); }); + + it("should replace exclusive legacy filters when the new grouped filter is edited", () => { + // Given - a legacy deep link with the exact check_id filter applied. + setSearchParams({ + "filter[check_id]": "teams_external_users_can_join", + }); + const { result } = renderHook(() => + useFilterBatch({ + exclusiveFilterGroups: [["filter[check_id]", "filter[check_id__in]"]], + }), + ); + + // When - the Finding Group multi-select writes the __in filter. + act(() => { + result.current.setPending("filter[check_id__in]", [ + "teams_external_users_cannot_join", + ]); + }); + + // Then - the stale exact filter is removed from pending state. + expect(result.current.pendingFilters).toEqual({ + "filter[check_id__in]": ["teams_external_users_cannot_join"], + }); + }); }); // ── getFilterValue ───────────────────────────────────────────────────────── @@ -246,6 +270,24 @@ describe("useFilterBatch", () => { // Then expect(values).toEqual(["critical"]); }); + + it("should expose legacy exclusive filter values through the replacement key", () => { + // Given - a legacy grouped finding deep link uses filter[check_id]. + setSearchParams({ + "filter[check_id]": "teams_external_users_can_join", + }); + const { result } = renderHook(() => + useFilterBatch({ + exclusiveFilterGroups: [["filter[check_id]", "filter[check_id__in]"]], + }), + ); + + // When - the new Finding Group control asks for filter[check_id__in]. + const values = result.current.getFilterValue("filter[check_id__in]"); + + // Then - the existing exact value appears selected in the control. + expect(values).toEqual(["teams_external_users_can_join"]); + }); }); // ── hasChanges & changeCount ─────────────────────────────────────────────── diff --git a/ui/hooks/use-filter-batch.ts b/ui/hooks/use-filter-batch.ts index 8e13ee8bc8..4451b29c61 100644 --- a/ui/hooks/use-filter-batch.ts +++ b/ui/hooks/use-filter-batch.ts @@ -151,6 +151,23 @@ export interface UseFilterBatchOptions { * (e.g. `{ "filter[muted]": "false" }` on the Findings page). */ defaultParams?: Record; + /** + * Filter keys that represent the same logical control. Updating one removes + * the others so legacy exact params and new multi-select params cannot be + * applied together. + */ + exclusiveFilterGroups?: string[][]; +} + +function normalizeFilterKey(key: string): string { + return key.startsWith("filter[") ? key : `filter[${key}]`; +} + +function getExclusiveFilterGroup( + filterKey: string, + exclusiveFilterGroups: string[][] | undefined, +): string[] | undefined { + return exclusiveFilterGroups?.find((group) => group.includes(filterKey)); } /** @@ -187,15 +204,27 @@ export const useFilterBatch = ( }, [searchParams]); const setPending = (key: string, values: string[]) => { - const filterKey = key.startsWith("filter[") ? key : `filter[${key}]`; - setPendingFilters((prev) => ({ - ...prev, - [filterKey]: values, - })); + const filterKey = normalizeFilterKey(key); + const exclusiveGroup = getExclusiveFilterGroup( + filterKey, + options?.exclusiveFilterGroups, + ); + setPendingFilters((prev) => { + const next = { ...prev }; + + exclusiveGroup + ?.filter((exclusiveKey) => exclusiveKey !== filterKey) + .forEach((exclusiveKey) => { + delete next[exclusiveKey]; + }); + + next[filterKey] = values; + return next; + }); }; const removePending = (key: string) => { - const filterKey = key.startsWith("filter[") ? key : `filter[${key}]`; + const filterKey = normalizeFilterKey(key); setPendingFilters((prev) => { const next = { ...prev }; delete next[filterKey]; @@ -265,7 +294,7 @@ export const useFilterBatch = ( }; const removeAppliedAndApply = (key: string, value?: string) => { - const filterKey = key.startsWith("filter[") ? key : `filter[${key}]`; + const filterKey = normalizeFilterKey(key); const applied = deriveAppliedFromUrl( new URLSearchParams(searchParams.toString()), ); @@ -286,8 +315,20 @@ export const useFilterBatch = ( }; const getFilterValue = (key: string): string[] => { - const filterKey = key.startsWith("filter[") ? key : `filter[${key}]`; - return pendingFilters[filterKey] ?? []; + const filterKey = normalizeFilterKey(key); + const values = pendingFilters[filterKey]; + if (values) return values; + + const exclusiveGroup = getExclusiveFilterGroup( + filterKey, + options?.exclusiveFilterGroups, + ); + const alternateKey = exclusiveGroup?.find( + (exclusiveKey) => + exclusiveKey !== filterKey && pendingFilters[exclusiveKey], + ); + + return alternateKey ? pendingFilters[alternateKey] : []; }; const hasChanges = !areFiltersEqual(pendingFilters, appliedFilters); diff --git a/ui/lib/deployment.test.ts b/ui/lib/deployment.test.ts index c702cc37fa..6d5c9ee16f 100644 --- a/ui/lib/deployment.test.ts +++ b/ui/lib/deployment.test.ts @@ -10,54 +10,6 @@ describe("enterprise feature flags", () => { vi.unstubAllEnvs(); }); - it("should enable billing and PostHog by default", async () => { - // Given / When - const { isBillingEnabled, isPostHogEnabled } = await importFresh(); - - // Then - expect(isBillingEnabled()).toBe(true); - expect(isPostHogEnabled()).toBe(true); - }); - - it("should disable billing independently from PostHog", async () => { - // Given - vi.stubEnv("NEXT_PUBLIC_PROWLER_ENTERPRISE_BILLING_ENABLED", "false"); - vi.stubEnv("NEXT_PUBLIC_PROWLER_ENTERPRISE_POSTHOG_ENABLED", "true"); - - // When - const { isBillingEnabled, isPostHogEnabled } = await importFresh(); - - // Then - expect(isBillingEnabled()).toBe(false); - expect(isPostHogEnabled()).toBe(true); - }); - - it("should disable PostHog when billing and PostHog are disabled", async () => { - // Given - vi.stubEnv("NEXT_PUBLIC_PROWLER_ENTERPRISE_BILLING_ENABLED", "false"); - vi.stubEnv("NEXT_PUBLIC_PROWLER_ENTERPRISE_POSTHOG_ENABLED", "false"); - - // When - const { isBillingEnabled, isPostHogEnabled } = await importFresh(); - - // Then - expect(isBillingEnabled()).toBe(false); - expect(isPostHogEnabled()).toBe(false); - }); - - it("should force PostHog on when billing is enabled", async () => { - // Given - vi.stubEnv("NEXT_PUBLIC_PROWLER_ENTERPRISE_BILLING_ENABLED", "true"); - vi.stubEnv("NEXT_PUBLIC_PROWLER_ENTERPRISE_POSTHOG_ENABLED", "false"); - - // When - const { isBillingEnabled, isPostHogEnabled } = await importFresh(); - - // Then - expect(isBillingEnabled()).toBe(true); - expect(isPostHogEnabled()).toBe(true); - }); - it("should keep grouped Jira dispatch disabled by default", async () => { // Given / When const { isGroupedJiraDispatchEnabled } = await importFresh(); diff --git a/ui/lib/deployment.ts b/ui/lib/deployment.ts index 2498f764ea..a1c97aec4e 100644 --- a/ui/lib/deployment.ts +++ b/ui/lib/deployment.ts @@ -4,10 +4,8 @@ export const DEPLOYMENT_MODE = { } as const; export const ENTERPRISE_FEATURE_ENV = { - BILLING_ENABLED: "NEXT_PUBLIC_PROWLER_ENTERPRISE_BILLING_ENABLED", GROUPED_JIRA_DISPATCH_ENABLED: "NEXT_PUBLIC_PROWLER_ENTERPRISE_GROUPED_JIRA_DISPATCH_ENABLED", - POSTHOG_ENABLED: "NEXT_PUBLIC_PROWLER_ENTERPRISE_POSTHOG_ENABLED", } as const; export const PROWLER_CLOUD_ONLY_TOOLTIP = "Available only in Prowler Cloud"; @@ -21,16 +19,12 @@ type EnterpriseFeatureEnv = const getEnterpriseFeatureValue = ( envName: EnterpriseFeatureEnv, ): string | undefined => { - if (envName === ENTERPRISE_FEATURE_ENV.BILLING_ENABLED) { - return process.env.NEXT_PUBLIC_PROWLER_ENTERPRISE_BILLING_ENABLED; - } - if (envName === ENTERPRISE_FEATURE_ENV.GROUPED_JIRA_DISPATCH_ENABLED) { return process.env .NEXT_PUBLIC_PROWLER_ENTERPRISE_GROUPED_JIRA_DISPATCH_ENABLED; } - return process.env.NEXT_PUBLIC_PROWLER_ENTERPRISE_POSTHOG_ENABLED; + return undefined; }; const getBooleanEnv = ( @@ -56,16 +50,6 @@ export const getDeploymentMode = (): DeploymentMode | undefined => { return undefined; }; -export const isOnPremiseDeployment = (): boolean => - getDeploymentMode() === DEPLOYMENT_MODE.ON_PREMISE; - -export const isBillingEnabled = (): boolean => - getBooleanEnv(ENTERPRISE_FEATURE_ENV.BILLING_ENABLED, true); - -export const isPostHogEnabled = (): boolean => - isBillingEnabled() || - getBooleanEnv(ENTERPRISE_FEATURE_ENV.POSTHOG_ENABLED, true); - export const isGroupedJiraDispatchEnabled = (): boolean => getDeploymentMode() === DEPLOYMENT_MODE.CLOUD && getBooleanEnv(ENTERPRISE_FEATURE_ENV.GROUPED_JIRA_DISPATCH_ENABLED, false);