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
36 changes: 27 additions & 9 deletions apps/web/src/lab/entries/service-runtimes.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { OrgId } from "@maple/domain"
import { Effect, Result, Schema } from "effect"
import { Effect, Option, Result, Schema } from "effect"
import { AsyncData, Command, type Update } from "foldkit"
import type { HtmlBuilder } from "foldkit/html"
import { defineMessageUnion } from "foldkit/message"
import { fromString as urlFromString } from "foldkit/url"
import type { PageContext, PageReturn } from "../../page/contract"
import { Message as PageMessage } from "../../page/services/detail/message"
import { Model as PageModel } from "../../page/services/detail/model"
import * as DetailUpdate from "../../page/services/detail/update"
import { view as pageView } from "../../page/services/detail/view"
import type { Resources } from "../../platform/resources"
import { urlToAppRoute } from "../../route"
import {
RUNTIMES_COMMITS,
RUNTIMES_DEPENDENCIES,
Expand All @@ -24,16 +26,16 @@ import {
} from "../service-runtimes-fixture"
import { defineShellLab } from "../shell-lab"

// `/lab/service-runtimes`: the service page's Overview tab for `fleet-api` (Google Cloud Run, no
// Kubernetes) over one hour, in the real shell. Every read is answered from the fixture.
// `/lab/service-runtimes[?tab=operations|dependencies]`: the service page for `fleet-api` (Google
// Cloud Run, no Kubernetes) over one hour, in the real shell. Every read is answered from the fixture.

const CONTEXT: PageContext = {
orgId: Schema.decodeSync(OrgId)("org_lab_service_runtimes"),
pinnedNamespace: null,
timeZone: "UTC",
}

const Flags = Schema.Struct({})
const Flags = Schema.Struct({ tab: Schema.NullOr(Schema.String) })
type Flags = typeof Flags.Type

const Model = Schema.Struct({ page: PageModel })
Expand Down Expand Up @@ -144,9 +146,19 @@ const step = (result: PageReturn<PageModel, PageMessage>): Return => {
}
}

