← Files Codex Security CloudARCHIVED FILE

.internal/defense-factory-ui/src/workbench/findings-filters.tsx

17.5 KB · Oct 3, 2026 · 06:25 UTC

↓ Download file

import { WorkbenchButton as Button } from "./controls";
import { Select } from "@oai/ds/ui/select";
import { Code as CodeIcon } from "@oai/ds/icons/code";
import { CheckCircleDashed as StatusIcon } from "@oai/ds/icons/check-circle-dashed";
import { Filter as FilterIcon } from "@oai/ds/icons/filter";
import { Speed as SeverityIcon } from "@oai/ds/icons/speed";
import { X as CloseIcon } from "@oai/ds/icons/x";
import { Menu } from "@oai/ds/ui/menu";
import type { ReactNode } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useLocation, useNavigate } from "react-router";

import { useMediaQuery } from "../hooks/use-media-query";
import {
  findingFilters,
  getCsvFilterValues,
  getFindingFilterValues,
} from "./findings-fields";
import {
  getCodexSecurityFindingsFilterState,
  getCodexSecurityFindingsStatusSearch,
  CLOSED_FINDING_STATUSES,
  OPEN_FINDING_STATUSES,
} from "./findings-utils";
import { CRITICALITY_MESSAGES, STATUS_MESSAGES } from "./messages";
import {
  parseFindingSource,
  FINDING_SOURCES,
  findingSourceLabels,
  type FindingSourceType,
} from "./finding-source-types";
import { scanTypeMessages } from "./scan-types";
import type { FindingCriticality } from "./types";

const CRITICALITY_OPTIONS: FindingCriticality[] = [
  "critical",
  "high",
  "medium",
  "low",
  "informational",
];

const STATUS_OPTIONS = [...OPEN_FINDING_STATUSES, ...CLOSED_FINDING_STATUSES];

type FilterSection = "severity" | "scanType" | "status";

type ActiveFindingFilter = {
  key: string;
  label: string;
  param: string;
  value?: string;
};

export function FindingsStatusView() {
  const intl = useIntl();
  const location = useLocation();
  const navigate = useNavigate();
  const { statusView } = getCodexSecurityFindingsFilterState(location.search);
  const views = [
    {
      value: "open",
      label: intl.formatMessage({
        id: "defenseFactory.findings.view.open",
        defaultMessage: "Open findings",
        description:
          "Finding view containing new, triaged, and in-progress findings.",
      }),
    },
    {
      value: "closed",
      label: intl.formatMessage({
        id: "defenseFactory.findings.view.closed",
        defaultMessage: "Closed findings",
        description:
          "Finding view containing fixed, won't-fix, duplicate, and false-positive findings.",
      }),
    },
    {
      value: "all",
      label: intl.formatMessage({
        id: "defenseFactory.findings.view.all",
        defaultMessage: "All findings",
        description: "Finding view containing all finding statuses.",
      }),
    },
  ];
  return (
    <Select
      value={statusView}
      aria-label={intl.formatMessage({
        id: "defenseFactory.findings.view.label",
        defaultMessage: "Finding view",
        description: "Accessible label for the finding status view selector.",
      })}
      triggerClassName="df-control df-findings-view"
      options={
        statusView === "custom"
          ? [
              ...views,
              {
                value: "custom",
                label: intl.formatMessage({
                  id: "defenseFactory.findings.view.custom",
                  defaultMessage: "Custom statuses",
                  description:
                    "Finding view with individually selected statuses.",
                }),
              },
            ]
          : views
      }
      onChange={({ value }) => {
        if (value !== "open" && value !== "closed" && value !== "all") return;
        navigate(
          {
            search: getCodexSecurityFindingsStatusSearch(
              location.search,
              value,
            ),
            hash: location.hash,
          },
          { replace: true },
        );
      }}
    />
  );
}

function FindingsFilterChoice({
  checked,
  children,
  onCheckedChange,
}: {
  checked: boolean;
  children: ReactNode;
  onCheckedChange: () => void;
}) {
  return (
    <Menu.CheckboxItem
      checked={checked}
      onCheckedChange={onCheckedChange}
      onSelect={(event: Event) => event.preventDefault()}
    >
      {children}
    </Menu.CheckboxItem>
  );
}

