8000 Simplify PortForwardButton · coder/coder@54bf987 · GitHub
[go: up one dir, main page]

Skip to content

Commit 54bf987

Browse files
committed
Simplify PortForwardButton
1 parent 26224e3 commit 54bf987

File tree

5 files changed

+83
-84
lines changed

5 files changed

+83
-84
lines changed

site/src/modules/resources/AgentMetadata.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,24 +42,24 @@ export const AgentMetadataView: FC<AgentMetadataViewProps> = ({ metadata }) => {
4242

4343
interface AgentMetadataProps {
4444
agent: WorkspaceAgent;
45-
storybookMetadata?: WorkspaceAgentMetadata[];
45+
initialMetadata?: WorkspaceAgentMetadata[];
4646
}
4747

4848
const maxSocketErrorRetryCount = 3;
4949

5050
export const AgentMetadata: FC<AgentMetadataProps> = ({
5151
agent,
52-
storybookMetadata,
52+
initialMetadata,
5353
}) => {
54-
const [activeMetadata, setActiveMetadata] = useState(storybookMetadata);
54+
const [activeMetadata, setActiveMetadata] = useState(initialMetadata);
5555
useEffect(() => {
5656
// This is an unfortunate pitfall with this component's testing setup,
57-
// but even though we use the value of storybookMetadata as the initial
57+
// but even though we use the value of initialMetadata as the initial
5858
// value of the activeMetadata, we cannot put activeMetadata itself into
5959
// the dependency array. If we did, we would destroy and rebuild each
6060
// connection every single time a new message comes in from the socket,
6161
// because the socket has to be wired up to the state setter
62-
if (storybookMetadata !== undefined) {
62+
if (initialMetadata !== undefined) {
6363
return;
6464
}
6565

@@ -118,7 +118,7 @@ export const AgentMetadata: FC<AgentMetadataProps> = ({
118118
window.clearTimeout(timeoutId);
119119
activeSocket?.close();
120120
};
121-
}, [agent.id, storybookMetadata]);
121+
}, [agent.id, initialMetadata]);
122122

123123
if (activeMetadata === undefined) {
124124
return (

site/src/modules/resources/AgentRow.stories.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ const meta: Meta<typeof AgentRow> = {
9696
},
9797
workspace: M.MockWorkspace,
9898
showApps: true,
99-
storybookAgentMetadata: defaultAgentMetadata,
99+
initialMetadata: defaultAgentMetadata,
100100
},
101101
decorators: [withProxyProvider(), withDashboardProvider, withWebSocket],
102102
parameters: {
@@ -162,7 +162,7 @@ export const BunchOfApps: Story = {
162162
export const Connecting: Story = {
163163
args: {
164164
agent: M.MockWorkspaceAgentConnecting,
165-
storybookAgentMetadata: [],
165+
initialMetadata: [],
166166
},
167167
};
168168

@@ -190,7 +190,7 @@ export const Started: Story = {
190190
export const StartedNoMetadata: Story = {
191191
args: {
192192
...Started.args,
193-
storybookAgentMetadata: [],
193+
initialMetadata: [],
194194
},
195195
};
196196

site/src/modules/resources/AgentRow.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,12 @@ import { TerminalLink } from "./TerminalLink/TerminalLink";
4343
import { VSCodeDesktopButton } from "./VSCodeDesktopButton/VSCodeDesktopButton";
4444
import { useAgentLogs } from "./useAgentLogs";
4545
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
46-
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
47-
import { buildInfo } from "api/queries/buildInfo";
4846

4947
export interface AgentRowProps {
5048
agent: WorkspaceAgent;
5149
workspace: Workspace;
5250
template: Template;
53-
storybookAgentMetadata?: WorkspaceAgentMetadata[];
51+
initialMetadata?: WorkspaceAgentMetadata[];
5452
onUpdateAgent: () => void;
5553
}
5654

@@ -59,7 +57,7 @@ export const AgentRow: FC<AgentRowProps> = ({
5957
workspace,
6058
template,
6159
onUpdateAgent,
62-
storybookAgentMetadata,
60+
initialMetadata,
6361
}) => {
6462
// Apps visibility
6563
const { browser_only } = useFeatureVisibility();
@@ -196,10 +194,8 @@ export const AgentRow: FC<AgentRowProps> = ({
196194
agent.display_apps.includes("port_forwarding_helper") && (
197195
<PortForwardButton
198196
host={proxy.preferredWildcardHostname}
199-
workspaceName={workspace.name}
197+
workspace={workspace}
200198
agent={agent}
201-
username={workspace.owner_name}
202-
workspaceID={workspace.id}
203199
template={template}
204200
/>
205201
)}
@@ -281,10 +277,7 @@ export const AgentRow: FC<AgentRowProps> = ({
281277
</section>
282278
)}
283279

284-
<AgentMetadata
285-
storybookMetadata={storybookAgentMetadata}
286-
agent={agent}
287-
/>
280+
<AgentMetadata initialMetadata={initialMetadata} agent={agent} />
288281
</div>
289282

290283
{hasStartupFeatures && (

site/src/modules/resources/PortForwardButton.tsx

Lines changed: 70 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ import {
1818
import {
1919
type Template,
2020
type UpsertWorkspaceAgentPortShareRequest,
21+
type Workspace,
2122
type WorkspaceAgent,
2223
type WorkspaceAgentListeningPort,
24+
type WorkspaceAgentPortShare,
2325
type WorkspaceAgentPortShareLevel,
2426
type WorkspaceAgentPortShareProtocol,
2527
WorkspaceAppSharingLevels,
@@ -64,121 +66,131 @@ import * as Yup from "yup";
6466

6567
interface PortForwardButtonProps {
6668
host: string;
67-
username: string;
68-
workspaceName: string;
69-
workspaceID: string;
69+
workspace: Workspace;
7070
agent: WorkspaceAgent;
7171
template: Template;
7272
}
7373

74-
export const PortForwardButton: FC<PortForwardButtonProps> = (props) => {
75-
const { agent } = props;
74+
export const PortForwardButton: FC<PortForwardButtonProps> = ({
75+
host,
76+
workspace,
77+
template,
78+
agent,
79+
}) => {
7680
const { entitlements } = useDashboard();
7781
const paper = useClassName(classNames.paper, []);
7882

79-
const portsQuery = useQuery({
83+
const { data: listeningPorts } = useQuery({
8084
queryKey: ["portForward", agent.id],
8185
queryFn: () => API.getAgentListeningPorts(agent.id),
8286
enabled: agent.status === "connected",
8387
refetchInterval: 5_000,
88+
select: (res) => res.ports,
89+
});
90+
91+
const { data: sharedPorts, refetch: refetchSharedPorts } = useQuery({
92+
...workspacePortShares(workspace.id),
93+
enabled: agent.status === "connected",
94+
select: (res) => res.shares,
95+
initialData: { shares: [] },
8496
});
8597

8698
return (
8799
<Popover>
88100
<PopoverTrigger>
89-
<Button disabled={!portsQuery.data} size="sm" variant="subtle">
90-
<Spinner loading={!portsQuery.data}>
91-
<span css={styles.portCount}>{portsQuery.data?.ports.length}</span>
101+
<Button disabled={!listeningPorts} size="sm" variant="subtle">
102+
<Spinner loading={!listeningPorts}>
103+
<span css={styles.portCount}>{listeningPorts?.length}</span>
92104
</Spinner>
93105
Open ports
94106
<ChevronDownIcon className="size-4" />
95107
</Button>
96108
</PopoverTrigger>
97109
<PopoverContent horizontal="right" classes={{ paper }}>
98110
<PortForwardPopoverView
99-
{...props}
100-
listeningPorts={portsQuery.data?.ports}
111+
host={host}
112+
agent={agent}
113+
workspace={workspace}
114+
template={template}
115+
sharedPorts={sharedPorts}
116+
listeningPorts={listeningPorts ?? []}
101117
portSharingControlsEnabled={
102118
entitlements.features.control_shared_ports.enabled
103119
}
120+
refetchSharedPorts={refetchSharedPorts}
104121
/>
105122
</PopoverContent>
106123
</Popover>
107124
);
108125
};
109126

110-
const getValidationSchema = (): Yup.AnyObjectSchema =>
127+
const openPortSchema = (): Yup.AnyObjectSchema =>
111128
Yup.object({
112129
port: Yup.number().required().min(9).max(65535),
113130
share_level: Yup.string().required().oneOf(WorkspaceAppSharingLevels),
114131
});
115132

116-
interface PortForwardPopoverViewProps extends PortForwardButtonProps {
117-
listeningPorts?: readonly WorkspaceAgentListeningPort[];
133+
interface PortForwardPopoverViewProps {
134+
host: string;
135+
workspace: Workspace;
136+
agent: WorkspaceAgent;
137+
template: Template;
138+
sharedPorts: readonly WorkspaceAgentPortShare[];
139+
listeningPorts: readonly WorkspaceAgentListeningPort[];
118140
portSharingControlsEnabled: boolean;
141+
refetchSharedPorts: () => void;
119142
}
120143

121-
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
122-
123144
export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
124145
host,
125-
workspaceName,
126-
workspaceID,
146+
workspace,
127147
agent,
128148
template,
129-
username,
149+
sharedPorts,
130150
listeningPorts,
131151
portSharingControlsEnabled,
152+
refetchSharedPorts,
132153
}) => {
133154
const theme = useTheme();
134155
const [listeningPortProtocol, setListeningPortProtocol] = useState(
135-
getWorkspaceListeningPortsProtocol(workspaceID),
156+
getWorkspaceListeningPortsProtocol(workspace.id),
136157
);
137158

138-
const sharedPortsQuery = useQuery({
139-
...workspacePortShares(workspaceID),
140-
enabled: agent.status === "connected",
159+
const upsertSharedPortMutation = useMutation({
160+
...upsertWorkspacePortShare(workspace.id),
161+
onSuccess: refetchSharedPorts,
141162
});
142-
const sharedPorts = sharedPortsQuery.data?.shares || [];
143163

144-
const upsertSharedPortMutation = useMutation(
145-
upsertWorkspacePortShare(workspaceID),
146-
);
147-
148-
const deleteSharedPortMutation = useMutation(
149-
deleteWorkspacePortShare(workspaceID),
150-
);
164+
const deleteSharedPortMutation = useMutation({
165+
...deleteWorkspacePortShare(workspace.id),
166+
onSuccess: refetchSharedPorts,
167+
});
151168

152-
// share port form
153169
const {
154170
mutateAsync: upsertWorkspacePortShareForm,
155171
isPending: isSubmitting,
156172
error: submitError,
157-
} = useMutation(upsertWorkspacePortShare(workspaceID));
158-
const validationSchema = getValidationSchema();
159-
// TODO: do partial here
160-
const form: FormikContextType<
161-
Optional<UpsertWorkspaceAgentPortShareRequest, "port">
162-
> = useFormik<Optional<UpsertWorkspaceAgentPortShareRequest, "port">>({
173+
} = useMutation({
174+
...upsertWorkspacePortShare(workspace.id),
175+
onSuccess: refetchSharedPorts,
176+
});
177+
178+
const form = useFormik({
163179
initialValues: {
164180
agent_name: agent.name,
165-
port: undefined,
181+
port: "",
166182
protocol: "http",
167183
share_level: "authenticated",
168184
},
169-
validationSchema,
170-
onSubmit: async (values) => {
171-
// we need port to be optional in the initialValues so it appears empty instead of 0.
172-
// because of this we need to reset the form to clear the port field manually.
173-
form.resetForm();
174-
await form.setFieldValue("port", "");
175-
176-
const port = Number(values.port);
185+
validationSchema: openPortSchema(),
186+
onSubmit: async (values, { resetForm }) => {
187+
resetForm();
177188
await upsertWorkspacePortShareForm({
178-
...values,
179-
port,
189+
agent_name: values.agent_name,
190+
port: Number(values.port),
191+
share_level: values.share_level as WorkspaceAgentPortShareLevel,
192+
protocol: values.protocol as WorkspaceAgentPortShareProtocol,
180193
});
181-
await sharedPortsQuery.refetch();
182194
},
183195
});
184196
const getFieldHelpers = getFormHelpers(form, submitError);
@@ -188,7 +200,7 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
188200
(port) => port.agent_name === agent.name,
189201
);
190202
// we don't want to show listening ports if it's a shared port
191-
const filteredListeningPorts = (listeningPorts ?? []).filter((port) =>
203+
const filteredListeningPorts = listeningPorts.filter((port) =>
192204
filteredSharedPorts.every((sharedPort) => sharedPort.port !== port.port),
193205
);
194206
// only disable the form if shared port controls are entitled and the template doesn't allow sharing ports
@@ -257,7 +269,7 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
257269
| "https";
258270
setListeningPortProtocol(selectedProtocol);
259271
saveWorkspaceListeningPortsProtocol(
260-
workspaceID,
272+
workspace.id,
261273
selectedProtocol,
262274
);
263275
}}
@@ -276,8 +288,8 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
276288
host,
277289
port,
278290
agent.name,
279-
workspaceName,
280-
username,
291+
workspace.name,
292+
workspace.owner_name,
281293
listeningPortProtocol,
282294
);
283295
window.open(url, "_blank");
@@ -317,8 +329,8 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
317329
host,
318330
port.port,
319331
agent.name,
320-
workspaceName,
321-
username,
332+
workspace.name,
333+
workspace.owner_name,
322334
listeningPortProtocol,
323335
);
324336
const label =
@@ -371,7 +383,6 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
371383
protocol: listeningPortProtocol,
372384
share_level: "authenticated",
373385
});
374-
await sharedPortsQuery.refetch();
375386
}}
376387
>
377388
<ShareIcon />
@@ -407,8 +418,8 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
407418
host,
408419
share.port,
409420
agent.name,
410-
workspaceName,
411-
username,
421+
workspace.name,
422+
workspace.owner_name,
412423
share.protocol,
413424
);
414425
const label = share.port;
@@ -445,7 +456,6 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
445456
.value as WorkspaceAgentPortShareProtocol,
446457
share_level: share.share_level,
447458
});
448-
await sharedPortsQuery.refetch();
449459
}}
450460
>
451461
<MenuItem value="http">HTTP</MenuItem>
@@ -469,7 +479,6 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
469479
share_level: event.target
470480
.value as WorkspaceAgentPortShareLevel,
471481
});
472-
await sharedPortsQuery.refetch();
473482
}}
474483
>
475484
<MenuItem value="authenticated">Authenticated</MenuItem>
@@ -488,7 +497,6 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
488497
agent_name: agent.name,
489498
port: share.port,
490499
});
491-
await sharedPortsQuery.refetch();
492500
}}
493501
>
494502
<XIcon

0 commit comments

Comments
 (0)
0