From 7cf3361f769bd90451e7e014d5f8dd052bb5b590 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Tue, 23 Jan 2024 18:37:32 +0000 Subject: [PATCH 1/5] refactor(site)verify external auth before display ws form --- .../CreateWorkspacePageView.tsx | 257 +++++++++--------- .../ExternalAuthBanner.stories.tsx | 34 +++ .../ExternalAuthBanner/ExternalAuthBanner.tsx | 90 ++++++ .../ExternalAuthItem.stories.tsx | 50 ++++ .../ExternalAuthItem.test.tsx | 62 +++++ .../ExternalAuthBanner/ExternalAuthItem.tsx | 124 +++++++++ .../ExternalAuthButton.stories.tsx | 108 -------- .../ExternalAuthButton.tsx | 74 ----- site/src/theme/dark/mui.ts | 6 +- 9 files changed, 485 insertions(+), 320 deletions(-) create mode 100644 site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthBanner.stories.tsx create mode 100644 site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthBanner.tsx create mode 100644 site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthItem.stories.tsx create mode 100644 site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthItem.test.tsx create mode 100644 site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthItem.tsx delete mode 100644 site/src/pages/CreateWorkspacePage/ExternalAuthButton.stories.tsx delete mode 100644 site/src/pages/CreateWorkspacePage/ExternalAuthButton.tsx diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx index 7c30434f6b73f..e4b7046453981 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx @@ -27,7 +27,6 @@ import { ImmutableTemplateParametersSection, MutableTemplateParametersSection, } from "components/TemplateParameters/TemplateParameters"; -import { ExternalAuthButton } from "./ExternalAuthButton"; import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Stack } from "components/Stack/Stack"; import { @@ -37,6 +36,7 @@ import { import { useSearchParams } from "react-router-dom"; import { CreateWSPermissions } from "./permissions"; import { Alert } from "components/Alert/Alert"; +import { ExternalAuthBanner } from "./ExternalAuthBanner/ExternalAuthBanner"; export const Language = { duplicationWarning: @@ -126,152 +126,139 @@ export const CreateWorkspacePageView: FC = ({ return ( - - {Boolean(error) && } - - {mode === "duplicate" && ( - - {Language.duplicationWarning} - - )} + {requiresExternalAuth ? ( + + ) : ( + + {Boolean(error) && } - {/* General info */} - - - - {versionId && versionId !== template.active_version_id && ( - - - - This parameter has been preset, and cannot be modified. - - - )} - - - + {mode === "duplicate" && ( + + {Language.duplicationWarning} + + )} - {permissions.createWorkspaceForUser && ( - { - setOwner(user ?? defaultOwner); - }} - label="Owner" - size="medium" + + {versionId && versionId !== template.active_version_id && ( + + + + This parameter has been preset, and cannot be modified. + + + )} + - )} - {externalAuth && externalAuth.length > 0 && ( - - - {requiresExternalAuth && ( - - To create a workspace using the selected template, please - ensure you are authenticated with all the external providers - listed below. - - )} - {externalAuth.map((auth) => ( - + + { + setOwner(user ?? defaultOwner); + }} + label="Owner" + size="medium" /> - ))} - - - )} + + + )} - {parameters && ( - <> - { - return { - ...getFieldHelpers( - "rich_parameter_values[" + index + "].value", - ), - onChange: async (value) => { - await form.setFieldValue("rich_parameter_values." + index, { - name: parameter.name, - value: value, - }); - }, - disabled: - disabledParamsList?.includes( - parameter.name.toLowerCase().replace(/ /g, "_"), - ) || creatingWorkspace, - }; - }} - /> - { - return { - ...getFieldHelpers( - "rich_parameter_values[" + index + "].value", - ), - onChange: async (value) => { - await form.setFieldValue("rich_parameter_values." + index, { - name: parameter.name, - value: value, - }); - }, - disabled: - disabledParamsList?.includes( - parameter.name.toLowerCase().replace(/ /g, "_"), - ) || creatingWorkspace, - }; - }} - /> - - )} + {parameters && ( + <> + { + return { + ...getFieldHelpers( + "rich_parameter_values[" + index + "].value", + ), + onChange: async (value) => { + await form.setFieldValue( + "rich_parameter_values." + index, + { + name: parameter.name, + value: value, + }, + ); + }, + disabled: + disabledParamsList?.includes( + parameter.name.toLowerCase().replace(/ /g, "_"), + ) || creatingWorkspace, + }; + }} + /> + { + return { + ...getFieldHelpers( + "rich_parameter_values[" + index + "].value", + ), + onChange: async (value) => { + await form.setFieldValue( + "rich_parameter_values." + index, + { + name: parameter.name, + value: value, + }, + ); + }, + disabled: + disabledParamsList?.includes( + parameter.name.toLowerCase().replace(/ /g, "_"), + ) || creatingWorkspace, + }; + }} + /> + + )} - - + + + )} ); }; diff --git a/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthBanner.stories.tsx b/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthBanner.stories.tsx new file mode 100644 index 0000000000000..0dae914e88ca3 --- /dev/null +++ b/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthBanner.stories.tsx @@ -0,0 +1,34 @@ +import { TemplateVersionExternalAuth } from "api/typesGenerated"; +import { ExternalAuthBanner } from "./ExternalAuthBanner"; +import type { Meta, StoryObj } from "@storybook/react"; + +const MockExternalAuth: TemplateVersionExternalAuth = { + id: "", + type: "", + display_name: "GitHub", + display_icon: "/icon/github.svg", + authenticate_url: "", + authenticated: false, +}; + +const meta: Meta = { + title: "pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthBanner", + component: ExternalAuthBanner, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + providers: [ + MockExternalAuth, + { + ...MockExternalAuth, + display_name: "Google", + display_icon: "/icon/google.svg", + authenticated: true, + }, + ], + }, +}; diff --git a/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthBanner.tsx b/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthBanner.tsx new file mode 100644 index 0000000000000..75a73c0f19566 --- /dev/null +++ b/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthBanner.tsx @@ -0,0 +1,90 @@ +import { Interpolation, Theme } from "@emotion/react"; +import { TemplateVersionExternalAuth } from "api/typesGenerated"; +import { ExternalAuthPollingState } from "../CreateWorkspacePage"; +import { ExternalAuthItem } from "./ExternalAuthItem"; + +type ExternalAuthBannerProps = { + providers: TemplateVersionExternalAuth[]; + pollingState: ExternalAuthPollingState; + onStartPolling: () => void; +}; + +export const ExternalAuthBanner = ({ + providers, + pollingState, + onStartPolling, +}: ExternalAuthBannerProps) => { + return ( +
+
+
+

External authentication

+

+ To create a workspace using the selected template, please ensure you + are connected with all the external services. +

+
+ +
    + {providers.map((p) => ( + + ))} +
+
+
+ ); +}; + +const styles = { + root: (theme) => ({ + display: "flex", + alignItems: "center", + justifyContent: "center", + padding: 48, + minHeight: 460, + border: `1px solid ${theme.palette.divider}`, + borderRadius: 8, + lineHeight: "1.5", + }), + + header: { + textAlign: "center", + // Better text distribution + maxWidth: 324, + margin: "auto", + }, + + content: { + maxWidth: 380, + }, + + title: { + fontSize: 20, + fontWeight: 400, + margin: 0, + lineHeight: "1.2", + }, + + description: (theme) => ({ + margin: 0, + marginTop: 12, + fontSize: 14, + color: theme.palette.text.secondary, + }), + + providerList: { + listStyle: "none", + padding: 0, + margin: 0, + display: "flex", + flexDirection: "column", + gap: 8, + marginTop: 24, + }, +} as Record>; diff --git a/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthItem.stories.tsx b/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthItem.stories.tsx new file mode 100644 index 0000000000000..a60b3e317c19e --- /dev/null +++ b/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthItem.stories.tsx @@ -0,0 +1,50 @@ +import { TemplateVersionExternalAuth } from "api/typesGenerated"; +import { ExternalAuthItem } from "./ExternalAuthItem"; +import type { Meta, StoryObj } from "@storybook/react"; + +const MockExternalAuth: TemplateVersionExternalAuth = { + id: "", + type: "", + display_name: "GitHub", + display_icon: "/icon/github.svg", + authenticate_url: "", + authenticated: false, +}; + +const meta: Meta = { + title: "pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthItem", + component: ExternalAuthItem, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + provider: MockExternalAuth, + }, +}; + +export const Connected: Story = { + args: { + provider: { + ...MockExternalAuth, + authenticated: true, + }, + }, +}; + +export const Connecting: Story = { + args: { + provider: MockExternalAuth, + defaultStatus: "connecting", + isPolling: true, + }, +}; diff --git a/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthItem.test.tsx b/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthItem.test.tsx new file mode 100644 index 0000000000000..f0d0871b9f7e3 --- /dev/null +++ b/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthItem.test.tsx @@ -0,0 +1,62 @@ +import { render, screen } from "@testing-library/react"; +import { ExternalAuthItem } from "./ExternalAuthItem"; +import { ThemeProvider } from "contexts/ThemeProvider"; +import { TemplateVersionExternalAuth } from "api/typesGenerated"; +import userEvent from "@testing-library/user-event"; + +jest.spyOn(window, "open").mockImplementation(() => null); + +const MockExternalAuth: TemplateVersionExternalAuth = { + id: "", + type: "", + display_name: "GitHub", + display_icon: "/icon/github.svg", + authenticate_url: "", + authenticated: false, +}; + +test("changes to idle when polling stops", async () => { + const user = userEvent.setup(); + const startPollingFn = jest.fn(); + const { rerender } = render( + , + { wrapper: ThemeProvider }, + ); + + const connectButton = screen.getByText(/connect/i); + expect(isLoading(connectButton)).toBeFalsy(); + + await user.click(connectButton); + expect(startPollingFn).toHaveBeenCalledTimes(1); + expect(window.open).toHaveBeenCalledTimes(1); + + rerender( + , + ); + + // Check if the button is loading + screen.getByRole("progressbar"); + + rerender( + , + ); + + expect(isLoading(connectButton)).toBeFalsy(); +}); + +function isLoading(el: HTMLButtonElement) { + const progressBar = el.querySelector('[role="progressbar"]'); + return Boolean(progressBar); +} diff --git a/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthItem.tsx b/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthItem.tsx new file mode 100644 index 0000000000000..dcd97ed2bcab0 --- /dev/null +++ b/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthItem.tsx @@ -0,0 +1,124 @@ +import { Interpolation, Theme } from "@emotion/react"; +import DoneAllOutlined from "@mui/icons-material/DoneAllOutlined"; +import LoadingButton from "@mui/lab/LoadingButton"; +import { TemplateVersionExternalAuth } from "api/typesGenerated"; +import { ExternalImage } from "components/ExternalImage/ExternalImage"; +import { ComponentProps, useEffect, useState } from "react"; +// eslint-disable-next-line no-restricted-imports -- used to allow extension with "component" +import Box from "@mui/material/Box"; + +type Status = "idle" | "connecting"; + +type ExternalAuthItemProps = { + provider: TemplateVersionExternalAuth; + isPolling: boolean; + defaultStatus?: Status; + onStartPolling: () => void; +} & ComponentProps; + +export const ExternalAuthItem = ({ + provider, + isPolling, + defaultStatus = "idle", + onStartPolling, + ...boxProps +}: ExternalAuthItemProps) => { + const [status, setStatus] = useState(defaultStatus); + + useEffect(() => { + if (!isPolling) { + setStatus("idle"); + } + }, [isPolling]); + + return ( + + + + {provider.display_name} + + {provider.authenticated ? ( + + Connected + + + ) : ( + { + setStatus("connecting"); + window.open( + provider.authenticate_url, + "_blank", + "width=900,height=600", + ); + onStartPolling(); + }} + > + Connect… + + )} + + ); +}; + +const styles = { + providerItem: (theme) => ({ + display: "flex", + alignItems: "center", + padding: "8px 8px 8px 20px", + border: `1px solid ${theme.palette.divider}`, + borderRadius: 6, + justifyContent: "space-between", + gap: 24, + fontSize: 14, + }), + + providerHeader: { + display: "flex", + alignItems: "center", + gap: 12, + flex: 1, + overflow: "hidden", + }, + + providerName: { + fontWeight: 500, + display: "block", + whiteSpace: "nowrap", + maxWidth: "100%", + textOverflow: "ellipsis", + overflow: "hidden", + }, + + providerIcon: { + width: 16, + height: 16, + }, + + connectButton: { + flexShrink: 0, + borderRadius: 4, + }, + + providerConnectedLabel: (theme) => ({ + fontSize: 13, + display: "flex", + alignItems: "center", + color: theme.palette.text.disabled, + gap: 8, + // Have the same height of the button + height: 32, + // Better visual alignment + padding: "0 8px", + }), + + providerConnectedLabelIcon: (theme) => ({ + color: theme.experimental.roles.success.fill, + fontSize: 16, + }), +} as Record>; diff --git a/site/src/pages/CreateWorkspacePage/ExternalAuthButton.stories.tsx b/site/src/pages/CreateWorkspacePage/ExternalAuthButton.stories.tsx deleted file mode 100644 index 97c9d743552ad..0000000000000 --- a/site/src/pages/CreateWorkspacePage/ExternalAuthButton.stories.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { TemplateVersionExternalAuth } from "api/typesGenerated"; -import { ExternalAuthButton } from "./ExternalAuthButton"; -import type { Meta, StoryObj } from "@storybook/react"; - -const MockExternalAuth: TemplateVersionExternalAuth = { - id: "", - type: "", - display_name: "GitHub", - display_icon: "/icon/github.svg", - authenticate_url: "", - authenticated: false, -}; - -const meta: Meta = { - title: "pages/CreateWorkspacePage/ExternalAuth", - component: ExternalAuthButton, -}; - -export default meta; -type Story = StoryObj; - -export const Github: Story = { - args: { - auth: MockExternalAuth, - }, -}; - -export const GithubWithRetry: Story = { - args: { - auth: MockExternalAuth, - displayRetry: true, - }, -}; - -export const GithubAuthenticated: Story = { - args: { - auth: { - ...MockExternalAuth, - authenticated: true, - }, - }, -}; - -export const Gitlab: Story = { - args: { - auth: { - ...MockExternalAuth, - display_icon: "/icon/gitlab.svg", - display_name: "GitLab", - authenticated: false, - }, - }, -}; - -export const GitlabAuthenticated: Story = { - args: { - auth: { - ...MockExternalAuth, - display_icon: "/icon/gitlab.svg", - display_name: "GitLab", - authenticated: true, - }, - }, -}; - -export const AzureDevOps: Story = { - args: { - auth: { - ...MockExternalAuth, - display_icon: "/icon/azure-devops.svg", - display_name: "Azure DevOps", - authenticated: false, - }, - }, -}; - -export const AzureDevOpsAuthenticated: Story = { - args: { - auth: { - ...MockExternalAuth, - display_icon: "/icon/azure-devops.svg", - display_name: "Azure DevOps", - authenticated: true, - }, - }, -}; - -export const Bitbucket: Story = { - args: { - auth: { - ...MockExternalAuth, - display_icon: "/icon/bitbucket.svg", - display_name: "Bitbucket", - authenticated: false, - }, - }, -}; - -export const BitbucketAuthenticated: Story = { - args: { - auth: { - ...MockExternalAuth, - display_icon: "/icon/bitbucket.svg", - display_name: "Bitbucket", - authenticated: true, - }, - }, -}; diff --git a/site/src/pages/CreateWorkspacePage/ExternalAuthButton.tsx b/site/src/pages/CreateWorkspacePage/ExternalAuthButton.tsx deleted file mode 100644 index 3412a9aac0b3d..0000000000000 --- a/site/src/pages/CreateWorkspacePage/ExternalAuthButton.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import ReplayIcon from "@mui/icons-material/Replay"; -import Button from "@mui/material/Button"; -import Tooltip from "@mui/material/Tooltip"; -import { type FC } from "react"; -import LoadingButton from "@mui/lab/LoadingButton"; -import { visuallyHidden } from "@mui/utils"; -import { ExternalImage } from "components/ExternalImage/ExternalImage"; -import { TemplateVersionExternalAuth } from "api/typesGenerated"; - -export interface ExternalAuthButtonProps { - auth: TemplateVersionExternalAuth; - displayRetry: boolean; - isLoading: boolean; - onStartPolling: () => void; -} - -export const ExternalAuthButton: FC = ({ - auth, - displayRetry, - isLoading, - onStartPolling, -}) => { - return ( - <> -
- - ) - } - disabled={auth.authenticated} - onClick={() => { - window.open( - auth.authenticate_url, - "_blank", - "width=900,height=600", - ); - onStartPolling(); - }} - > - {auth.authenticated - ? `Authenticated with ${auth.display_name}` - : `Login with ${auth.display_name}`} - - - {displayRetry && ( - - - - )} -
- - ); -}; diff --git a/site/src/theme/dark/mui.ts b/site/src/theme/dark/mui.ts index aadad14a87e81..a663dc37bd7fb 100644 --- a/site/src/theme/dark/mui.ts +++ b/site/src/theme/dark/mui.ts @@ -7,10 +7,10 @@ const muiTheme = createTheme({ palette: { mode: "dark", primary: { - main: tw.sky[500], + main: tw.sky[600], contrastText: tw.sky[50], - light: tw.sky[300], - dark: tw.sky[400], + light: tw.sky[400], + dark: tw.sky[500], }, secondary: { main: tw.zinc[500], From c59abfd30c8ca077cd9da35bd570797b5ca0a2da Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Wed, 24 Jan 2024 11:43:00 +0000 Subject: [PATCH 2/5] Apply PR review suggestions --- .../ExternalAuthBanner/ExternalAuthBanner.stories.tsx | 2 +- .../ExternalAuthBanner/ExternalAuthBanner.tsx | 5 +++-- .../ExternalAuthBanner/ExternalAuthItem.tsx | 10 +++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthBanner.stories.tsx b/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthBanner.stories.tsx index 0dae914e88ca3..2d9c4e9c45359 100644 --- a/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthBanner.stories.tsx +++ b/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthBanner.stories.tsx @@ -12,7 +12,7 @@ const MockExternalAuth: TemplateVersionExternalAuth = { }; const meta: Meta = { - title: "pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthBanner", + title: "pages/CreateWorkspacePage/ExternalAuthBanner", component: ExternalAuthBanner, }; diff --git a/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthBanner.tsx b/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthBanner.tsx index 75a73c0f19566..ee8e4b47f546d 100644 --- a/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthBanner.tsx +++ b/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthBanner.tsx @@ -2,6 +2,7 @@ import { Interpolation, Theme } from "@emotion/react"; import { TemplateVersionExternalAuth } from "api/typesGenerated"; import { ExternalAuthPollingState } from "../CreateWorkspacePage"; import { ExternalAuthItem } from "./ExternalAuthItem"; +import { FC } from "react"; type ExternalAuthBannerProps = { providers: TemplateVersionExternalAuth[]; @@ -9,11 +10,11 @@ type ExternalAuthBannerProps = { onStartPolling: () => void; }; -export const ExternalAuthBanner = ({ +export const ExternalAuthBanner: FC = ({ providers, pollingState, onStartPolling, -}: ExternalAuthBannerProps) => { +}) => { return (
diff --git a/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthItem.tsx b/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthItem.tsx index dcd97ed2bcab0..d50174e58eea8 100644 --- a/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthItem.tsx +++ b/site/src/pages/CreateWorkspacePage/ExternalAuthBanner/ExternalAuthItem.tsx @@ -3,9 +3,9 @@ import DoneAllOutlined from "@mui/icons-material/DoneAllOutlined"; import LoadingButton from "@mui/lab/LoadingButton"; import { TemplateVersionExternalAuth } from "api/typesGenerated"; import { ExternalImage } from "components/ExternalImage/ExternalImage"; -import { ComponentProps, useEffect, useState } from "react"; +import { FC, useEffect, useState } from "react"; // eslint-disable-next-line no-restricted-imports -- used to allow extension with "component" -import Box from "@mui/material/Box"; +import Box, { BoxProps } from "@mui/material/Box"; type Status = "idle" | "connecting"; @@ -14,15 +14,15 @@ type ExternalAuthItemProps = { isPolling: boolean; defaultStatus?: Status; onStartPolling: () => void; -} & ComponentProps; +} & BoxProps; -export const ExternalAuthItem = ({ +export const ExternalAuthItem: FC = ({ provider, isPolling, defaultStatus = "idle", onStartPolling, ...boxProps -}: ExternalAuthItemProps) => { +}) => { const [status, setStatus] = useState(defaultStatus); useEffect(() => { From 1aa34e4b1516d0b9465d6517d9567c55a2083abc Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Wed, 24 Jan 2024 12:01:39 +0000 Subject: [PATCH 3/5] Improve color contrast --- site/src/theme/dark/mui.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/src/theme/dark/mui.ts b/site/src/theme/dark/mui.ts index a663dc37bd7fb..52927eda2e0c9 100644 --- a/site/src/theme/dark/mui.ts +++ b/site/src/theme/dark/mui.ts @@ -7,10 +7,10 @@ const muiTheme = createTheme({ palette: { mode: "dark", primary: { - main: tw.sky[600], - contrastText: tw.sky[50], + main: tw.sky[500], + contrastText: tw.white, light: tw.sky[400], - dark: tw.sky[500], + dark: tw.sky[600], }, secondary: { main: tw.zinc[500], From 8e2e09352fe5b7ffa71363ebe79fe1f7c7e73a58 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Wed, 24 Jan 2024 12:06:31 +0000 Subject: [PATCH 4/5] Fix minor storybook --- .../CreateWorkspacePage/CreateWorkspacePageView.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx index 002b6c678a857..38e118431a559 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx @@ -89,7 +89,7 @@ export const Parameters: Story = { }, }; -export const ExternalAuth: Story = { +export const RequiresExternalAuth: Story = { args: { externalAuth: [ { From 2c8fc68f649dbf2476dea1c53c73fd873ea43b03 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Wed, 24 Jan 2024 12:34:53 +0000 Subject: [PATCH 5/5] Fix tests --- .../CreateWorkspacePage.test.tsx | 103 ++++++++---------- .../CreateWorkspacePageView.tsx | 1 + 2 files changed, 45 insertions(+), 59 deletions(-) diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx index 78a1653e0eed9..a616c106bf41c 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.test.tsx @@ -6,14 +6,12 @@ import { MockUser, MockWorkspace, MockWorkspaceQuota, - MockWorkspaceRequest, MockWorkspaceRichParametersRequest, MockTemplateVersionParameter1, MockTemplateVersionParameter2, MockTemplateVersionParameter3, MockTemplateVersionExternalAuthGithub, MockOrganization, - MockTemplateVersionExternalAuthGithubAuthenticated, } from "testHelpers/entities"; import { renderWithAuth, @@ -21,6 +19,8 @@ import { } from "testHelpers/renderHelpers"; import CreateWorkspacePage from "./CreateWorkspacePage"; import { Language } from "./CreateWorkspacePageView"; +import { server } from "testHelpers/server"; +import { rest } from "msw"; const nameLabelText = "Workspace Name"; const createWorkspaceText = "Create Workspace"; @@ -157,63 +157,6 @@ describe("CreateWorkspacePage", () => { expect(validationError).toBeInTheDocument(); }); - it("external auth authenticates and succeeds", async () => { - jest - .spyOn(API, "getWorkspaceQuota") - .mockResolvedValueOnce(MockWorkspaceQuota); - jest - .spyOn(API, "getUsers") - .mockResolvedValueOnce({ users: [MockUser], count: 1 }); - jest.spyOn(API, "createWorkspace").mockResolvedValueOnce(MockWorkspace); - jest - .spyOn(API, "getTemplateVersionExternalAuth") - .mockResolvedValue([MockTemplateVersionExternalAuthGithub]); - - renderCreateWorkspacePage(); - await waitForLoaderToBeRemoved(); - - const nameField = await screen.findByLabelText(nameLabelText); - // have to use fireEvent b/c userEvent isn't cleaning up properly between tests - fireEvent.change(nameField, { - target: { value: "test" }, - }); - - const githubButton = await screen.findByText("Login with GitHub"); - await userEvent.click(githubButton); - - jest - .spyOn(API, "getTemplateVersionExternalAuth") - .mockResolvedValue([MockTemplateVersionExternalAuthGithubAuthenticated]); - - await screen.findByText("Authenticated with GitHub"); - - const submitButton = screen.getByText(createWorkspaceText); - await userEvent.click(submitButton); - - await waitFor(() => - expect(API.createWorkspace).toBeCalledWith( - MockUser.organization_ids[0], - MockUser.id, - expect.objectContaining({ - ...MockWorkspaceRequest, - }), - ), - ); - }); - - 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 () => { const param = "first_parameter"; const paramValue = "It works!"; @@ -284,4 +227,46 @@ describe("CreateWorkspacePage", () => { expect(warningMessage).toHaveTextContent(Language.duplicationWarning); expect(nameInput).toHaveValue(`${MockWorkspace.name}-copy`); }); + + it("displays the form after connecting to all the external services", async () => { + jest.spyOn(window, "open").mockImplementation(() => null); + const user = userEvent.setup(); + const notAuthenticatedExternalAuth = { + ...MockTemplateVersionExternalAuthGithub, + authenticated: false, + }; + server.use( + rest.get( + "/api/v2/templateversions/:versionId/external-auth", + (req, res, ctx) => { + return res(ctx.json([notAuthenticatedExternalAuth])); + }, + ), + ); + renderCreateWorkspacePage(); + + await screen.findByText("External authentication"); + expect(screen.queryByRole("form")).not.toBeInTheDocument(); + + const connectButton = screen.getByRole("button", { + name: /connect/i, + }); + server.use( + rest.get( + "/api/v2/templateversions/:versionId/external-auth", + (req, res, ctx) => { + const authenticatedExternalAuth = { + ...MockTemplateVersionExternalAuthGithub, + authenticated: true, + }; + return res(ctx.json([authenticatedExternalAuth])); + }, + ), + ); + await user.click(connectButton); + // TODO: Consider improving the timeout by simulating react-query polling. + // Current implementation could not achieve this, further research is + // needed. + await screen.findByRole("form", undefined, { timeout: 10_000 }); + }); }); diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx index ff909a8da32e6..b6b6968e945d6 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx @@ -163,6 +163,7 @@ export const CreateWorkspacePageView: FC = ({ /> ) : ( pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy