mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(site): add under-construction navbar stripes for pre-release builds (#24157)
Dev and RC builds now show diagonal warning stripes in the navbar plus a
centered version badge, making it impossible to miss which build you're
running.
**Devel build:** amber "warning" from theme
**RC build:** sky "pending" from theme
> 🤖 Written by a Coder Agent. Will be reviewed by a human.
This commit is contained in:
@@ -156,6 +156,50 @@
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
/* Map each stripe variant to a color token so the
|
||||
pseudo-element rules can stay DRY. */
|
||||
.navbar-stripe-devel {
|
||||
--stripe-color: var(--content-warning);
|
||||
}
|
||||
|
||||
.navbar-stripe-rc {
|
||||
--stripe-color: var(--border-sky);
|
||||
}
|
||||
|
||||
/* Thin stripe bars at the top and bottom edges of the
|
||||
navbar. Using pseudo-elements keeps the stripes out of
|
||||
the content area so nav links stay readable. */
|
||||
.navbar-stripe-devel::before,
|
||||
.navbar-stripe-devel::after,
|
||||
.navbar-stripe-rc::before,
|
||||
.navbar-stripe-rc::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: repeating-linear-gradient(
|
||||
-45deg,
|
||||
transparent,
|
||||
transparent 4px,
|
||||
hsl(var(--stripe-color) / 0.5) 4px,
|
||||
hsl(var(--stripe-color) / 0.5) 8px
|
||||
);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.navbar-stripe-devel::before,
|
||||
.navbar-stripe-rc::before {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.navbar-stripe-devel::after,
|
||||
.navbar-stripe-rc::after {
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { userEvent, within } from "storybook/test";
|
||||
import type { TasksFilter } from "#/api/typesGenerated";
|
||||
import { chromaticWithTablet } from "#/testHelpers/chromatic";
|
||||
import {
|
||||
MockBuildInfo,
|
||||
MockTasks,
|
||||
MockUserMember,
|
||||
MockUserOwner,
|
||||
@@ -170,3 +171,33 @@ export const DefaultSupportLinks: Story = {
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const DevelBuild: Story = {
|
||||
args: {
|
||||
buildInfo: {
|
||||
...MockBuildInfo,
|
||||
version: "v2.21.0-devel+abc123",
|
||||
external_url: "https://github.com/coder/coder/commit/abc123",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const RcBuild: Story = {
|
||||
args: {
|
||||
buildInfo: {
|
||||
...MockBuildInfo,
|
||||
version: "v2.21.0-rc.1+def456",
|
||||
external_url: "https://github.com/coder/coder/releases/tag/v2.21.0-rc.1",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const RcDevelBuild: Story = {
|
||||
args: {
|
||||
buildInfo: {
|
||||
...MockBuildInfo,
|
||||
version: "v2.33.0-rc.1-devel+727ec00f7",
|
||||
external_url: "https://github.com/coder/coder/commit/727ec00f7",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ import type { ProxyContextValue } from "#/contexts/ProxyContext";
|
||||
import { useEmbeddedMetadata } from "#/hooks/useEmbeddedMetadata";
|
||||
import { useDashboard } from "#/modules/dashboard/useDashboard";
|
||||
import { NotificationsInbox } from "#/modules/notifications/NotificationsInbox/NotificationsInbox";
|
||||
import { isDevBuild } from "#/utils/buildInfo";
|
||||
import { isDevBuild, isRcBuild } from "#/utils/buildInfo";
|
||||
import { cn } from "#/utils/cn";
|
||||
import { DeploymentDropdown } from "./DeploymentDropdown";
|
||||
import { MobileMenu } from "./MobileMenu";
|
||||
@@ -59,8 +59,17 @@ export const NavbarView: FC<NavbarViewProps> = ({
|
||||
canViewAIBridge,
|
||||
proxyContextValue,
|
||||
}) => {
|
||||
const isDev = buildInfo ? isDevBuild(buildInfo) : false;
|
||||
const isRc = buildInfo ? isRcBuild(buildInfo) : false;
|
||||
const isPreRelease = isDev || isRc;
|
||||
|
||||
return (
|
||||
<div className="sticky top-0 bg-surface-primary z-40 border-0 border-b border-solid h-[72px] min-h-[72px] flex items-center leading-none px-6">
|
||||
<div
|
||||
className={cn(
|
||||
"sticky top-0 bg-surface-primary z-40 border-0 border-b border-solid h-[72px] min-h-[72px] flex items-center leading-none px-6 relative",
|
||||
isRc ? "navbar-stripe-rc" : isDev ? "navbar-stripe-devel" : undefined,
|
||||
)}
|
||||
>
|
||||
<NavLink to="/workspaces">
|
||||
{logo_url ? (
|
||||
<ExternalImage className="h-7" src={logo_url} alt="Custom Logo" />
|
||||
@@ -71,6 +80,23 @@ export const NavbarView: FC<NavbarViewProps> = ({
|
||||
|
||||
<NavItems className="ml-4" user={user} />
|
||||
|
||||
{isPreRelease && buildInfo?.version && (
|
||||
<a
|
||||
href={buildInfo.external_url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="absolute top-1 left-1/2 -translate-x-1/2 no-underline z-10"
|
||||
>
|
||||
<Badge
|
||||
variant={isRc ? "info" : "warning"}
|
||||
size="sm"
|
||||
className="font-mono"
|
||||
>
|
||||
{buildInfo.version}
|
||||
</Badge>
|
||||
</a>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-3 ml-auto">
|
||||
{supportLinks.filter(isNavbarLink).map((link) => (
|
||||
<div key={link.name} className="hidden md:block">
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import type { BuildInfoResponse } from "#/api/typesGenerated";
|
||||
import { isDevBuild, isRcBuild } from "./buildInfo";
|
||||
|
||||
const baseBuildInfo: BuildInfoResponse = {
|
||||
agent_api_version: "1.0",
|
||||
provisioner_api_version: "1.1",
|
||||
external_url: "https://github.com/coder/coder",
|
||||
version: "",
|
||||
dashboard_url: "https://example.com",
|
||||
workspace_proxy: false,
|
||||
upgrade_message: "",
|
||||
deployment_id: "test",
|
||||
telemetry: false,
|
||||
};
|
||||
|
||||
describe("isDevBuild", () => {
|
||||
it("returns true for -devel versions", () => {
|
||||
expect(
|
||||
isDevBuild({ ...baseBuildInfo, version: "v2.16.0-devel+abc123" }),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for bare -devel versions", () => {
|
||||
expect(isDevBuild({ ...baseBuildInfo, version: "v2.32.0-devel" })).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns true for v0.0.0", () => {
|
||||
expect(isDevBuild({ ...baseBuildInfo, version: "v0.0.0" })).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for release versions", () => {
|
||||
expect(isDevBuild({ ...baseBuildInfo, version: "v2.16.0" })).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for RC versions", () => {
|
||||
expect(isDevBuild({ ...baseBuildInfo, version: "v2.32.0-rc.1" })).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns true for combined rc+devel versions", () => {
|
||||
expect(
|
||||
isDevBuild({
|
||||
...baseBuildInfo,
|
||||
version: "v2.33.0-rc.1-devel+727ec00f7",
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for empty version", () => {
|
||||
expect(isDevBuild({ ...baseBuildInfo, version: "" })).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isRcBuild", () => {
|
||||
it("returns true for -rc.0 versions", () => {
|
||||
expect(isRcBuild({ ...baseBuildInfo, version: "v2.32.0-rc.0" })).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for -rc.1 with build metadata", () => {
|
||||
expect(
|
||||
isRcBuild({ ...baseBuildInfo, version: "v2.32.0-rc.1+abc123" }),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for higher RC numbers", () => {
|
||||
expect(isRcBuild({ ...baseBuildInfo, version: "v2.32.0-rc.12" })).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("returns false for release versions", () => {
|
||||
expect(isRcBuild({ ...baseBuildInfo, version: "v2.16.0" })).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for devel versions", () => {
|
||||
expect(
|
||||
isRcBuild({ ...baseBuildInfo, version: "v2.16.0-devel+abc123" }),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for empty version", () => {
|
||||
expect(isRcBuild({ ...baseBuildInfo, version: "" })).toBe(false);
|
||||
});
|
||||
|
||||
it("returns false for versions with rc but no dot", () => {
|
||||
expect(isRcBuild({ ...baseBuildInfo, version: "v2.32.0-rc" })).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true for combined rc+devel versions", () => {
|
||||
expect(
|
||||
isRcBuild({
|
||||
...baseBuildInfo,
|
||||
version: "v2.33.0-rc.1-devel+727ec00f7",
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -35,3 +35,15 @@ export const isDevBuild = (input: BuildInfoResponse): boolean => {
|
||||
// Check for dev version pattern (contains "-devel") or no version (v0.0.0)
|
||||
return version.includes("-devel") || version === "v0.0.0";
|
||||
};
|
||||
|
||||
// Check if the current build is a release candidate. Release
|
||||
// candidates have versions containing "-rc." (e.g. v2.32.0-rc.0,
|
||||
// v2.32.0-rc.1+abc123, v2.33.0-rc.1-devel+727ec00f7).
|
||||
export const isRcBuild = (input: BuildInfoResponse): boolean => {
|
||||
const version = input.version;
|
||||
if (!version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return version.includes("-rc.");
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user