export function FindingsFilterMenu({
  onClearFilters,
}: {
  onClearFilters: () => void;
}) {
  const intl = useIntl();
  const location = useLocation();
  const navigate = useNavigate();
  // Side-by-side menus need room for both the category list and its choices.
  const useSubmenus = useMediaQuery("(min-width: 640px)");
  const activeFilterCount = findingFilters.reduce(
    (count, filter) =>
      count +
      (filter.kind === "keyword"
        ? 0
        : getFindingFilterValues(location.search, filter).length),
    0,
  );
  const selectedCriticalities = new Set(
    getCsvFilterValues(location.search, "criticality"),
  );
  const selectedStatuses = new Set(
    getCsvFilterValues(location.search, "status"),
  );
  const selectedScanType = parseFindingSource(
    new URLSearchParams(location.search).get("source"),
  );

  const sections: Array<{
    id: FilterSection;
    label: string;
    count: number;
    icon: ReactNode;
  }> = [
    {
      id: "severity",
      label: intl.formatMessage(
        findingFilters.find((filter) => filter.kind === "severity")!.label,
      ),
      count: selectedCriticalities.size,
      icon: <SeverityIcon className="icon-sm" aria-hidden />,
    },
    {
      id: "scanType",
      label: intl.formatMessage(scanTypeMessages.label),
      count: selectedScanType ? 1 : 0,
      icon: <CodeIcon className="icon-sm" aria-hidden />,
    },
    {
      id: "status",
      label: intl.formatMessage(
        findingFilters.find((filter) => filter.kind === "status")!.label,
      ),
      count: selectedStatuses.size,
      icon: <StatusIcon className="icon-sm" aria-hidden />,
    },
  ];

  function changeSearch(params: URLSearchParams) {
    params.delete("cursor");
    params.delete("page");
    const nextSearch = params.toString();
    navigate(
      { search: nextSearch ? `?${nextSearch}` : "", hash: location.hash },
      { replace: true },
    );
  }

  function toggleCsvValue(param: string, value: string) {
    const params = new URLSearchParams(location.search);
    const values = new Set(getCsvFilterValues(location.search, param));
    if (values.has(value)) values.delete(value);
    else values.add(value);
    if (values.size > 0) params.set(param, Array.from(values).join(","));
    else if (param === "status") params.set(param, "");
    else params.delete(param);
    if (param === "criticality") params.delete("sev");
    changeSearch(params);
  }

  function changeScanType(scanType: FindingSourceType | "all") {
    const params = new URLSearchParams(location.search);
    if (scanType === "all") params.delete("source");
    else params.set("source", scanType);
    changeSearch(params);
  }

  function sectionChoices(section: FilterSection) {
    return section === "severity" ? (
      <div className="flex flex-col">
        {CRITICALITY_OPTIONS.map((criticality) => (
          <FindingsFilterChoice
            key={criticality}
            checked={selectedCriticalities.has(criticality)}
            onCheckedChange={() => toggleCsvValue("criticality", criticality)}
          >
            {intl.formatMessage(CRITICALITY_MESSAGES[criticality])}
          </FindingsFilterChoice>
        ))}
      </div>
    ) : section === "status" ? (
      <div className="flex flex-col">
        <FindingsFilterChoice
          checked={selectedStatuses.size === 0}
          onCheckedChange={() => {
            const params = new URLSearchParams(location.search);
            params.set("status", "");
            changeSearch(params);
          }}
        >
          <FormattedMessage
            id="codex.security.workbench.findings.filters.allStatuses"
            defaultMessage="All statuses"
            description="Finding filter option that includes findings in every status."
          />
        </FindingsFilterChoice>
        {STATUS_OPTIONS.map((status) => (
          <FindingsFilterChoice
            key={status}
            checked={selectedStatuses.has(status)}
            onCheckedChange={() => toggleCsvValue("status", status)}
          >
            {intl.formatMessage(STATUS_MESSAGES[status])}
          </FindingsFilterChoice>
        ))}
      </div>
    ) : (
      <Menu.RadioGroup<FindingSourceType | "all">
        value={selectedScanType ?? "all"}
        onChange={changeScanType}
      >
        <Menu.RadioItem
          value="all"
          onSelect={(event: Event) => event.preventDefault()}
        >
          {intl.formatMessage(scanTypeMessages.all)}
        </Menu.RadioItem>
        {FINDING_SOURCES.map((scanType) => (
          <Menu.RadioItem
            key={scanType}
            value={scanType}
            onSelect={(event: Event) => event.preventDefault()}
          >
            {intl.formatMessage(findingSourceLabels[scanType])}
          </Menu.RadioItem>
        ))}
      </Menu.RadioGroup>
    );
  }

  return (
    <Menu>
      <Menu.Trigger>
        <Button
          type="button"
          color="outlineSurface"
          size="toolbar"
          aria-label={intl.formatMessage(
            {
              id: "codex.security.workbench.findings.filters.active_aria",
              defaultMessage:
                "Filter findings, {count, plural, one {# active filter} other {# active filters}}",
              description:
                "Accessible label on the Filters button above the Security Findings table. The count includes selected severities, scan type, and statuses, but excludes search and repository scope.",
            },
            { count: activeFilterCount },
          )}
        >
          <FilterIcon className="icon-sm" aria-hidden />
          <FormattedMessage
            id="codex.security.workbench.filters.button.label"
            defaultMessage="Filters"
            description="Short button label above the Security findings table. Opens the table filter categories."
          />
          {activeFilterCount > 0 ? (
            <span
              aria-hidden
              className="bg-token-bg-tertiary text-token-text-primary inline-flex h-5 min-w-5 items-center justify-center rounded px-1 text-xs font-medium"
            >
              {intl.formatNumber(activeFilterCount)}
            </span>
          ) : null}
        </Button>
      </Menu.Trigger>
      <Menu.Content align="end" minWidth={240}>
        {sections.map((section) =>
          useSubmenus ? (
            <Menu.Sub key={section.id}>
              <Menu.SubTrigger>
                {section.icon}
                <span className="min-w-0 flex-1 truncate">{section.label}</span>
                {section.count > 0 ? (
                  <span className="text-token-text-secondary tabular-nums">
                    {section.count}
                  </span>
                ) : null}
              </Menu.SubTrigger>
              <Menu.SubContent minWidth={220} maxHeight={400}>
                {sectionChoices(section.id)}
              </Menu.SubContent>
            </Menu.Sub>
          ) : (
            <div key={section.id} role="group" aria-label={section.label}>
              <div className="text-token-text-secondary px-3 py-2 text-sm font-medium">
                {section.label}
              </div>
              {sectionChoices(section.id)}
              <Menu.Separator />
            </div>
          ),
        )}
        {useSubmenus && <Menu.Separator />}
        <Menu.Item onSelect={onClearFilters}>
          <FormattedMessage
            id="codex.security.workbench.findings.filters.clear"
            defaultMessage="Clear filters"
            description="Button that removes visible Security finding filters."
          />
        </Menu.Item>
      </Menu.Content>
    </Menu>
  );
}