const init = (_flags: Flags): Return => {
/** The page's URL writes (a tab switch), applied to its own search the way a route change would. */
const follow = (model: PageModel, url: string): PageReturn<PageModel, PageMessage> =>
Option.match(urlFromString(new URL(url, "http://localhost").href), {
onNone: () => ({ model }),
onSome: (parsed) => {
const route = urlToAppRoute(parsed)
return route._tag === "Service" ? DetailUpdate.routeChanged(model, route, CONTEXT) : { model }
},
})

const init = (flags: Flags): Return => {
const started = DetailUpdate.init(
{ serviceName: RUNTIMES_SERVICE, search: { timePreset: "1h" } },
{ serviceName: RUNTIMES_SERVICE, search: { timePreset: "1h", tab: flags.tab ?? undefined } },
CONTEXT,
)
const clocked = DetailUpdate.update(
Expand All @@ -157,10 +169,16 @@ const init = (_flags: Flags): Return => {
return step({ ...clocked, commands: [...(started.commands ?? []), ...(clocked.commands ?? [])] })
}

/** Navigation out of the page (a tab, a row) is dropped: the lab stays on its one view. */
/** A tab switch is followed in place; navigation out of the page (a row) is dropped. */
const update = (model: Model, message: Message): Return =>
Message.match<Return>(message, {
GotPageMessage: ({ message: inner }) => step(DetailUpdate.update(model.page, inner, CONTEXT)),
GotPageMessage: ({ message: inner }) => {
const next = DetailUpdate.update(model.page, inner, CONTEXT)
const out = next.outMessage
if (out === undefined || out._tag !== "RequestedNavigation") return step(next)
const followed = follow(next.model, out.url)
return step({ ...followed, commands: [...(next.commands ?? []), ...(followed.commands ?? [])] })
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
},
})

const view = (model: Model, h: HtmlBuilder<Message>) =>
Expand All @@ -176,7 +194,7 @@ export const lab = defineShellLab("Service runtimes", {
Model,
Message,
Flags,
flags: Effect.succeed({}),
flags: Effect.sync(() => ({ tab: new URLSearchParams(window.location.search).get("tab") })),
init,
update,
view,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/lab/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ export const LAB_ENTRIES: ReadonlyArray<LabEntry> = [
path: "/lab/service-runtimes",
title: "Service runtimes",
description:
"The service page's Overview tab for fleet-api, a Google Cloud Run service with no Kubernetes workloads, over one hour.",
"The service page for fleet-api, a Google Cloud Run service with no Kubernetes workloads, over one hour; ?tab=operations or ?tab=dependencies opens that tab.",
kind: "lab",
session: "none",
},
Expand Down
82 changes: 82 additions & 0 deletions apps/web/src/page/services/detail/detail.scene.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,88 @@ describe("service detail view", () => {
)
})

test("the Dependencies tab shows a skeleton, not an empty table, until its read answers", () => {
const model = entered({ tab: "dependencies" })
scene(
{ update: pageUpdate, view },
given(model),
themeResolved(),
expectView(text("No downstream dependencies in this window.")).toBeAbsent(),
)
const empty = pageUpdate(
model,
Message.CompletedFetchDependencies({
key: model.dependencies.key ?? "",
result: Result.succeed({ serviceEdges: [], dbEdges: [], externalEdges: [] }),
}),
).model
scene(
{ update: pageUpdate, view },
given(empty),
themeResolved(),
expectView(text("No downstream dependencies in this window.")).toExist(),
)
})

test("a failed dependencies read shows its error instead of an empty table", () => {
const model = entered({ tab: "dependencies" })
const failed = pageUpdate(
model,
Message.CompletedFetchDependencies({
key: model.dependencies.key ?? "",
result: Result.fail(toLoadFailure("boom")),
}),
).model
scene(
{ update: pageUpdate, view },
given(failed),
themeResolved(),
expectView(text("Couldn't load dependencies")).toExist(),
expectView(text("No downstream dependencies in this window.")).toBeAbsent(),
)
})

test("a failed dependencies reload keeps the last rows on screen", () => {
const model = entered({ tab: "dependencies" })
const edge = {
callCount: 10,
estimatedCallCount: 10,
errorCount: 0,
errorRate: 0,
avgDurationMs: 2,
maxDurationMs: 4,
hasSampling: false,
samplingWeight: 1,
}
const loaded = pageUpdate(
model,
Message.CompletedFetchDependencies({
key: model.dependencies.key ?? "",
result: Result.succeed({
serviceEdges: [{ sourceService: "payment-svc", targetService: "ledger-svc", ...edge }],
dbEdges: [],
externalEdges: [],
}),
}),
).model
const reloading = pageUpdate(loaded, Message.ClickedReload()).model
const stale = pageUpdate(
reloading,
Message.CompletedFetchDependencies({
key: reloading.dependencies.key ?? "",
result: Result.fail(toLoadFailure("boom")),
}),
).model
expect(stale.dependencies.data._tag).toBe("Stale")
scene(
{ update: pageUpdate, view },
given(stale),
themeResolved(),
expectView(text("ledger-svc")).toExist(),
expectView(text("Couldn't load dependencies")).toBeAbsent(),
)
})

test("more than eight deploys list eight until Show all", () => {
const model = entered()
const releases = Array.from({ length: 10 }, (_, index) =>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/page/services/detail/view-api-charts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const STATUS_COLORS = {
const STACK_ORDER = ["5xx", "4xx", "2xx", "unknown"] as const

export const SPARK_TOKENS = {
requests: "--chart-p50",
requests: "--chart-throughput",
client: "--warning",
server: "--severity-error",
latency: "--chart-p95",
Expand Down
Loading
Loading