mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(site): display prerequisites in base template step (#26524)
Display the base template prerequisites in the Template Builder wizard. Stacked on #26523. The `base-parameters` wizard step now renders the prerequisites markdown (served by the backend `prerequisites` field) below the variable configuration fields using `MemoizedMarkdown`. The step is shown when the base has parameters **or** prerequisites, so Docker (no parameters, has prerequisites) now shows this step. ## Changes - `SelectedBaseMeta` gains `hasPrerequisites` boolean - `toSelectedBaseMeta()` populates it from `base.prerequisites` - `base-parameters` step skip logic: show when base has parameters or prerequisites - `BaseTemplateParametersStep`: render full prerequisites markdown as-is (headings intact) - Updated all test fixtures with the new field *Generated with the assistance of an AI coding agent. Reviewed by @jeremyruppel.* Relates to https://linear.app/codercom/issue/DEVEX-446
This commit is contained in:
@@ -20,6 +20,7 @@ function toSelectedBaseMeta(base: TemplateBuilderBase): SelectedBaseMeta {
|
||||
os: base.os,
|
||||
hasParameters:
|
||||
base.variables.length > 0 && base.variables.some((v) => !v.sensitive),
|
||||
hasPrerequisites: Boolean(base.prerequisites?.length),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
TemplateBuilderBasesResponse,
|
||||
TemplateBuilderModuleVariable,
|
||||
} from "#/api/typesGenerated";
|
||||
import { MemoizedMarkdown } from "#/components/Markdown/Markdown";
|
||||
import type { ConfigurationFieldDefinition } from "./ConfigurationField";
|
||||
import { TemplateConfiguration } from "./TemplateConfiguration";
|
||||
|
||||
@@ -89,6 +90,7 @@ export const BaseTemplateParametersStep: FC<
|
||||
const { data } = useQuery(templateBuilderBases());
|
||||
const base = data?.bases.find((b) => b.id === baseId);
|
||||
const variables = base?.variables.filter((v) => !v.sensitive) ?? [];
|
||||
const prerequisites = base?.prerequisites ?? "";
|
||||
|
||||
const handleChange = (name: string, value: string) => {
|
||||
onChangeValues({ ...values, [name]: value });
|
||||
@@ -105,6 +107,12 @@ export const BaseTemplateParametersStep: FC<
|
||||
iconUrl={base?.icon}
|
||||
detailsUrl={detailsUrl(baseId)}
|
||||
fields={fields}
|
||||
/>
|
||||
>
|
||||
{prerequisites && (
|
||||
<div className="mt-6">
|
||||
<MemoizedMarkdown>{prerequisites}</MemoizedMarkdown>
|
||||
</div>
|
||||
)}
|
||||
</TemplateConfiguration>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { Link } from "#/components/Link/Link";
|
||||
import {
|
||||
ConfigurationField,
|
||||
type ConfigurationFieldDefinition,
|
||||
} from "./ConfigurationField";
|
||||
|
||||
type TemplateConfigurationProps = {
|
||||
type TemplateConfigurationProps = PropsWithChildren<{
|
||||
name: string;
|
||||
description: string;
|
||||
iconUrl?: string;
|
||||
detailsUrl?: string;
|
||||
fields?: ConfigurationFieldDefinition[];
|
||||
};
|
||||
}>;
|
||||
|
||||
export const TemplateConfiguration: React.FC<TemplateConfigurationProps> = ({
|
||||
name,
|
||||
@@ -18,6 +19,7 @@ export const TemplateConfiguration: React.FC<TemplateConfigurationProps> = ({
|
||||
iconUrl,
|
||||
detailsUrl,
|
||||
fields,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<section className="pt-4 px-4 pb-6 rounded bg-surface-secondary">
|
||||
@@ -59,6 +61,7 @@ export const TemplateConfiguration: React.FC<TemplateConfigurationProps> = ({
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -35,6 +35,7 @@ describe("shouldSkip", () => {
|
||||
id: "docker",
|
||||
name: "Docker",
|
||||
hasParameters: false,
|
||||
hasPrerequisites: false,
|
||||
},
|
||||
});
|
||||
expect(stepById("base-parameters").shouldSkip(noParams)).toBe(true);
|
||||
@@ -46,6 +47,7 @@ describe("shouldSkip", () => {
|
||||
id: "aws-linux",
|
||||
name: "AWS Linux",
|
||||
hasParameters: true,
|
||||
hasPrerequisites: false,
|
||||
},
|
||||
});
|
||||
expect(stepById("base-parameters").shouldSkip(withParams)).toBe(false);
|
||||
@@ -137,6 +139,7 @@ describe("findNextVisibleIndex", () => {
|
||||
id: "docker",
|
||||
name: "Docker",
|
||||
hasParameters: false,
|
||||
hasPrerequisites: false,
|
||||
},
|
||||
});
|
||||
// From base-infra (index 0), next visible should be module-select (index 2).
|
||||
@@ -160,6 +163,7 @@ describe("findNextVisibleIndex", () => {
|
||||
id: "aws-linux",
|
||||
name: "AWS Linux",
|
||||
hasParameters: true,
|
||||
hasPrerequisites: false,
|
||||
},
|
||||
selectedModules: [
|
||||
{
|
||||
@@ -186,6 +190,7 @@ describe("findPrevVisibleIndex", () => {
|
||||
id: "docker",
|
||||
name: "Docker",
|
||||
hasParameters: false,
|
||||
hasPrerequisites: false,
|
||||
},
|
||||
});
|
||||
// From module-select (index 2), prev visible should be base-infra (index 0).
|
||||
@@ -220,6 +225,7 @@ describe("nearestVisible", () => {
|
||||
id: "docker",
|
||||
name: "Docker",
|
||||
hasParameters: false,
|
||||
hasPrerequisites: false,
|
||||
},
|
||||
};
|
||||
// Index 1 (base-parameters) is skipped, nearest backward is 0 (base-infra).
|
||||
|
||||
@@ -40,7 +40,9 @@ export const WIZARD_STEPS: readonly WizardStep[] = [
|
||||
{
|
||||
id: "base-parameters",
|
||||
group: 1,
|
||||
shouldSkip: (state) => !state.selectedBase?.hasParameters,
|
||||
shouldSkip: (state) =>
|
||||
!state.selectedBase?.hasParameters &&
|
||||
!state.selectedBase?.hasPrerequisites,
|
||||
},
|
||||
{
|
||||
id: "module-select",
|
||||
|
||||
@@ -26,6 +26,7 @@ describe("wizardReducer", () => {
|
||||
id: "docker",
|
||||
name: "Docker",
|
||||
hasParameters: false,
|
||||
hasPrerequisites: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
@@ -37,7 +38,12 @@ describe("wizardReducer", () => {
|
||||
const state = reduce([
|
||||
{
|
||||
type: "SET_BASE",
|
||||
base: { id: "docker", name: "Docker", hasParameters: true },
|
||||
base: {
|
||||
id: "docker",
|
||||
name: "Docker",
|
||||
hasParameters: true,
|
||||
hasPrerequisites: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "SET_BASE_VARIABLES",
|
||||
@@ -45,7 +51,12 @@ describe("wizardReducer", () => {
|
||||
},
|
||||
{
|
||||
type: "SET_BASE",
|
||||
base: { id: "aws-linux", name: "AWS Linux", hasParameters: true },
|
||||
base: {
|
||||
id: "aws-linux",
|
||||
name: "AWS Linux",
|
||||
hasParameters: true,
|
||||
hasPrerequisites: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(state.baseTemplateId).toBe("aws-linux");
|
||||
@@ -56,7 +67,12 @@ describe("wizardReducer", () => {
|
||||
const state = reduce([
|
||||
{
|
||||
type: "SET_BASE",
|
||||
base: { id: "docker", name: "Docker", hasParameters: true },
|
||||
base: {
|
||||
id: "docker",
|
||||
name: "Docker",
|
||||
hasParameters: true,
|
||||
hasPrerequisites: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "SET_BASE_VARIABLES",
|
||||
@@ -64,7 +80,12 @@ describe("wizardReducer", () => {
|
||||
},
|
||||
{
|
||||
type: "SET_BASE",
|
||||
base: { id: "docker", name: "Docker", hasParameters: true },
|
||||
base: {
|
||||
id: "docker",
|
||||
name: "Docker",
|
||||
hasParameters: true,
|
||||
hasPrerequisites: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(state.baseVariableValues).toEqual({ image: "ubuntu" });
|
||||
@@ -254,7 +275,12 @@ describe("wizardReducer", () => {
|
||||
const state = reduce([
|
||||
{
|
||||
type: "SET_BASE",
|
||||
base: { id: "docker", name: "Docker", hasParameters: true },
|
||||
base: {
|
||||
id: "docker",
|
||||
name: "Docker",
|
||||
hasParameters: true,
|
||||
hasPrerequisites: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "SET_CUSTOMIZATION",
|
||||
|
||||
@@ -15,6 +15,7 @@ export type SelectedBaseMeta = {
|
||||
iconUrl?: string;
|
||||
os?: string;
|
||||
hasParameters: boolean;
|
||||
hasPrerequisites: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user