mirror of
https://github.com/coder/coder.git
synced 2025-07-18 14:17:22 +00:00
feat(site): allow creating a workspace without connecting optional external auth providers (#12251)
This commit is contained in:
committed by
GitHub
parent
b8a53230c7
commit
7e6cb66a50
@ -233,19 +233,6 @@ describe("CreateWorkspacePage", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("external auth errors if unauthenticated", async () => {
|
|
||||||
jest
|
|
||||||
.spyOn(API, "getTemplateVersionExternalAuth")
|
|
||||||
.mockResolvedValueOnce([MockTemplateVersionExternalAuthGithub]);
|
|
||||||
|
|
||||||
renderCreateWorkspacePage();
|
|
||||||
await waitForLoaderToBeRemoved();
|
|
||||||
|
|
||||||
await screen.findByText(
|
|
||||||
"To create a workspace using the selected template, please ensure you are authenticated with all the external providers listed below.",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("auto create a workspace if uses mode=auto", async () => {
|
it("auto create a workspace if uses mode=auto", async () => {
|
||||||
const param = "first_parameter";
|
const param = "first_parameter";
|
||||||
const paramValue = "It works!";
|
const paramValue = "It works!";
|
||||||
@ -312,7 +299,7 @@ describe("CreateWorkspacePage", () => {
|
|||||||
route: `/templates/${MockWorkspace.name}/workspace?${params.toString()}`,
|
route: `/templates/${MockWorkspace.name}/workspace?${params.toString()}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const warningMessage = await screen.findByRole("alert");
|
const warningMessage = await screen.findByTestId("duplication-warning");
|
||||||
const nameInput = await screen.findByRole("textbox", {
|
const nameInput = await screen.findByRole("textbox", {
|
||||||
name: "Workspace Name",
|
name: "Workspace Name",
|
||||||
});
|
});
|
||||||
|
@ -126,6 +126,80 @@ export const ExternalAuth: Story = {
|
|||||||
authenticate_url: "",
|
authenticate_url: "",
|
||||||
display_icon: "/icon/gitlab.svg",
|
display_icon: "/icon/gitlab.svg",
|
||||||
display_name: "GitLab",
|
display_name: "GitLab",
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ExternalAuthError: Story = {
|
||||||
|
args: {
|
||||||
|
error: true,
|
||||||
|
externalAuth: [
|
||||||
|
{
|
||||||
|
id: "github",
|
||||||
|
type: "github",
|
||||||
|
authenticated: false,
|
||||||
|
authenticate_url: "",
|
||||||
|
display_icon: "/icon/github.svg",
|
||||||
|
display_name: "GitHub",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "gitlab",
|
||||||
|
type: "gitlab",
|
||||||
|
authenticated: false,
|
||||||
|
authenticate_url: "",
|
||||||
|
display_icon: "/icon/gitlab.svg",
|
||||||
|
display_name: "GitLab",
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ExternalAuthAllRequiredConnected: Story = {
|
||||||
|
args: {
|
||||||
|
externalAuth: [
|
||||||
|
{
|
||||||
|
id: "github",
|
||||||
|
type: "github",
|
||||||
|
authenticated: true,
|
||||||
|
authenticate_url: "",
|
||||||
|
display_icon: "/icon/github.svg",
|
||||||
|
display_name: "GitHub",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "gitlab",
|
||||||
|
type: "gitlab",
|
||||||
|
authenticated: false,
|
||||||
|
authenticate_url: "",
|
||||||
|
display_icon: "/icon/gitlab.svg",
|
||||||
|
display_name: "GitLab",
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ExternalAuthAllConnected: Story = {
|
||||||
|
args: {
|
||||||
|
externalAuth: [
|
||||||
|
{
|
||||||
|
id: "github",
|
||||||
|
type: "github",
|
||||||
|
authenticated: true,
|
||||||
|
authenticate_url: "",
|
||||||
|
display_icon: "/icon/github.svg",
|
||||||
|
display_name: "GitHub",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "gitlab",
|
||||||
|
type: "gitlab",
|
||||||
|
authenticated: true,
|
||||||
|
authenticate_url: "",
|
||||||
|
display_icon: "/icon/gitlab.svg",
|
||||||
|
display_name: "GitLab",
|
||||||
|
optional: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -147,6 +147,10 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
|
|||||||
);
|
);
|
||||||
}, [autofillParameters]);
|
}, [autofillParameters]);
|
||||||
|
|
||||||
|
const hasAllRequiredExternalAuth = externalAuth.every(
|
||||||
|
(auth) => auth.optional || auth.authenticated,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Margins size="medium">
|
<Margins size="medium">
|
||||||
<PageHeader actions={<Button onClick={onCancel}>Cancel</Button>}>
|
<PageHeader actions={<Button onClick={onCancel}>Cancel</Button>}>
|
||||||
@ -179,7 +183,7 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
|
|||||||
{Boolean(error) && <ErrorAlert error={error} />}
|
{Boolean(error) && <ErrorAlert error={error} />}
|
||||||
|
|
||||||
{mode === "duplicate" && (
|
{mode === "duplicate" && (
|
||||||
<Alert severity="info" dismissible>
|
<Alert severity="info" dismissible data-testid="duplication-warning">
|
||||||
{Language.duplicationWarning}
|
{Language.duplicationWarning}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
@ -248,21 +252,19 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
|
|||||||
{externalAuth && externalAuth.length > 0 && (
|
{externalAuth && externalAuth.length > 0 && (
|
||||||
<FormSection
|
<FormSection
|
||||||
title="External Authentication"
|
title="External Authentication"
|
||||||
description="This template requires authentication to external services."
|
description="This template uses external services for authentication."
|
||||||
>
|
>
|
||||||
<FormFields>
|
<FormFields>
|
||||||
{requiresExternalAuth && (
|
{Boolean(error) && !hasAllRequiredExternalAuth && (
|
||||||
// This should really be a `notice` but `severity` is a MUI prop, and we'd need
|
<Alert severity="error">
|
||||||
// to basically make our own `Alert` component.
|
To create a workspace using this template, please connect to
|
||||||
<Alert severity="info">
|
all required external authentication providers listed below.
|
||||||
To create a workspace using the selected template, please
|
|
||||||
ensure you are authenticated with all the external providers
|
|
||||||
listed below.
|
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
{externalAuth.map((auth) => (
|
{externalAuth.map((auth) => (
|
||||||
<ExternalAuthButton
|
<ExternalAuthButton
|
||||||
key={auth.id}
|
key={auth.id}
|
||||||
|
error={error}
|
||||||
auth={auth}
|
auth={auth}
|
||||||
isLoading={externalAuthPollingState === "polling"}
|
isLoading={externalAuthPollingState === "polling"}
|
||||||
onStartPolling={startPollingExternalAuth}
|
onStartPolling={startPollingExternalAuth}
|
||||||
@ -313,6 +315,7 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
|
|||||||
<FormFooter
|
<FormFooter
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
isLoading={creatingWorkspace}
|
isLoading={creatingWorkspace}
|
||||||
|
submitDisabled={!hasAllRequiredExternalAuth}
|
||||||
submitLabel="Create Workspace"
|
submitLabel="Create Workspace"
|
||||||
/>
|
/>
|
||||||
</HorizontalForm>
|
</HorizontalForm>
|
||||||
|
@ -12,7 +12,7 @@ const MockExternalAuth: TemplateVersionExternalAuth = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const meta: Meta<typeof ExternalAuthButton> = {
|
const meta: Meta<typeof ExternalAuthButton> = {
|
||||||
title: "pages/CreateWorkspacePage/ExternalAuth",
|
title: "pages/CreateWorkspacePage/ExternalAuthButton",
|
||||||
component: ExternalAuthButton,
|
component: ExternalAuthButton,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -25,6 +25,15 @@ export const Github: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const GithubOptional: Story = {
|
||||||
|
args: {
|
||||||
|
auth: {
|
||||||
|
...MockExternalAuth,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const GithubWithRetry: Story = {
|
export const GithubWithRetry: Story = {
|
||||||
args: {
|
args: {
|
||||||
auth: MockExternalAuth,
|
auth: MockExternalAuth,
|
||||||
@ -48,6 +57,7 @@ export const Gitlab: Story = {
|
|||||||
display_icon: "/icon/gitlab.svg",
|
display_icon: "/icon/gitlab.svg",
|
||||||
display_name: "GitLab",
|
display_name: "GitLab",
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
|
optional: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -70,6 +80,7 @@ export const AzureDevOps: Story = {
|
|||||||
display_icon: "/icon/azure-devops.svg",
|
display_icon: "/icon/azure-devops.svg",
|
||||||
display_name: "Azure DevOps",
|
display_name: "Azure DevOps",
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
|
optional: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -92,6 +103,7 @@ export const Bitbucket: Story = {
|
|||||||
display_icon: "/icon/bitbucket.svg",
|
display_icon: "/icon/bitbucket.svg",
|
||||||
display_name: "Bitbucket",
|
display_name: "Bitbucket",
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
|
optional: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
import ReplayIcon from "@mui/icons-material/Replay";
|
import ReplayIcon from "@mui/icons-material/Replay";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import { type FC } from "react";
|
|
||||||
import LoadingButton from "@mui/lab/LoadingButton";
|
import LoadingButton from "@mui/lab/LoadingButton";
|
||||||
import { visuallyHidden } from "@mui/utils";
|
import { visuallyHidden } from "@mui/utils";
|
||||||
|
import { type FC } from "react";
|
||||||
|
import type { TemplateVersionExternalAuth } from "api/typesGenerated";
|
||||||
import { ExternalImage } from "components/ExternalImage/ExternalImage";
|
import { ExternalImage } from "components/ExternalImage/ExternalImage";
|
||||||
import { TemplateVersionExternalAuth } from "api/typesGenerated";
|
import { Pill } from "components/Pill/Pill";
|
||||||
|
|
||||||
export interface ExternalAuthButtonProps {
|
export interface ExternalAuthButtonProps {
|
||||||
auth: TemplateVersionExternalAuth;
|
auth: TemplateVersionExternalAuth;
|
||||||
displayRetry: boolean;
|
displayRetry: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
onStartPolling: () => void;
|
onStartPolling: () => void;
|
||||||
|
error?: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExternalAuthButton: FC<ExternalAuthButtonProps> = ({
|
export const ExternalAuthButton: FC<ExternalAuthButtonProps> = ({
|
||||||
@ -19,6 +21,7 @@ export const ExternalAuthButton: FC<ExternalAuthButtonProps> = ({
|
|||||||
displayRetry,
|
displayRetry,
|
||||||
isLoading,
|
isLoading,
|
||||||
onStartPolling,
|
onStartPolling,
|
||||||
|
error,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -48,9 +51,18 @@ export const ExternalAuthButton: FC<ExternalAuthButtonProps> = ({
|
|||||||
onStartPolling();
|
onStartPolling();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{auth.authenticated
|
{auth.authenticated ? (
|
||||||
? `Authenticated with ${auth.display_name}`
|
`Authenticated with ${auth.display_name}`
|
||||||
: `Login with ${auth.display_name}`}
|
) : (
|
||||||
|
<>
|
||||||
|
Login with {auth.display_name}
|
||||||
|
{!auth.optional && (
|
||||||
|
<Pill type={error ? "error" : "info"} css={{ marginLeft: 12 }}>
|
||||||
|
Required
|
||||||
|
</Pill>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
|
|
||||||
{displayRetry && (
|
{displayRetry && (
|
||||||
|
Reference in New Issue
Block a user