Skip to content

feat: add port sharing frontend #12119

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Feb 20, 2024
Prev Previous commit
Next Next commit
work on form submission
  • Loading branch information
f0ssel committed Feb 20, 2024
commit 267405b7ba953b70d430747c22ec8e8be88377ce
23 changes: 13 additions & 10 deletions site/src/modules/resources/PortForwardButton.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Meta, StoryObj } from "@storybook/react";
import {
MockListeningPortsResponse,
MockSharedPortsResponse,
MockWorkspace,
MockWorkspaceAgent,
} from "testHelpers/entities";

Expand All @@ -18,16 +19,18 @@ export default meta;
type Story = StoryObj<typeof PortForwardButton>;

export const Example: Story = {
args: {
storybook: {
listeningPortsQueryData: MockListeningPortsResponse,
sharedPortsQueryData: MockSharedPortsResponse,
},
parameters: {
queries: [
{
key: ["portForward", MockWorkspaceAgent.id],
data: MockListeningPortsResponse,
},
{
key: ["sharedPorts", MockWorkspace.id],
data: MockSharedPortsResponse,
},
],
},
};

export const Loading: Story = {
args: {
storybook: {},
},
};
export const Loading: Story = {};
73 changes: 30 additions & 43 deletions site/src/modules/resources/PortForwardButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ import {
type Template,
type WorkspaceAgent,
type WorkspaceAgentListeningPort,
type WorkspaceAgentListeningPortsResponse,
type WorkspaceAgentPortShareLevel,
type WorkspaceAgentPortShares,
UpsertWorkspaceAgentPortShareRequest,
} from "api/typesGenerated";
import { portForwardURL } from "utils/portForward";
Expand Down Expand Up @@ -56,46 +54,34 @@ export interface PortForwardButtonProps {
workspaceID: string;
agent: WorkspaceAgent;
template: Template;

/**
* Only for use in Storybook
*/
storybook?: {
listeningPortsQueryData?: WorkspaceAgentListeningPortsResponse;
sharedPortsQueryData?: WorkspaceAgentPortShares;
};
}

export const PortForwardButton: FC<PortForwardButtonProps> = (props) => {
const { agent, storybook } = props;
const { agent } = props;
const { entitlements, experiments } = useDashboard();
const paper = useClassName(classNames.paper, []);

const portsQuery = useQuery({
queryKey: ["portForward", agent.id],
queryFn: () => getAgentListeningPorts(agent.id),
enabled: !storybook && agent.status === "connected",
enabled: agent.status === "connected",
refetchInterval: 5_000,
});

const listeningPorts = storybook
? storybook.listeningPortsQueryData
: portsQuery.data;

return (
<Popover>
<PopoverTrigger>
<Button
disabled={!listeningPorts}
disabled={!portsQuery.data}
size="small"
variant="text"
endIcon={<KeyboardArrowDown />}
css={{ fontSize: 13, padding: "8px 12px" }}
startIcon={
listeningPorts ? (
portsQuery.data ? (
<div>
<span css={styles.portCount}>
{listeningPorts.ports.length}
{portsQuery.data.ports.length}
</span>
</div>
) : (
Expand All @@ -109,7 +95,7 @@ export const PortForwardButton: FC<PortForwardButtonProps> = (props) => {
<PopoverContent horizontal="right" classes={{ paper }}>
<PortForwardPopoverView
{...props}
listeningPorts={listeningPorts?.ports}
listeningPorts={portsQuery.data?.ports}
portSharingExperimentEnabled={experiments.includes("shared-ports")}
portSharingControlsEnabled={
entitlements.features.control_shared_ports.enabled
Expand Down Expand Up @@ -142,17 +128,14 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
listeningPorts,
portSharingExperimentEnabled,
portSharingControlsEnabled,
storybook,
}) => {
const theme = useTheme();

const sharedPortsQuery = useQuery({
...workspacePortShares(workspaceID),
enabled: !storybook && agent.status === "connected",
enabled: agent.status === "connected",
});
const sharedPorts = storybook
? storybook.sharedPortsQueryData?.shares || []
: sharedPortsQuery.data?.shares || [];
const sharedPorts = sharedPortsQuery.data?.shares || [];

const upsertSharedPortMutation = useMutation(
upsertWorkspacePortShare(workspaceID),
Expand All @@ -169,6 +152,7 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
error: submitError,
} = useMutation(upsertWorkspacePortShare(workspaceID));
const validationSchema = getValidationSchema();
// TODO: do partial here
const form: FormikContextType<UpsertWorkspaceAgentPortShareRequest> =
useFormik<UpsertWorkspaceAgentPortShareRequest>({
initialValues: {
Expand All @@ -191,9 +175,7 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
// we don't want to show listening ports if it's a shared port
const filteredListeningPorts = listeningPorts?.filter((port) => {
for (let i = 0; i < filteredSharedPorts.length; i++) {
if (
filteredSharedPorts[i].port === port.port
) {
if (filteredSharedPorts[i].port === port.port) {
return false;
}
}
Expand Down Expand Up @@ -439,10 +421,10 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
</Stack>
);
})}
<form onSubmit={form.submitForm}>
<form onSubmit={form.handleSubmit}>
<Stack
direction="column"
gap={1}
gap={2}
justifyContent="flex-end"
sx={{
marginTop: 2,
Expand All @@ -456,19 +438,24 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
variant="outlined"
type="number"
/>
<FormControl size="small">
<Select
{...getFieldHelpers("share_level")}
value={form.values.share_level}
onChange={form.handleChange}
>
<MenuItem value="authenticated">Authenticated</MenuItem>
<MenuItem value="public" disabled={!canSharePortsPublic}>
Public
</MenuItem>
</Select>
</FormControl>
<Button variant="contained" onClick={form.submitForm}>
<TextField
{...getFieldHelpers("share_level")}
disabled={isSubmitting}
fullWidth
select
value={form.values.share_level}
label="Sharing Level"
>
<MenuItem value="authenticated">Authenticated</MenuItem>
<MenuItem value="public" disabled={!canSharePortsPublic}>
Public
</MenuItem>
</TextField>
<Button
variant="contained"
type="submit"
disabled={isSubmitting}
>
Share Port
</Button>
</Stack>
Expand Down
54 changes: 38 additions & 16 deletions site/src/modules/resources/PortForwardPopoverView.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
MockListeningPortsResponse,
MockSharedPortsResponse,
MockTemplate,
MockWorkspace,
MockWorkspaceAgent,
} from "testHelpers/entities";

Expand All @@ -27,6 +28,7 @@ const meta: Meta<typeof PortForwardPopoverView> = {
args: {
agent: MockWorkspaceAgent,
template: MockTemplate,
workspaceID: MockWorkspace.id,
portSharingExperimentEnabled: true,
portSharingControlsEnabled: true,
},
Expand All @@ -38,18 +40,28 @@ type Story = StoryObj<typeof PortForwardPopoverView>;
export const WithPorts: Story = {
args: {
listeningPorts: MockListeningPortsResponse.ports,
storybook: {
sharedPortsQueryData: MockSharedPortsResponse,
},
},
parameters: {
queries: [
{
key: ["sharedPorts", MockWorkspace.id],
data: MockSharedPortsResponse,
},
],
},
};

export const Empty: Story = {
args: {
listeningPorts: [],
storybook: {
sharedPortsQueryData: { shares: [] },
},
},
parameters: {
queries: [
{
key: ["sharedPorts", MockWorkspace.id],
data: { shares: [] },
},
],
},
};

Expand All @@ -63,11 +75,16 @@ export const NoPortSharingExperiment: Story = {
export const AGPLPortSharing: Story = {
args: {
listeningPorts: MockListeningPortsResponse.ports,
storybook: {
sharedPortsQueryData: MockSharedPortsResponse,
},
portSharingControlsEnabled: false,
},
parameters: {
queries: [
{
key: ["sharedPorts", MockWorkspace.id],
data: MockSharedPortsResponse,
},
],
},
};

export const EnterprisePortSharingControlsOwner: Story = {
Expand All @@ -83,16 +100,21 @@ export const EnterprisePortSharingControlsOwner: Story = {
export const EnterprisePortSharingControlsAuthenticated: Story = {
args: {
listeningPorts: MockListeningPortsResponse.ports,
storybook: {
sharedPortsQueryData: {
shares: MockSharedPortsResponse.shares.filter((share) => {
return share.share_level === "authenticated";
}),
},
},
template: {
...MockTemplate,
max_port_share_level: "authenticated",
},
},
parameters: {
queries: [
{
key: ["sharedPorts", MockWorkspace.id],
data: {
shares: MockSharedPortsResponse.shares.filter((share) => {
return share.share_level === "authenticated";
}),
},
},
],
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import {
HelpTooltipTrigger,
} from "components/HelpTooltip/HelpTooltip";
import { EnterpriseBadge } from "components/Badges/Badges";
import Select from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";

const MAX_DESCRIPTION_CHAR_LIMIT = 128;
Expand Down Expand Up @@ -279,33 +278,25 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
only be accessed by the workspace owner."
>
<FormFields>
<Stack direction="column" spacing={0.5}>
<Stack
direction="row"
alignItems="center"
spacing={0.5}
css={styles.optionText}
>
Maximum Port Sharing Level
</Stack>
<span css={styles.optionHelperText}>
The maximum level of port sharing allowed for workspaces.
</span>
</Stack>
<Select
id="max_port_share_level"
name="max_port_share_level"
<TextField
{...getFieldHelpers("max_port_share_level", {
helperText:
"The maximum level of port sharing allowed for workspaces.",
})}
disabled={isSubmitting || !portSharingControlsEnabled}
fullWidth
// TODO: Fix label being black on dark mode
select
value={
portSharingControlsEnabled
? form.values.max_port_share_level
: "public"
}
label="Maximum Port Sharing Level"
onChange={form.handleChange}
value={form.values.max_port_share_level}
>
<MenuItem value="owner">Owner</MenuItem>
<MenuItem value="authenticated">Authenticated</MenuItem>
<MenuItem value="public">Public</MenuItem>
</Select>
</TextField>
{!portSharingControlsEnabled && (
<Stack direction="row">
<EnterpriseBadge />
Expand Down