mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 13:01:56 +00:00
fix(ui): address grouped Jira review comments
This commit is contained in:
@@ -8,16 +8,9 @@ import type {
|
||||
JiraDispatchMode,
|
||||
JiraDispatchRequest,
|
||||
JiraDispatchResponse,
|
||||
JiraDispatchTarget,
|
||||
} from "@/types/integrations";
|
||||
import { JIRA_DISPATCH_MODE } from "@/types/integrations";
|
||||
|
||||
const JIRA_DISPATCH_FILTER = {
|
||||
CHECK_ID: "check_id",
|
||||
FINDING_ID: "finding_id",
|
||||
} as const;
|
||||
|
||||
type JiraDispatchFilter =
|
||||
(typeof JIRA_DISPATCH_FILTER)[keyof typeof JIRA_DISPATCH_FILTER];
|
||||
import { JIRA_DISPATCH_MODE, JIRA_DISPATCH_TARGET } from "@/types/integrations";
|
||||
|
||||
type JiraTaskResult = NonNullable<
|
||||
JiraDispatchResponse["data"]["attributes"]["result"]
|
||||
@@ -26,7 +19,7 @@ type JiraTaskResult = NonNullable<
|
||||
interface JiraDispatchInput {
|
||||
integrationId: string;
|
||||
targetIds: string[];
|
||||
filter: JiraDispatchFilter;
|
||||
filter: JiraDispatchTarget;
|
||||
projectKey: string;
|
||||
issueType: string;
|
||||
dispatchMode?: JiraDispatchMode;
|
||||
@@ -150,7 +143,7 @@ export const sendFindingToJira = async (
|
||||
return sendJiraDispatch({
|
||||
integrationId,
|
||||
targetIds: [findingId],
|
||||
filter: JIRA_DISPATCH_FILTER.FINDING_ID,
|
||||
filter: JIRA_DISPATCH_TARGET.FINDING_ID,
|
||||
projectKey,
|
||||
issueType,
|
||||
});
|
||||
|
||||
@@ -47,7 +47,13 @@ describe("findings page", () => {
|
||||
|
||||
it("loads finding groups as selectable Finding Group filter options", () => {
|
||||
expect(source).toContain("fetchFindingGroupFilterOptions");
|
||||
expect(source).toContain("pageSize: 100");
|
||||
expect(source).toContain("FINDING_GROUP_FILTER_OPTION_PAGE_SIZE");
|
||||
expect(source).toContain("checkOptions={checkOptions}");
|
||||
});
|
||||
|
||||
it("excludes Finding Group's own filters while loading all option pages", () => {
|
||||
expect(source).toContain("excludeFindingGroupOwnFilters");
|
||||
expect(source).toContain('"filter[check_id__in]"');
|
||||
expect(source).toContain("page <= totalPages");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,6 +29,60 @@ import { isCloud } from "@/lib/shared/env";
|
||||
import { ScanEntity, ScanProps } from "@/types";
|
||||
import { SearchParamsProps } from "@/types/components";
|
||||
|
||||
const FINDING_GROUP_FILTER_OPTION_PAGE_SIZE = 100;
|
||||
const FINDING_GROUP_OWN_FILTER_KEYS = [
|
||||
"filter[check_id]",
|
||||
"filter[check_id__in]",
|
||||
] as const;
|
||||
|
||||
type FindingGroupFilterFetcher = typeof getFindingGroups;
|
||||
|
||||
function excludeFindingGroupOwnFilters(
|
||||
filters: Record<string, string | string[] | undefined>,
|
||||
) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(filters).filter(
|
||||
([key]) =>
|
||||
!FINDING_GROUP_OWN_FILTER_KEYS.includes(
|
||||
key as (typeof FINDING_GROUP_OWN_FILTER_KEYS)[number],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async function getFindingGroupFilterOptions({
|
||||
fetchFindingGroups,
|
||||
filters,
|
||||
}: {
|
||||
fetchFindingGroups: FindingGroupFilterFetcher;
|
||||
filters: Record<string, string | string[] | undefined>;
|
||||
}) {
|
||||
const optionFilters = excludeFindingGroupOwnFilters(filters);
|
||||
const options = new Map<string, { checkId: string; checkTitle: string }>();
|
||||
let page = 1;
|
||||
let totalPages = 1;
|
||||
|
||||
do {
|
||||
const response = await fetchFindingGroups({
|
||||
filters: optionFilters,
|
||||
page,
|
||||
pageSize: FINDING_GROUP_FILTER_OPTION_PAGE_SIZE,
|
||||
});
|
||||
|
||||
for (const group of adaptFindingGroupsResponse(response)) {
|
||||
options.set(group.checkId, {
|
||||
checkId: group.checkId,
|
||||
checkTitle: group.checkTitle,
|
||||
});
|
||||
}
|
||||
|
||||
totalPages = response?.meta?.pagination?.pages ?? page;
|
||||
page += 1;
|
||||
} while (page <= totalPages);
|
||||
|
||||
return Array.from(options.values());
|
||||
}
|
||||
|
||||
export default async function Findings({
|
||||
searchParams,
|
||||
}: {
|
||||
@@ -71,16 +125,10 @@ export default async function Findings({
|
||||
const fetchFindingGroupFilterOptions = hasHistoricalData
|
||||
? getFindingGroups
|
||||
: getLatestFindingGroups;
|
||||
const findingGroupFilterOptionsData = await fetchFindingGroupFilterOptions({
|
||||
const checkOptions = await getFindingGroupFilterOptions({
|
||||
fetchFindingGroups: fetchFindingGroupFilterOptions,
|
||||
filters: resolvedFilters,
|
||||
pageSize: 100,
|
||||
});
|
||||
const checkOptions = adaptFindingGroupsResponse(
|
||||
findingGroupFilterOptionsData,
|
||||
).map((group) => ({
|
||||
checkId: group.checkId,
|
||||
checkTitle: group.checkTitle,
|
||||
}));
|
||||
|
||||
const completedScans = scansData?.data?.filter(
|
||||
(scan: ScanProps) =>
|
||||
|
||||
@@ -408,7 +408,7 @@ describe("buildFindingGroupFilterOption", () => {
|
||||
|
||||
// Then
|
||||
expect(filter).toMatchObject({
|
||||
key: "check_id",
|
||||
key: "check_id__in",
|
||||
labelCheckboxGroup: "Finding Group",
|
||||
values: ["teams_external_users_can_join", "s3_bucket_public_access"],
|
||||
index: 3,
|
||||
|
||||
@@ -139,7 +139,7 @@ export function buildFindingGroupFilterOption({
|
||||
}
|
||||
|
||||
return {
|
||||
key: "check_id",
|
||||
key: "check_id__in",
|
||||
labelCheckboxGroup: "Finding Group",
|
||||
values,
|
||||
labelFormatter: (value: string) =>
|
||||
|
||||
@@ -61,7 +61,6 @@ describe("FloatingMuteButton — onBeforeOpen error handling", () => {
|
||||
|
||||
// When — click the button (triggers onBeforeOpen which rejects)
|
||||
await user.click(button);
|
||||
await user.click(screen.getByRole("button", { name: "Mute" }));
|
||||
|
||||
// Then — button should NOT be disabled (isResolving reset to false)
|
||||
await waitFor(() => {
|
||||
@@ -84,7 +83,6 @@ describe("FloatingMuteButton — onBeforeOpen error handling", () => {
|
||||
|
||||
// When
|
||||
await user.click(screen.getByRole("button"));
|
||||
await user.click(screen.getByRole("button", { name: "Mute" }));
|
||||
|
||||
// Then
|
||||
await waitFor(() => {
|
||||
@@ -124,7 +122,6 @@ describe("FloatingMuteButton — onBeforeOpen error handling", () => {
|
||||
|
||||
// When
|
||||
await user.click(screen.getByRole("button"));
|
||||
await user.click(screen.getByRole("button", { name: "Mute" }));
|
||||
|
||||
// Then — modal opened (MuteFindingsModal called with isOpen=true)
|
||||
await waitFor(() => {
|
||||
@@ -159,7 +156,6 @@ describe("FloatingMuteButton — onBeforeOpen error handling", () => {
|
||||
|
||||
// When
|
||||
await user.click(screen.getByRole("button"));
|
||||
await user.click(screen.getByRole("button", { name: "Mute" }));
|
||||
|
||||
// Then
|
||||
const preparingCall = (
|
||||
@@ -219,7 +215,7 @@ describe("FloatingMuteButton — onBeforeOpen error handling", () => {
|
||||
);
|
||||
|
||||
// When
|
||||
await user.click(screen.getByRole("button", { name: "Mute (1)" }));
|
||||
await user.click(screen.getByRole("button", { name: "1 selected" }));
|
||||
await user.click(screen.getByRole("button", { name: "Send to Jira" }));
|
||||
|
||||
// Then
|
||||
@@ -281,7 +277,7 @@ describe("FloatingMuteButton — onBeforeOpen error handling", () => {
|
||||
);
|
||||
|
||||
// When
|
||||
await user.click(screen.getByRole("button", { name: "Mute (1)" }));
|
||||
await user.click(screen.getByRole("button", { name: "1 selected" }));
|
||||
const jiraButton = screen.getByRole("button", { name: "Send to Jira" });
|
||||
|
||||
// Then
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/shadcn/tooltip";
|
||||
import { PROWLER_CLOUD_ONLY_TOOLTIP } from "@/lib/deployment";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { MuteFindingsModal } from "./mute-findings-modal";
|
||||
@@ -42,8 +43,6 @@ interface FloatingMuteButtonProps {
|
||||
jiraDisabledTooltip?: string;
|
||||
}
|
||||
|
||||
export const PROWLER_CLOUD_ONLY_TOOLTIP = "Available only in Prowler Cloud";
|
||||
|
||||
const CloudFeatureBadgeLink = () => (
|
||||
<Badge variant="cloud" asChild>
|
||||
<a
|
||||
@@ -136,6 +135,15 @@ export function FloatingMuteButton({
|
||||
};
|
||||
|
||||
const findingIds = onBeforeOpen ? resolvedIds : selectedFindingIds;
|
||||
const hasMultipleActions = showSendToJira;
|
||||
const handlePrimaryClick = () => {
|
||||
if (hasMultipleActions) {
|
||||
setIsActionChooserOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
void handleMuteClick();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -205,7 +213,7 @@ export function FloatingMuteButton({
|
||||
? createPortal(
|
||||
<div className="animate-in fade-in slide-in-from-bottom-4 fixed right-6 bottom-6 z-50 flex gap-2 duration-300">
|
||||
<Button
|
||||
onClick={() => setIsActionChooserOpen(true)}
|
||||
onClick={handlePrimaryClick}
|
||||
disabled={isResolving}
|
||||
size="lg"
|
||||
className="shadow-lg"
|
||||
@@ -215,7 +223,9 @@ export function FloatingMuteButton({
|
||||
) : (
|
||||
<Ellipsis className="size-5" />
|
||||
)}
|
||||
{label ?? `Mute (${selectedCount})`}
|
||||
{hasMultipleActions
|
||||
? (label ?? `${selectedCount} selected`)
|
||||
: `Mute (${selectedCount})`}
|
||||
</Button>
|
||||
</div>,
|
||||
document.body,
|
||||
|
||||
@@ -483,7 +483,7 @@ describe("SendToJiraModal", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("shows an error toast when one mixed dispatch batch fails", async () => {
|
||||
it("shows a partial success toast when one mixed dispatch batch fails after another succeeds", async () => {
|
||||
// Given
|
||||
const user = userEvent.setup();
|
||||
const onOpenChange = vi.fn();
|
||||
@@ -542,10 +542,9 @@ describe("SendToJiraModal", () => {
|
||||
// Then
|
||||
await waitFor(() =>
|
||||
expect(toastMock).toHaveBeenCalledWith({
|
||||
variant: "destructive",
|
||||
title: "Error",
|
||||
title: "Partial success",
|
||||
description:
|
||||
"Jira dispatch completed with 1 failed and 1 created issue.",
|
||||
"Finding successfully sent to Jira! Some Jira dispatches failed: Jira dispatch completed with 1 failed and 1 created issue.",
|
||||
}),
|
||||
);
|
||||
expect(toastMock).not.toHaveBeenCalledWith(
|
||||
@@ -605,9 +604,9 @@ describe("SendToJiraModal", () => {
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(toastMock).toHaveBeenCalledWith({
|
||||
variant: "destructive",
|
||||
title: "Error",
|
||||
description: "Failed to launch Finding batch.",
|
||||
title: "Partial success",
|
||||
description:
|
||||
"Finding successfully sent to Jira! Some Jira dispatches failed: Failed to launch Finding batch.",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -27,7 +27,9 @@ import { toast } from "@/components/shadcn/toast";
|
||||
import {
|
||||
IntegrationProps,
|
||||
JIRA_DISPATCH_MODE,
|
||||
JIRA_DISPATCH_TARGET,
|
||||
type JiraDispatchMode,
|
||||
type JiraDispatchTarget,
|
||||
} from "@/types/integrations";
|
||||
|
||||
import {
|
||||
@@ -35,28 +37,17 @@ import {
|
||||
JIRA_SELECTION_KIND,
|
||||
} from "./send-to-jira-modal-copy";
|
||||
|
||||
const JIRA_DISPATCH_TARGET = {
|
||||
CHECK_ID: "check_id",
|
||||
FINDING_ID: "finding_id",
|
||||
} as const;
|
||||
|
||||
type JiraDispatchTarget =
|
||||
(typeof JIRA_DISPATCH_TARGET)[keyof typeof JIRA_DISPATCH_TARGET];
|
||||
|
||||
interface JiraDispatchTargetBatch {
|
||||
targetIds: string[];
|
||||
targetType: JiraDispatchTarget;
|
||||
dispatchMode?: JiraDispatchMode;
|
||||
}
|
||||
|
||||
interface SendToJiraModalProps {
|
||||
interface SendToJiraModalBaseProps {
|
||||
isOpen: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
findingId: string;
|
||||
findingTitle?: string;
|
||||
targetIds?: string[];
|
||||
targetType?: JiraDispatchTarget;
|
||||
targetBatches?: JiraDispatchTargetBatch[];
|
||||
defaultDispatchMode?: JiraDispatchMode;
|
||||
canChooseGroupedDispatch?: boolean;
|
||||
isFindingGroupSelection?: boolean;
|
||||
@@ -64,6 +55,29 @@ interface SendToJiraModalProps {
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface SendToJiraSingleTargetProps extends SendToJiraModalBaseProps {
|
||||
targetIds?: never;
|
||||
targetType?: never;
|
||||
targetBatches?: never;
|
||||
}
|
||||
|
||||
interface SendToJiraTargetListProps extends SendToJiraModalBaseProps {
|
||||
targetIds?: string[];
|
||||
targetType?: JiraDispatchTarget;
|
||||
targetBatches?: never;
|
||||
}
|
||||
|
||||
interface SendToJiraBatchProps extends SendToJiraModalBaseProps {
|
||||
targetIds?: string[];
|
||||
targetType?: JiraDispatchTarget;
|
||||
targetBatches?: JiraDispatchTargetBatch[];
|
||||
}
|
||||
|
||||
type SendToJiraModalProps =
|
||||
| SendToJiraSingleTargetProps
|
||||
| SendToJiraTargetListProps
|
||||
| SendToJiraBatchProps;
|
||||
|
||||
const sendToJiraSchema = z.object({
|
||||
integration: z.string().min(1, "Please select a Jira integration"),
|
||||
project: z.string().min(1, "Please select a project"),
|
||||
@@ -217,7 +231,7 @@ export const SendToJiraModal = ({
|
||||
form.reset();
|
||||
setFetchedIssueTypes({});
|
||||
}
|
||||
}, [isOpen, form, toast]);
|
||||
}, [isOpen, form]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
@@ -281,6 +295,17 @@ export const SendToJiraModal = ({
|
||||
];
|
||||
|
||||
if (errors.length > 0) {
|
||||
const successfulTask = taskResults.find(
|
||||
(taskResult) => taskResult.success,
|
||||
);
|
||||
if (successfulTask) {
|
||||
toast({
|
||||
title: "Partial success",
|
||||
description: `${successfulTask.message || "Some Jira issues were created successfully."} Some Jira dispatches failed: ${errors.join(" ")}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(errors.join(" "));
|
||||
}
|
||||
|
||||
@@ -377,7 +402,6 @@ export const SendToJiraModal = ({
|
||||
selectedProject,
|
||||
issueTypesFromConfig.length,
|
||||
fetchedIssueTypes,
|
||||
toast,
|
||||
]);
|
||||
|
||||
const issueTypeOptions = issueTypesForProject.map((type) => ({
|
||||
|
||||
@@ -181,6 +181,7 @@ vi.mock("@/lib/date-utils", () => ({
|
||||
|
||||
vi.mock("@/lib/deployment", () => ({
|
||||
isGroupedJiraDispatchEnabled: isGroupedJiraDispatchEnabledMock,
|
||||
PROWLER_CLOUD_ONLY_TOOLTIP: "Available only in Prowler Cloud",
|
||||
}));
|
||||
|
||||
const notificationIndicatorMock = vi.fn((_props: unknown) => null);
|
||||
|
||||
@@ -19,7 +19,10 @@ import { Spinner } from "@/components/shadcn/spinner/spinner";
|
||||
import { SeverityBadge } from "@/components/shadcn/table";
|
||||
import { DataTableColumnHeader } from "@/components/shadcn/table/data-table-column-header";
|
||||
import { getFailingForLabel } from "@/lib/date-utils";
|
||||
import { isGroupedJiraDispatchEnabled } from "@/lib/deployment";
|
||||
import {
|
||||
isGroupedJiraDispatchEnabled,
|
||||
PROWLER_CLOUD_ONLY_TOOLTIP,
|
||||
} from "@/lib/deployment";
|
||||
import { FindingResourceRow } from "@/types";
|
||||
import type {
|
||||
FindingTriageLoadedNote,
|
||||
@@ -27,7 +30,6 @@ import type {
|
||||
} from "@/types/findings-triage";
|
||||
import { JIRA_DISPATCH_MODE } from "@/types/integrations";
|
||||
|
||||
import { PROWLER_CLOUD_ONLY_TOOLTIP } from "../floating-mute-button";
|
||||
import { canMuteFindingResource } from "./finding-resource-selection";
|
||||
import {
|
||||
FindingNoteActionItem,
|
||||
|
||||
@@ -27,6 +27,7 @@ vi.mock("@/components/findings/send-to-jira-modal", () => ({
|
||||
|
||||
vi.mock("@/lib/deployment", () => ({
|
||||
isGroupedJiraDispatchEnabled: isGroupedJiraDispatchEnabledMock,
|
||||
PROWLER_CLOUD_ONLY_TOOLTIP: "Available only in Prowler Cloud",
|
||||
}));
|
||||
|
||||
vi.mock("@/components/icons/services/IconServices", () => ({
|
||||
|
||||
@@ -13,7 +13,10 @@ import {
|
||||
ActionDropdownItem,
|
||||
} from "@/components/shadcn/dropdown";
|
||||
import { Spinner } from "@/components/shadcn/spinner/spinner";
|
||||
import { isGroupedJiraDispatchEnabled } from "@/lib/deployment";
|
||||
import {
|
||||
isGroupedJiraDispatchEnabled,
|
||||
PROWLER_CLOUD_ONLY_TOOLTIP,
|
||||
} from "@/lib/deployment";
|
||||
import { isFindingGroupMuted } from "@/lib/findings-groups";
|
||||
import { getOptionalText } from "@/lib/utils";
|
||||
import type {
|
||||
@@ -23,7 +26,6 @@ import type {
|
||||
import { JIRA_DISPATCH_MODE } from "@/types/integrations";
|
||||
import type { ProviderType } from "@/types/providers";
|
||||
|
||||
import { PROWLER_CLOUD_ONLY_TOOLTIP } from "../floating-mute-button";
|
||||
import { canMuteFindingGroup } from "./finding-group-selection";
|
||||
import type { FindingTriageContext } from "./finding-note-modal";
|
||||
import { FindingNoteActionItem } from "./finding-triage-cells";
|
||||
|
||||
@@ -26,7 +26,10 @@ import {
|
||||
import { SeverityBadge, StatusFindingBadge } from "@/components/shadcn/table";
|
||||
import { useFindingGroupResourceState } from "@/hooks/use-finding-group-resource-state";
|
||||
import { cn, hasHistoricalFindingFilter } from "@/lib";
|
||||
import { isGroupedJiraDispatchEnabled } from "@/lib/deployment";
|
||||
import {
|
||||
isGroupedJiraDispatchEnabled,
|
||||
PROWLER_CLOUD_ONLY_TOOLTIP,
|
||||
} from "@/lib/deployment";
|
||||
import {
|
||||
getFilteredFindingGroupDelta,
|
||||
getFindingGroupImpactedCounts,
|
||||
@@ -35,10 +38,7 @@ import {
|
||||
import { FindingGroupRow } from "@/types";
|
||||
import { JIRA_DISPATCH_MODE } from "@/types/integrations";
|
||||
|
||||
import {
|
||||
FloatingMuteButton,
|
||||
PROWLER_CLOUD_ONLY_TOOLTIP,
|
||||
} from "../floating-mute-button";
|
||||
import { FloatingMuteButton } from "../floating-mute-button";
|
||||
import { getColumnFindingResources } from "./column-finding-resources";
|
||||
import { FindingsSelectionContext } from "./findings-selection-context";
|
||||
import { ImpactedResourcesCell } from "./impacted-resources-cell";
|
||||
|
||||
@@ -122,6 +122,7 @@ vi.mock("@/actions/findings/findings-by-resource", () => ({
|
||||
|
||||
vi.mock("@/lib/deployment", () => ({
|
||||
isGroupedJiraDispatchEnabled: isGroupedJiraDispatchEnabledMock,
|
||||
PROWLER_CLOUD_ONLY_TOOLTIP: "Available only in Prowler Cloud",
|
||||
}));
|
||||
|
||||
vi.mock("../send-to-jira-modal", () => ({
|
||||
|
||||
@@ -9,17 +9,17 @@ import { CustomCheckboxMutedFindings } from "@/components/filters/custom-checkbo
|
||||
import { SendToJiraModal } from "@/components/findings/send-to-jira-modal";
|
||||
import { OnboardingTrigger, PageReady } from "@/components/onboarding";
|
||||
import { DataTable } from "@/components/shadcn/table";
|
||||
import { isGroupedJiraDispatchEnabled } from "@/lib/deployment";
|
||||
import {
|
||||
isGroupedJiraDispatchEnabled,
|
||||
PROWLER_CLOUD_ONLY_TOOLTIP,
|
||||
} from "@/lib/deployment";
|
||||
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 {
|
||||
FloatingMuteButton,
|
||||
PROWLER_CLOUD_ONLY_TOOLTIP,
|
||||
} from "../floating-mute-button";
|
||||
import { FloatingMuteButton } from "../floating-mute-button";
|
||||
import { getColumnFindingGroups } from "./column-finding-groups";
|
||||
import { canMuteFindingGroup } from "./finding-group-selection";
|
||||
import { FindingsSelectionContext } from "./findings-selection-context";
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from "./inline-resource-container";
|
||||
|
||||
const exploreFindingsFlow = getFlowById("explore-findings")!;
|
||||
const EMPTY_FINDING_GROUPS: FindingGroupRow[] = [];
|
||||
|
||||
function buildSelectionSummary(
|
||||
groupCount: number,
|
||||
@@ -105,7 +106,7 @@ export function FindingsGroupTable({
|
||||
undefined,
|
||||
);
|
||||
|
||||
const safeData = data ?? [];
|
||||
const safeData = data ?? EMPTY_FINDING_GROUPS;
|
||||
const hasResourceSelection = resourceSelection.length > 0;
|
||||
const filters = resolvedFilters;
|
||||
const groupedJiraDispatchEnabled = isGroupedJiraDispatchEnabled();
|
||||
|
||||
@@ -10,6 +10,8 @@ export const ENTERPRISE_FEATURE_ENV = {
|
||||
POSTHOG_ENABLED: "NEXT_PUBLIC_PROWLER_ENTERPRISE_POSTHOG_ENABLED",
|
||||
} as const;
|
||||
|
||||
export const PROWLER_CLOUD_ONLY_TOOLTIP = "Available only in Prowler Cloud";
|
||||
|
||||
export type DeploymentMode =
|
||||
(typeof DEPLOYMENT_MODE)[keyof typeof DEPLOYMENT_MODE];
|
||||
|
||||
|
||||
@@ -12,6 +12,14 @@ export const JIRA_DISPATCH_MODE = {
|
||||
export type JiraDispatchMode =
|
||||
(typeof JIRA_DISPATCH_MODE)[keyof typeof JIRA_DISPATCH_MODE];
|
||||
|
||||
export const JIRA_DISPATCH_TARGET = {
|
||||
CHECK_ID: "check_id",
|
||||
FINDING_ID: "finding_id",
|
||||
} as const;
|
||||
|
||||
export type JiraDispatchTarget =
|
||||
(typeof JIRA_DISPATCH_TARGET)[keyof typeof JIRA_DISPATCH_TARGET];
|
||||
|
||||
export interface IntegrationProps {
|
||||
type: "integrations";
|
||||
id: string;
|
||||
|
||||
Reference in New Issue
Block a user