← Files Codex Security CloudARCHIVED FILE

.internal/defense-factory-ui/src/finding-detail.tsx

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

↓ Download file

import type { components } from "@oai/aardvark-client/components";
import { ComposeEditSquare } from "@oai/ds/icons/compose-edit-square";
import { DotsHorizontal as DotsHorizontalIcon } from "@oai/ds/icons/dots-horizontal";
import { Github } from "@oai/ds/icons/github";
import { ThumbDown as ThumbDownIcon } from "@oai/ds/icons/thumb-down";
import { ThumbUp as ThumbUpIcon } from "@oai/ds/icons/thumb-up";
import { Menu } from "@oai/ds/ui/menu";
import { Select } from "@oai/ds/ui/select";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useLocation } from "react-router";

import {
  copyText,
  getShareUrl,
  logSecurityEvent,
  securityClient,
} from "./client";
import { useCloud } from "./app-context";
import { FindingChat } from "./finding-chat";
import { statusLabel } from "./labels";
import { ExternalLink } from "./link";
import { findingPatch } from "./patch-model";
import { RequestState, Section } from "./ui";
import {
  CommitFindingArchiveBanner,
  CommitFindingMetadata,
  CommitFindingPatch,
  CommitFindingReport,
  CommitFindingViewToggle,
  isFindingPatchPending,
} from "./workbench/commit-finding-content";
import {
  WorkbenchButton as Button,
  WorkbenchInput as Input,
} from "./workbench/controls";
import {
  FindingDetailHeading,
  FindingDetailToolbar,
} from "./workbench/finding-detail-layout";
import { WorkbenchState } from "./workbench/layout";
import { RepositoryFindingDetail } from "./workbench/repository-finding-detail";

function normalizeRepositoryUrlForLookup(url: string): string {
  return url
    .trim()
    .replace(/\/+$/, "")
    .replace(/\.git$/, "")
    .replace(/\/+$/, "");
}

function matchesRepositoryScope(
  finding: components["schemas"]["FindingDetail"],
  repositoryId: string | null,
  repositoryUrl: string | null,
): boolean {
  if (repositoryId) return finding.repo_id === repositoryId;
  if (!repositoryUrl) return true;
  const actual = finding.repo_url.trim().replace(/\/+$/, "");
  const expected = repositoryUrl.trim().replace(/\/+$/, "");
  if (actual.toLowerCase() === expected.toLowerCase()) return true;
  const normalizedActual =
    finding.source.kind === "repository_scan"
      ? actual
      : normalizeRepositoryUrlForLookup(actual);
  return (
    normalizedActual.toLowerCase() ===
    normalizeRepositoryUrlForLookup(expected).toLowerCase()
  );
}

export function FindingDetail({
  id,
  repositoryId = null,
  repositoryUrl = null,
}: {
  id: string;
  repositoryId?: string | null;
  repositoryUrl?: string | null;
}) {
  const cloud = useCloud();
  const {
    data: finding,
    isPending,
    isFetching,
    isFetchedAfterMount,
    error,
    refetch,
  } = useQuery({
    queryKey: ["finding", cloud.accountId, cloud.identity?.userId, id],
    queryFn: () =>
      securityClient.request({
        operation: "findings_get",
        parameters: { path: { finding_id: id } },
      }),
    staleTime: 15_000,
    refetchOnMount: "always",
    refetchInterval: (query) => {
      const commit = query.state.data?.commit_detail;
      return commit && isFindingPatchPending(commit) ? 4_000 : false;
    },
  });
  if (isPending || (isFetching && !isFetchedAfterMount))
    return (
      <WorkbenchState loading variant="panel">
        <FormattedMessage
          id="codex.security.findings.detail.loading"
          defaultMessage="Loading finding…"
          description="Loading status while an authorized Security finding is retrieved."
        />
      </WorkbenchState>
    );
  if (
    error ||
    !finding ||
    !matchesRepositoryScope(finding, repositoryId, repositoryUrl)
  )
    return (
      <WorkbenchState
        variant="panel"
        action={
          <Button
            color="secondary"
            type="button"
            onClick={() => void refetch()}
          >
            <FormattedMessage
              id="codex.security.findings.detail.retry.label"
              defaultMessage="Retry"
              description="Retry loading the current authorized Security finding."
            />
          </Button>
        }
      >
        <FormattedMessage
          id="codex.security.findings.detail.unavailable"
          defaultMessage="This finding could not be loaded."
          description="Privacy-preserving error when a Security finding is unavailable or inaccessible."
        />
      </WorkbenchState>
    );
  if (!finding.commit_detail) {
    return <RepositoryFindingDetail finding={finding} />;
  }
  return (
    <CommitFinding
      key={finding.commit_detail.hid}
      finding={finding.commit_detail}
      title={finding.title}
      refresh={() => refetch()}
    />
  );
}

