diff --git a/backend/src/ref_backend/core/config.py b/backend/src/ref_backend/core/config.py index 8f192314..fffede1d 100644 --- a/backend/src/ref_backend/core/config.py +++ b/backend/src/ref_backend/core/config.py @@ -30,7 +30,7 @@ class Settings(BaseSettings): API_V1_STR: str = "/api/v1" FRONTEND_HOST: str = "http://localhost:5173" BACKEND_HOST: str = "http://localhost:8000" - ENVIRONMENT: Literal["local", "staging", "production"] = "local" + ENVIRONMENT: Literal["local", "staging", "preview", "production"] = "local" LOG_LEVEL: str = "INFO" GIT_COMMIT: str | None = None """Commit the image was built from, set at build time.""" diff --git a/backend/src/ref_backend/models/about.py b/backend/src/ref_backend/models/about.py index bb4e23f5..8224c2c4 100644 --- a/backend/src/ref_backend/models/about.py +++ b/backend/src/ref_backend/models/about.py @@ -19,7 +19,7 @@ class About(BaseModel): last_updated: datetime | None """When an execution group was last updated, or None if there are no executions.""" - environment: Literal["local", "staging", "production"] + environment: Literal["local", "staging", "preview", "production"] """ The deployment this API is serving. diff --git a/backend/tests/test_core/test_config.py b/backend/tests/test_core/test_config.py index 08ae5f97..de58daee 100644 --- a/backend/tests/test_core/test_config.py +++ b/backend/tests/test_core/test_config.py @@ -1,6 +1,6 @@ """Tests for the Settings CORS parsing.""" -from ref_backend.core.config import parse_cors +from ref_backend.core.config import Settings, parse_cors class TestParseCors: @@ -15,3 +15,9 @@ def test_list_passes_through(self): """A list value passes through unchanged.""" result = parse_cors(["http://a.example", "http://b.example"]) assert result == ["http://a.example", "http://b.example"] + + +def test_preview_environment_is_accepted(monkeypatch): + """Pull request previews deploy with ENVIRONMENT=preview.""" + monkeypatch.setenv("ENVIRONMENT", "preview") + assert Settings().ENVIRONMENT == "preview" diff --git a/changelog/141.fix.md b/changelog/141.fix.md new file mode 100644 index 00000000..81058dec --- /dev/null +++ b/changelog/141.fix.md @@ -0,0 +1 @@ +Pull request previews now start, and show the staging banner and sparse results like the staging site. diff --git a/frontend/src/client/types.gen.ts b/frontend/src/client/types.gen.ts index 2aeb20ed..7ad35540 100644 --- a/frontend/src/client/types.gen.ts +++ b/frontend/src/client/types.gen.ts @@ -400,7 +400,7 @@ export type About = { /** * Environment */ - environment: 'local' | 'staging' | 'production'; + environment: 'local' | 'staging' | 'preview' | 'production'; }; /** diff --git a/frontend/src/components/app/stagingBanner.test.tsx b/frontend/src/components/app/stagingBanner.test.tsx index 5b61ddcc..a91ad6b8 100644 --- a/frontend/src/components/app/stagingBanner.test.tsx +++ b/frontend/src/components/app/stagingBanner.test.tsx @@ -4,7 +4,7 @@ import { describe, expect, it } from "vitest"; import { utilsAboutOptions } from "@/client/@tanstack/react-query.gen"; import { StagingBanner } from "./stagingBanner"; -const renderBanner = (environment?: "production" | "staging") => { +const renderBanner = (environment?: "production" | "staging" | "preview") => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, }); @@ -30,6 +30,11 @@ describe("StagingBanner", () => { expect(screen.getByText(/Staging site/)).toBeInTheDocument(); }); + it("names a pull request preview as staging", () => { + renderBanner("preview"); + expect(screen.getByText(/Staging site/)).toBeInTheDocument(); + }); + it("stays out of the way in production", () => { renderBanner("production"); expect(screen.queryByText(/Staging site/)).not.toBeInTheDocument(); diff --git a/frontend/src/components/charts/mipEraSections.test.tsx b/frontend/src/components/charts/mipEraSections.test.tsx index 9a43b139..5b097348 100644 --- a/frontend/src/components/charts/mipEraSections.test.tsx +++ b/frontend/src/components/charts/mipEraSections.test.tsx @@ -20,7 +20,7 @@ const four = (era: string | undefined) => const renderChart = ( values: ReturnType[], mipEra?: "CMIP6" | "CMIP7", - environment: "production" | "staging" = "production", + environment: "production" | "staging" | "preview" = "production", ) => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, @@ -113,4 +113,12 @@ describe("MipEraSections with a page selection", () => { screen.queryByText("Not enough models to plot"), ).not.toBeInTheDocument(); }); + + it("plots a chart with too few models on a preview site", () => { + renderChart([model("CMIP6", "A"), model("CMIP6", "B")], "CMIP6", "preview"); + expect(screen.getByTestId("chart")).toBeInTheDocument(); + expect( + screen.queryByText("Not enough models to plot"), + ).not.toBeInTheDocument(); + }); }); diff --git a/frontend/src/hooks/useStagingMode.ts b/frontend/src/hooks/useStagingMode.ts index 3e09a027..534c3335 100644 --- a/frontend/src/hooks/useStagingMode.ts +++ b/frontend/src/hooks/useStagingMode.ts @@ -2,6 +2,10 @@ import { useQuery } from "@tanstack/react-query"; import { utilsAboutOptions } from "@/client/@tanstack/react-query.gen"; import { getDefaultApiEndpoint } from "@/lib/apiEndpoint"; +// A pull request preview serves unreleased code, so it is a staging site too. +const isStagingEnvironment = (environment?: string) => + environment === "staging" || environment === "preview"; + /** * Whether the API being read is a staging deployment. * @@ -10,7 +14,7 @@ import { getDefaultApiEndpoint } from "@/lib/apiEndpoint"; */ export function useStagingMode(): boolean { const { data } = useQuery(utilsAboutOptions()); - return data?.environment === "staging"; + return isStagingEnvironment(data?.environment); } /** @@ -23,5 +27,5 @@ export function useStagingDashboard(): boolean { const { data } = useQuery( utilsAboutOptions({ baseUrl: getDefaultApiEndpoint() }), ); - return data?.environment === "staging"; + return isStagingEnvironment(data?.environment); }