Skip to content
Source
Sign inSign up
Open History to see the last commit

PullRequestRoutingSettings.tsx

122 lines4.5 KB

1import { useAtomValue } from "@effect/atom-react";
2import { resolveEnvironmentMachineKind } from "@t3tools/contracts";
3import {
4 pullRequestRoutingConnectionKey,
5 pullRequestRoutingPermissionFor,
6 type PullRequestRoutingPermission,
7} from "@t3tools/client-runtime/connection";
8import { useState } from "react";
9​
10import { environmentCatalog } from "~/connection/catalog";
11import type { EnvironmentPresentation } from "~/state/environments";
12import { useAtomCommand } from "~/state/use-atom-command";
13import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select";
14import { toastManager } from "../ui/toast";
15import { EnvironmentRow, EnvironmentTransportLabel } from "./EnvironmentRow";
16import { FoldedSettingsSection } from "./FoldedSettingsSection";
17import { searchableSetting } from "./settingsSearch";
18​
19const options: ReadonlyArray<{ value: PullRequestRoutingPermission; label: string }> = [
20 { value: "off", label: "Off" },
21 { value: "read", label: "Read PRs" },
22 { value: "read-write", label: "Read and act" },
23];
24​
25const summaryLabels = { "read-write": "read and act", read: "read PRs" } as const;
26​
27function summarizePullRequestRouting(
28 entries: ReadonlyArray<{
29 readonly label: string;
30 readonly permission: PullRequestRoutingPermission;
31 }>,
32): string | null {
33 const groups = (["read-write", "read"] as const).flatMap((permission) => {
34 const labels = entries.filter((entry) => entry.permission === permission);
35 return labels.length === 0
36 ? []
37 : [`${labels.map((entry) => entry.label).join(", ")} ${summaryLabels[permission]}`];
38 });
39 return groups.length === 0 ? null : groups.join(" · ");
41​
42export function PullRequestRoutingSettings({
43 environments,
44}: {
45 readonly environments: ReadonlyArray<EnvironmentPresentation>;
46}) {
47 const permissions = useAtomValue(environmentCatalog.pullRequestRoutingPermissionsValueAtom);
48 const catalog = useAtomValue(environmentCatalog.catalogValueAtom);
49 const update = useAtomCommand(environmentCatalog.setPullRequestRoutingPermission);
50 const [saving, setSaving] = useState(false);
51​
52 if (environments.length < 2) return null;
53​
54 const { id, title } = searchableSetting("pull-request-routing");
55 return (
56 <FoldedSettingsSection
57 id={id}
58 title={title}
59 summary={
60 summarizePullRequestRouting(
61 environments.map((environment) => ({
62 label: environment.label,
63 permission: pullRequestRoutingPermissionFor(environment.entry, permissions),
64 })),
65 ) ?? "Off"
66 }
67 >
68 <p className="px-3 py-2.5 text-xs text-muted-foreground sm:px-4">
69 Machines you trust here can read PR data through each other's source control access. Enable
70 both machines. Read and act may use broader permissions than the machine that owns them.
71 This applies only to this device.
72 </p>
73 {environments.map((environment) => (
74 <EnvironmentRow
75 key={environment.environmentId}
76 kind={resolveEnvironmentMachineKind(environment.serverConfig)}
77 label={environment.label}
78 subtitle={<EnvironmentTransportLabel environment={environment} />}
79 >
80 <Select
81 items={options}
82 value={pullRequestRoutingPermissionFor(environment.entry, permissions)}
83 disabled={
84 !catalog.isReady ||
85 saving ||
86 pullRequestRoutingConnectionKey(environment.entry) === null
87 }
88 onValueChange={(permission) => {
89 if (permission === null) return;
90 setSaving(true);
91 void update({ environmentId: environment.environmentId, permission }).then(
92 (result) => {
93 setSaving(false);
94 if (result._tag === "Failure")
95 toastManager.add({
96 type: "error",
97 title: "Could not save pull request routing permission",
98 });
99 },
100 );
101 }}
102 >
103 <SelectTrigger
104 size="xs"
105 className="w-32"
106 aria-label={`${environment.label} pull request routing`}
107 >
108 <SelectValue />
109 </SelectTrigger>
110 <SelectPopup align="end" alignItemWithTrigger={false}>
111 {options.map(({ value, label }) => (
112 <SelectItem key={value} value={value}>
113 {label}
114 </SelectItem>
115 ))}
116 </SelectPopup>
117 </Select>
118 </EnvironmentRow>
119 ))}
120 </FoldedSettingsSection>
121 );