-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
Maintenance feature flag
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,9 @@ | |
/.next/ | ||
/out/ | ||
|
||
# vercel | ||
.vercel | ||
|
||
# production | ||
/build | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { FeatureFlag } from "@/app/lib/flags"; | ||
import { env } from "@/env.mjs"; | ||
import { get } from "@vercel/edge-config"; | ||
import { NextResponse } from "next/server"; | ||
|
||
export const runtime = "edge"; | ||
|
||
export async function GET() { | ||
if (!env.EDGE_CONFIG || !env.VERCEL_ENV) { | ||
console.error( | ||
"Missing environment variables for feature flags, both should be defined:", | ||
{ | ||
EDGE_CONFIG: !!env.EDGE_CONFIG, | ||
VERCEL_ENV: !!env.VERCEL_ENV, | ||
}, | ||
); | ||
} | ||
|
||
const flags = await get<FeatureFlag>(env.VERCEL_ENV); | ||
|
||
if (!flags) { | ||
console.error("Missing feature flag configuration"); | ||
return NextResponse.json( | ||
{ message: "Missing feature flag configuration" }, | ||
{ status: 500 }, | ||
); | ||
} | ||
|
||
return NextResponse.json(flags); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export type FeatureFlag = { | ||
beta: boolean; | ||
maintenance: boolean; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { render, screen } from "@testing-library/react"; | ||
import Page from "./page"; | ||
|
||
const refresh = jest.fn(); | ||
jest.mock("next/navigation", () => ({ | ||
useRouter: () => ({ | ||
refresh, | ||
}), | ||
})); | ||
|
||
describe("Maintenance", () => { | ||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it("renders page title 'Under Maintenance'", () => { | ||
render(<Page />); | ||
const messageElement = screen.getByText(/Under Maintenance/); | ||
expect(messageElement).toBeInTheDocument(); | ||
}); | ||
|
||
it("renders a message", () => { | ||
render(<Page />); | ||
const messageElement = screen.getByText( | ||
/You can try to refresh the page to see if the issue is resolved./, | ||
); | ||
expect(messageElement).toBeInTheDocument(); | ||
}); | ||
|
||
it("renders a 'Refresh' button", () => { | ||
render(<Page />); | ||
const button = screen.getByText("Refresh"); | ||
expect(button).toBeInTheDocument(); | ||
button.click(); | ||
expect(refresh).toHaveBeenCalled(); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
"use client"; | ||
import { useRouter } from "next/navigation"; | ||
|
||
export default function Page() { | ||
const { refresh } = useRouter(); | ||
|
||
return ( | ||
<main className="min-h-screen w-screen p-4 flex flex-col items-center text-center gap-6 prose prose-neutral prose-invert bg-gradient-to-b from-red-900 via-red-600 to-red-900"> | ||
<h1>Under Maintenance</h1> | ||
<p className="bg-neutral-800 bg-opacity-90 p-2 rounded-md"> | ||
You can try to refresh the page to see if the issue is resolved. | ||
</p> | ||
<button | ||
className="bg-neutral-950 enabled:hover:bg-neutral-700 font-bold py-2 px-4 rounded-full" | ||
onClick={refresh} | ||
> | ||
Refresh | ||
</button> | ||
</main> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { expect, test } from "@playwright/test"; | ||
|
||
test("should return the feature flags successfully", async ({ request }) => { | ||
const flags = await request.get("/api/flags"); | ||
expect(flags.ok()).toBeTruthy(); | ||
expect(flags.status()).toBe(200); | ||
// Note, playwright tests are run against the preview environment. Tests will fail if the preview feature flags are altered. | ||
expect(await flags.json()).toEqual({ | ||
Check failure on line 8 in e2e/api/flags/route.spec.ts GitHub Actions / e2e-tests[Mobile Chrome] › api/flags/route.spec.ts:3:5 › should return the feature flags successfully
Check failure on line 8 in e2e/api/flags/route.spec.ts GitHub Actions / e2e-tests[Mobile Chrome] › api/flags/route.spec.ts:3:5 › should return the feature flags successfully
Check failure on line 8 in e2e/api/flags/route.spec.ts GitHub Actions / e2e-tests[Mobile Chrome] › api/flags/route.spec.ts:3:5 › should return the feature flags successfully
Check failure on line 8 in e2e/api/flags/route.spec.ts GitHub Actions / e2e-tests[Mobile Safari] › api/flags/route.spec.ts:3:5 › should return the feature flags successfully
Check failure on line 8 in e2e/api/flags/route.spec.ts GitHub Actions / e2e-tests[Mobile Safari] › api/flags/route.spec.ts:3:5 › should return the feature flags successfully
Check failure on line 8 in e2e/api/flags/route.spec.ts GitHub Actions / e2e-tests[Mobile Safari] › api/flags/route.spec.ts:3:5 › should return the feature flags successfully
Check failure on line 8 in e2e/api/flags/route.spec.ts GitHub Actions / e2e-tests[Microsoft Edge] › api/flags/route.spec.ts:3:5 › should return the feature flags successfully
Check failure on line 8 in e2e/api/flags/route.spec.ts GitHub Actions / e2e-tests[Microsoft Edge] › api/flags/route.spec.ts:3:5 › should return the feature flags successfully
Check failure on line 8 in e2e/api/flags/route.spec.ts GitHub Actions / e2e-tests[Microsoft Edge] › api/flags/route.spec.ts:3:5 › should return the feature flags successfully
Check failure on line 8 in e2e/api/flags/route.spec.ts GitHub Actions / e2e-tests[Google Chrome] › api/flags/route.spec.ts:3:5 › should return the feature flags successfully
|
||
beta: true, | ||
maintenance: false, | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { FeatureFlag } from "@/app/lib/flags"; | ||
import { env } from "@/env.mjs"; | ||
import { get } from "@vercel/edge-config"; | ||
import { NextRequest, NextResponse } from "next/server"; | ||
|
||
export const config = { | ||
matcher: [ | ||
/* | ||
* Match all request paths except for the ones starting with: | ||
* - api (API routes) | ||
* - _next/static (static files) | ||
* - _next/image (image optimization files) | ||
* - favicon.ico (favicon file) | ||
* - maintenance (maintenance page) | ||
*/ | ||
"/((?!api|_next/static|_next/image|favicon.ico|maintenance).*)", | ||
], | ||
}; | ||
|
||
export async function middleware(req: NextRequest) { | ||
if (!env.EDGE_CONFIG || !env.VERCEL_ENV) { | ||
// If the environment variables are not defined, log an error and continue | ||
console.error( | ||
"Missing environment variables for feature flags, both should be defined:", | ||
{ | ||
EDGE_CONFIG: !!env.EDGE_CONFIG, | ||
VERCEL_ENV: !!env.VERCEL_ENV, | ||
}, | ||
); | ||
return NextResponse.next(); | ||
} | ||
|
||
try { | ||
// Check whether the maintenance page should be shown | ||
const flags = await get<FeatureFlag>(env.VERCEL_ENV); | ||
|
||
// If is in maintenance mode, point the url pathname to the maintenance page | ||
if (flags?.maintenance) { | ||
req.nextUrl.pathname = `/maintenance`; | ||
|
||
// Rewrite to the url | ||
return NextResponse.rewrite(req.nextUrl); | ||
} | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.