function CommitFinding({
  finding,
  title,
  refresh,
}: {
  title: string;
  finding: components["schemas"]["ScanFindingResponse"];
  refresh: () => Promise<unknown>;
}) {
  const location = useLocation();
  const cache = useQueryClient();
  const intl = useIntl();
  const [editing, setEditing] = useState(false);
  const [status, setStatus] = useState(finding.status);
  const [severity, setSeverity] = useState(finding.criticality);
  const [reason, setReason] = useState("");
  const [assignee, setAssignee] = useState(finding.assignee?.email ?? "");
  const [showPatch, setShowPatch] = useState(false);
  const { patch, description, pullRequestUrl } = findingPatch(finding);
  const patchPending = isFindingPatchPending(finding);
  const {
    mutate: update,
    error,
    isPending,
  } = useMutation({
    mutationFn: (body: components["schemas"]["ScanFindingUpdate"]) =>
      securityClient.request({
        operation: "findings_update",
        parameters: { path: { id: finding.hid } },
        requestBody: { ...body, version: finding.version },
      }),
    onSuccess: async () => {
      setEditing(false);
      await Promise.all([
        refresh(),
        cache.invalidateQueries({ queryKey: ["findings"] }),
      ]);
    },
  });
  const {
    mutate: createPr,
    error: prError,
    isPending: creatingPr,
  } = useMutation({
    mutationFn: () =>
      securityClient.request({
        operation: "findings_create_pr",
        parameters: { path: { id: finding.hid } },
        requestBody: { mode: "draft" },
      }),
    onSuccess: refresh,
  });
  const {
    mutate: copy,
    error: copyError,
    isSuccess: copied,
  } = useMutation({
    mutationFn: async (link: boolean) => {
      const analysis = finding.commit_analysis;
      await copyText(
        link
          ? await getShareUrl(`${location.pathname}${location.search}`)
          : [
              title,
              `Repository: ${finding.repo_url}`,
              `Commit: ${analysis?.commit_hash ?? ""}`,
              `Author: ${analysis?.author_email ?? ""}`,
              `Created: ${finding.created_at}`,
              `Assignee: ${
                finding.assignee?.name ??
                finding.assignee?.email ??
                "Unassigned"
              }`,
              `Severity: ${finding.criticality}`,
              `Status: ${finding.status}`,
              finding.criticality_reason,
              finding.resolution_reason,
              analysis?.bugs_found_or_fixed,
              analysis?.description,
              analysis?.validation_rubric,
              analysis?.validation_report ?? analysis?.validation_str,
              patch,
              JSON.stringify(
                {
                  relevant_lines: analysis?.relevant_lines,
                  attack_path_analysis: analysis?.attack_path_analysis,
                },
                null,
                2,
              ),
            ]
              .filter(Boolean)
              .join("\n\n"),
      );
      logSecurityEvent({
        name: link
          ? "CodexSecurityFindingShared"
          : "CodexSecurityFindingCopied",
        data: {
          scanFindingId: finding.id,
          scanFindingDisplayId: finding.hid,
          status: finding.status,
          criticality: finding.criticality,
          patchGenerated:
            finding.commit_analysis?.proposed_patch_generated === true,
          sourcePage: "detail",
        },
      });
    },
  });
  function viewPatch() {
    if (!showPatch)
      logSecurityEvent({
        name: "CodexSecurityPatchViewed",
        data: {
          scanFindingId: finding.id,
          scanFindingDisplayId: finding.hid,
          status: finding.status,
          criticality: finding.criticality,
          patchGenerated:
            finding.commit_analysis?.proposed_patch_generated === true,
          patchId: finding.proposed_patch?.id,
          patchStatus: finding.proposed_patch?.status,
          patchGenerationSuccess:
            finding.proposed_patch?.latest_task?.patch_generation_success ??
            undefined,
          patchGenerationFailureReason:
            finding.proposed_patch?.latest_task
              ?.patch_generation_failure_reason ?? undefined,
        },
      });
    setShowPatch(true);
  }
  const {
    mutate: feedback,
    error: feedbackError,
    isSuccess: feedbackSaved,
    isPending: feedbackPending,
  } = useMutation({
    mutationFn: (rating: "thumbs_up" | "thumbs_down") =>
      securityClient.request({
        operation: "findings_feedback",
        parameters: { path: { id: finding.hid } },
        requestBody: { thumb_rating: rating },
      }),
  });
  return (
    <section className="df-finding-detail">
      <FindingDetailToolbar
        actions={
          <>
            {pullRequestUrl ? (
              <ExternalLink
                className="df-button df-control inline-flex items-center gap-1 border border-default px-3 text-secondary"
                href={pullRequestUrl}
              >
                <Github className="size-4" aria-hidden />
                <FormattedMessage
                  id="defenseFactory.plugin.viewPr"
                  defaultMessage="View pull request"
                  description="Open the existing fix pull request"
                />
              </ExternalLink>
            ) : patch ? (
              <Button
                color="outlineSurface"
                onClick={() => createPr()}
                loading={creatingPr}
              >
                <Github className="size-4" aria-hidden />
                <FormattedMessage
                  id="defenseFactory.plugin.createPr"
                  defaultMessage="Create draft pull request"
                  description="Create a draft pull request for the generated fix"
                />
              </Button>
            ) : (
              <Button
                color="secondary"
                disabled={isPending || patchPending}
                onClick={() => update({ manual_patch_requested: true })}
              >
                <ComposeEditSquare className="size-4" aria-hidden />
                {patchPending ? (
                  <FormattedMessage
                    id="codex.security.findings.detail.patch.generating.action"
                    defaultMessage="Generating patch"
                    description="Disabled action label while Codex generates a patch."
                  />
                ) : (
                  <FormattedMessage
                    id="codex.security.findings.detail.patch.request.action"
                    defaultMessage="Fix with Codex"
                    description="Action that requests a generated patch for a Security finding."
                  />
                )}
              </Button>
            )}
            <Menu>
              <Menu.Trigger>
                <Button
                  type="button"
                  color="ghost"
                  size="iconCircle"
                  uniform
                  aria-label={intl.formatMessage({
                    id: "codex.security.findings.detail.actions.more.label",
                    defaultMessage: "More finding actions",
                    description:
                      "Accessible label for the overflow menu on a Security finding.",
                  })}
                >
                  <DotsHorizontalIcon className="icon-sm" aria-hidden />
                </Button>
              </Menu.Trigger>
              <Menu.Content align="end" minWidth={190}>
                <Menu.Item
                  onSelect={() => {
                    setStatus(finding.status);
                    setSeverity(finding.criticality);
                    setAssignee(finding.assignee?.email ?? "");
                    setReason("");
                    setEditing(!editing);
                  }}
                >
                  <FormattedMessage
                    id="defenseFactory.plugin.triage"
                    defaultMessage="Triage finding"
                    description="Edit finding status, severity, and assignee"
                  />
                </Menu.Item>
                <Menu.Item onSelect={() => copy(false)}>
                  <FormattedMessage
                    id="defenseFactory.plugin.copyFinding"
                    defaultMessage="Copy finding"
                    description="Copy the finding report and saved evidence"
                  />
                </Menu.Item>
                <Menu.Item onSelect={() => copy(true)}>
                  <FormattedMessage
                    id="defenseFactory.plugin.copyFindingLink"
                    defaultMessage="Copy link"
                    description="Copy the shareable finding URL"
                  />
                </Menu.Item>
                <Menu.Item onSelect={() => void refresh()}>
                  <FormattedMessage
                    id="defenseFactory.plugin.refresh"
                    defaultMessage="Refresh"
                    description="Reload the current data"
                  />
                </Menu.Item>
              </Menu.Content>
            </Menu>
          </>
        }
      />
      <div className="df-finding-detail-notice">
        <CommitFindingArchiveBanner finding={finding} />
      </div>
      {copied ? (
        <span role="status" className="text-token-text-secondary text-sm">
          <FormattedMessage
            id="defenseFactory.plugin.copied"
            defaultMessage="Copied"
            description="Finding content or link was copied to the clipboard"
          />
        </span>
      ) : null}

      <div className="df-finding-detail-grid">
        <div className="df-finding-detail-main">
          <FindingDetailHeading
            title={title}
            status={finding.status}
            updatedAt={finding.updated_at}
          />
          <CommitFindingViewToggle
            showPatch={showPatch}
            onShowReport={() => setShowPatch(false)}
            onShowPatch={viewPatch}
            patchAvailable={Boolean(finding.proposed_patch) || patchPending}
          />
          <div className="min-w-0">
            {showPatch ? (
              <CommitFindingPatch
                finding={finding}
                description={description}
                patch={patch}
                pullRequestUrl={pullRequestUrl}
              />
            ) : (
              <CommitFindingReport finding={finding} />
            )}
          </div>
          <div
            role="group"
            aria-label={intl.formatMessage({
              id: "codex.security.findings.detail.feedback.group.label",
              defaultMessage: "Give feedback on this finding",
              description:
                "Accessible label for feedback controls on a Security finding.",
            })}
            className="flex items-center gap-1"
          >
            <Button
              color="ghostSecondary"
              uniform
              disabled={feedbackPending}
              aria-label={intl.formatMessage({
                id: "codex.security.findings.detail.feedback.positive.label",
                defaultMessage: "Give thumbs up feedback",
                description: "Accessible label for positive finding feedback.",
              })}
              onClick={() => feedback("thumbs_up")}
            >
              <ThumbUpIcon className="icon-sm" aria-hidden />
            </Button>
            <Button
              color="ghostSecondary"
              uniform
              disabled={feedbackPending}
              aria-label={intl.formatMessage({
                id: "codex.security.findings.detail.feedback.negative.label",
                defaultMessage: "Give thumbs down feedback",
                description: "Accessible label for negative finding feedback.",
              })}
              onClick={() => feedback("thumbs_down")}
            >
              <ThumbDownIcon className="icon-sm" aria-hidden />
            </Button>
            {feedbackSaved && (
              <span role="status" className="text-token-text-secondary text-sm">
                <FormattedMessage
                  id="defenseFactory.plugin.feedbackSaved"
                  defaultMessage="Feedback saved"
                  description="Feedback was submitted"
                />
              </span>
            )}
          </div>
        </div>

        <aside className="df-finding-detail-aside">
          <CommitFindingMetadata finding={finding} />
          <FindingChat
            id={finding.hid}
            existingTaskId={finding.followup_task_id}
          />
          {editing && (
            <Section
              title={
                <FormattedMessage
                  id="defenseFactory.plugin.triage"
                  defaultMessage="Triage finding"
                  description="Edit finding status, severity, and assignee"
                />
              }
            >
              <form
                className="flex flex-col gap-4"
                onSubmit={(event) => {
                  event.preventDefault();
                  update({
                    status: status !== finding.status ? status : undefined,
                    criticality:
                      severity !== finding.criticality ? severity : undefined,
                    resolution_reason:
                      status !== finding.status ? reason || null : undefined,
                    criticality_reason:
                      severity !== finding.criticality
                        ? reason || null
                        : undefined,
                    assignee_user_email:
                      assignee !== (finding.assignee?.email ?? "")
                        ? assignee || null
                        : undefined,
                  });
                }}
              >
                <div className="flex flex-wrap gap-3">
                  <Select
                    triggerClassName="df-control"
                    value={status}
                    options={[
                      "new",
                      "triaged",
                      "in_progress",
                      "fixed",
                      "wontfix",
                      "duplicate",
                      "false_positive",
                    ].map((value) => ({
                      value,
                      label: statusLabel(intl, value),
                    }))}
                    onChange={(option) =>
                      setStatus(option.value as typeof status)
                    }
                  />
                  <Select
                    triggerClassName="df-control"
                    value={severity}
                    options={[
                      "critical",
                      "high",
                      "medium",
                      "low",
                      "informational",
                    ].map((value) => ({
                      value,
                      label: statusLabel(intl, value),
                    }))}
                    onChange={(option) =>
                      setSeverity(option.value as typeof severity)
                    }
                  />
                </div>
                <label className="flex flex-col gap-2 text-[14px]">
                  <FormattedMessage
                    id="defenseFactory.plugin.assignee"
                    defaultMessage="Assignee email"
                    description="Email of the person assigned to this finding"
                  />
                  <Input
                    value={assignee}
                    onChange={(e) => setAssignee(e.target.value)}
                    type="email"
                  />
                </label>
                <label className="flex flex-col gap-2 text-[14px]">
                  <FormattedMessage
                    id="defenseFactory.plugin.reason"
                    defaultMessage="Reason"
                    description="Explanation of a finding status or severity change"
                  />
                  <Input
                    value={reason}
                    onChange={(e) => setReason(e.target.value)}
                  />
                </label>
                <div>
                  <Button color="primary" type="submit" loading={isPending}>
                    <FormattedMessage
                      id="defenseFactory.plugin.save"
                      defaultMessage="Save"
                      description="Save changes"
                    />
                  </Button>
                </div>
              </form>
            </Section>
          )}
          {(error || prError || feedbackError || copyError) && (
            <RequestState
              error={error ?? prError ?? feedbackError ?? copyError}
            />
          )}
        </aside>
      </div>
    </section>
  );
}

SHA-256: 2b07ad11513823a9857de7ed8a0de5a9a992aa4e3a9f7dff6f51b6145ea200c8