Skip to content

Request v3 access (from the app) and disable v2 projects by default #1123

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 8 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/webapp/app/assets/icons/v3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 2 additions & 15 deletions apps/webapp/app/models/organization.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,12 @@ export async function createOrganization(
{
title,
userId,
projectName,
companySize,
projectVersion,
}: Pick<Organization, "title" | "companySize"> & {
userId: User["id"];
projectName: string;
projectVersion: "v2" | "v3";
},
attemptCount = 0
): Promise<Organization & { projects: Project[] }> {
): Promise<Organization> {
if (typeof process.env.BLOCKED_USERS === "string" && process.env.BLOCKED_USERS.includes(userId)) {
throw new Error("Organization could not be created.");
}
Expand All @@ -50,9 +46,7 @@ export async function createOrganization(
{
title,
userId,
projectName,
companySize,
projectVersion,
},
attemptCount + 1
);
Expand All @@ -76,14 +70,7 @@ export async function createOrganization(
},
});

const project = await createProject({
organizationSlug: organization.slug,
name: projectName,
userId,
version: projectVersion,
});

return { ...organization, projects: [project] };
return { ...organization };
}

export async function createEnvironment(
Expand Down
4 changes: 4 additions & 0 deletions apps/webapp/app/presenters/OrganizationsPresenter.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ export class OrganizationsPresenter {
);
}

if (project.organizationId !== organization.id) {
throw redirect(newProjectPath({ slug: organizationSlug }), request);
}

return { organizations, organization, project };
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ import { FormTitle } from "~/components/primitives/FormTitle";
import { Input } from "~/components/primitives/Input";
import { InputGroup } from "~/components/primitives/InputGroup";
import { Label } from "~/components/primitives/Label";
import { Paragraph } from "~/components/primitives/Paragraph";
import { Select, SelectItem } from "~/components/primitives/Select";
import { TextLink } from "~/components/primitives/TextLink";
import { prisma } from "~/db.server";
import { useFeatures } from "~/hooks/useFeatures";
import { useUser } from "~/hooks/useUser";
import { redirectWithSuccessMessage } from "~/models/message.server";
import { createProject } from "~/models/project.server";
import { requireUserId } from "~/services/session.server";
import { OrganizationParamsSchema, organizationPath, projectPath } from "~/utils/pathBuilder";
import { RequestV3Access } from "../resources.orgs.$organizationSlug.v3-access";

export async function loader({ params, request }: LoaderFunctionArgs) {
const userId = await requireUserId(request);
Expand All @@ -34,10 +38,14 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
id: true,
title: true,
v3Enabled: true,
v2Enabled: true,
hasRequestedV3: true,
_count: {
select: {
projects: {
where: { deletedAt: null },
where: {
deletedAt: null,
},
},
},
},
Expand All @@ -57,6 +65,8 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
slug: organizationSlug,
projectsCount: organization._count.projects,
v3Enabled: organization.v3Enabled,
v2Enabled: organization.v2Enabled,
hasRequestedV3: organization.hasRequestedV3,
},
defaultVersion: url.searchParams.get("version") ?? "v2",
});
Expand Down Expand Up @@ -98,11 +108,23 @@ export const action: ActionFunction = async ({ request, params }) => {
};

