mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat: support icon and description in preset (#18977)
## Description This PR adds support for `description` and `icon` fields to `template_version_presets`. These fields will allow displaying richer information for presets in the UI, improving the user experience when creating a workspace. Both fields are optional, non-nullable, and default to empty strings. ## Changes * Database migration with the addition of `description VARCHAR(128)` and `icon VARCHAR(256)` columns to the `template_version_presets` table. * Updated the `CreateWorkspacePageView` in the UI Note: UI changes will be addressed in a separate PR
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
"last_seen_at": "====[timestamp]=====",
|
||||
"name": "test-daemon",
|
||||
"version": "v0.0.0-devel",
|
||||
"api_version": "1.7",
|
||||
"api_version": "1.8",
|
||||
"provisioners": [
|
||||
"echo"
|
||||
],
|
||||
|
||||
Generated
+6
@@ -14880,9 +14880,15 @@ const docTemplate = `{
|
||||
"default": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"desiredPrebuildInstances": {
|
||||
"type": "integer"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
Generated
+6
@@ -13487,9 +13487,15 @@
|
||||
"default": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"desiredPrebuildInstances": {
|
||||
"type": "integer"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -417,6 +417,8 @@ func (t TemplateVersionBuilder) Do() TemplateVersionResponse {
|
||||
InvalidateAfterSecs: preset.InvalidateAfterSecs,
|
||||
SchedulingTimezone: preset.SchedulingTimezone,
|
||||
IsDefault: false,
|
||||
Description: preset.Description,
|
||||
Icon: preset.Icon,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1392,6 +1392,8 @@ func Preset(t testing.TB, db database.Store, seed database.InsertPresetParams) d
|
||||
InvalidateAfterSecs: seed.InvalidateAfterSecs,
|
||||
SchedulingTimezone: seed.SchedulingTimezone,
|
||||
IsDefault: seed.IsDefault,
|
||||
Description: seed.Description,
|
||||
Icon: seed.Icon,
|
||||
})
|
||||
require.NoError(t, err, "insert preset")
|
||||
return preset
|
||||
|
||||
Generated
+7
-1
@@ -1618,9 +1618,15 @@ CREATE TABLE template_version_presets (
|
||||
invalidate_after_secs integer DEFAULT 0,
|
||||
prebuild_status prebuild_status DEFAULT 'healthy'::prebuild_status NOT NULL,
|
||||
scheduling_timezone text DEFAULT ''::text NOT NULL,
|
||||
is_default boolean DEFAULT false NOT NULL
|
||||
is_default boolean DEFAULT false NOT NULL,
|
||||
description character varying(128) DEFAULT ''::character varying NOT NULL,
|
||||
icon character varying(256) DEFAULT ''::character varying NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN template_version_presets.description IS 'Short text describing the preset (max 128 characters).';
|
||||
|
||||
COMMENT ON COLUMN template_version_presets.icon IS 'URL or path to an icon representing the preset (max 256 characters).';
|
||||
|
||||
CREATE TABLE template_version_terraform_values (
|
||||
template_version_id uuid NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE template_version_presets
|
||||
DROP COLUMN IF EXISTS description,
|
||||
DROP COLUMN IF EXISTS icon;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
ALTER TABLE template_version_presets
|
||||
ADD COLUMN IF NOT EXISTS description VARCHAR(128) NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS icon VARCHAR(256) NOT NULL DEFAULT '';
|
||||
|
||||
COMMENT ON COLUMN template_version_presets.description IS 'Short text describing the preset (max 128 characters).';
|
||||
COMMENT ON COLUMN template_version_presets.icon IS 'URL or path to an icon representing the preset (max 256 characters).';
|
||||
@@ -3621,6 +3621,10 @@ type TemplateVersionPreset struct {
|
||||
PrebuildStatus PrebuildStatus `db:"prebuild_status" json:"prebuild_status"`
|
||||
SchedulingTimezone string `db:"scheduling_timezone" json:"scheduling_timezone"`
|
||||
IsDefault bool `db:"is_default" json:"is_default"`
|
||||
// Short text describing the preset (max 128 characters).
|
||||
Description string `db:"description" json:"description"`
|
||||
// URL or path to an icon representing the preset (max 256 characters).
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
}
|
||||
|
||||
type TemplateVersionPresetParameter struct {
|
||||
|
||||
@@ -7628,7 +7628,7 @@ func (q *sqlQuerier) GetActivePresetPrebuildSchedules(ctx context.Context) ([]Te
|
||||
}
|
||||
|
||||
const getPresetByID = `-- name: GetPresetByID :one
|
||||
SELECT tvp.id, tvp.template_version_id, tvp.name, tvp.created_at, tvp.desired_instances, tvp.invalidate_after_secs, tvp.prebuild_status, tvp.scheduling_timezone, tvp.is_default, tv.template_id, tv.organization_id FROM
|
||||
SELECT tvp.id, tvp.template_version_id, tvp.name, tvp.created_at, tvp.desired_instances, tvp.invalidate_after_secs, tvp.prebuild_status, tvp.scheduling_timezone, tvp.is_default, tvp.description, tvp.icon, tv.template_id, tv.organization_id FROM
|
||||
template_version_presets tvp
|
||||
INNER JOIN template_versions tv ON tvp.template_version_id = tv.id
|
||||
WHERE tvp.id = $1
|
||||
@@ -7644,6 +7644,8 @@ type GetPresetByIDRow struct {
|
||||
PrebuildStatus PrebuildStatus `db:"prebuild_status" json:"prebuild_status"`
|
||||
SchedulingTimezone string `db:"scheduling_timezone" json:"scheduling_timezone"`
|
||||
IsDefault bool `db:"is_default" json:"is_default"`
|
||||
Description string `db:"description" json:"description"`
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
TemplateID uuid.NullUUID `db:"template_id" json:"template_id"`
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
}
|
||||
@@ -7661,6 +7663,8 @@ func (q *sqlQuerier) GetPresetByID(ctx context.Context, presetID uuid.UUID) (Get
|
||||
&i.PrebuildStatus,
|
||||
&i.SchedulingTimezone,
|
||||
&i.IsDefault,
|
||||
&i.Description,
|
||||
&i.Icon,
|
||||
&i.TemplateID,
|
||||
&i.OrganizationID,
|
||||
)
|
||||
@@ -7669,7 +7673,7 @@ func (q *sqlQuerier) GetPresetByID(ctx context.Context, presetID uuid.UUID) (Get
|
||||
|
||||
const getPresetByWorkspaceBuildID = `-- name: GetPresetByWorkspaceBuildID :one
|
||||
SELECT
|
||||
template_version_presets.id, template_version_presets.template_version_id, template_version_presets.name, template_version_presets.created_at, template_version_presets.desired_instances, template_version_presets.invalidate_after_secs, template_version_presets.prebuild_status, template_version_presets.scheduling_timezone, template_version_presets.is_default
|
||||
template_version_presets.id, template_version_presets.template_version_id, template_version_presets.name, template_version_presets.created_at, template_version_presets.desired_instances, template_version_presets.invalidate_after_secs, template_version_presets.prebuild_status, template_version_presets.scheduling_timezone, template_version_presets.is_default, template_version_presets.description, template_version_presets.icon
|
||||
FROM
|
||||
template_version_presets
|
||||
INNER JOIN workspace_builds ON workspace_builds.template_version_preset_id = template_version_presets.id
|
||||
@@ -7690,6 +7694,8 @@ func (q *sqlQuerier) GetPresetByWorkspaceBuildID(ctx context.Context, workspaceB
|
||||
&i.PrebuildStatus,
|
||||
&i.SchedulingTimezone,
|
||||
&i.IsDefault,
|
||||
&i.Description,
|
||||
&i.Icon,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -7771,7 +7777,7 @@ func (q *sqlQuerier) GetPresetParametersByTemplateVersionID(ctx context.Context,
|
||||
|
||||
const getPresetsByTemplateVersionID = `-- name: GetPresetsByTemplateVersionID :many
|
||||
SELECT
|
||||
id, template_version_id, name, created_at, desired_instances, invalidate_after_secs, prebuild_status, scheduling_timezone, is_default
|
||||
id, template_version_id, name, created_at, desired_instances, invalidate_after_secs, prebuild_status, scheduling_timezone, is_default, description, icon
|
||||
FROM
|
||||
template_version_presets
|
||||
WHERE
|
||||
@@ -7797,6 +7803,8 @@ func (q *sqlQuerier) GetPresetsByTemplateVersionID(ctx context.Context, template
|
||||
&i.PrebuildStatus,
|
||||
&i.SchedulingTimezone,
|
||||
&i.IsDefault,
|
||||
&i.Description,
|
||||
&i.Icon,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -7820,7 +7828,9 @@ INSERT INTO template_version_presets (
|
||||
desired_instances,
|
||||
invalidate_after_secs,
|
||||
scheduling_timezone,
|
||||
is_default
|
||||
is_default,
|
||||
description,
|
||||
icon
|
||||
)
|
||||
VALUES (
|
||||
$1,
|
||||
@@ -7830,8 +7840,10 @@ VALUES (
|
||||
$5,
|
||||
$6,
|
||||
$7,
|
||||
$8
|
||||
) RETURNING id, template_version_id, name, created_at, desired_instances, invalidate_after_secs, prebuild_status, scheduling_timezone, is_default
|
||||
$8,
|
||||
$9,
|
||||
$10
|
||||
) RETURNING id, template_version_id, name, created_at, desired_instances, invalidate_after_secs, prebuild_status, scheduling_timezone, is_default, description, icon
|
||||
`
|
||||
|
||||
type InsertPresetParams struct {
|
||||
@@ -7843,6 +7855,8 @@ type InsertPresetParams struct {
|
||||
InvalidateAfterSecs sql.NullInt32 `db:"invalidate_after_secs" json:"invalidate_after_secs"`
|
||||
SchedulingTimezone string `db:"scheduling_timezone" json:"scheduling_timezone"`
|
||||
IsDefault bool `db:"is_default" json:"is_default"`
|
||||
Description string `db:"description" json:"description"`
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertPreset(ctx context.Context, arg InsertPresetParams) (TemplateVersionPreset, error) {
|
||||
@@ -7855,6 +7869,8 @@ func (q *sqlQuerier) InsertPreset(ctx context.Context, arg InsertPresetParams) (
|
||||
arg.InvalidateAfterSecs,
|
||||
arg.SchedulingTimezone,
|
||||
arg.IsDefault,
|
||||
arg.Description,
|
||||
arg.Icon,
|
||||
)
|
||||
var i TemplateVersionPreset
|
||||
err := row.Scan(
|
||||
@@ -7867,6 +7883,8 @@ func (q *sqlQuerier) InsertPreset(ctx context.Context, arg InsertPresetParams) (
|
||||
&i.PrebuildStatus,
|
||||
&i.SchedulingTimezone,
|
||||
&i.IsDefault,
|
||||
&i.Description,
|
||||
&i.Icon,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ INSERT INTO template_version_presets (
|
||||
desired_instances,
|
||||
invalidate_after_secs,
|
||||
scheduling_timezone,
|
||||
is_default
|
||||
is_default,
|
||||
description,
|
||||
icon
|
||||
)
|
||||
VALUES (
|
||||
@id,
|
||||
@@ -17,7 +19,9 @@ VALUES (
|
||||
@desired_instances,
|
||||
@invalidate_after_secs,
|
||||
@scheduling_timezone,
|
||||
@is_default
|
||||
@is_default,
|
||||
@description,
|
||||
@icon
|
||||
) RETURNING *;
|
||||
|
||||
-- name: InsertPresetParameters :many
|
||||
|
||||
@@ -54,6 +54,8 @@ func (api *API) templateVersionPresets(rw http.ResponseWriter, r *http.Request)
|
||||
Name: preset.Name,
|
||||
Default: preset.IsDefault,
|
||||
DesiredPrebuildInstances: convertPrebuildInstances(preset.DesiredInstances),
|
||||
Description: preset.Description,
|
||||
Icon: preset.Icon,
|
||||
}
|
||||
for _, presetParam := range presetParams {
|
||||
if presetParam.TemplateVersionPresetID != preset.ID {
|
||||
|
||||
@@ -2264,6 +2264,7 @@ func InsertWorkspacePresetAndParameters(ctx context.Context, db database.Store,
|
||||
prebuildSchedules = protoPreset.Prebuild.Scheduling.Schedule
|
||||
}
|
||||
}
|
||||
|
||||
dbPreset, err := tx.InsertPreset(ctx, database.InsertPresetParams{
|
||||
ID: uuid.New(),
|
||||
TemplateVersionID: templateVersionID,
|
||||
@@ -2273,6 +2274,8 @@ func InsertWorkspacePresetAndParameters(ctx context.Context, db database.Store,
|
||||
InvalidateAfterSecs: ttl,
|
||||
SchedulingTimezone: schedulingTimezone,
|
||||
IsDefault: protoPreset.GetDefault(),
|
||||
Description: protoPreset.Description,
|
||||
Icon: protoPreset.Icon,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("insert preset: %w", err)
|
||||
|
||||
@@ -16,6 +16,8 @@ type Preset struct {
|
||||
Parameters []PresetParameter
|
||||
Default bool
|
||||
DesiredPrebuildInstances *int
|
||||
Description string
|
||||
Icon string
|
||||
}
|
||||
|
||||
type PresetParameter struct {
|
||||
|
||||
Generated
+4
@@ -5513,7 +5513,9 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
|
||||
```json
|
||||
{
|
||||
"default": true,
|
||||
"description": "string",
|
||||
"desiredPrebuildInstances": 0,
|
||||
"icon": "string",
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"parameters": [
|
||||
@@ -5530,7 +5532,9 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
|----------------------------|---------------------------------------------------------------|----------|--------------|-------------|
|
||||
| `default` | boolean | false | | |
|
||||
| `description` | string | false | | |
|
||||
| `desiredPrebuildInstances` | integer | false | | |
|
||||
| `icon` | string | false | | |
|
||||
| `id` | string | false | | |
|
||||
| `name` | string | false | | |
|
||||
| `parameters` | array of [codersdk.PresetParameter](#codersdkpresetparameter) | false | | |
|
||||
|
||||
Generated
+4
@@ -2914,7 +2914,9 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/p
|
||||
[
|
||||
{
|
||||
"default": true,
|
||||
"description": "string",
|
||||
"desiredPrebuildInstances": 0,
|
||||
"icon": "string",
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"parameters": [
|
||||
@@ -2941,7 +2943,9 @@ Status Code **200**
|
||||
|------------------------------|---------|----------|--------------|-------------|
|
||||
| `[array item]` | array | false | | |
|
||||
| `» default` | boolean | false | | |
|
||||
| `» description` | string | false | | |
|
||||
| `» desiredPrebuildInstances` | integer | false | | |
|
||||
| `» icon` | string | false | | |
|
||||
| `» id` | string | false | | |
|
||||
| `» name` | string | false | | |
|
||||
| `» parameters` | array | false | | |
|
||||
|
||||
@@ -965,7 +965,9 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s
|
||||
ExpirationPolicy: expirationPolicy,
|
||||
Scheduling: scheduling,
|
||||
},
|
||||
Default: preset.Default,
|
||||
Default: preset.Default,
|
||||
Description: preset.Description,
|
||||
Icon: preset.Icon,
|
||||
}
|
||||
|
||||
if slice.Contains(duplicatedPresetNames, preset.Name) {
|
||||
|
||||
@@ -44,9 +44,12 @@ import "github.com/coder/coder/v2/apiversion"
|
||||
// -> `has_ai_tasks` in `CompleteJob.TemplateImport`
|
||||
// -> `has_ai_tasks` and `ai_tasks` in `PlanComplete`
|
||||
// -> new message types `AITaskSidebarApp` and `AITask`
|
||||
//
|
||||
// API v1.8:
|
||||
// - Add new fields `description` and `icon` to `Preset`.
|
||||
const (
|
||||
CurrentMajor = 1
|
||||
CurrentMinor = 7
|
||||
CurrentMinor = 8
|
||||
)
|
||||
|
||||
// CurrentVersion is the current provisionerd API version.
|
||||
|
||||
Generated
+586
-567
File diff suppressed because it is too large
Load Diff
@@ -101,6 +101,8 @@ message Preset {
|
||||
repeated PresetParameter parameters = 2;
|
||||
Prebuild prebuild = 3;
|
||||
bool default = 4;
|
||||
string description = 5;
|
||||
string icon = 6;
|
||||
}
|
||||
|
||||
message PresetParameter {
|
||||
|
||||
Generated
+8
@@ -162,6 +162,8 @@ export interface Preset {
|
||||
parameters: PresetParameter[];
|
||||
prebuild: Prebuild | undefined;
|
||||
default: boolean;
|
||||
description: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export interface PresetParameter {
|
||||
@@ -715,6 +717,12 @@ export const Preset = {
|
||||
if (message.default === true) {
|
||||
writer.uint32(32).bool(message.default);
|
||||
}
|
||||
if (message.description !== "") {
|
||||
writer.uint32(42).string(message.description);
|
||||
}
|
||||
if (message.icon !== "") {
|
||||
writer.uint32(50).string(message.icon);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
};
|
||||
|
||||
Generated
+2
@@ -1998,6 +1998,8 @@ export interface Preset {
|
||||
readonly Parameters: readonly PresetParameter[];
|
||||
readonly Default: boolean;
|
||||
readonly DesiredPrebuildInstances: number | null;
|
||||
readonly Description: string;
|
||||
readonly Icon: string;
|
||||
}
|
||||
|
||||
// From codersdk/presets.go
|
||||
|
||||
@@ -126,6 +126,8 @@ export const PresetsButNoneSelected: Story = {
|
||||
{
|
||||
ID: "preset-1",
|
||||
Name: "Preset 1",
|
||||
Description: "",
|
||||
Icon: "",
|
||||
Default: false,
|
||||
Parameters: [
|
||||
{
|
||||
@@ -138,6 +140,9 @@ export const PresetsButNoneSelected: Story = {
|
||||
{
|
||||
ID: "preset-2",
|
||||
Name: "Preset 2",
|
||||
Description:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse imperdiet ultricies massa, eu dapibus ex fermentum ac.",
|
||||
Icon: "/emojis/1f60e.png",
|
||||
Default: false,
|
||||
Parameters: [
|
||||
{
|
||||
@@ -252,6 +257,8 @@ export const PresetsWithDefault: Story = {
|
||||
{
|
||||
ID: "preset-1",
|
||||
Name: "Preset 1",
|
||||
Icon: "",
|
||||
Description: "",
|
||||
Default: false,
|
||||
Parameters: [
|
||||
{
|
||||
@@ -264,6 +271,9 @@ export const PresetsWithDefault: Story = {
|
||||
{
|
||||
ID: "preset-2",
|
||||
Name: "Preset 2",
|
||||
Icon: "/emojis/1f60e.png",
|
||||
Description:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse imperdiet ultricies massa, eu dapibus ex fermentum ac.",
|
||||
Default: true,
|
||||
Parameters: [
|
||||
{
|
||||
|
||||
@@ -4577,6 +4577,8 @@ export const MockPresets: TypesGen.Preset[] = [
|
||||
{
|
||||
ID: "preset-1",
|
||||
Name: "Development",
|
||||
Description: "",
|
||||
Icon: "",
|
||||
Parameters: [
|
||||
{ Name: "cpu", Value: "4" },
|
||||
{ Name: "memory", Value: "8GB" },
|
||||
@@ -4587,6 +4589,8 @@ export const MockPresets: TypesGen.Preset[] = [
|
||||
{
|
||||
ID: "preset-2",
|
||||
Name: "Testing",
|
||||
Description: "",
|
||||
Icon: "",
|
||||
Parameters: [
|
||||
{ Name: "cpu", Value: "2" },
|
||||
{ Name: "memory", Value: "4GB" },
|
||||
@@ -4597,6 +4601,8 @@ export const MockPresets: TypesGen.Preset[] = [
|
||||
{
|
||||
ID: "preset-3",
|
||||
Name: "Production",
|
||||
Description: "",
|
||||
Icon: "",
|
||||
Parameters: [
|
||||
{ Name: "cpu", Value: "8" },
|
||||
{ Name: "memory", Value: "16GB" },
|
||||
@@ -4610,6 +4616,8 @@ export const MockAIPromptPresets: TypesGen.Preset[] = [
|
||||
{
|
||||
ID: "ai-preset-1",
|
||||
Name: "Code Review",
|
||||
Description: "",
|
||||
Icon: "",
|
||||
Parameters: [
|
||||
{ Name: "AI Prompt", Value: "Review the code for best practices" },
|
||||
{ Name: "cpu", Value: "4" },
|
||||
@@ -4621,6 +4629,8 @@ export const MockAIPromptPresets: TypesGen.Preset[] = [
|
||||
{
|
||||
ID: "ai-preset-2",
|
||||
Name: "Custom Prompt",
|
||||
Description: "",
|
||||
Icon: "",
|
||||
Parameters: [
|
||||
{ Name: "cpu", Value: "4" },
|
||||
{ Name: "memory", Value: "8GB" },
|
||||
|
||||
Reference in New Issue
Block a user