134 lines · 5.1 KB · TypeScript
RawDownload
/** * Asking someone to review, from the row that says who is already reviewing. * * The people who may be asked are read only once this menu opens: on a large repository that is * a list of everyone with access, which is worth a request when somebody wants it and worth * nothing on every pull request they merely open. */import type {  EnvironmentId,  PullRequestRef,  PullRequestReviewerCandidate,} from "@t3tools/contracts";import { CheckIcon, UserPlusIcon } from "lucide-react";import { useMemo, useState } from "react"; import { pullRequestEnvironment } from "~/state/pullRequests";import { useEnvironmentQuery } from "~/state/query";import { useAtomCommand } from "~/state/use-atom-command";import { squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime"; import { toastManager } from "../ui/toast";import { PullRequestCandidatePicker } from "./PullRequestCandidatePicker";import { PullRequestActorLabel } from "./pullRequestPresentation";import { readableFailure } from "./pullRequestDetail.logic"; /** Long lists are common — an organisation repository lists everyone — so what arrived can be * narrowed here. It narrows only what arrived: the host is asked once, when the menu opens. */function matches(candidate: PullRequestReviewerCandidate, query: string): boolean {  if (query.length === 0) return true;  const needle = query.toLowerCase();  return (    candidate.login.toLowerCase().includes(needle) ||    (candidate.name ?? "").toLowerCase().includes(needle)  );} export function PullRequestReviewerPicker({  environmentId,  reference,  allowed,}: {  environmentId: EnvironmentId;  reference: PullRequestRef;  /** False where the host would refuse this account's request, which is worth saying rather than   * hiding: the control disabled with a reason answers the question its absence would raise. */  allowed: boolean;}) {  const [open, setOpen] = useState(false);  const [query, setQuery] = useState("");  const [pending, setPending] = useState<string | null>(null);   // Mounted with the menu closed, so nothing is asked of the host until it opens.  const candidatesQuery = useEnvironmentQuery(    open ? pullRequestEnvironment.reviewerCandidates({ environmentId, input: reference }) : null,  );  const requestReviewers = useAtomCommand(pullRequestEnvironment.requestReviewers, {    reportFailure: false,  });   const candidates = useMemo(    () => (candidatesQuery.data?.candidates ?? []).filter((entry) => matches(entry, query)),    [candidatesQuery.data, query],  );   const toggle = async (candidate: PullRequestReviewerCandidate) => {    if (pending !== null) return;    setPending(candidate.id);    const result = await requestReviewers({      environmentId,      input: {        ...reference,        reviewers: [{ id: candidate.id, kind: candidate.kind }],        requested: !candidate.isRequested,      },    });    setPending(null);    if (result._tag === "Failure") {      toastManager.add({        type: "error",        title: candidate.isRequested          ? `Could not take back the review request to ${candidate.login}`          : `Could not ask ${candidate.login} for a review`,        description: readableFailure(          squashAtomCommandFailure(result),          "The host refused it. Check that you have write access on this repository, and that they still have access to it.",        ),      });      return;    }    toastManager.add({      type: "success",      title: candidate.isRequested        ? `Review request to ${candidate.login} taken back`        : `Review requested from ${candidate.login}`,    });  };   return (    <PullRequestCandidatePicker      icon={<UserPlusIcon className="size-3.5" />}      label="Request a review"      allowed={allowed}      disabledReason="Asking someone to review needs write access on this repository"      open={open}      onOpenChange={setOpen}      query={query}      onQueryChange={setQuery}      searchLabel="Search people with access"      isPending={candidatesQuery.isPending && candidatesQuery.data === null}      error={candidatesQuery.data === null ? candidatesQuery.error : null}      candidates={candidates}      emptyLabel="Nobody else has access to this repository."      noMatchLabel="Nobody with access matches that."      errorLabel="The people with access could not be read."      truncated={candidatesQuery.data?.truncated === true}      truncatedLabel="This repository has more people with access than are listed here. Ask for the rest on the host."      candidateKey={(candidate) => `${candidate.kind}:${candidate.id}`}      disabled={pending !== null}      onSelect={(candidate) => void toggle(candidate)}    >      {(candidate) => (        <>          <PullRequestActorLabel actor={candidate} className="min-w-0 flex-1 truncate" />          {candidate.kind === "team" ? (            <span className="shrink-0 text-muted-foreground">team</span>          ) : null}          {candidate.isRequested ? (            <CheckIcon aria-label="Already asked" className="size-3.5 shrink-0" />          ) : null}        </>      )}    </PullRequestCandidatePicker>  );}