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
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,10 @@ vi.mock("@/hooks/use-configuration-builder", () => ({
}),
}));

const useLatestJavaAgentVersionMock = vi.fn();
vi.mock("@/hooks/use-latest-java-agent-version", () => ({
useLatestJavaAgentVersion: () => useLatestJavaAgentVersionMock(),
}));

const downloadSpy = vi.spyOn(downloadModule, "downloadText").mockImplementation(() => {});

describe("PreviewCard", () => {
beforeEach(() => {
useLatestJavaAgentVersionMock.mockReturnValue("2.27.0");
downloadSpy.mockClear();
});

Expand All @@ -71,7 +65,7 @@ describe("PreviewCard", () => {
});

it("renders the Output Preview title and action buttons", () => {
render(<PreviewCard schema={schema} />);
render(<PreviewCard schema={schema} javaAgentVersion="2.27.0" />);
expect(screen.getByText("Output Preview")).toBeInTheDocument();
expect(screen.getByRole("button", { name: /copy/i })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /download/i })).toBeInTheDocument();
Expand All @@ -80,25 +74,25 @@ describe("PreviewCard", () => {
});

it("triggers enableAllSections on Add all click", () => {
render(<PreviewCard schema={schema} />);
render(<PreviewCard schema={schema} javaAgentVersion="2.27.0" />);
fireEvent.click(screen.getByRole("button", { name: /add all/i }));
expect(enableAllSections).toHaveBeenCalledTimes(1);
});

it("Reset calls resetToDefaults (no confirm when state is clean)", () => {
render(<PreviewCard schema={schema} />);
render(<PreviewCard schema={schema} javaAgentVersion="2.27.0" />);
fireEvent.click(screen.getByRole("button", { name: /reset/i }));
expect(resetToDefaults).toHaveBeenCalledTimes(1);
});

it("triggers validateAll on Copy click regardless of clipboard availability", () => {
render(<PreviewCard schema={schema} />);
render(<PreviewCard schema={schema} javaAgentVersion="2.27.0" />);
fireEvent.click(screen.getByRole("button", { name: /copy/i }));
expect(validateAll).toHaveBeenCalledTimes(1);
});

it("renders the YAML output via YamlCodeBlock with token spans", () => {
const { container } = render(<PreviewCard schema={schema} />);
const { container } = render(<PreviewCard schema={schema} javaAgentVersion="2.27.0" />);
const pre = container.querySelector("pre");
expect(pre).not.toBeNull();
expect(pre?.querySelectorAll("span.y-key").length).toBeGreaterThan(0);
Expand All @@ -107,23 +101,28 @@ describe("PreviewCard", () => {
});

it("includes the resolved Java agent version in the rendered YAML header", () => {
render(<PreviewCard schema={schema} />);
render(<PreviewCard schema={schema} javaAgentVersion="2.27.0" />);
const codeBlock = screen.getByLabelText("Output Preview").querySelector("pre");
expect(codeBlock).not.toBeNull();
expect(codeBlock?.textContent).toContain("Schema version: 1.0.0");
expect(codeBlock?.textContent).toContain("Java agent: 2.27.0");
});

it("renders the header without the agent line while the version is still loading", () => {
useLatestJavaAgentVersionMock.mockReturnValue(undefined);
render(<PreviewCard schema={schema} />);
render(<PreviewCard schema={schema} javaAgentVersion="" />);
const codeBlock = screen.getByLabelText("Output Preview").querySelector("pre");
expect(codeBlock?.textContent).toContain("Schema version: 1.0.0");
expect(codeBlock?.textContent).not.toContain("Java agent:");
});

it("reflects a non-latest Java agent version selection in the YAML header", () => {
render(<PreviewCard schema={schema} javaAgentVersion="2.26.1" />);
const codeBlock = screen.getByLabelText("Output Preview").querySelector("pre");
expect(codeBlock?.textContent).toContain("Java agent: 2.26.1");
});

it("downloads the YAML with the schema-versioned filename and agent-stamped content", () => {
render(<PreviewCard schema={schema} />);
render(<PreviewCard schema={schema} javaAgentVersion="2.27.0" />);
fireEvent.click(screen.getByRole("button", { name: /download/i }));
expect(downloadSpy).toHaveBeenCalledTimes(1);
const [filename, body, mime] = downloadSpy.mock.calls[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,20 @@ import { useMemo, type JSX } from "react";
import { Download, RefreshCcw, ListPlus } from "lucide-react";
import type { ConfigNode } from "@/types/configuration";
import { useConfigurationBuilder } from "@/hooks/use-configuration-builder";
import { useLatestJavaAgentVersion } from "@/hooks/use-latest-java-agent-version";
import { generateYaml } from "@/lib/yaml-generator";
import { downloadText } from "@/lib/download-text";
import { CopyButton } from "@/components/ui/copy-button";
import { YamlCodeBlock } from "./yaml-code-block";

interface PreviewCardProps {
schema: ConfigNode;
javaAgentVersion: string;
}

export function PreviewCard({ schema }: PreviewCardProps): JSX.Element {
export function PreviewCard({ schema, javaAgentVersion }: PreviewCardProps): JSX.Element {
const { state, enableAllSections, resetToDefaults, validateAll } = useConfigurationBuilder();
const javaAgentVersion = useLatestJavaAgentVersion();
const yaml = useMemo(
() => generateYaml(state, schema, { javaAgentVersion }),
() => generateYaml(state, schema, { javaAgentVersion: javaAgentVersion || undefined }),
[state, schema, javaAgentVersion]
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ import {
} from "@/hooks/use-configuration-data";
import { ConfigurationBuilderProvider } from "@/hooks/configuration-builder-provider";
import { useConfigurationBuilder } from "@/hooks/use-configuration-builder";
import { useInstrumentations } from "@/hooks/use-javaagent-data";
import { useLatestJavaAgentVersion } from "@/hooks/use-latest-java-agent-version";
import { useInstrumentations, useVersions } from "@/hooks/use-javaagent-data";
import { groupByModule } from "@/lib/normalize-instrumentation";
import { useCustomizedModules } from "@/hooks/use-customized-modules";
import type { GroupNode } from "@/types/configuration";
Expand Down Expand Up @@ -74,11 +73,18 @@ const GENERAL_SETTINGS_LABEL = "General settings";
interface SdkTabContentProps {
schema: GroupNode;
starter: ReturnType<typeof useConfigStarter>["data"];
version: string;
schemaVersion: string;
javaAgentVersion: string;
activeTab: string;
}

function SdkTabContent({ schema, starter, version, activeTab }: SdkTabContentProps) {
function SdkTabContent({
schema,
starter,
schemaVersion,
javaAgentVersion,
activeTab,
}: SdkTabContentProps) {
const { groupChildren, leafChildren } = useMemo(() => {
const visible = schema.children.filter((c) => !SDK_HIDDEN_KEYS.has(c.key));
return {
Expand All @@ -99,7 +105,12 @@ function SdkTabContent({ schema, starter, version, activeTab }: SdkTabContentPro
const { activeKey, scrollToSection } = useActiveSection(sectionKeys, sectionsContainerRef);

return (
<ConfigurationBuilderProvider key={version} schema={schema} version={version} starter={starter}>
<ConfigurationBuilderProvider
key={schemaVersion}
schema={schema}
version={schemaVersion}
starter={starter}
>
<div className={BUILDER_GRID}>
<ConfigurationTocSidebar
activeTab={activeTab}
Expand All @@ -115,7 +126,7 @@ function SdkTabContent({ schema, starter, version, activeTab }: SdkTabContentPro
<SchemaRenderer key={child.key} node={child} depth={0} path={child.key} />
))}
</div>
<PreviewCard schema={schema} />
<PreviewCard schema={schema} javaAgentVersion={javaAgentVersion} />
</div>
</ConfigurationBuilderProvider>
);
Expand All @@ -124,14 +135,16 @@ function SdkTabContent({ schema, starter, version, activeTab }: SdkTabContentPro
interface InstrumentationTabContentProps {
schema: GroupNode;
starter: ReturnType<typeof useConfigStarter>["data"];
version: string;
schemaVersion: string;
javaAgentVersion: string;
activeTab: string;
}

function InstrumentationTabContent({
schema,
starter,
version,
schemaVersion,
javaAgentVersion,
activeTab,
}: InstrumentationTabContentProps) {
const generalNode = useMemo<GroupNode | null>(() => {
Expand All @@ -143,8 +156,18 @@ function InstrumentationTabContent({
}, [schema]);

return (
<ConfigurationBuilderProvider key={version} schema={schema} version={version} starter={starter}>
<InstrumentationTabBody activeTab={activeTab} schema={schema} generalNode={generalNode} />
<ConfigurationBuilderProvider
key={schemaVersion}
schema={schema}
version={schemaVersion}
starter={starter}
>
<InstrumentationTabBody
activeTab={activeTab}
schema={schema}
generalNode={generalNode}
javaAgentVersion={javaAgentVersion}
/>
</ConfigurationBuilderProvider>
);
}
Expand All @@ -153,17 +176,18 @@ interface InstrumentationTabBodyProps {
activeTab: string;
schema: GroupNode;
generalNode: GroupNode | null;
javaAgentVersion: string;
}

function InstrumentationTabBody({ activeTab, schema, generalNode }: InstrumentationTabBodyProps) {
function InstrumentationTabBody({
activeTab,
schema,
generalNode,
javaAgentVersion,
}: InstrumentationTabBodyProps) {
const [search, setSearch] = useState("");
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all");

// The page-level `version` is the SDK config schema version (e.g. "1.0.0").
// The instrumentation registry is keyed by Java agent version (e.g. "2.27.0"):
// a separate namespace. Resolve the latest agent version via the shared hook.
const javaAgentVersion = useLatestJavaAgentVersion() ?? "";

const tocSections: TocSection[] = useMemo(
() => [
{ key: GENERAL_SECTION_KEY, label: GENERAL_SETTINGS_LABEL },
Expand Down Expand Up @@ -237,26 +261,39 @@ function InstrumentationTabBody({ activeTab, schema, generalNode }: Instrumentat
onJumpToGeneral={scrollToSection}
/>
</div>
<PreviewCard schema={schema} />
<PreviewCard schema={schema} javaAgentVersion={javaAgentVersion} />
</div>
);
}

export function ConfigurationBuilderPage() {
const versions = useConfigVersions();
const latest = useMemo(
const schemaVersionsState = useConfigVersions();
const latestSchemaVersion = useMemo(
() =>
versions.data?.versions.find((v) => v.is_latest)?.version ??
versions.data?.versions[0]?.version ??
schemaVersionsState.data?.versions.find((v) => v.is_latest)?.version ??
schemaVersionsState.data?.versions[0]?.version ??
"",
[versions.data]
[schemaVersionsState.data]
);
const [currentVersion, setCurrentVersion] = useState<string>("");
const version = currentVersion || latest;
const [currentSchemaVersion, setCurrentSchemaVersion] = useState<string>("");
const schemaVersion = currentSchemaVersion || latestSchemaVersion;
const [activeTab, setActiveTab] = useState("sdk");

const schema = useConfigSchema(version);
const starter = useConfigStarter(version);
const javaAgentVersionsState = useVersions();
const javaAgentVersions = useMemo(
() => javaAgentVersionsState.data?.versions ?? [],
[javaAgentVersionsState.data]
);
const latestJavaAgentVersion = useMemo(
() =>
javaAgentVersions.find((v) => v.is_latest)?.version ?? javaAgentVersions[0]?.version ?? "",
[javaAgentVersions]
);
const [currentJavaAgentVersion, setCurrentJavaAgentVersion] = useState<string>("");
const javaAgentVersion = currentJavaAgentVersion || latestJavaAgentVersion;

const schema = useConfigSchema(schemaVersion);
const starter = useConfigStarter(schemaVersion);
const root = (schema.data as GroupNode | null) ?? null;

return (
Expand Down Expand Up @@ -294,17 +331,30 @@ export function ConfigurationBuilderPage() {
</a>
</p>
</div>
{versions.data && version ? (
<VersionSelector
versions={versions.data.versions}
currentVersion={version}
onVersionChange={setCurrentVersion}
/>
) : null}
<div className="flex flex-wrap items-center gap-4">
{schemaVersionsState.data && schemaVersion ? (
<VersionSelector
versions={schemaVersionsState.data.versions}
currentVersion={schemaVersion}
onVersionChange={setCurrentSchemaVersion}
label="Schema"
id="schema-version-select"
/>
) : null}
{javaAgentVersions.length > 0 && javaAgentVersion ? (
<VersionSelector
versions={javaAgentVersions}
currentVersion={javaAgentVersion}
onVersionChange={setCurrentJavaAgentVersion}
label="Agent"
id="java-agent-version-select"
/>
) : null}
</div>
</div>
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsContent value="sdk">
{!version || schema.loading || starter.loading ? (
{!schemaVersion || schema.loading || starter.loading ? (
<p className="text-muted-foreground mt-4 text-sm">Loading schema…</p>
) : schema.error ? (
<p className="mt-4 text-sm text-red-400">Failed to load schema.</p>
Expand All @@ -314,13 +364,14 @@ export function ConfigurationBuilderPage() {
<SdkTabContent
schema={root}
starter={starter.data}
version={version}
schemaVersion={schemaVersion}
javaAgentVersion={javaAgentVersion}
activeTab={activeTab}
/>
) : null}
</TabsContent>
<TabsContent value="instrumentation">
{!version || schema.loading || starter.loading ? (
{!schemaVersion || schema.loading || starter.loading ? (
<p className="text-muted-foreground mt-4 text-sm">Loading schema…</p>
) : schema.error ? (
<p className="mt-4 text-sm text-red-400">Failed to load schema.</p>
Expand All @@ -330,7 +381,8 @@ export function ConfigurationBuilderPage() {
<InstrumentationTabContent
schema={root}
starter={starter.data}
version={version}
schemaVersion={schemaVersion}
javaAgentVersion={javaAgentVersion}
activeTab={activeTab}
/>
) : null}
Expand Down
Loading
Loading