mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-11-08 12:24:45 +00:00
fix(ui): address grouped Jira follow-up feedback
This commit is contained in:
15 files changed
+196
-131
No files matched your search
@@ -74,6 +74,7 @@ const countVisibleFilterKeys = (filters: Record<string, string[]>): 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 (
|
||||
|
||||
@@ -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(
|
||||
<SendToJiraModal
|
||||
@@ -472,10 +471,9 @@ describe("SendToJiraModal", () => {
|
||||
// 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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<T extends FindingRowData>({
|
||||
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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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 && (
|
||||
<SendToJiraModal
|
||||
isOpen={isJiraModalOpen}
|
||||
onOpenChange={setIsJiraModalOpen}
|
||||
findingId={jiraTargetIds[0] ?? ""}
|
||||
findingTitle={jiraTitle}
|
||||
targetBatches={jiraBatches}
|
||||
defaultDispatchMode={jiraDispatchMode}
|
||||
isFindingGroupSelection={
|
||||
!jiraGroupSelectionTakesPrecedence && Boolean(expandedGroup)
|
||||
}
|
||||
canChooseGroupedDispatch={canChooseGroupedJiraDispatch}
|
||||
selectedResourceCount={selectedJiraResourceCount}
|
||||
description={jiraDescription}
|
||||
/>
|
||||
)}
|
||||
|
||||
{canSendToJira && !jiraBatches && (
|
||||
<SendToJiraModal
|
||||
isOpen={isJiraModalOpen}
|
||||
onOpenChange={setIsJiraModalOpen}
|
||||
@@ -446,7 +463,6 @@ export function FindingsGroupTable({
|
||||
findingTitle={jiraTitle}
|
||||
targetIds={jiraTargetIds}
|
||||
targetType={jiraTargetType}
|
||||
targetBatches={jiraBatches}
|
||||
defaultDispatchMode={jiraDispatchMode}
|
||||
isFindingGroupSelection={
|
||||
!jiraGroupSelectionTakesPrecedence && Boolean(expandedGroup)
|
||||
|
||||
Reference in new issue
Block a user