export default function NewOrganizationPage() {
const { organization, defaultVersion } = useTypedLoaderData<typeof loader>();
const { organization } = useTypedLoaderData<typeof loader>();
const lastSubmission = useActionData();
const { v3Enabled } = useFeatures();
const { v3Enabled, isManagedCloud } = useFeatures();

const canCreateV3Projects = organization.v3Enabled && v3Enabled;
const canCreateV2Projects = organization.v2Enabled || !isManagedCloud;
const canCreateProjects = canCreateV2Projects || canCreateV3Projects;

if (!canCreateProjects) {
return (
<RequestV3Access
hasRequestedV3={organization.hasRequestedV3}
organizationSlug={organization.slug}
projectsCount={organization.projectsCount}
/>
);
}

const [form, { projectName, projectVersion }] = useForm({
id: "create-project",
Expand All @@ -119,7 +141,7 @@ export default function NewOrganizationPage() {
<FormTitle
LeadingIcon="folder"
title="Create a new project"
description={`This will create a new project in your "${organization.title}" organization. `}
description={`This will create a new project in your "${organization.title}" organization.`}
/>
<Form method="post" {...form.props}>
{organization.projectsCount === 0 && (
Expand All @@ -138,7 +160,7 @@ export default function NewOrganizationPage() {
/>
<FormError id={projectName.errorId}>{projectName.error}</FormError>
</InputGroup>
{canCreateV3Projects ? (
{canCreateV2Projects && canCreateV3Projects ? (
<InputGroup>
<Label htmlFor={projectVersion.id}>Project version</Label>
<Select
Expand All @@ -161,8 +183,16 @@ export default function NewOrganizationPage() {
</Select>
<FormError id={projectVersion.errorId}>{projectVersion.error}</FormError>
</InputGroup>
) : canCreateV3Projects ? (
<>
<Callout variant="info">This will be a v3 project</Callout>
<input {...conform.input(projectVersion, { type: "hidden" })} value={"v3"} />
</>
) : (
<input {...conform.input(projectVersion, { type: "hidden" })} value="v2" />
<>
<Callout variant="info">This will be a v2 project</Callout>
<input {...conform.input(projectVersion, { type: "hidden" })} value={"v2"} />
</>
)}
<FormButtons
confirmButton={
Expand Down
71 changes: 4 additions & 67 deletions apps/webapp/app/routes/_app.orgs.new/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,14 @@ import { Input } from "~/components/primitives/Input";
import { InputGroup } from "~/components/primitives/InputGroup";
import { Label } from "~/components/primitives/Label";
import { RadioGroupItem } from "~/components/primitives/RadioButton";
import { Select, SelectItem } from "~/components/primitives/Select";
import { featuresForRequest } from "~/features.server";
import { useFeatures } from "~/hooks/useFeatures";
import { createOrganization } from "~/models/organization.server";
import { NewOrganizationPresenter } from "~/presenters/NewOrganizationPresenter.server";
import { commitCurrentProjectSession, setCurrentProjectId } from "~/services/currentProject.server";
import { requireUserId } from "~/services/session.server";
import { projectPath, rootPath, selectPlanPath } from "~/utils/pathBuilder";
import { organizationPath, rootPath } from "~/utils/pathBuilder";

const schema = z.object({
orgName: z.string().min(3).max(50),
projectName: z.string().min(3).max(50),
projectVersion: z.enum(["v2", "v3"]),
companySize: z.string().optional(),
});

Expand Down Expand Up @@ -57,29 +52,10 @@ export const action: ActionFunction = async ({ request }) => {
const organization = await createOrganization({
title: submission.value.orgName,
userId,
projectName: submission.value.projectName,
companySize: submission.value.companySize ?? null,
projectVersion: submission.value.projectVersion,
});

const project = organization.projects[0];
const session = await setCurrentProjectId(project.id, request);

const { isManagedCloud } = featuresForRequest(request);

const headers = {
"Set-Cookie": await commitCurrentProjectSession(session),
};

if (isManagedCloud && submission.value.projectVersion === "v2") {
return redirect(selectPlanPath(organization), {
headers,
});
}

return redirect(projectPath(organization, project), {
headers,
});
return redirect(organizationPath(organization));
} catch (error: any) {
return json({ errors: { body: error.message } }, { status: 400 });
}
Expand All @@ -91,10 +67,7 @@ export default function NewOrganizationPage() {
const { isManagedCloud } = useFeatures();
const navigation = useNavigation();

//this is temporary whilst v3 is invite-only. Switch to the useFeatures value when v3 is generally available.
const v3Enabled = false;

const [form, { orgName, projectName, projectVersion }] = useForm({
const [form, { orgName }] = useForm({
id: "create-organization",
// TODO: type this
lastSubmission: lastSubmission as any,
Expand Down Expand Up @@ -123,45 +96,9 @@ export default function NewOrganizationPage() {
<Hint>E.g. your company name or your workspace name.</Hint>
<FormError id={orgName.errorId}>{orgName.error}</FormError>
</InputGroup>
<InputGroup>
<Label htmlFor={projectName.id}>Project name</Label>
<Input
{...conform.input(projectName, { type: "text" })}
placeholder="Your Project name"
icon="folder"
/>
<Hint>Your Jobs will live inside this Project.</Hint>
<FormError id={projectName.errorId}>{projectName.error}</FormError>
</InputGroup>
{v3Enabled ? (
<InputGroup>
<Label htmlFor={projectVersion.id}>Project version</Label>
<Select
{...conform.select(projectVersion)}
defaultValue={undefined}
variant="tertiary/medium"
placeholder="Select version"
dropdownIcon
text={(value) => {
switch (value) {
case "v2":
return "Version 2";
case "v3":
return "Version 3";
}
}}
>
<SelectItem value="v2">Version 2</SelectItem>
<SelectItem value="v3">Version 3 (Developer Preview)</SelectItem>
</Select>
<FormError id={projectVersion.errorId}>{projectVersion.error}</FormError>
</InputGroup>
) : (
<input {...conform.input(projectVersion, { type: "hidden" })} value="v2" />
)}
{isManagedCloud && (
<InputGroup>
<Label htmlFor={projectName.id}>Number of employees</Label>
<Label htmlFor={"companySize"}>Number of employees</Label>
<RadioGroup name="companySize" className="flex items-center justify-between gap-2">
<RadioGroupItem
id="employees-1-5"
Expand Down
Loading
Loading