Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/landing/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,8 @@
"page_panel_eyebrow": "The surface",
"page_cap_eyebrow": "What it does",
"page_artifact_eyebrow": "In motion",
"page_gallery_eyebrow": "More screens",
"page_gallery_title": "{feature}, screen by screen",
"page_story_eyebrow": "The path",
"page_outcome_label": "outcome",
"page_cross_eyebrow": "Keep going",
Expand Down
2 changes: 2 additions & 0 deletions apps/landing/messages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,8 @@
"page_panel_eyebrow": "\u753b\u9762",
"page_cap_eyebrow": "\u3067\u304d\u308b\u3053\u3068",
"page_artifact_eyebrow": "\u52d5\u304d",
"page_gallery_eyebrow": "\u305d\u306e\u4ed6\u306e\u753b\u9762",
"page_gallery_title": "{feature}\u306e\u753b\u9762",
"page_story_eyebrow": "\u7d4c\u8def",
"page_outcome_label": "\u7d50\u679c",
"page_cross_eyebrow": "\u6b21\u306f",
Expand Down
2 changes: 2 additions & 0 deletions apps/landing/messages/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,8 @@
"page_panel_eyebrow": "\ud654\uba74",
"page_cap_eyebrow": "\ud560 \uc218 \uc788\ub294 \uc77c",
"page_artifact_eyebrow": "\uc6c0\uc9c1\uc784",
"page_gallery_eyebrow": "\ub354 \ub9ce\uc740 \ud654\uba74",
"page_gallery_title": "{feature} \ud654\uba74",
"page_story_eyebrow": "\uacbd\ub85c",
"page_outcome_label": "\uacb0\uacfc",
"page_cross_eyebrow": "\ub2e4\uc74c\uc740",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/landing/public/screenshots/surface-errors.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/landing/public/screenshots/surface-service-detail.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/landing/public/screenshots/surface-service-map.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/landing/public/screenshots/surface-traces-peek.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
101 changes: 97 additions & 4 deletions apps/landing/scripts/screenshots/shots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,112 @@ export const TIMEZONE = "America/New_York"

const lastMinutes = (minutes: number) => ({ fromMinutes: minutes, toMinutes: 0 })

// Failed checkouts: payment-svc never owns the root span, so filter on the entry point.
const FAILED_CHECKOUTS = { spanNames: ["POST /api/checkout"], hasError: true }

const openPeek = async (page: Page) => {
await page.locator("tr[data-index]").first().click()
await page.waitForURL(/[?&]peek=/)
}

/** In-app navigation drops `quota_preview`, so follow a link by URL with the bypass kept. */
const followLink = async (page: Page, href: string | null, drop: ReadonlyArray<string> = []) => {
if (href === null) throw new Error("no link to follow")
const url = new URL(href, page.url())
for (const key of drop) url.searchParams.delete(key)
url.searchParams.set("quota_preview", "1")
await page.goto(url.toString())
}

/** The first row's own trace page, by its link: the row overlay is not clickable as a link. */
const openTrace = async (page: Page) => {
const href = await page.locator('a[aria-label^="Open trace"]').first().getAttribute("href")
await followLink(page, href, ["peek", "peekT"])
}

export const SHOTS: ReadonlyArray<Shot> = [
// Distributed tracing
{ id: "gallery-traces-list", route: "/traces", range: lastMinutes(60) },
{
id: "surface-traces-peek",
route: "/traces",
// Failed checkouts: payment-svc never owns the root span, so filter on the entry point.
search: { spanNames: ["POST /api/checkout"], hasError: true },
search: FAILED_CHECKOUTS,
range: lastMinutes(90),
setup: openPeek,
},
{
id: "gallery-trace-waterfall",
route: "/traces",
search: FAILED_CHECKOUTS,
range: lastMinutes(90),
setup: openTrace,
},
{
id: "gallery-trace-flow",
route: "/traces",
search: FAILED_CHECKOUTS,
range: lastMinutes(90),
setup: async (page) => {
await page.locator("tr[data-index]").first().click()
await page.waitForURL(/[?&]peek=/)
await openTrace(page)
await page.getByRole("tab", { name: /flow/i }).first().click()
},
},

// Logs
{ id: "gallery-logs-errors", route: "/logs", search: { severities: ["ERROR"] }, range: lastMinutes(60) },
// Severity, not a service or text filter: with those set the facet rail reads empty.
{
id: "gallery-logs-service",
route: "/logs",
search: { severities: ["WARN"] },
range: lastMinutes(30),
},

// Metrics and dashboards
{ id: "gallery-metrics-list", route: "/metrics", range: lastMinutes(60) },
{
id: "gallery-metric-latency",
route: "/metrics/http.server.request.duration",
range: lastMinutes(6 * 60),
},
{
id: "gallery-metric-pool",
route: "/metrics/db.client.connection.pending_requests",
range: lastMinutes(6 * 60),
},
{ id: "gallery-dashboard-templates", route: "/dashboards/templates" },

// Service catalog
{
id: "gallery-service-operations",
route: "/services/payment-svc",
search: { tab: "operations" },
range: lastMinutes(60),
},
{ id: "surface-service-map", route: "/service-map", range: lastMinutes(60) },
{ id: "surface-service-detail", route: "/services/payment-svc", range: lastMinutes(6 * 60) },
{
id: "gallery-service-dependencies",
route: "/services/payment-svc",
search: { tab: "dependencies" },
range: lastMinutes(60),
},

// Errors
{ id: "surface-errors", route: "/errors" },
{
id: "gallery-issue-detail",
route: "/errors",
setup: async (page) => {
const row = page.locator('a[href*="/errors/issues/"]', { hasText: "ConnectionTimeout" }).first()
await followLink(page, await row.getAttribute("href"))
},
},

// Alerts
{
id: "gallery-alert-create",
route: "/alerts/create",
search: { serviceName: "payment-svc", template: "high_error_rate" },
},
]
26 changes: 26 additions & 0 deletions apps/landing/src/__tests__/feature-galleries.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { existsSync } from "node:fs"
import { join, resolve } from "node:path"
import { describe, expect, it } from "vitest"
import { SHOTS } from "../../scripts/screenshots/shots"
import { features } from "../lib/features"