export function ActiveFindingsFilters({
  onClearFilters,
}: {
  onClearFilters: () => void;
}) {
  const intl = useIntl();
  const location = useLocation();
  const navigate = useNavigate();
  const { statusView, hasNarrowingFilters } =
    getCodexSecurityFindingsFilterState(location.search);
  const chips: ActiveFindingFilter[] = [];

  for (const filter of findingFilters) {
    if (
      filter.kind === "status" &&
      (statusView === "open" || statusView === "all")
    ) {
      continue;
    }
    const values =
      filter.kind === "status" && statusView === "closed"
        ? [
            {
              value: undefined,
              label: intl.formatMessage({
                id: "codex.security.workbench.findings.status.closed",
                defaultMessage: "Closed",
                description:
                  "Short label for Security findings in any closed state.",
              }),
            },
          ]
        : getFindingFilterValues(location.search, filter).map((value) => {
            const scanType = parseFindingSource(value);
            const message =
              filter.kind === "severity"
                ? CRITICALITY_MESSAGES[
                    value as keyof typeof CRITICALITY_MESSAGES
                  ]
                : filter.kind === "status"
                  ? STATUS_MESSAGES[value as keyof typeof STATUS_MESSAGES]
                  : filter.kind === "scanType" && scanType
                    ? findingSourceLabels[scanType]
                    : undefined;
            return {
              value: filter.kind === "keyword" ? undefined : value,
              label: message ? intl.formatMessage(message) : value,
            };
          });
    for (const { value, label } of values) {
      chips.push({
        key: `${filter.param}:${value ?? "all"}`,
        param: filter.param,
        value,
        label: intl.formatMessage(
          {
            id: "codex.security.workbench.findings.filter_chip.field",
            defaultMessage: "{field}: {value}",
            description:
              "Removable finding filter showing its field and localized value.",
          },
          { field: intl.formatMessage(filter.label), value: label },
        ),
      });
    }
  }

  const periodParams = new URLSearchParams(location.search);
  const startAt = periodParams.get("start_at");
  const endAt = periodParams.get("end_at");
  if (startAt || endAt) {
    const formatBoundary = (value: string) =>
      Number.isFinite(Date.parse(value))
        ? intl.formatDate(value, {
            year: "numeric",
            month: "short",
            day: "numeric",
            hour: "numeric",
            minute: "2-digit",
            timeZone: "UTC",
          })
        : value;
    // End is exclusive, so say "before" rather than implying an inclusive date.
    for (const [param, value, message] of [
      [
        "start_at",
        startAt,
        {
          id: "codex.security.workbench.findings.filter_chip.discoveredFrom",
          defaultMessage: "Discovered from {date} UTC",
          description: "Inclusive discovery-time filter carried from Pipeline.",
        },
      ],
      [
        "end_at",
        endAt,
        {
          id: "codex.security.workbench.findings.filter_chip.discoveredBefore",
          defaultMessage: "Discovered before {date} UTC",
          description: "Exclusive discovery-time filter carried from Pipeline.",
        },
      ],
    ] as const) {
      if (value)
        chips.push({
          key: param,
          param,
          label: intl.formatMessage(message, { date: formatBoundary(value) }),
        });
    }
  }

  function removeFilter(param: string, value?: string) {
    const next = new URLSearchParams(location.search);
    const remaining =
      value === undefined
        ? []
        : getCsvFilterValues(location.search, param).filter(
            (candidate) => candidate !== value,
          );
    if (remaining.length > 0) next.set(param, remaining.join(","));
    else if (param === "status") next.set(param, "");
    else next.delete(param);
    if (param === "criticality") next.delete("sev");
    next.delete("cursor");
    next.delete("page");
    const nextSearch = next.toString();
    navigate(
      { search: nextSearch ? `?${nextSearch}` : "", hash: location.hash },
      { replace: true },
    );
  }

  if (chips.length === 0) return null;

  return (
    <div
      className="df-findings-active-filters flex flex-wrap items-center gap-2"
      role="group"
      aria-label={intl.formatMessage({
        id: "codex.security.workbench.findings.active_filters.label",
        defaultMessage: "Active filters",
        description:
          "Accessible label for removable active-filter chips above the Security findings table.",
      })}
    >
      {chips.map((chip) => (
        <Button
          key={chip.key}
          type="button"
          color="secondary"
          size="compact"
          className="max-w-full rounded-full"
          aria-label={intl.formatMessage(
            {
              id: "codex.security.workbench.findings.filter_chip.remove",
              defaultMessage: "Remove filter: {filter}",
              description:
                "Accessible label for a removable Security finding filter chip.",
            },
            { filter: chip.label },
          )}
          onClick={() => removeFilter(chip.param, chip.value)}
        >
          <span className="truncate">{chip.label}</span>
          <CloseIcon className="icon-sm shrink-0" aria-hidden />
        </Button>
      ))}
      {hasNarrowingFilters || statusView !== "all" ? (
        <Button
          type="button"
          color="ghostSecondary"
          size="compact"
          onClick={onClearFilters}
        >
          <FormattedMessage
            id="codex.security.workbench.findings.filters.clear"
            defaultMessage="Clear filters"
            description="Button that removes visible Security finding filters."
          />
        </Button>
      ) : null}
    </div>
  );
}

SHA-256: 866d7cd607e1b7ca1a678ba93d33b36a99908015fe8c90672daf7e878e25a96f