Compare commits

...

6 Commits

Author SHA1 Message Date
pandeymangg
34b6e2d73a Merge branch 'main' into fix/ee 2024-06-21 17:08:19 +05:30
pandeymangg
479c9ac9a9 fix: fixtures 2024-06-21 17:05:46 +05:30
pandeymangg
9474321a04 Merge branch 'main' into fix/ee 2024-06-20 16:49:51 +05:30
pandeymangg
222f572d63 fix: ee 2024-06-20 16:49:29 +05:30
pandeymangg
190911eeaa Merge branch 'main' into fix/ee 2024-06-20 13:37:50 +05:30
pandeymangg
e0edbd1f86 wip 2024-06-20 13:37:28 +05:30
14 changed files with 283 additions and 69 deletions

View File

@@ -1,7 +1,7 @@
import { MainNavigation } from "@/app/(app)/environments/[environmentId]/components/MainNavigation"; import { MainNavigation } from "@/app/(app)/environments/[environmentId]/components/MainNavigation";
import { TopControlBar } from "@/app/(app)/environments/[environmentId]/components/TopControlBar"; import { TopControlBar } from "@/app/(app)/environments/[environmentId]/components/TopControlBar";
import type { Session } from "next-auth"; import type { Session } from "next-auth";
import { getIsMultiOrgEnabled } from "@formbricks/ee/lib/service"; import { getEnterpriseLicense } from "@formbricks/ee/lib/service";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { getEnvironment, getEnvironments } from "@formbricks/lib/environment/service"; import { getEnvironment, getEnvironments } from "@formbricks/lib/environment/service";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
@@ -13,6 +13,7 @@ import { getProducts } from "@formbricks/lib/product/service";
import { DevEnvironmentBanner } from "@formbricks/ui/DevEnvironmentBanner"; import { DevEnvironmentBanner } from "@formbricks/ui/DevEnvironmentBanner";
import { ErrorComponent } from "@formbricks/ui/ErrorComponent"; import { ErrorComponent } from "@formbricks/ui/ErrorComponent";
import { LimitsReachedBanner } from "@formbricks/ui/LimitsReachedBanner"; import { LimitsReachedBanner } from "@formbricks/ui/LimitsReachedBanner";
import { PendingDowngradeBanner } from "@formbricks/ui/PendingDowngradeBanner";
interface EnvironmentLayoutProps { interface EnvironmentLayoutProps {
environmentId: string; environmentId: string;
@@ -41,7 +42,9 @@ export const EnvironmentLayout = async ({ environmentId, session, children }: En
} }
const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id); const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id);
const isMultiOrgEnabled = await getIsMultiOrgEnabled(); const { features, lastChecked, isPendingDowngrade, active } = await getEnterpriseLicense();
const isMultiOrgEnabled = features?.isMultiOrgEnabled ?? false;
const currentProductChannel = const currentProductChannel =
products.find((product) => product.id === environment.productId)?.config.channel ?? null; products.find((product) => product.id === environment.productId)?.config.channel ?? null;
@@ -52,6 +55,12 @@ export const EnvironmentLayout = async ({ environmentId, session, children }: En
{IS_FORMBRICKS_CLOUD && <LimitsReachedBanner organization={organization} />} {IS_FORMBRICKS_CLOUD && <LimitsReachedBanner organization={organization} />}
<PendingDowngradeBanner
lastChecked={lastChecked}
isPendingDowngrade={isPendingDowngrade ?? false}
active={active}
/>
<div className="flex h-full"> <div className="flex h-full">
<MainNavigation <MainNavigation
environment={environment} environment={environment}

View File

@@ -2,7 +2,7 @@ import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmen
import { CheckIcon } from "lucide-react"; import { CheckIcon } from "lucide-react";
import { getServerSession } from "next-auth"; import { getServerSession } from "next-auth";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { getIsEnterpriseEdition } from "@formbricks/ee/lib/service"; import { getEnterpriseLicense } from "@formbricks/ee/lib/service";
import { authOptions } from "@formbricks/lib/authOptions"; import { authOptions } from "@formbricks/lib/authOptions";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
@@ -37,7 +37,7 @@ const Page = async ({ params }) => {
notFound(); notFound();
} }
const isEnterpriseEdition = await getIsEnterpriseEdition(); const { active: isEnterpriseEdition } = await getEnterpriseLicense();
const paidFeatures = [ const paidFeatures = [
{ {
@@ -166,7 +166,7 @@ const Page = async ({ params }) => {
</li> </li>
))} ))}
</ul> </ul>
<p className="my-6 text-sm text-slate-700"> <p className="my-6 text-sm text-slate-700">
No call needed, no strings attached: Request a free 30-day trial license to test all features No call needed, no strings attached: Request a free 30-day trial license to test all features
by filling out this form: by filling out this form:
</p> </p>
@@ -176,7 +176,7 @@ const Page = async ({ params }) => {
target="_blank"> target="_blank">
Request 30-day Trial License Request 30-day Trial License
</Button> </Button>
<p className="mt-2 text-xs text-slate-500">No credit card. No sales call. Just test it :)</p> <p className="mt-2 text-xs text-slate-500">No credit card. No sales call. Just test it :)</p>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,19 +1,23 @@
import { actions, users } from "@/playwright/utils/mock"; import { actions, users } from "@/playwright/utils/mock";
import { Page, expect, test } from "@playwright/test"; import { Page, expect } from "@playwright/test";
import { test } from "./lib/fixtures";
import { finishOnboarding, login, signUpAndLogin } from "./utils/helper"; import { finishOnboarding, login, signUpAndLogin } from "./utils/helper";
const createNoCodeClickAction = async ( const createNoCodeClickAction = async (
page: Page, page: Page,
username: string, // username: string,
email: string, // email: string,
password: string, // password: string,
actionName: string, actionName: string,
description: string, description: string,
selector: string selector: string
) => { ) => {
await signUpAndLogin(page, username, email, password); // await signUpAndLogin(page, username, email, password);
await finishOnboarding(page);
// await login(page, email, password);
// await finishOnboarding(page);
// await page.waitForURL(/\/environments\/[^/]+\/surveys/);
await page.getByRole("link", { name: "Actions" }).click(); await page.getByRole("link", { name: "Actions" }).click();
await page.waitForURL(/\/environments\/[^/]+\/actions/); await page.waitForURL(/\/environments\/[^/]+\/actions/);
@@ -135,44 +139,54 @@ const getActionButtonLocator = (page: Page, actionName: string) => {
test.describe("Create and Edit No Code Click Action", async () => { test.describe("Create and Edit No Code Click Action", async () => {
test.describe.configure({ mode: "serial" }); test.describe.configure({ mode: "serial" });
const { email, password, name: username } = users.action[0]; // const { email, password, name: username } = users.action[0];
test("Create No Code Click Action by CSS Selector", async ({ page, users: usersFixture }) => {
const { email, name } = users.action[0];
const user = await usersFixture.create({
email,
name,
organizationName: "Test Organization",
});
await user.login();
test("Create No Code Click Action by CSS Selector", async ({ page }) => {
await createNoCodeClickAction( await createNoCodeClickAction(
page, page,
username, // name,
email, // email,
password, // password,
actions.create.noCode.click.name, actions.create.noCode.click.name,
actions.create.noCode.click.description, actions.create.noCode.click.description,
actions.create.noCode.click.selector actions.create.noCode.click.selector
); );
}); });
test("Edit No Code Click Action", async ({ page }) => { // test("Edit No Code Click Action", async ({ page }) => {
await login(page, email, password); // const { email, password } = users.action[0];
await page.getByRole("link", { name: "Actions" }).click(); // await login(page, email, password);
await page.waitForURL(/\/environments\/[^/]+\/actions/); // await page.getByRole("link", { name: "Actions" }).click();
// await page.waitForURL(/\/environments\/[^/]+\/actions/);
const actionButton = getActionButtonLocator(page, actions.create.noCode.click.name); // const actionButton = getActionButtonLocator(page, actions.create.noCode.click.name);
await expect(actionButton).toBeVisible(); // await expect(actionButton).toBeVisible();
await actionButton.click(); // await actionButton.click();
await page.getByRole("button", { name: "Settings", exact: true }).click(); // await page.getByRole("button", { name: "Settings", exact: true }).click();
await expect(page.getByLabel("What did your user do?")).toBeVisible(); // await expect(page.getByLabel("What did your user do?")).toBeVisible();
await page.getByLabel("What did your user do?").fill(actions.edit.noCode.click.name); // await page.getByLabel("What did your user do?").fill(actions.edit.noCode.click.name);
await expect(page.getByLabel("Description")).toBeVisible(); // await expect(page.getByLabel("Description")).toBeVisible();
await page.getByLabel("Description").fill(actions.edit.noCode.click.description); // await page.getByLabel("Description").fill(actions.edit.noCode.click.description);
await expect(page.locator("[name='noCodeConfig.elementSelector.cssSelector']")).toBeVisible(); // await expect(page.locator("[name='noCodeConfig.elementSelector.cssSelector']")).toBeVisible();
await page // await page
.locator("[name='noCodeConfig.elementSelector.cssSelector']") // .locator("[name='noCodeConfig.elementSelector.cssSelector']")
.fill(actions.edit.noCode.click.selector); // .fill(actions.edit.noCode.click.selector);
await page.getByRole("button", { name: "Save changes", exact: true }).click(); // await page.getByRole("button", { name: "Save changes", exact: true }).click();
}); // });
}); });
test.describe("Create and Edit No Code Page view Action", async () => { test.describe("Create and Edit No Code Page view Action", async () => {
@@ -347,14 +361,14 @@ test.describe("Create and Edit Code Action", async () => {
test.describe("Create and Delete Action", async () => { test.describe("Create and Delete Action", async () => {
test.describe.configure({ mode: "serial" }); test.describe.configure({ mode: "serial" });
const { email, password, name: username } = users.action[5]; const { email, password } = users.action[5];
test("Create Action", async ({ page }) => { test("Create Action", async ({ page }) => {
await createNoCodeClickAction( await createNoCodeClickAction(
page, page,
username, // username,
email, // email,
password, // password,
actions.delete.noCode.name, actions.delete.noCode.name,
actions.delete.noCode.description, actions.delete.noCode.description,
actions.delete.noCode.selector actions.delete.noCode.selector

View File

@@ -1,6 +1,7 @@
import { finishOnboarding, signUpAndLogin } from "@/playwright/utils/helper"; import { finishOnboarding, signUpAndLogin } from "@/playwright/utils/helper";
import { users } from "@/playwright/utils/mock"; import { users } from "@/playwright/utils/mock";
import { expect, test } from "@playwright/test"; import { expect } from "@playwright/test";
import { test } from "../../lib/fixtures";
const { name, email, password } = users.survey[2]; const { name, email, password } = users.survey[2];

View File

@@ -0,0 +1,87 @@
import { Prisma } from "@prisma/client";
import bcrypt from "bcryptjs";
import { Page } from "playwright";
import { expect } from "playwright/test";
import { prisma } from "@formbricks/database";
export const login = async (user: Prisma.UserGetPayload<{ include: { memberships: true } }>, page: Page) => {
console.log("logging in", user);
await page.goto("/auth/login");
await expect(page.getByRole("button", { name: "Login with Email" })).toBeVisible();
await page.getByRole("button", { name: "Login with Email" }).click();
await expect(page.getByPlaceholder("work@email.com")).toBeVisible();
await page.getByPlaceholder("work@email.com").fill(user.email);
await expect(page.getByPlaceholder("*******")).toBeVisible();
await page.getByPlaceholder("*******").click();
await page.getByPlaceholder("*******").fill(user.name);
await page.getByRole("button", { name: "Login with Email" }).click();
};
export const createUserFixture = (
user: Prisma.UserGetPayload<{ include: { memberships: true } }>,
page: Page
) => {
return {
login: async () => {
await login(user, page);
},
};
};
export const createUsersFixture = (page: Page) => {
return {
create: async (params: {
name: string;
email: string;
organizationName: string;
productName?: string;
}) => {
const hashedPassword = await bcrypt.hash(params.name, 10);
const user = await prisma.user.create({
data: {
name: params.name,
email: params.email,
password: hashedPassword,
memberships: {
create: {
organization: {
create: {
name: params.organizationName,
billing: {
plan: "free",
limits: { monthly: { responses: 500, miu: 1000 } },
stripeCustomerId: null,
periodStart: new Date(),
period: "monthly",
},
products: {
create: {
name: params.productName ?? "My Product",
environments: {
create: [{ type: "development" }, { type: "production" }],
},
},
},
},
},
role: "owner",
},
},
},
include: { memberships: true },
});
console.log("created user", user);
const userFixture = createUserFixture(user, page);
return userFixture;
},
};
};

View File

@@ -1,4 +1,5 @@
import { expect, test } from "@playwright/test"; import { expect } from "@playwright/test";
import { test } from "./lib/fixtures";
import { finishOnboarding, login, replaceEnvironmentIdInHtml, signUpAndLogin } from "./utils/helper"; import { finishOnboarding, login, replaceEnvironmentIdInHtml, signUpAndLogin } from "./utils/helper";
import { users } from "./utils/mock"; import { users } from "./utils/mock";

View File

@@ -0,0 +1,13 @@
import { test as base } from "@playwright/test";
import { createUsersFixture } from "../fixtures/users";
export interface Fixtures {
users: ReturnType<typeof createUsersFixture>;
}
export const test = base.extend<Fixtures>({
users: async ({ page }, use) => {
const usersFixture = createUsersFixture(page);
await use(usersFixture);
},
});

View File

@@ -1,4 +1,5 @@
import { expect, test } from "@playwright/test"; import { expect } from "@playwright/test";
import { test } from "./lib/fixtures";
import { signUpAndLogin } from "./utils/helper"; import { signUpAndLogin } from "./utils/helper";
import { organizations, users } from "./utils/mock"; import { organizations, users } from "./utils/mock";

View File

@@ -1,4 +1,5 @@
import { expect, test } from "playwright/test"; import { expect } from "playwright/test";
import { test } from "./lib/fixtures";
import { finishOnboarding, login, signUpAndLogin, signupUsingInviteToken } from "./utils/helper"; import { finishOnboarding, login, signUpAndLogin, signupUsingInviteToken } from "./utils/helper";
import { invites, users } from "./utils/mock"; import { invites, users } from "./utils/mock";

View File

@@ -1,4 +1,5 @@
import { expect, test } from "@playwright/test"; import { expect } from "@playwright/test";
import { test } from "./lib/fixtures";
import { users } from "./utils/mock"; import { users } from "./utils/mock";
const { name, email, password } = users.signup[0]; const { name, email, password } = users.signup[0];

View File

@@ -1,5 +1,6 @@
import { surveys, users } from "@/playwright/utils/mock"; import { surveys, users } from "@/playwright/utils/mock";
import { expect, test } from "@playwright/test"; import { expect } from "@playwright/test";
import { test } from "./lib/fixtures";
import { createSurvey, finishOnboarding, signUpAndLogin } from "./utils/helper"; import { createSurvey, finishOnboarding, signUpAndLogin } from "./utils/helper";
test.describe("Survey Create & Submit Response", async () => { test.describe("Survey Create & Submit Response", async () => {

View File

@@ -19,7 +19,7 @@ const PREVIOUS_RESULTS_CACHE_TAG_KEY = `getPreviousResult-${hashedKey}` as const
// This function is used to get the previous result of the license check from the cache // This function is used to get the previous result of the license check from the cache
// This might seem confusing at first since we only return the default value from this function, // This might seem confusing at first since we only return the default value from this function,
// but since we are using a cache and the cache key is the same, the cache will return the previous result - so this functions as a cache getter // but since we are using a cache and the cache key is the same, the cache will return the previous result - so this function acts as a cache getter
const getPreviousResult = (): Promise<{ const getPreviousResult = (): Promise<{
active: boolean | null; active: boolean | null;
lastChecked: Date; lastChecked: Date;
@@ -89,47 +89,86 @@ const fetchLicenseForE2ETesting = async (): Promise<{
} }
}; };
export const getIsEnterpriseEdition = async (): Promise<boolean> => { export const getEnterpriseLicense = async (): Promise<{
active: boolean;
features: TEnterpriseLicenseFeatures | null;
lastChecked: Date;
isPendingDowngrade?: boolean;
}> => {
if (!ENTERPRISE_LICENSE_KEY || ENTERPRISE_LICENSE_KEY.length === 0) { if (!ENTERPRISE_LICENSE_KEY || ENTERPRISE_LICENSE_KEY.length === 0) {
return false; return {
active: false,
features: null,
lastChecked: new Date(),
};
} }
if (E2E_TESTING) { if (E2E_TESTING) {
const previousResult = await fetchLicenseForE2ETesting(); const previousResult = await fetchLicenseForE2ETesting();
return previousResult && previousResult.active !== null ? previousResult.active : false; // return previousResult && previousResult.active !== null ? previousResult.active : false;
return {
active: previousResult ? previousResult.active : false,
features: previousResult ? previousResult.features : null,
lastChecked: previousResult ? previousResult.lastChecked : new Date(),
};
} }
// if the server responds with a boolean, we return it // if the server responds with a boolean, we return it
// if the server errors, we return null // if the server errors, we return null
// null signifies an error // null signifies an error
const license = await fetchLicense(); const license = await fetchLicense();
const isValid = license ? license.status === "active" : null; const isValid = license ? license.status === "active" : null;
const threeDaysInMillis = 3 * 24 * 60 * 60 * 1000;
const currentTime = new Date();
const previousResult = await getPreviousResult(); const previousResult = await getPreviousResult();
if (previousResult.active === null) { if (previousResult.active === null) {
if (isValid === null) { if (isValid === null) {
await setPreviousResult({ const newResult = {
active: false, active: false,
features: { isMultiOrgEnabled: false }, features: { isMultiOrgEnabled: false },
lastChecked: new Date(), lastChecked: new Date(),
}); };
return false;
await setPreviousResult(newResult);
return newResult;
} }
} }
if (isValid !== null && license) { if (isValid !== null && license) {
await setPreviousResult({ active: isValid, features: license.features, lastChecked: new Date() }); const newResult = {
return isValid; active: isValid,
features: license.features,
lastChecked: new Date(),
};
await setPreviousResult(newResult);
return newResult;
} else { } else {
// if result is undefined -> error // if result is undefined -> error
// if the last check was less than 72 hours, return the previous value: // if the last check was less than 72 hours, return the previous value:
if (new Date().getTime() - previousResult.lastChecked.getTime() <= 3 * 24 * 60 * 60 * 1000) {
return previousResult.active !== null ? previousResult.active : false; const elapsedTime = currentTime.getTime() - previousResult.lastChecked.getTime();
if (elapsedTime < threeDaysInMillis) {
return {
active: previousResult.active !== null ? previousResult.active : false,
features: previousResult.features,
lastChecked: previousResult.lastChecked,
isPendingDowngrade: true,
};
} }
// if the last check was more than 72 hours, return false and log the error // Log error only after 72 hours
console.error("Error while checking license: The license check failed"); console.error("Error while checking license: The license check failed");
return false;
return {
active: false,
features: null,
lastChecked: previousResult.lastChecked,
isPendingDowngrade: true,
};
} }
}; };
@@ -139,14 +178,13 @@ export const getLicenseFeatures = async (): Promise<TEnterpriseLicenseFeatures |
return previousResult.features; return previousResult.features;
} else { } else {
const license = await fetchLicense(); const license = await fetchLicense();
if (!license) return null; if (!license || !license.features) return null;
const features = await license.features; return license.features;
return features;
} }
}; };
export const fetchLicense = async () => { export const fetchLicense = async (): Promise<TEnterpriseLicenseDetails | null> =>
const licenseResult: TEnterpriseLicenseDetails | null = await cache( await cache(
async () => { async () => {
if (!env.ENTERPRISE_LICENSE_KEY) return null; if (!env.ENTERPRISE_LICENSE_KEY) return null;
try { try {
@@ -192,8 +230,6 @@ export const fetchLicense = async () => {
[`fetchLicense-${hashedKey}`], [`fetchLicense-${hashedKey}`],
{ revalidate: 60 * 60 * 24 } { revalidate: 60 * 60 * 24 }
)(); )();
return licenseResult;
};
export const getRemoveInAppBrandingPermission = (organization: TOrganization): boolean => { export const getRemoveInAppBrandingPermission = (organization: TOrganization): boolean => {
if (IS_FORMBRICKS_CLOUD) return organization.billing.plan !== PRODUCT_FEATURE_KEYS.FREE; if (IS_FORMBRICKS_CLOUD) return organization.billing.plan !== PRODUCT_FEATURE_KEYS.FREE;
@@ -213,7 +249,7 @@ export const getRoleManagementPermission = async (organization: TOrganization):
organization.billing.plan === PRODUCT_FEATURE_KEYS.SCALE || organization.billing.plan === PRODUCT_FEATURE_KEYS.SCALE ||
organization.billing.plan === PRODUCT_FEATURE_KEYS.ENTERPRISE organization.billing.plan === PRODUCT_FEATURE_KEYS.ENTERPRISE
); );
else if (!IS_FORMBRICKS_CLOUD) return await getIsEnterpriseEdition(); else if (!IS_FORMBRICKS_CLOUD) return (await getEnterpriseLicense()).active;
return false; return false;
}; };
@@ -223,13 +259,13 @@ export const getAdvancedTargetingPermission = async (organization: TOrganization
organization.billing.plan === PRODUCT_FEATURE_KEYS.SCALE || organization.billing.plan === PRODUCT_FEATURE_KEYS.SCALE ||
organization.billing.plan === PRODUCT_FEATURE_KEYS.ENTERPRISE organization.billing.plan === PRODUCT_FEATURE_KEYS.ENTERPRISE
); );
else if (!IS_FORMBRICKS_CLOUD) return await getIsEnterpriseEdition(); else if (!IS_FORMBRICKS_CLOUD) return (await getEnterpriseLicense()).active;
else return false; else return false;
}; };
export const getBiggerUploadFileSizePermission = async (organization: TOrganization): Promise<boolean> => { export const getBiggerUploadFileSizePermission = async (organization: TOrganization): Promise<boolean> => {
if (IS_FORMBRICKS_CLOUD) return organization.billing.plan !== PRODUCT_FEATURE_KEYS.FREE; if (IS_FORMBRICKS_CLOUD) return organization.billing.plan !== PRODUCT_FEATURE_KEYS.FREE;
else if (!IS_FORMBRICKS_CLOUD) return await getIsEnterpriseEdition(); else if (!IS_FORMBRICKS_CLOUD) return (await getEnterpriseLicense()).active;
return false; return false;
}; };
@@ -243,7 +279,7 @@ export const getMultiLanguagePermission = async (organization: TOrganization): P
organization.billing.plan === PRODUCT_FEATURE_KEYS.SCALE || organization.billing.plan === PRODUCT_FEATURE_KEYS.SCALE ||
organization.billing.plan === PRODUCT_FEATURE_KEYS.ENTERPRISE organization.billing.plan === PRODUCT_FEATURE_KEYS.ENTERPRISE
); );
else if (!IS_FORMBRICKS_CLOUD) return await getIsEnterpriseEdition(); else if (!IS_FORMBRICKS_CLOUD) return (await getEnterpriseLicense()).active;
return false; return false;
}; };

View File

@@ -0,0 +1,45 @@
interface PendingDowngradeBannerProps {
lastChecked: Date;
active: boolean;
isPendingDowngrade: boolean;
}
export const PendingDowngradeBanner = ({
lastChecked,
active,
isPendingDowngrade,
}: PendingDowngradeBannerProps) => {
const threeDaysInMillis = 3 * 24 * 60 * 60 * 1000;
const isLastCheckedWithin72Hours = lastChecked
? new Date().getTime() - lastChecked.getTime() < threeDaysInMillis
: false;
const scheduledDowngradeDate = new Date(lastChecked.getTime() + threeDaysInMillis);
if (active) {
if (isPendingDowngrade && isLastCheckedWithin72Hours) {
return (
<>
<div className="z-40 flex h-5 items-center justify-center bg-orange-800 text-center text-xs text-white">
We were unable to verify your license because the license server is unreachable. You will be
downgraded to the Community Edition on {scheduledDowngradeDate.toLocaleDateString()}
</div>
</>
);
}
}
if (isPendingDowngrade && !isLastCheckedWithin72Hours) {
return (
<>
<div className="z-40 flex h-5 items-center justify-center bg-orange-800 text-center text-xs text-white">
We were unable to verify your license because the license server is unreachable. You are downgraded
to the Community Edition.
</div>
</>
);
}
return null;
};

View File

@@ -1,5 +1,7 @@
import { defineConfig, devices } from "@playwright/test"; import { defineConfig, devices } from "@playwright/test";
// import os from "os";
/** /**
* Read environment variables from file. * Read environment variables from file.
* https://github.com/motdotla/dotenv * https://github.com/motdotla/dotenv
@@ -18,6 +20,7 @@ export default defineConfig({
/* Timeout for each test */ /* Timeout for each test */
timeout: 120000, timeout: 120000,
/* Opt out of parallel tests on CI. */ /* Opt out of parallel tests on CI. */
// workers: os.cpus().length,
workers: 1, workers: 1,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html", reporter: "html",
@@ -36,6 +39,7 @@ export default defineConfig({
{ {
name: "chromium", name: "chromium",
use: { ...devices["Desktop Chrome"] }, use: { ...devices["Desktop Chrome"] },
testMatch: "**/*.spec.ts",
}, },
// { // {