mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-08-19 09:30:21 +00:00
fix(ui): restore finding delta colors and shorten update labels (#12160)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
Finding delta colors and integration update button labels restored
|
||||
@@ -0,0 +1,18 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { FILTER_FIELD, FILTER_SELECTION_MODE } from "@/types/filters";
|
||||
|
||||
import { filterFindings } from "./data-filters";
|
||||
|
||||
describe("filterFindings", () => {
|
||||
it("configures delta as a single-select filter", () => {
|
||||
const deltaFilter = filterFindings.find(
|
||||
(filter) => filter.key === FILTER_FIELD.DELTA,
|
||||
);
|
||||
|
||||
expect(deltaFilter).toMatchObject({
|
||||
selectionMode: FILTER_SELECTION_MODE.SINGLE,
|
||||
values: ["new", "changed"],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,9 @@
|
||||
import { CONNECTION_STATUS_MAPPING } from "@/lib/helper-filters";
|
||||
import { FILTER_FIELD, FilterOption } from "@/types/filters";
|
||||
import {
|
||||
FILTER_FIELD,
|
||||
FILTER_SELECTION_MODE,
|
||||
type FilterOption,
|
||||
} from "@/types/filters";
|
||||
import {
|
||||
PROVIDER_DISPLAY_NAMES,
|
||||
PROVIDER_TYPES,
|
||||
@@ -79,9 +83,10 @@ export const filterFindings = [
|
||||
key: FILTER_FIELD.DELTA,
|
||||
labelCheckboxGroup: "Delta",
|
||||
values: ["new", "changed"],
|
||||
selectionMode: FILTER_SELECTION_MODE.SINGLE,
|
||||
index: 2,
|
||||
},
|
||||
];
|
||||
] satisfies FilterOption[];
|
||||
|
||||
export const filterUsers = [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { render } from "@testing-library/react";
|
||||
import type { ReactNode } from "react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@/components/shadcn/tooltip", () => ({
|
||||
Tooltip: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
TooltipTrigger: ({ children }: { children: ReactNode }) => <>{children}</>,
|
||||
TooltipContent: ({ children }: { children: ReactNode }) => (
|
||||
<div>{children}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
import { DeltaIndicator } from "./delta-indicator";
|
||||
|
||||
describe("DeltaIndicator", () => {
|
||||
it("uses the design-system fail color for new findings", () => {
|
||||
// Given
|
||||
const { container } = render(<DeltaIndicator delta="new" />);
|
||||
|
||||
// When
|
||||
const deltaDot = container.querySelector(".rounded-full");
|
||||
|
||||
// Then
|
||||
expect(deltaDot).toHaveClass("bg-bg-fail");
|
||||
});
|
||||
|
||||
it("uses the design-system warning color for changed findings", () => {
|
||||
// Given
|
||||
const { container } = render(<DeltaIndicator delta="changed" />);
|
||||
|
||||
// When
|
||||
const deltaDot = container.querySelector(".rounded-full");
|
||||
|
||||
// Then
|
||||
expect(deltaDot).toHaveClass("bg-bg-warning");
|
||||
});
|
||||
});
|
||||
@@ -19,9 +19,9 @@ export const DeltaIndicator = ({ delta }: DeltaIndicatorProps) => {
|
||||
className={cn(
|
||||
"h-2 w-2 min-w-2 cursor-pointer rounded-full",
|
||||
delta === "new"
|
||||
? "bg-bg-data-high"
|
||||
? "bg-bg-fail"
|
||||
: delta === "changed"
|
||||
? "bg-bg-data-low"
|
||||
? "bg-bg-warning"
|
||||
: "bg-text-neutral-tertiary",
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { render } from "@testing-library/react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import type { ReactNode } from "react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
@@ -61,6 +61,39 @@ vi.mock("@/components/shadcn/tooltip", () => ({
|
||||
import { NotificationIndicator } from "./notification-indicator";
|
||||
|
||||
describe("NotificationIndicator", () => {
|
||||
it("does not show a ring around the delta trigger", () => {
|
||||
// Given
|
||||
render(<NotificationIndicator delta="new" />);
|
||||
|
||||
// When
|
||||
const deltaTrigger = screen.getByRole("button");
|
||||
|
||||
// Then
|
||||
expect(deltaTrigger).toHaveClass("outline-none", "ring-0");
|
||||
});
|
||||
|
||||
it("uses the design-system fail color for new findings", () => {
|
||||
// Given
|
||||
const { container } = render(<NotificationIndicator delta="new" />);
|
||||
|
||||
// When
|
||||
const deltaDot = container.querySelector(".rounded-full");
|
||||
|
||||
// Then
|
||||
expect(deltaDot).toHaveClass("bg-bg-fail");
|
||||
});
|
||||
|
||||
it("uses the design-system warning color for changed findings", () => {
|
||||
// Given
|
||||
const { container } = render(<NotificationIndicator delta="changed" />);
|
||||
|
||||
// When
|
||||
const deltaDot = container.querySelector(".rounded-full");
|
||||
|
||||
// Then
|
||||
expect(deltaDot).toHaveClass("bg-bg-warning");
|
||||
});
|
||||
|
||||
it("reserves the muted slot for delta-only rows when requested", () => {
|
||||
const { container } = render(
|
||||
<NotificationIndicator delta="new" showDeltaWhenMuted reserveMutedSlot />,
|
||||
|
||||
@@ -96,12 +96,12 @@ function DeltaIndicator({
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onMouseEnter={() => setOpen(true)}
|
||||
onMouseLeave={() => setOpen(false)}
|
||||
className="flex w-2 shrink-0 cursor-pointer items-center justify-center bg-transparent p-0"
|
||||
className="flex w-2 shrink-0 cursor-pointer items-center justify-center bg-transparent p-0 ring-0 outline-none"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"size-1.5 rounded-full",
|
||||
delta === DeltaValues.NEW ? "bg-bg-data-high" : "bg-bg-data-low",
|
||||
delta === DeltaValues.NEW ? "bg-bg-fail" : "bg-bg-warning",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { IntegrationProps } from "@/types/integrations";
|
||||
|
||||
import { JiraIntegrationForm } from "./jira-integration-form";
|
||||
|
||||
vi.mock("@/actions/integrations", () => ({
|
||||
createIntegration: vi.fn(),
|
||||
updateIntegration: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/shadcn", async (importOriginal) => ({
|
||||
...(await importOriginal<Record<string, unknown>>()),
|
||||
useToast: () => ({
|
||||
toast: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
const integration: IntegrationProps = {
|
||||
type: "integrations",
|
||||
id: "integration-1",
|
||||
attributes: {
|
||||
inserted_at: "2026-07-28T00:00:00Z",
|
||||
updated_at: "2026-07-28T00:00:00Z",
|
||||
enabled: true,
|
||||
connected: true,
|
||||
connection_last_checked_at: "2026-07-28T00:00:00Z",
|
||||
integration_type: "jira",
|
||||
configuration: {
|
||||
domain: "prowler.atlassian.net",
|
||||
},
|
||||
},
|
||||
links: {
|
||||
self: "/integrations/integration-1",
|
||||
},
|
||||
};
|
||||
|
||||
describe("JiraIntegrationForm", () => {
|
||||
it("uses the short update label when editing credentials", () => {
|
||||
render(
|
||||
<JiraIntegrationForm
|
||||
integration={integration}
|
||||
onSuccess={vi.fn()}
|
||||
onCancel={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole("button", { name: "Update" })).toBeVisible();
|
||||
expect(
|
||||
screen.queryByRole("button", { name: "Update Credentials" }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -209,7 +209,7 @@ export const JiraIntegrationForm = ({
|
||||
|
||||
const getButtonLabel = () => {
|
||||
if (isEditing) {
|
||||
return "Update Credentials";
|
||||
return "Update";
|
||||
}
|
||||
return "Create Integration";
|
||||
};
|
||||
|
||||
@@ -226,6 +226,10 @@ describe("S3IntegrationForm", () => {
|
||||
expect(
|
||||
screen.queryByLabelText(/Bucket owner account ID/i),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "Update" })).toBeVisible();
|
||||
expect(
|
||||
screen.queryByRole("button", { name: "Update Configuration" }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should allow changing the bucket owner account for credential updates", () => {
|
||||
@@ -240,5 +244,9 @@ describe("S3IntegrationForm", () => {
|
||||
expect(
|
||||
screen.getByLabelText(/Bucket owner account ID/i),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "Update" })).toBeVisible();
|
||||
expect(
|
||||
screen.queryByRole("button", { name: "Update Credentials" }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -433,11 +433,9 @@ export const S3IntegrationForm = ({
|
||||
const renderStepButtons = () => {
|
||||
// Single edit mode (configuration or credentials)
|
||||
if (isEditingConfig || isEditingCredentials) {
|
||||
const updateText = isEditingConfig
|
||||
? "Update Configuration"
|
||||
: "Update Credentials";
|
||||
const updateText = "Update";
|
||||
const loadingText = isEditingConfig
|
||||
? "Updating Configuration..."
|
||||
? "Updating..."
|
||||
: "Updating Credentials...";
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import type { ComponentProps } from "react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { IntegrationProps } from "@/types/integrations";
|
||||
|
||||
import { SecurityHubIntegrationForm } from "./security-hub-integration-form";
|
||||
|
||||
vi.mock("@/actions/integrations", () => ({
|
||||
createIntegration: vi.fn(),
|
||||
updateIntegration: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("next-auth/react", () => ({
|
||||
useSession: () => ({
|
||||
data: {
|
||||
tenantId: "tenant-id",
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/shadcn", async (importOriginal) => ({
|
||||
...(await importOriginal<Record<string, unknown>>()),
|
||||
useToast: () => ({
|
||||
toast: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock(
|
||||
"@/components/providers/workflow/forms/select-credentials-type/aws/credentials-type/aws-role-credentials-form",
|
||||
() => ({
|
||||
AWSRoleCredentialsForm: () => null,
|
||||
}),
|
||||
);
|
||||
|
||||
vi.mock("@/lib", () => ({
|
||||
getAWSCredentialsTemplateLinks: () => ({
|
||||
cloudformation: "https://example.com/cloudformation",
|
||||
terraform: "https://example.com/terraform",
|
||||
cloudformationQuickLink: "https://example.com/quick-create",
|
||||
}),
|
||||
}));
|
||||
|
||||
function renderSecurityHubIntegrationForm(
|
||||
props?: Partial<ComponentProps<typeof SecurityHubIntegrationForm>>,
|
||||
) {
|
||||
return render(
|
||||
<SecurityHubIntegrationForm
|
||||
providers={[]}
|
||||
onSuccess={vi.fn()}
|
||||
onCancel={vi.fn()}
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
const integration: IntegrationProps = {
|
||||
type: "integrations",
|
||||
id: "integration-1",
|
||||
attributes: {
|
||||
inserted_at: "2026-07-28T00:00:00Z",
|
||||
updated_at: "2026-07-28T00:00:00Z",
|
||||
enabled: true,
|
||||
connected: true,
|
||||
connection_last_checked_at: "2026-07-28T00:00:00Z",
|
||||
integration_type: "aws_security_hub",
|
||||
configuration: {
|
||||
send_only_fails: true,
|
||||
archive_previous_findings: false,
|
||||
},
|
||||
},
|
||||
relationships: {
|
||||
providers: {
|
||||
data: [{ type: "providers", id: "aws-provider" }],
|
||||
},
|
||||
},
|
||||
links: {
|
||||
self: "/integrations/integration-1",
|
||||
},
|
||||
};
|
||||
|
||||
describe("SecurityHubIntegrationForm", () => {
|
||||
it("uses the short update label when editing configuration", () => {
|
||||
renderSecurityHubIntegrationForm({
|
||||
integration,
|
||||
editMode: "configuration",
|
||||
});
|
||||
|
||||
expect(screen.getByRole("button", { name: "Update" })).toBeVisible();
|
||||
expect(
|
||||
screen.queryByRole("button", { name: "Update Configuration" }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("uses the short update label when editing credentials", () => {
|
||||
renderSecurityHubIntegrationForm({
|
||||
integration,
|
||||
editMode: "credentials",
|
||||
});
|
||||
|
||||
expect(screen.getByRole("button", { name: "Update" })).toBeVisible();
|
||||
expect(
|
||||
screen.queryByRole("button", { name: "Update Credentials" }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -481,11 +481,9 @@ export const SecurityHubIntegrationForm = ({
|
||||
|
||||
const renderStepButtons = () => {
|
||||
if (isEditingConfig || isEditingCredentials) {
|
||||
const updateText = isEditingConfig
|
||||
? "Update Configuration"
|
||||
: "Update Credentials";
|
||||
const updateText = "Update";
|
||||
const loadingText = isEditingConfig
|
||||
? "Updating Configuration..."
|
||||
? "Updating..."
|
||||
: "Updating Credentials...";
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,7 +2,7 @@ import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { FilterOption } from "@/types/filters";
|
||||
import type { FilterOption } from "@/types/filters";
|
||||
|
||||
// ── next/navigation mock ────────────────────────────────────────────────────
|
||||
const mockPush = vi.fn();
|
||||
@@ -94,6 +94,37 @@ vi.mock("@/components/shadcn/select/multiselect", () => ({
|
||||
}) => <option value={value}>{children}</option>,
|
||||
}));
|
||||
|
||||
vi.mock("@/components/shadcn/select/select", () => ({
|
||||
Select: ({
|
||||
children,
|
||||
value,
|
||||
onValueChange,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
value?: string;
|
||||
onValueChange?: (value: string) => void;
|
||||
}) => (
|
||||
<select
|
||||
data-testid="single-select"
|
||||
value={value}
|
||||
onChange={(event) => onValueChange?.(event.target.value)}
|
||||
>
|
||||
<option value="">All</option>
|
||||
{children}
|
||||
</select>
|
||||
),
|
||||
SelectTrigger: () => null,
|
||||
SelectValue: () => null,
|
||||
SelectContent: ({ children }: { children: React.ReactNode }) => children,
|
||||
SelectItem: ({
|
||||
children,
|
||||
value,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
value: string;
|
||||
}) => <option value={value}>{children}</option>,
|
||||
}));
|
||||
|
||||
// ── ClearFiltersButton stub ─────────────────────────────────────────────────
|
||||
vi.mock("@/components/filters/clear-filters-button", () => ({
|
||||
ClearFiltersButton: () => <button type="button">Clear</button>,
|
||||
@@ -137,6 +168,13 @@ const scanFilter: FilterOption = {
|
||||
width: "wide",
|
||||
};
|
||||
|
||||
const deltaFilter = {
|
||||
key: "filter[delta]",
|
||||
labelCheckboxGroup: "Delta",
|
||||
values: ["new", "changed"],
|
||||
selectionMode: "single",
|
||||
} as const satisfies FilterOption;
|
||||
|
||||
describe("DataTableFilterCustom — batch vs instant mode", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -270,6 +308,26 @@ describe("DataTableFilterCustom — batch vs instant mode", () => {
|
||||
const multiselect = screen.getByTestId("multiselect");
|
||||
expect(multiselect).toHaveAttribute("data-values", JSON.stringify([]));
|
||||
});
|
||||
|
||||
it("should replace the delta value through a single-select control", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onBatchChange = vi.fn();
|
||||
|
||||
render(
|
||||
<DataTableFilterCustom
|
||||
filters={[deltaFilter]}
|
||||
mode="batch"
|
||||
onBatchChange={onBatchChange}
|
||||
getFilterValue={() => ["new"]}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId("multiselect")).not.toBeInTheDocument();
|
||||
|
||||
await user.selectOptions(screen.getByTestId("single-select"), "changed");
|
||||
|
||||
expect(onBatchChange).toHaveBeenCalledWith("filter[delta]", ["changed"]);
|
||||
});
|
||||
});
|
||||
|
||||
// ── hideClearButton ──────────────────────────────────────────────────────
|
||||
|
||||
@@ -15,6 +15,13 @@ import {
|
||||
MultiSelectTrigger,
|
||||
MultiSelectValue,
|
||||
} from "@/components/shadcn/select/multiselect";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/shadcn/select/select";
|
||||
import { useUrlFilters } from "@/hooks/use-url-filters";
|
||||
import {
|
||||
getScanEntityLabel,
|
||||
@@ -28,7 +35,11 @@ import {
|
||||
ProviderEntity,
|
||||
ScanEntity,
|
||||
} from "@/types";
|
||||
import { DATA_TABLE_FILTER_MODE, DataTableFilterMode } from "@/types/filters";
|
||||
import {
|
||||
DATA_TABLE_FILTER_MODE,
|
||||
FILTER_SELECTION_MODE,
|
||||
type DataTableFilterMode,
|
||||
} from "@/types/filters";
|
||||
import { ProviderConnectionStatus } from "@/types/providers";
|
||||
|
||||
function isNonEmptyString(value: string | null | undefined): value is string {
|
||||
@@ -234,6 +245,43 @@ export const DataTableFilterCustom = ({
|
||||
{sortedFilters().map((filter) => {
|
||||
const selectedValues = getSelectedValues(filter);
|
||||
|
||||
if (filter.selectionMode === FILTER_SELECTION_MODE.SINGLE) {
|
||||
const selectedValue = selectedValues[0] ?? "";
|
||||
|
||||
return (
|
||||
<Select
|
||||
key={filter.key}
|
||||
allowDeselect
|
||||
open={openFilterKey === filter.key}
|
||||
onOpenChange={(open) =>
|
||||
setOpenFilterKey(open ? filter.key : null)
|
||||
}
|
||||
value={selectedValue}
|
||||
onValueChange={(value) =>
|
||||
pushDropdownFilter(filter, value ? [value] : [])
|
||||
}
|
||||
>
|
||||
<SelectTrigger aria-label={filter.labelCheckboxGroup}>
|
||||
<SelectValue placeholder={`All ${filter.labelCheckboxGroup}`} />
|
||||
</SelectTrigger>
|
||||
<SelectContent width={filter.width ?? "default"}>
|
||||
{filter.values.map((value) => {
|
||||
const entity = getEntityForValue(filter, value);
|
||||
const displayLabel = filter.labelFormatter
|
||||
? filter.labelFormatter(value)
|
||||
: value;
|
||||
|
||||
return (
|
||||
<SelectItem key={value} value={value}>
|
||||
{entity ? renderEntityContent(entity) : displayLabel}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MultiSelect
|
||||
key={filter.key}
|
||||
|
||||
@@ -107,7 +107,10 @@ const ToastDescription = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm opacity-90", className)}
|
||||
className={cn(
|
||||
"max-h-48 overflow-x-hidden overflow-y-auto text-sm break-all whitespace-pre-wrap opacity-90",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { Toaster } from "./Toaster";
|
||||
|
||||
const LONG_ERROR =
|
||||
"AWSAssumeRoleError[1012]: arn:aws:sts::106908755756:assumed-role/prowler-cloud-task-20241002104346361000000003/00f08a82c8ee4e07ba";
|
||||
|
||||
vi.mock("./use-toast", () => ({
|
||||
useToast: () => ({
|
||||
toasts: [
|
||||
{
|
||||
id: "long-error",
|
||||
title: "Connection test failed",
|
||||
description: LONG_ERROR,
|
||||
variant: "destructive",
|
||||
open: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("Toaster", () => {
|
||||
it("wraps long error messages inside the toast width", () => {
|
||||
render(<Toaster />);
|
||||
|
||||
const description = screen.getByText(LONG_ERROR);
|
||||
|
||||
expect(description).toHaveClass(
|
||||
"max-h-48",
|
||||
"overflow-x-hidden",
|
||||
"overflow-y-auto",
|
||||
"break-all",
|
||||
"whitespace-pre-wrap",
|
||||
);
|
||||
expect(description.parentElement).toHaveClass(
|
||||
"min-w-0",
|
||||
"max-w-full",
|
||||
"flex-1",
|
||||
"overflow-x-hidden",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -19,7 +19,7 @@ export function Toaster() {
|
||||
{toasts.map(function ({ id, title, description, action, ...props }) {
|
||||
return (
|
||||
<Toast key={id} {...props}>
|
||||
<div className="grid gap-1">
|
||||
<div className="grid max-w-full min-w-0 flex-1 gap-1 overflow-x-hidden">
|
||||
{title && <ToastTitle>{title}</ToastTitle>}
|
||||
{description && (
|
||||
<ToastDescription>{description}</ToastDescription>
|
||||
|
||||
@@ -11,10 +11,19 @@ export type FilterEntity =
|
||||
| ProviderConnectionStatus
|
||||
| GroupFilterEntity;
|
||||
|
||||
export const FILTER_SELECTION_MODE = {
|
||||
SINGLE: "single",
|
||||
MULTIPLE: "multiple",
|
||||
} as const;
|
||||
|
||||
export type FilterSelectionMode =
|
||||
(typeof FILTER_SELECTION_MODE)[keyof typeof FILTER_SELECTION_MODE];
|
||||
|
||||
export interface FilterOption {
|
||||
key: string;
|
||||
labelCheckboxGroup: string;
|
||||
values: string[];
|
||||
selectionMode?: FilterSelectionMode;
|
||||
width?: "default" | "wide";
|
||||
valueLabelMapping?: Array<{ [uid: string]: FilterEntity }>;
|
||||
labelFormatter?: (value: string) => string;
|
||||
|
||||
Reference in New Issue
Block a user