Skip to content

Commit

Permalink
chore: migrate apps/[slug]/setup, apps/[slug]/ page
Browse files Browse the repository at this point in the history
  • Loading branch information
DmytroHryshyn committed Dec 1, 2023
1 parent c4565da commit 4f5a66e
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,5 @@ E2E_TEST_OIDC_USER_PASSWORD=
AB_TEST_BUCKET_PROBABILITY=50
# whether we redirect to the future/event-types from event-types or not
APP_ROUTER_EVENT_TYPES_ENABLED=1
APP_ROUTER_APPS_SLUG_ENABLED=1
APP_ROUTER_APPS_SLUG_SETUP_ENABLED=1
4 changes: 3 additions & 1 deletion apps/web/abTest/middlewareFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { NextResponse, URLPattern } from "next/server";
import z from "zod";

const ROUTES: [URLPattern, boolean][] = [
["/event-types", process.env.APP_ROUTER_EVENT_TYPES_ENABLED === "1"] as const,
["/apps/:slug", Boolean(process.env.APP_ROUTER_APPS_SLUG_ENABLED)] as const,
["/apps/:slug/setup", Boolean(process.env.APP_ROUTER_APPS_SLUG_SETUP_ENABLED)] as const,
["/event-types", Boolean(process.env.APP_ROUTER_EVENT_TYPES_ENABLED)] as const,
].map(([pathname, enabled]) => [
new URLPattern({
pathname,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { type ReactElement } from "react";

import PageWrapper from "@components/PageWrapperAppDir";

type EventTypesLayoutProps = {
children: ReactElement;
};

export default function Layout({ children }: EventTypesLayoutProps) {
return (
<PageWrapper getLayout={null} requiresLicense={false} nonce={undefined} themeBasis={null}>
{children}
</PageWrapper>
);
}
126 changes: 126 additions & 0 deletions apps/web/app/future/(individual-page-wrapper)/apps/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import AppPage from "@pages/apps/[slug]/index";
import { Prisma } from "@prisma/client";
import { _generateMetadata } from "app/_utils";
import fs from "fs";
import matter from "gray-matter";
import { notFound } from "next/navigation";
import path from "path";
import { z } from "zod";

import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
import { getAppAssetFullPath } from "@calcom/app-store/getAppAssetFullPath";
import { APP_NAME, IS_PRODUCTION } from "@calcom/lib/constants";
import prisma from "@calcom/prisma";

const sourceSchema = z.object({
content: z.string(),
data: z.object({
description: z.string().optional(),
items: z
.array(
z.union([
z.string(),
z.object({
iframe: z.object({ src: z.string() }),
}),
])
)
.optional(),
}),
});

export const generateMetadata = async ({ params }: { params: Record<string, string | string[]> }) => {
const { data } = await getPageProps({ params });

return await _generateMetadata(
() => `${data.name} | ${APP_NAME}`,
() => data.description
);
};

export const generateStaticParams = async () => {
try {
const appStore = await prisma.app.findMany({ select: { slug: true } });
return appStore.map(({ slug }) => ({ slug }));
} catch (e: unknown) {
if (e instanceof Prisma.PrismaClientInitializationError) {
// Database is not available at build time, but that's ok – we fall back to resolving paths on demand
} else {
throw e;
}
}

return [];
};

const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
if (typeof params?.slug !== "string") {
notFound();
}

const appMeta = await getAppWithMetadata({
slug: params?.slug,
});

const appFromDb = await prisma.app.findUnique({
where: { slug: params.slug.toLowerCase() },
});

const isAppAvailableInFileSystem = appMeta;
const isAppDisabled = isAppAvailableInFileSystem && (!appFromDb || !appFromDb.enabled);

if (!IS_PRODUCTION && isAppDisabled) {
return {
isAppDisabled: true as const,
data: {
...appMeta,
},
};
}

if (!appFromDb || !appMeta || isAppDisabled) {
notFound();
}

const isTemplate = appMeta.isTemplate;
const appDirname = path.join(isTemplate ? "templates" : "", appFromDb.dirName);
const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/DESCRIPTION.md`);
const postFilePath = path.join(README_PATH);
let source = "";

try {
source = fs.readFileSync(postFilePath).toString();
source = source.replace(/{DESCRIPTION}/g, appMeta.description);
} catch (error) {
/* If the app doesn't have a README we fallback to the package description */
console.log(`No DESCRIPTION.md provided for: ${appDirname}`);
source = appMeta.description;
}

const result = matter(source);
const { content, data } = sourceSchema.parse({ content: result.content, data: result.data });
if (data.items) {
data.items = data.items.map((item) => {
if (typeof item === "string") {
return getAppAssetFullPath(item, {
dirName: appMeta.dirName,
isTemplate: appMeta.isTemplate,
});
}
return item;
});
}
return {
isAppDisabled: false as const,
source: { content, data },
data: appMeta,
};
};

export default async function Page({ params }: { params: Record<string, string | string[]> }) {
const pageProps = await getPageProps({ params });

return <AppPage {...pageProps} />;
}

export const dynamic = "force-static";
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import SetupPage from "@pages/apps/[slug]/setup";
import { _generateMetadata } from "app/_utils";
import type { GetServerSidePropsContext } from "next";
import { cookies, headers } from "next/headers";
import { notFound, redirect } from "next/navigation";

import { getServerSideProps } from "@calcom/app-store/_pages/setup/_getServerSideProps";
import { APP_NAME } from "@calcom/lib/constants";

export const generateMetadata = async ({ params }: { params: Record<string, string | string[]> }) => {
return await _generateMetadata(
() => `${params.slug} | ${APP_NAME}`,
() => ""
);
};

const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
const req = { headers: headers(), cookies: cookies() };

const result = await getServerSideProps({ params, req } as unknown as GetServerSidePropsContext);

if (!result || "notFound" in result) {
notFound();
}

if ("redirect" in result) {
redirect(result.redirect.destination);
}

return result.props;
};

export default async function Page({ params }: { params: Record<string, string | string[]> }) {
const pageProps = await getPageProps({ params });
return <SetupPage {...pageProps} />;
}
6 changes: 6 additions & 0 deletions apps/web/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ export const config = {
"/apps/routing_forms/:path*",
"/event-types",
"/future/event-types/",

"/apps/:slug/",
"/future/apps/:slug/",

"/apps/:slug/setup/",
"/future/apps/:slug/setup/",
],
};

Expand Down
2 changes: 2 additions & 0 deletions apps/web/pages/apps/[slug]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import { Prisma } from "@prisma/client";
import fs from "fs";
import matter from "gray-matter";
Expand Down
2 changes: 2 additions & 0 deletions apps/web/pages/apps/[slug]/setup.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import type { InferGetServerSidePropsType } from "next";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
Expand Down
61 changes: 61 additions & 0 deletions apps/web/playwright/ab-tests-redirect.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { expect } from "@playwright/test";

import { test } from "./lib/fixtures";

test.describe.configure({ mode: "parallel" });

test.describe("apps/ A/B tests", () => {
test("should point to the /future/apps/[slug]", async ({ page, users, context }) => {
await context.addCookies([
{
name: "x-calcom-future-routes-override",
value: "1",
url: "http://localhost:3000",
},
]);
const user = await users.create();

await user.apiLogin();

await page.goto("/apps/telegram");

await page.waitForLoadState();

const dataNextJsRouter = await page.evaluate(() =>
window.document.documentElement.getAttribute("data-nextjs-router")
);

expect(dataNextJsRouter).toEqual("app");

const locator = page.getByRole("heading", { name: "Telegram" });

await expect(locator).toBeVisible();
});

test("should point to the /future/apps/[slug]/setup", async ({ page, users, context }) => {
await context.addCookies([
{
name: "x-calcom-future-routes-override",
value: "1",
url: "http://localhost:3000",
},
]);
const user = await users.create();

await user.apiLogin();

await page.goto("/apps/apple-calendar/setup");

await page.waitForLoadState();

const dataNextJsRouter = await page.evaluate(() =>
window.document.documentElement.getAttribute("data-nextjs-router")
);

expect(dataNextJsRouter).toEqual("app");

const locator = page.getByRole("heading", { name: "Connect to Apple Server" });

await expect(locator).toBeVisible();
});
});
2 changes: 2 additions & 0 deletions turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@
"ANALYZE",
"API_KEY_PREFIX",
"APP_ROUTER_EVENT_TYPES_ENABLED",
"APP_ROUTER_APPS_SLUG_ENABLED",
"APP_ROUTER_APPS_SLUG_SETUP_ENABLED",
"APP_USER_NAME",
"BASECAMP3_CLIENT_ID",
"BASECAMP3_CLIENT_SECRET",
Expand Down

0 comments on commit 4f5a66e

Please sign in to comment.