/**
* A gallery shot is only real if the capture script knows how to take it and
* the file it writes is checked in. Either half missing renders a broken image.
*/
const screenshotsDir = resolve(__dirname, "../../public/screenshots")
const shotIds = new Set(SHOTS.map((shot) => shot.id))

describe("feature galleries", () => {
const gallery = features.flatMap((feature) =>
feature.gallery.map((shot) => ({ slug: feature.slug, id: shot.id })),
)

it.each(gallery)("$slug: $id is a capture in shots.ts", ({ id }) => {
expect(shotIds.has(id)).toBe(true)
})

it.each(gallery)("$slug: $id is checked in", ({ id }) => {
expect(existsSync(join(screenshotsDir, `${id}.webp`))).toBe(true)
})
})
21 changes: 17 additions & 4 deletions apps/landing/src/components/page/FeaturePage.astro
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
* 00 PageHero bg-bg feature engraving (page top)
* 01 SurfacePanel bg-bg border-t tone flat → hairline
* 02 ArtifactSection bg-bg border-t tone flat → hairline
* 03 [extra sections] per section kubernetes only
* 04 CapabilityLedger bg-bg-deep no border tone steps → tone is the rule
* 05 CrossLinks bg-bg-deep border-t tone flat → hairline
* 06 PageCta amber the one peak
* 03 ScreenGallery bg-bg border-t more of the surface
* 04 [extra sections] per section kubernetes only
* 05 CapabilityLedger bg-bg-deep no border tone steps → tone is the rule
* 06 CrossLinks bg-bg-deep border-t tone flat → hairline
* 07 PageCta amber the one peak
*
* The tonal rule is tighter here than on the homepage because these pages carry
* more frames: `.live-frame` is itself `--bg-deep`, so any section holding one
Expand All @@ -35,6 +36,7 @@ import CapabilityLedger from "./CapabilityLedger.astro";
import CrossLinks from "./CrossLinks.astro";
import PageCta from "./PageCta.astro";
import PageHero from "./PageHero.astro";
import ScreenGallery from "./ScreenGallery.astro";
import SurfacePanel from "./SurfacePanel.astro";
import { ARTIFACTS, EXTRA_SECTIONS } from "./slots";

Expand Down Expand Up @@ -127,6 +129,17 @@ const crossLinks = [
)
}

{
feature.gallery.length > 0 && (
<ScreenGallery
class="border-t border-border"
eyebrow={m.page_gallery_eyebrow()}
title={m.page_gallery_title({ feature: feature.navLabel() })}
shots={feature.gallery}
/>
)
}

{extraSections.map((Section) => <Section />)}

<CapabilityLedger
Expand Down
52 changes: 52 additions & 0 deletions apps/landing/src/components/page/ScreenGallery.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
/**
* ScreenGallery: the rest of a feature's surface, after the panel and the
* artifact. Each capture is one `.live-frame` (route left, subject right) with
* the shot as its body, two to a row on desktop. The shots come from the
* seeded demo world, so a page shows one consistent story across all of them.
*/
import type { GalleryShot } from "../../lib/page-registry";
import { GALLERY_SHOT_SIZE } from "../../lib/page-registry";

interface Props {
eyebrow: string;
title: string;
shots: GalleryShot[];
class?: string;
}

const { eyebrow, title, shots, class: className = "" } = Astro.props;
---

<section class={`bg-bg ${className}`}>
<div class="shell py-16 md:py-24">
<p class="text-[10px] font-medium uppercase tracking-[0.18em] text-fg-muted">{eyebrow}</p>
<h2
class="mt-4 max-w-[24ch] font-display text-3xl font-medium leading-[1.05] tracking-[-0.02em] text-fg md:text-[40px]"
>
{title}
</h2>
<div class="mt-10 grid gap-6 md:mt-14 lg:grid-cols-2">
{
shots.map((shot) => (
<figure class="live-frame min-w-0">
<figcaption class="live-frame__head">
<span>{shot.route}</span>
<span class="text-fg normal-case tracking-normal">{shot.title}</span>
</figcaption>
<img
src={`/screenshots/${shot.id}.webp`}
alt={shot.alt}
width={GALLERY_SHOT_SIZE.width}
height={GALLERY_SHOT_SIZE.height}
loading="lazy"
decoding="async"
data-zoomable=""
class="block h-auto w-full"
/>
</figure>
))
}
</div>
</div>
</section>
Loading
Loading