mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor: poll for git auth updates when creating a workspace (#9804)
This commit is contained in:
@@ -120,3 +120,16 @@ export const updateActiveTemplateVersion = (
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const templateVersionGitAuthKey = (versionId: string) => [
|
||||
"templateVersion",
|
||||
versionId,
|
||||
"gitAuth",
|
||||
];
|
||||
|
||||
export const templateVersionGitAuth = (versionId: string) => {
|
||||
return {
|
||||
queryKey: templateVersionGitAuthKey(versionId),
|
||||
queryFn: () => API.getTemplateVersionGitAuth(versionId),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -7,11 +7,13 @@ import {
|
||||
MockWorkspace,
|
||||
MockWorkspaceQuota,
|
||||
MockWorkspaceRequest,
|
||||
MockWorkspaceRichParametersRequest,
|
||||
MockTemplateVersionParameter1,
|
||||
MockTemplateVersionParameter2,
|
||||
MockTemplateVersionParameter3,
|
||||
MockTemplateVersionGitAuth,
|
||||
MockOrganization,
|
||||
MockTemplateVersionGitAuthAuthenticated,
|
||||
} from "testHelpers/entities";
|
||||
import {
|
||||
renderWithAuth,
|
||||
@@ -31,17 +33,6 @@ const renderCreateWorkspacePage = () => {
|
||||
});
|
||||
};
|
||||
|
||||
Object.defineProperty(window, "BroadcastChannel", {
|
||||
value: class {
|
||||
addEventListener() {
|
||||
// noop
|
||||
}
|
||||
close() {
|
||||
// noop
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
describe("CreateWorkspacePage", () => {
|
||||
it("succeeds with default owner", async () => {
|
||||
jest
|
||||
@@ -71,9 +62,9 @@ describe("CreateWorkspacePage", () => {
|
||||
expect(API.createWorkspace).toBeCalledWith(
|
||||
MockUser.organization_ids[0],
|
||||
MockUser.id,
|
||||
{
|
||||
...MockWorkspaceRequest,
|
||||
},
|
||||
expect.objectContaining({
|
||||
...MockWorkspaceRichParametersRequest,
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -165,6 +156,50 @@ describe("CreateWorkspacePage", () => {
|
||||
expect(validationError).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("gitauth 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, "getTemplateVersionGitAuth")
|
||||
.mockResolvedValue([MockTemplateVersionGitAuth]);
|
||||
|
||||
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, "getTemplateVersionGitAuth")
|
||||
.mockResolvedValue([MockTemplateVersionGitAuthAuthenticated]);
|
||||
|
||||
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("gitauth: errors if unauthenticated and submits", async () => {
|
||||
jest
|
||||
.spyOn(API, "getTemplateVersionGitAuth")
|
||||
@@ -210,4 +245,29 @@ describe("CreateWorkspacePage", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("auto create a workspace if uses mode=auto and version=version-id", async () => {
|
||||
const param = "first_parameter";
|
||||
const paramValue = "It works!";
|
||||
const createWorkspaceSpy = jest.spyOn(API, "createWorkspace");
|
||||
|
||||
renderWithAuth(<CreateWorkspacePage />, {
|
||||
route:
|
||||
"/templates/" +
|
||||
MockTemplate.name +
|
||||
`/workspace?param.${param}=${paramValue}&mode=auto&version=test-template-version`,
|
||||
path: "/templates/:template/workspace",
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(createWorkspaceSpy).toBeCalledWith(
|
||||
MockOrganization.id,
|
||||
"me",
|
||||
expect.objectContaining({
|
||||
template_version_id: MockTemplate.active_version_id,
|
||||
rich_parameter_values: [{ name: param, value: paramValue }],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { useMachine } from "@xstate/react";
|
||||
import {
|
||||
Template,
|
||||
TemplateVersionGitAuth,
|
||||
TemplateVersionParameter,
|
||||
WorkspaceBuildParameter,
|
||||
} from "api/typesGenerated";
|
||||
import { useMe } from "hooks/useMe";
|
||||
import { useOrganizationId } from "hooks/useOrganizationId";
|
||||
import { FC } from "react";
|
||||
import { type FC, useCallback, useState, useEffect } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
import { pageTitle } from "utils/page";
|
||||
@@ -25,6 +23,10 @@ import {
|
||||
colors,
|
||||
NumberDictionary,
|
||||
} from "unique-names-generator";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { templateVersionGitAuth } from "api/queries/templates";
|
||||
|
||||
export type GitAuthPollingState = "idle" | "polling" | "abandoned";
|
||||
|
||||
const CreateWorkspacePage: FC = () => {
|
||||
const organizationId = useOrganizationId();
|
||||
@@ -50,18 +52,49 @@ const CreateWorkspacePage: FC = () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const {
|
||||
template,
|
||||
error,
|
||||
parameters,
|
||||
permissions,
|
||||
gitAuth,
|
||||
defaultName,
|
||||
versionId,
|
||||
} = createWorkspaceState.context;
|
||||
const { template, parameters, permissions, defaultName, versionId } =
|
||||
createWorkspaceState.context;
|
||||
const title = createWorkspaceState.matches("autoCreating")
|
||||
? "Creating workspace..."
|
||||
: "Create Workspace";
|
||||
: "Create workspace";
|
||||
|
||||
const [gitAuthPollingState, setGitAuthPollingState] =
|
||||
useState<GitAuthPollingState>("idle");
|
||||
|
||||
const startPollingGitAuth = useCallback(() => {
|
||||
setGitAuthPollingState("polling");
|
||||
}, []);
|
||||
|
||||
const { data: gitAuth, error } = useQuery(
|
||||
versionId
|
||||
? {
|
||||
...templateVersionGitAuth(versionId),
|
||||
refetchInterval: gitAuthPollingState === "polling" ? 1000 : false,
|
||||
}
|
||||
: { enabled: false },
|
||||
);
|
||||
|
||||
const allSignedIn = gitAuth?.every((it) => it.authenticated);
|
||||
|
||||
useEffect(() => {
|
||||
if (allSignedIn) {
|
||||
setGitAuthPollingState("idle");
|
||||
return;
|
||||
}
|
||||
|
||||
if (gitAuthPollingState !== "polling") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Poll for a maximum of one minute
|
||||
const quitPolling = setTimeout(
|
||||
() => setGitAuthPollingState("abandoned"),
|
||||
60_000,
|
||||
);
|
||||
return () => {
|
||||
clearTimeout(quitPolling);
|
||||
};
|
||||
}, [gitAuthPollingState, allSignedIn]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -81,11 +114,13 @@ const CreateWorkspacePage: FC = () => {
|
||||
defaultOwner={me}
|
||||
defaultBuildParameters={defaultBuildParameters}
|
||||
error={error}
|
||||
template={template as Template}
|
||||
template={template!}
|
||||
versionId={versionId}
|
||||
gitAuth={gitAuth as TemplateVersionGitAuth[]}
|
||||
gitAuth={gitAuth ?? []}
|
||||
gitAuthPollingState={gitAuthPollingState}
|
||||
startPollingGitAuth={startPollingGitAuth}
|
||||
permissions={permissions as CreateWSPermissions}
|
||||
parameters={parameters as TemplateVersionParameter[]}
|
||||
parameters={parameters!}
|
||||
creatingWorkspace={createWorkspaceState.matches("creatingWorkspace")}
|
||||
onCancel={() => {
|
||||
navigate(-1);
|
||||
|
||||
@@ -30,6 +30,7 @@ import { CreateWSPermissions } from "xServices/createWorkspace/createWorkspaceXS
|
||||
import { GitAuth } from "./GitAuth";
|
||||
import { ErrorAlert } from "components/Alert/ErrorAlert";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { type GitAuthPollingState } from "./CreateWorkspacePage";
|
||||
|
||||
export interface CreateWorkspacePageViewProps {
|
||||
error: unknown;
|
||||
@@ -38,6 +39,8 @@ export interface CreateWorkspacePageViewProps {
|
||||
template: TypesGen.Template;
|
||||
versionId?: string;
|
||||
gitAuth: TypesGen.TemplateVersionGitAuth[];
|
||||
gitAuthPollingState: GitAuthPollingState;
|
||||
startPollingGitAuth: () => void;
|
||||
parameters: TypesGen.TemplateVersionParameter[];
|
||||
defaultBuildParameters: TypesGen.WorkspaceBuildParameter[];
|
||||
permissions: CreateWSPermissions;
|
||||
@@ -56,6 +59,8 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
|
||||
template,
|
||||
versionId,
|
||||
gitAuth,
|
||||
gitAuthPollingState,
|
||||
startPollingGitAuth,
|
||||
parameters,
|
||||
defaultBuildParameters,
|
||||
permissions,
|
||||
@@ -113,7 +118,7 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
|
||||
>
|
||||
<FormFields>
|
||||
<SelectedTemplate template={template} />
|
||||
{versionId && (
|
||||
{versionId && versionId !== template.active_version_id && (
|
||||
<Stack spacing={1} className={styles.hasDescription}>
|
||||
<TextField
|
||||
disabled
|
||||
@@ -161,11 +166,13 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = ({
|
||||
description="This template requires authentication to automatically perform Git operations on create."
|
||||
>
|
||||
<FormFields>
|
||||
{gitAuth.map((auth, index) => (
|
||||
{gitAuth.map((auth) => (
|
||||
<GitAuth
|
||||
key={index}
|
||||
key={auth.id}
|
||||
authenticateURL={auth.authenticate_url}
|
||||
authenticated={auth.authenticated}
|
||||
gitAuthPollingState={gitAuthPollingState}
|
||||
startPollingGitAuth={startPollingGitAuth}
|
||||
type={auth.type}
|
||||
error={gitAuthErrors[auth.id]}
|
||||
/>
|
||||
@@ -229,12 +236,8 @@ type GitAuthErrors = Record<string, string>;
|
||||
const useGitAuthVerification = (gitAuth: TypesGen.TemplateVersionGitAuth[]) => {
|
||||
const [gitAuthErrors, setGitAuthErrors] = useState<GitAuthErrors>({});
|
||||
|
||||
// Clear errors when gitAuth is refreshed
|
||||
useEffect(() => {
|
||||
// templateGitAuth is refreshed automatically using a BroadcastChannel
|
||||
// which may change the `authenticated` property.
|
||||
//
|
||||
// If the provider becomes authenticated, we want the error message
|
||||
// to disappear.
|
||||
setGitAuthErrors({});
|
||||
}, [gitAuth]);
|
||||
|
||||
|
||||
@@ -9,20 +9,30 @@ import { BitbucketIcon } from "components/Icons/BitbucketIcon";
|
||||
import { GitlabIcon } from "components/Icons/GitlabIcon";
|
||||
import { FC } from "react";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { type GitAuthPollingState } from "./CreateWorkspacePage";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import ReplayIcon from "@mui/icons-material/Replay";
|
||||
import { LoadingButton } from "components/LoadingButton/LoadingButton";
|
||||
|
||||
export interface GitAuthProps {
|
||||
type: TypesGen.GitProvider;
|
||||
authenticated: boolean;
|
||||
authenticateURL: string;
|
||||
gitAuthPollingState: GitAuthPollingState;
|
||||
startPollingGitAuth: () => void;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export const GitAuth: FC<GitAuthProps> = ({
|
||||
type,
|
||||
authenticated,
|
||||
authenticateURL,
|
||||
error,
|
||||
}) => {
|
||||
export const GitAuth: FC<GitAuthProps> = (props) => {
|
||||
const {
|
||||
type,
|
||||
authenticated,
|
||||
authenticateURL,
|
||||
gitAuthPollingState,
|
||||
startPollingGitAuth,
|
||||
error,
|
||||
} = props;
|
||||
|
||||
const styles = useStyles({
|
||||
error: typeof error !== "undefined",
|
||||
});
|
||||
@@ -52,12 +62,11 @@ export const GitAuth: FC<GitAuthProps> = ({
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
authenticated ? "You're already authenticated! No action needed." : ``
|
||||
}
|
||||
title={authenticated && `${prettyName} has already been connected.`}
|
||||
>
|
||||
<div>
|
||||
<Button
|
||||
<Stack alignItems="center" spacing={1}>
|
||||
<LoadingButton
|
||||
loading={gitAuthPollingState === "polling"}
|
||||
href={authenticateURL}
|
||||
variant="contained"
|
||||
size="large"
|
||||
@@ -73,15 +82,21 @@ export const GitAuth: FC<GitAuthProps> = ({
|
||||
return;
|
||||
}
|
||||
window.open(authenticateURL, "_blank", "width=900,height=600");
|
||||
startPollingGitAuth();
|
||||
}}
|
||||
>
|
||||
{authenticated
|
||||
? `You're authenticated with ${prettyName}!`
|
||||
: `Click to login with ${prettyName}!`}
|
||||
</Button>
|
||||
? `Authenticated with ${prettyName}`
|
||||
: `Login with ${prettyName}`}
|
||||
</LoadingButton>
|
||||
|
||||
{gitAuthPollingState === "abandoned" && (
|
||||
<Button variant="text" onClick={startPollingGitAuth}>
|
||||
<ReplayIcon /> Check again
|
||||
</Button>
|
||||
)}
|
||||
{error && <FormHelperText error>{error}</FormHelperText>}
|
||||
</div>
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,12 +5,11 @@ import {
|
||||
getGitAuthProvider,
|
||||
} from "api/api";
|
||||
import { usePermissions } from "hooks";
|
||||
import { FC, useEffect } from "react";
|
||||
import { type FC } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import GitAuthPageView from "./GitAuthPageView";
|
||||
import { ApiErrorResponse } from "api/errors";
|
||||
import { isAxiosError } from "axios";
|
||||
import { REFRESH_GITAUTH_BROADCAST_CHANNEL } from "utils/gitAuth";
|
||||
|
||||
const GitAuthPage: FC = () => {
|
||||
const { provider } = useParams();
|
||||
@@ -52,17 +51,6 @@ const GitAuthPage: FC = () => {
|
||||
query.state.status === "success" ? false : "always",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!getGitAuthProviderQuery.data?.authenticated) {
|
||||
return;
|
||||
}
|
||||
// This is used to notify the parent window that the Git auth token has been refreshed.
|
||||
// It's critical in the create workspace flow!
|
||||
const bc = new BroadcastChannel(REFRESH_GITAUTH_BROADCAST_CHANNEL);
|
||||
// The message doesn't matter, any message refreshes the page!
|
||||
bc.postMessage("noop");
|
||||
}, [getGitAuthProviderQuery.data?.authenticated]);
|
||||
|
||||
if (getGitAuthProviderQuery.isLoading || !getGitAuthProviderQuery.data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@ import { Avatar } from "components/Avatar/Avatar";
|
||||
import { CopyButton } from "components/CopyButton/CopyButton";
|
||||
import { SignInLayout } from "components/SignInLayout/SignInLayout";
|
||||
import { Welcome } from "components/Welcome/Welcome";
|
||||
import { FC, useEffect } from "react";
|
||||
import { REFRESH_GITAUTH_BROADCAST_CHANNEL } from "utils/gitAuth";
|
||||
import { type FC } from "react";
|
||||
|
||||
export interface GitAuthPageViewProps {
|
||||
gitAuth: GitAuth;
|
||||
@@ -33,18 +32,6 @@ const GitAuthPageView: FC<GitAuthPageViewProps> = ({
|
||||
}) => {
|
||||
const styles = useStyles();
|
||||
|
||||
useEffect(() => {
|
||||
if (!gitAuth.authenticated) {
|
||||
return;
|
||||
}
|
||||
// This is used to notify the parent window that the Git auth token has been refreshed.
|
||||
// It's critical in the create workspace flow!
|
||||
// eslint-disable-next-line compat/compat -- It actually is supported... not sure why it's complaining.
|
||||
const bc = new BroadcastChannel(REFRESH_GITAUTH_BROADCAST_CHANNEL);
|
||||
// The message doesn't matter, any message refreshes the page!
|
||||
bc.postMessage("noop");
|
||||
}, [gitAuth.authenticated]);
|
||||
|
||||
if (!gitAuth.authenticated) {
|
||||
return (
|
||||
<SignInLayout>
|
||||
@@ -76,7 +63,7 @@ const GitAuthPageView: FC<GitAuthPageViewProps> = ({
|
||||
<SignInLayout>
|
||||
<Welcome message={`You've authenticated with ${gitAuth.type}!`} />
|
||||
<p className={styles.text}>
|
||||
Hey @{gitAuth.user?.login} 👋!{" "}
|
||||
Hey @{gitAuth.user?.login}! 👋{" "}
|
||||
{(!gitAuth.app_installable || gitAuth.installations.length > 0) &&
|
||||
"You are now authenticated with Git. Feel free to close this window!"}
|
||||
</p>
|
||||
|
||||
@@ -1190,18 +1190,24 @@ export const MockTemplateVersionVariable5: TypesGen.TemplateVersionVariable = {
|
||||
sensitive: false,
|
||||
};
|
||||
|
||||
// requests the MockWorkspace
|
||||
export const MockWorkspaceRequest: TypesGen.CreateWorkspaceRequest = {
|
||||
name: "test",
|
||||
template_id: "test-template",
|
||||
rich_parameter_values: [
|
||||
{
|
||||
name: MockTemplateVersionParameter1.name,
|
||||
value: MockTemplateVersionParameter1.default_value,
|
||||
},
|
||||
],
|
||||
template_version_id: "test-template-version",
|
||||
rich_parameter_values: [],
|
||||
};
|
||||
|
||||
export const MockWorkspaceRichParametersRequest: TypesGen.CreateWorkspaceRequest =
|
||||
{
|
||||
name: "test",
|
||||
template_version_id: "test-template-version",
|
||||
rich_parameter_values: [
|
||||
{
|
||||
name: MockTemplateVersionParameter1.name,
|
||||
value: MockTemplateVersionParameter1.default_value,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const MockUserAgent = {
|
||||
browser: "Chrome 99.0.4844",
|
||||
device: "Other",
|
||||
@@ -2184,6 +2190,14 @@ export const MockTemplateVersionGitAuth: TypesGen.TemplateVersionGitAuth = {
|
||||
authenticated: false,
|
||||
};
|
||||
|
||||
export const MockTemplateVersionGitAuthAuthenticated: TypesGen.TemplateVersionGitAuth =
|
||||
{
|
||||
id: "github",
|
||||
type: "github",
|
||||
authenticate_url: "https://example.com/gitauth/github",
|
||||
authenticated: true,
|
||||
};
|
||||
|
||||
export const MockDeploymentStats: TypesGen.DeploymentStats = {
|
||||
aggregated_from: "2023-03-06T19:08:55.211625Z",
|
||||
collected_at: "2023-03-06T19:12:55.211625Z",
|
||||
|
||||
@@ -2,13 +2,11 @@ import {
|
||||
checkAuthorization,
|
||||
createWorkspace,
|
||||
getTemplateByName,
|
||||
getTemplateVersionGitAuth,
|
||||
getTemplateVersionRichParameters,
|
||||
} from "api/api";
|
||||
import {
|
||||
CreateWorkspaceRequest,
|
||||
Template,
|
||||
TemplateVersionGitAuth,
|
||||
TemplateVersionParameter,
|
||||
User,
|
||||
Workspace,
|
||||
@@ -16,7 +14,6 @@ import {
|
||||
} from "api/typesGenerated";
|
||||
import { assign, createMachine } from "xstate";
|
||||
import { paramsUsedToCreateWorkspace } from "utils/workspace";
|
||||
import { REFRESH_GITAUTH_BROADCAST_CHANNEL } from "utils/gitAuth";
|
||||
|
||||
export type CreateWorkspaceMode = "form" | "auto";
|
||||
|
||||
@@ -33,7 +30,6 @@ type CreateWorkspaceContext = {
|
||||
template?: Template;
|
||||
parameters?: TemplateVersionParameter[];
|
||||
permissions?: Record<string, boolean>;
|
||||
gitAuth?: TemplateVersionGitAuth[];
|
||||
// Used on auto-create
|
||||
defaultBuildParameters?: WorkspaceBuildParameter[];
|
||||
};
|
||||
@@ -49,7 +45,7 @@ type RefreshGitAuthEvent = {
|
||||
};
|
||||
|
||||
export const createWorkspaceMachine =
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QGMBOYCGAXMB1A9qgNawAOGyYAyltmAHQxZYCWAdlACpgC2pANnVgBiCPjYN2AN3xEGaTDgLEyFarRyMwzdl14ChCafmTYW4gNoAGALrWbiUKXywWrcY5AAPRABYATAA0IACeiACMvgCs9ADssQBsVgDM-v5JyQnJUQkAvrnBCnTKJOSUNHRaOhzcfII4ImIS9MZy9EVKhKVqFZpMrDX69XBGbDKm7mz2FuEOSCDOrpOePgjJAJzr9AAcVr4J677bCeFWB3vBYQgBvvQJB-5R6+GxVulPUfmF6MVdquUaBj9XS1AwNYRgVCoQj0MEAM0IPHaP06KjK6kqwMGdUMxgm5imtnsnkWbgJK0QGy2u32h2Op3OvkuiH8vis9HCDxO0XC2z5CX8XxAHTwf3RvSB2gGehxOCoyAAFrwMKJxJIxrJ5CjRWieoCqtLQcN5UqeBhRuMzJYibYSS4yR55qtTv52bFeYlfPEor4XuFmddwlscqz1mdfMlfZlfEKRSV-hi+lKQUM6CblRCoTD4YjkYodd0AZjk9iwdRFcqLSYrYS7Lb5qTlk6Im83R6El7Yj6-QH4gl6M8ctsoht7skrJ8CsLtfHxfqsTKywAFDCoDA8bSQxpqloatpxsV64vVRfDFdrjc4VCwKv4611uZOe1N0DOgXhOKRjZf-axR4Btl2XWWJngSKJtgCHJwnSWMZ0PIskxPI06HPddN2vTNoVQWF6gRVAkQPXUEMlJDUxwVDLy3W8a2mesnyWclmwQTl-A-WIv3WH8Ej-KIAyyW4Em2Z5XV5fx1m48JYPzWcj0Qw0yLAABxNwAEEAFcsAVVVmlaLVpPgxMSPk2UlNUjSFWoyZaMfBZn0Y18WSDfsfUEvlfEHViA2SbZ-HoDZwPSe4omCwUp0IwtDINFMTOUrB1M0zDs1w3NwoTCUotLYZYviiy8Rom0bMbezvEc8T6BcvkII8-1Qj8dZfP2R4fXEziUliKTfiIyKK2QIhdCXSEeBYWBXHEbcdL3eQlV6gb8OG0a2FgYkGzsx0HIQfwfJiKJXmSZJoO88IRwAqwP22aD3OeLshP2DrUQi9Ker6jhZqGkaCRESEsJw7A8II6aiFe+aPuW+iHTYCkNqiKxtgHVi+XWYK2SZWqNr5HZslAiNoJSSdvn0rr0rhFh+H4frV3XEQAGEACUAFEVM4OmAH1cAAeRpgBpKglxUqm6dB2yGLWkq0cecrdv2-xDuO1GXluRH9s5H1wKObi7oLNL9WJ0nyYvEQqDpgAZOmqc4Zm2dwAA5OmacFoqRdWcd0asSWkZuoJUbSftpc2vZ6rZDsXg1mTiLzMwOFDsBtPVGR9zgwn9Q6XQo8sglrLtYWIaYzbxZ2lIpZl5IA3O+hLvWeleSiVj6pDgzHpRFODMS7Cc3w8P7q1ypk8jgy0-ve3Vuz9bc+2yWDvO2WrjE2HMcybZYiOHJYm2fIpzYfAIDgTxUrnOhM-ByGAFoEgDE-6CsS+3n8d0nl9aW8enAmHvnEtTyEA+X1F4cOWryM0k5JyTiAZWS3DSKBdIC9zjZDronY8xkyzpjNJ-YqqxXjORXuEfaEYAh9gAtLO4aQNgenqkdSMsCX7wOisuCmlFrwoMdhETazl9hCQ2NLKwFd1gnRiCkMM51og8k2BQruclqFZTMppBhw9RZBldvQTa0FzgdnuF5Y4ZdgrumyI8JyC8RF700E9fqg1gZjWkZDUBWwDgjjElgnawE1GxAUUJPYglwIjjeDGMKCdKGaB1mTF6tD4ArSzpDfabwdiXxApseqtjT6o1SE4txrVXTpHSF4-GnVfF6QjlAKO5ic5RCOn5LsbwjpHWeJyAMZVThHUgvcCSvp9GyRyTgCABT1rhN8rsV2MTYmgQDC6BR7xuznByCcZpYcvqEA6aLSxdxFa2OyCBWIAYimwxXvwoMrp3GrzXkAA */
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QGMBOYCGAXMB1A9qgNawAOGyYAyltmAHTIAWYyRAlgHZQCy+EYAMQBtAAwBdRKFL5Y7LO3ycpIAB6IATABYAnPR2iDWgOwBmAGynRo4wFZbAGhABPRDoAc9d+8OnTARlF-Uy0NUQ0AXwinNEwcAmIyCmpaHEYWNi5efiFhf0kkEBk5BSUVdQQNU1svEPcq9wsDU3ctJ1dKv3pRd3NzHVNjf1sjHX8omPQ6BJJySho6egwAVyx8AGEphW5BCCUGLgA3fCIGWOnCWeSFtJW1zbishCP8ZGxFTjFxL5Vi+Q-yoh7MZ9MZjLoQqItN5jDp2ogALT+PSWWwaDR9dzGDTeXTuCYgc7xS5JeapBh3DZbLKCMCoVCEeikAA22AAZoQALaMLZ4ElzFKLSkPd7cZ6cY5vUqfCQ-Qp-aWAhAtPQtWxDaE+OxQ4zwhD+dw1US2cw4-zmLQ9WyicwEol8xICm4MZn4DAQLIAMS5ABFsBhdvt6C9Tjy4g6rmTFq73V7ff7xZL3kovnLpLJ-mVChVzNb6P5tDoMei7P5-G0XIgQqZ6BjbFrWuZGrC7byZqTBWkYx7uN7UJy-bRafTGSz2VywxdHddyfRu3H+4OMInXsmZd8JL8M4rs4hejWCzYLOYDSfjOY9SENPpgmYy+bgjodLbooS2-yZ4t2BBmUJ1gAlABRABBAAVQCAH1cAAeX-ABpKgAAVgPWQC0yKbcAV3BBLHMWs61EQZjQ0ZF3D1ewa36S0QlhasQlbcN2ydWciSyJjkkDTgDglE4znfacozSVjuHYygVylD5U03eVMKzUAc1MPRGh0cEtBMJ9Wj1MxPFsKwmg0DwwmGBip0jTs+MeESP0oYcGVQJlWSwDl+0nYkBPM1y2OssBxLXKSCnTEosPkxBQn8eh7FaewBjRU1THIwIb2CLQ+nrXN1SiV9OByeBCntUTzK3IK5LUKstFrWE4uNGxwQxPUkRsfNqnRfxzybE1+hMtyzOddJWA4bg+AEIrM2UbD6gq58qmqsFQgvSsEGfehLEMExWp0LRSK6iMO164VqW4EadxC5Ui2W0wNDBDVekfLTrx8cIAnBKxgVsbaCt6+de3jWgjuC0qcIsZbfBtPE-F1BaGn0AzIWxGK-HxV98u83rv1-P6SpzSx6EUtT+kIsYT38PUtFscKDTUw1zGxMmy3elGWIOqACoxsaTtNPCLs2-oGlNVq9SJ2tQcCS0eZS+n3N6+0IFZpV3A2-MtBadx1uRcIyIWuxPDsforEM6x7AlnrZ27QCR1QWXxthHHLrBJ8nxtJsSd0ehsQsJWNHrYYi0yiIgA */
|
||||
createMachine(
|
||||
{
|
||||
id: "createWorkspaceState",
|
||||
@@ -64,7 +60,6 @@ export const createWorkspaceMachine =
|
||||
template: Template;
|
||||
permissions: CreateWSPermissions;
|
||||
parameters: TemplateVersionParameter[];
|
||||
gitAuth: TemplateVersionGitAuth[];
|
||||
};
|
||||
};
|
||||
createWorkspace: {
|
||||
@@ -112,16 +107,6 @@ export const createWorkspaceMachine =
|
||||
},
|
||||
},
|
||||
idle: {
|
||||
invoke: [
|
||||
{
|
||||
src: () => (callback) => {
|
||||
const channel = watchGitAuthRefresh(() => {
|
||||
callback("REFRESH_GITAUTH");
|
||||
});
|
||||
return () => channel.close();
|
||||
},
|
||||
},
|
||||
],
|
||||
on: {
|
||||
CREATE_WORKSPACE: {
|
||||
target: "creatingWorkspace",
|
||||
@@ -189,23 +174,24 @@ export const createWorkspaceMachine =
|
||||
rich_parameter_values: defaultBuildParameters,
|
||||
});
|
||||
},
|
||||
loadFormData: async ({ templateName, organizationId }) => {
|
||||
loadFormData: async ({ templateName, organizationId, versionId }) => {
|
||||
const [template, permissions] = await Promise.all([
|
||||
getTemplateByName(organizationId, templateName),
|
||||
checkCreateWSPermissions(organizationId),
|
||||
]);
|
||||
const [parameters, gitAuth] = await Promise.all([
|
||||
getTemplateVersionRichParameters(template.active_version_id).then(
|
||||
(p) => p.filter(paramsUsedToCreateWorkspace),
|
||||
|
||||
const realizedVersionId = versionId ?? template.active_version_id;
|
||||
const [parameters] = await Promise.all([
|
||||
getTemplateVersionRichParameters(realizedVersionId).then((p) =>
|
||||
p.filter(paramsUsedToCreateWorkspace),
|
||||
),
|
||||
getTemplateVersionGitAuth(template.active_version_id),
|
||||
]);
|
||||
|
||||
return {
|
||||
template,
|
||||
permissions,
|
||||
parameters,
|
||||
gitAuth,
|
||||
versionId: realizedVersionId,
|
||||
};
|
||||
},
|
||||
},
|
||||
@@ -246,12 +232,6 @@ const checkCreateWSPermissions = async (organizationId: string) => {
|
||||
}) as Promise<Record<keyof typeof permissionsToCheck, boolean>>;
|
||||
};
|
||||
|
||||
export const watchGitAuthRefresh = (callback: () => void) => {
|
||||
const bc = new BroadcastChannel(REFRESH_GITAUTH_BROADCAST_CHANNEL);
|
||||
bc.addEventListener("message", callback);
|
||||
return bc;
|
||||
};
|
||||
|
||||
export type CreateWSPermissions = Awaited<
|
||||
ReturnType<typeof checkCreateWSPermissions>
|
||||
>;
|
||||
|
||||
Reference in New Issue
Block a user