fix(ui): address grouped Jira follow-up feedback

This commit is contained in:
Hugo P.Brito committed 2026-07-17 12:48:56 +01:00
1 parent 1acf9bf417
commit e6f47a9bd4
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(
+12 -7
View File
@@ -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)