From c3fc023dbd62d95dd4ed2f2e8bfebfa08c635c04 Mon Sep 17 00:00:00 2001 From: Ariel Date: Wed, 19 Feb 2025 19:19:56 +0000 Subject: [PATCH 01/29] feat: add multi-enum support for survey variables --- db/Template.go | 7 ++++--- web/src/components/SurveyVars.vue | 10 ++++++++++ web/src/components/TaskForm.vue | 20 ++++++++++++++++++-- web/src/lang/en.js | 1 + 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/db/Template.go b/db/Template.go index d4ca07d03..ae985f4c4 100644 --- a/db/Template.go +++ b/db/Template.go @@ -59,9 +59,10 @@ func (t TemplateApp) IsTerraform() bool { type SurveyVarType string const ( - SurveyVarStr TemplateType = "" - SurveyVarInt TemplateType = "int" - SurveyVarEnum TemplateType = "enum" + SurveyVarStr TemplateType = "" + SurveyVarInt TemplateType = "int" + SurveyVarEnum TemplateType = "enum" + SurveyVarMultiEnum TemplateType = "multi_enum" ) type AnsibleTemplateParams struct { diff --git a/web/src/components/SurveyVars.vue b/web/src/components/SurveyVars.vue index 55587f160..ed9e410c3 100644 --- a/web/src/components/SurveyVars.vue +++ b/web/src/components/SurveyVars.vue @@ -93,9 +93,16 @@
Add Value + +
+ :multiple="v.type === 'multi_enum'" + :chips="v.type === 'multi_enum'" + > + + Date: Wed, 19 Feb 2025 19:59:55 +0000 Subject: [PATCH 02/29] fix: fix multi enum type --- web/src/components/SurveyVars.vue | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/web/src/components/SurveyVars.vue b/web/src/components/SurveyVars.vue index ed9e410c3..5dfe679d7 100644 --- a/web/src/components/SurveyVars.vue +++ b/web/src/components/SurveyVars.vue @@ -48,7 +48,7 @@ > Add Value - - Date: Tue, 7 Oct 2025 13:55:55 +0700 Subject: [PATCH 03/29] fix: update survey variable types from 'multi_enum' to 'select' in Template and TaskForm components Signed-off-by: blackb1rd --- db/Template.go | 8 ++++---- web/src/components/SurveyVars.vue | 10 +++++----- web/src/components/TaskForm.vue | 24 ++++++++++++------------ 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/db/Template.go b/db/Template.go index ae985f4c4..adebd3bfd 100644 --- a/db/Template.go +++ b/db/Template.go @@ -59,10 +59,10 @@ func (t TemplateApp) IsTerraform() bool { type SurveyVarType string const ( - SurveyVarStr TemplateType = "" - SurveyVarInt TemplateType = "int" - SurveyVarEnum TemplateType = "enum" - SurveyVarMultiEnum TemplateType = "multi_enum" + SurveyVarStr TemplateType = "" + SurveyVarInt TemplateType = "int" + SurveyVarEnum TemplateType = "enum" + SurveyVarSelect TemplateType = "select" ) type AnsibleTemplateParams struct { diff --git a/web/src/components/SurveyVars.vue b/web/src/components/SurveyVars.vue index 5dfe679d7..c50c72caa 100644 --- a/web/src/components/SurveyVars.vue +++ b/web/src/components/SurveyVars.vue @@ -48,7 +48,7 @@ > Add Value @@ -208,8 +208,8 @@ export default { id: 'enum', name: 'Enum', }, { - id: 'multi_enum', - name: 'Multi Enum', + id: 'select', + name: 'Select', }], formError: null, }; @@ -252,7 +252,7 @@ export default { return; } - if (this.editedVar.type === 'enum' || this.editedVar.type === 'multi_enum') { + if (this.editedVar.type === 'enum' || this.editedVar.type === 'select') { if (this.editedValues.length === 0) { this.formError = 'Enumeration must have values.'; return; diff --git a/web/src/components/TaskForm.vue b/web/src/components/TaskForm.vue index cafbfb269..75e49ac49 100644 --- a/web/src/components/TaskForm.vue +++ b/web/src/components/TaskForm.vue @@ -80,7 +80,7 @@ - + Date: Tue, 7 Oct 2025 16:42:24 +0700 Subject: [PATCH 04/29] feat: enhance select type handling in SurveyVars component with default value normalization and selection chips Signed-off-by: blackb1rd --- web/src/components/SurveyVars.vue | 55 +++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/web/src/components/SurveyVars.vue b/web/src/components/SurveyVars.vue index c50c72caa..74dff30a1 100644 --- a/web/src/components/SurveyVars.vue +++ b/web/src/components/SurveyVars.vue @@ -100,14 +100,27 @@ + :multiple="editedVar.type === 'select'" + :chips="editedVar.type === 'select'" + > + + 0 ? this.editedVar.default_value[0] : ''; + } + this.editedVarIndex = index; if (this.$refs.form) { @@ -278,6 +299,13 @@ export default { this.editedVar.values = []; } + // normalize default_value before saving: select keeps array, others use string + if (this.editedVar.type === 'select') { + if (!Array.isArray(this.editedVar.default_value)) this.editedVar.default_value = this.editedVar.default_value == null || this.editedVar.default_value === '' ? [] : [this.editedVar.default_value]; + } else { + if (Array.isArray(this.editedVar.default_value)) this.editedVar.default_value = this.editedVar.default_value.length > 0 ? this.editedVar.default_value[0] : ''; + } + if (this.editedVarIndex != null) { this.modifiedVars[this.editedVarIndex] = this.editedVar; } else { @@ -293,6 +321,29 @@ export default { this.modifiedVars.splice(index, 1); this.$emit('change', this.modifiedVars); }, + + // remove one selected default item (for multiple select) + removeDefaultItem(index) { + if (!this.editedVar || !Array.isArray(this.editedVar.default_value)) return; + if (index >= 0 && index < this.editedVar.default_value.length) this.editedVar.default_value.splice(index, 1); + }, + + // label shown in selection chips - item could be the value or an object + selectedItemLabel(item) { + if (!item) return ''; + if (typeof item === 'object' && item.name) return item.name; + const found = this.editedValues.find((v) => v.value === item || v.name === item); + return (found && found.name) || String(item); + }, + + onTypeChange(newType) { + if (!this.editedVar) return; + if (newType === 'select') { + if (!Array.isArray(this.editedVar.default_value)) this.editedVar.default_value = this.editedVar.default_value == null || this.editedVar.default_value === '' ? [] : [this.editedVar.default_value]; + } else { + if (Array.isArray(this.editedVar.default_value)) this.editedVar.default_value = this.editedVar.default_value.length > 0 ? this.editedVar.default_value[0] : ''; + } + }, }, }; From 7bd642427f3712e766c6932feb9a7a426d117426 Mon Sep 17 00:00:00 2001 From: blackb1rd Date: Tue, 7 Oct 2025 16:47:57 +0700 Subject: [PATCH 05/29] fix: normalize default value handling in onTypeChange method for select type Signed-off-by: blackb1rd --- web/src/components/SurveyVars.vue | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/web/src/components/SurveyVars.vue b/web/src/components/SurveyVars.vue index 74dff30a1..cfb23fc1f 100644 --- a/web/src/components/SurveyVars.vue +++ b/web/src/components/SurveyVars.vue @@ -339,9 +339,14 @@ export default { onTypeChange(newType) { if (!this.editedVar) return; if (newType === 'select') { - if (!Array.isArray(this.editedVar.default_value)) this.editedVar.default_value = this.editedVar.default_value == null || this.editedVar.default_value === '' ? [] : [this.editedVar.default_value]; - } else { - if (Array.isArray(this.editedVar.default_value)) this.editedVar.default_value = this.editedVar.default_value.length > 0 ? this.editedVar.default_value[0] : ''; + if (!Array.isArray(this.editedVar.default_value)) { + const dv = this.editedVar.default_value; + this.editedVar.default_value = dv == null || dv === '' ? [] : [dv]; + } + } else if (Array.isArray(this.editedVar.default_value)) { + this.editedVar.default_value = this.editedVar.default_value.length > 0 + ? this.editedVar.default_value[0] + : ''; } }, }, From d4685bebaa12b5448c223a5c906fe560eba9a7e8 Mon Sep 17 00:00:00 2001 From: blackb1rd Date: Tue, 7 Oct 2025 16:55:21 +0700 Subject: [PATCH 06/29] fix: improve default value normalization for select type in SurveyVars component Signed-off-by: blackb1rd --- web/src/components/SurveyVars.vue | 34 +++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/web/src/components/SurveyVars.vue b/web/src/components/SurveyVars.vue index cfb23fc1f..3ed14d6b2 100644 --- a/web/src/components/SurveyVars.vue +++ b/web/src/components/SurveyVars.vue @@ -251,10 +251,16 @@ export default { // normalize default_value for select vs enum if (this.editedVar.type === 'select') { - if (this.editedVar.default_value == null) this.editedVar.default_value = []; - else if (!Array.isArray(this.editedVar.default_value)) this.editedVar.default_value = this.editedVar.default_value === '' ? [] : [this.editedVar.default_value]; - } else { - if (Array.isArray(this.editedVar.default_value)) this.editedVar.default_value = this.editedVar.default_value.length > 0 ? this.editedVar.default_value[0] : ''; + if (this.editedVar.default_value == null) { + this.editedVar.default_value = []; + } else if (!Array.isArray(this.editedVar.default_value)) { + const dv = this.editedVar.default_value; + this.editedVar.default_value = dv === '' ? [] : [dv]; + } + } else if (Array.isArray(this.editedVar.default_value)) { + this.editedVar.default_value = this.editedVar.default_value.length > 0 + ? this.editedVar.default_value[0] + : ''; } this.editedVarIndex = index; @@ -301,9 +307,14 @@ export default { // normalize default_value before saving: select keeps array, others use string if (this.editedVar.type === 'select') { - if (!Array.isArray(this.editedVar.default_value)) this.editedVar.default_value = this.editedVar.default_value == null || this.editedVar.default_value === '' ? [] : [this.editedVar.default_value]; - } else { - if (Array.isArray(this.editedVar.default_value)) this.editedVar.default_value = this.editedVar.default_value.length > 0 ? this.editedVar.default_value[0] : ''; + if (!Array.isArray(this.editedVar.default_value)) { + const dv = this.editedVar.default_value; + this.editedVar.default_value = dv == null || dv === '' ? [] : [dv]; + } + } else if (Array.isArray(this.editedVar.default_value)) { + this.editedVar.default_value = this.editedVar.default_value.length > 0 + ? this.editedVar.default_value[0] + : ''; } if (this.editedVarIndex != null) { @@ -324,8 +335,13 @@ export default { // remove one selected default item (for multiple select) removeDefaultItem(index) { - if (!this.editedVar || !Array.isArray(this.editedVar.default_value)) return; - if (index >= 0 && index < this.editedVar.default_value.length) this.editedVar.default_value.splice(index, 1); + if (!this.editedVar || !Array.isArray(this.editedVar.default_value)) { + return; + } + + if (index >= 0 && index < this.editedVar.default_value.length) { + this.editedVar.default_value.splice(index, 1); + } }, // label shown in selection chips - item could be the value or an object From 3022928cbff3565c0bfaa32589fa82fd1c499ed7 Mon Sep 17 00:00:00 2001 From: blackb1rd Date: Tue, 7 Oct 2025 18:27:02 +0700 Subject: [PATCH 07/29] feat: implement SurveyVarDefaultValue for flexible JSON handling in SurveyVar Signed-off-by: blackb1rd --- db/Template.go | 67 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 7 deletions(-) diff --git a/db/Template.go b/db/Template.go index adebd3bfd..783287e05 100644 --- a/db/Template.go +++ b/db/Template.go @@ -1,7 +1,9 @@ package db import ( + "bytes" "encoding/json" + "fmt" ) type TemplateType string @@ -65,6 +67,57 @@ const ( SurveyVarSelect TemplateType = "select" ) +// SurveyVarDefaultValue supports both a single string or an array of strings in JSON. +// It preserves whether the original JSON was an array so encoding will keep the +// original shape when possible (single value -> string, multiple -> array). +type SurveyVarDefaultValue struct { + Values []string `json:"-"` + originalWasArray bool `json:"-"` +} + +func (d *SurveyVarDefaultValue) UnmarshalJSON(b []byte) error { + if len(bytes.TrimSpace(b)) == 0 || bytes.Equal(bytes.TrimSpace(b), []byte("null")) { + d.Values = nil + d.originalWasArray = false + return nil + } + + // try string + var s string + if err := json.Unmarshal(b, &s); err == nil { + d.Values = []string{s} + d.originalWasArray = false + return nil + } + + // try []string + var arr []string + if err := json.Unmarshal(b, &arr); err == nil { + d.Values = arr + d.originalWasArray = true + return nil + } + + return fmt.Errorf("invalid default_value: must be string or []string") +} + +func (d SurveyVarDefaultValue) MarshalJSON() ([]byte, error) { + if d.Values == nil { + return []byte("null"), nil + } + if len(d.Values) == 1 && !d.originalWasArray { + return json.Marshal(d.Values[0]) + } + return json.Marshal(d.Values) +} + +func (d SurveyVarDefaultValue) String() string { + if len(d.Values) == 0 { + return "" + } + return d.Values[0] +} + type AnsibleTemplateParams struct { AllowDebug bool `json:"allow_debug"` AllowOverrideInventory bool `json:"allow_override_inventory"` @@ -90,13 +143,13 @@ type SurveyVarEnumValue struct { } type SurveyVar struct { - Name string `json:"name" backup:"name"` - Title string `json:"title" backup:"title"` - Required bool `json:"required,omitempty" backup:"required"` - Type SurveyVarType `json:"type,omitempty" backup:"type"` - Description string `json:"description,omitempty" backup:"description"` - Values []SurveyVarEnumValue `json:"values,omitempty" backup:"values"` - DefaultValue string `json:"default_value,omitempty" backup:"default_value"` + Name string `json:"name" backup:"name"` + Title string `json:"title" backup:"title"` + Required bool `json:"required,omitempty" backup:"required"` + Type SurveyVarType `json:"type,omitempty" backup:"type"` + Description string `json:"description,omitempty" backup:"description"` + Values []SurveyVarEnumValue `json:"values,omitempty" backup:"values"` + DefaultValue *SurveyVarDefaultValue `json:"default_value,omitempty" backup:"default_value"` } type TemplateFilter struct { From 8d72ee517d3b681e76a25f595db869a27f90038c Mon Sep 17 00:00:00 2001 From: blackb1rd Date: Fri, 10 Oct 2025 15:24:53 +0700 Subject: [PATCH 08/29] feat: enhance handling of select type variables in TaskParamsForm for improved default value normalization Signed-off-by: Prachya Saechua --- web/src/components/TaskParamsForm.vue | 230 +++++++++++++++----------- 1 file changed, 130 insertions(+), 100 deletions(-) diff --git a/web/src/components/TaskParamsForm.vue b/web/src/components/TaskParamsForm.vue index 973645f53..d8eec7bec 100644 --- a/web/src/components/TaskParamsForm.vue +++ b/web/src/components/TaskParamsForm.vue @@ -1,112 +1,107 @@ From 44f3e8e7cb0038df2ca990eb71e0b1ca3d66dcf0 Mon Sep 17 00:00:00 2001 From: blackb1rd Date: Fri, 10 Oct 2025 15:45:59 +0700 Subject: [PATCH 09/29] fix: ensure proper handling of select type variables in TaskParamsForm for default value normalization Signed-off-by: Prachya Saechua --- web/src/components/TaskParamsForm.vue | 33 ++++++++++++--------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/web/src/components/TaskParamsForm.vue b/web/src/components/TaskParamsForm.vue index d8eec7bec..bb703d6a8 100644 --- a/web/src/components/TaskParamsForm.vue +++ b/web/src/components/TaskParamsForm.vue @@ -255,8 +255,8 @@ export default { if (surveyVar.type === 'select' && this.editedEnvironment[surveyVar.name] !== undefined) { const currentValue = this.editedEnvironment[surveyVar.name]; if (!Array.isArray(currentValue)) { - this.editedEnvironment[surveyVar.name] = - currentValue == null || currentValue === '' ? [] : [currentValue]; + this.editedEnvironment[surveyVar.name] = currentValue == null || currentValue === '' + ? [] : [currentValue]; } } }); @@ -287,22 +287,22 @@ export default { [this.buildTasks, this.inventory] = await Promise.all([ this.template.type === 'deploy' ? ( - await axios({ - keys: 'get', - url: `/api/project/${this.projectId}/templates/${this.template.build_template_id}/tasks?status=success&limit=20`, - responseType: 'json', - }) - ).data.filter((task) => task.status === 'success') + await axios({ + keys: 'get', + url: `/api/project/${this.projectId}/templates/${this.template.build_template_id}/tasks?status=success&limit=20`, + responseType: 'json', + }) + ).data.filter((task) => task.status === 'success') : [], this.needInventory ? ( - await axios({ - keys: 'get', - url: this.getInventoryUrl(), - responseType: 'json', - }) - ).data + await axios({ + keys: 'get', + url: this.getInventoryUrl(), + responseType: 'json', + }) + ).data : [], ]); @@ -322,10 +322,7 @@ export default { {}, ); - this.editedEnvironment = { - ...defaultVars, - ...this.editedEnvironment, - }; + this.editedEnvironment = { ...defaultVars, ...this.editedEnvironment }; // Ensure select type variables without values are initialized as empty arrays (this.template.survey_vars || []).forEach((surveyVar) => { From 7a6757dc4b1fe5f8316b571b4ab92aef2724c1b3 Mon Sep 17 00:00:00 2001 From: blackb1rd Date: Sat, 11 Oct 2025 20:56:00 +0700 Subject: [PATCH 10/29] Update web/src/components/TaskParamsForm.vue Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- web/src/components/TaskParamsForm.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/src/components/TaskParamsForm.vue b/web/src/components/TaskParamsForm.vue index bb703d6a8..5b49c5a1d 100644 --- a/web/src/components/TaskParamsForm.vue +++ b/web/src/components/TaskParamsForm.vue @@ -346,10 +346,12 @@ export default { }, removeSelectedItem(varName, index) { - if (!this.editedEnvironment[varName] || !Array.isArray(this.editedEnvironment[varName])) { + if (!Object.prototype.hasOwnProperty.call(this.editedEnvironment, varName) || !Array.isArray(this.editedEnvironment[varName])) { + // Optionally, log a warning for debugging: + // console.warn(`removeSelectedItem: '${varName}' is not a valid array key in editedEnvironment.`); return; } - if (index >= 0 && index < this.editedEnvironment[varName].length) { + if (index < this.editedEnvironment[varName].length) { this.editedEnvironment[varName].splice(index, 1); } }, From 456466dd71865bd7c84dfd7c4cb561c3405f8584 Mon Sep 17 00:00:00 2001 From: blackb1rd Date: Sat, 11 Oct 2025 20:56:37 +0700 Subject: [PATCH 11/29] Update web/src/components/TaskForm.vue Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- web/src/components/TaskForm.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/src/components/TaskForm.vue b/web/src/components/TaskForm.vue index 75e49ac49..7ff41e8c1 100644 --- a/web/src/components/TaskForm.vue +++ b/web/src/components/TaskForm.vue @@ -302,7 +302,9 @@ export default { }, deleteItem(name, index) { - this.editedEnvironment[name].splice(index, 1); + if (Array.isArray(this.editedEnvironment?.[name])) { + this.editedEnvironment[name].splice(index, 1); + } }, getTaskMessage(task) { From 273397becc9049b2a7f4f62ee22ddbf4029e7c27 Mon Sep 17 00:00:00 2001 From: blackb1rd Date: Sat, 11 Oct 2025 20:56:45 +0700 Subject: [PATCH 12/29] Update web/src/components/TaskParamsForm.vue Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- web/src/components/TaskParamsForm.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/components/TaskParamsForm.vue b/web/src/components/TaskParamsForm.vue index 5b49c5a1d..d31f02e7a 100644 --- a/web/src/components/TaskParamsForm.vue +++ b/web/src/components/TaskParamsForm.vue @@ -31,8 +31,8 @@ outlined dense > - From 451a7241772774dfee1a5fd8f2c4dc3adc03833d Mon Sep 17 00:00:00 2001 From: blackb1rd Date: Sat, 11 Oct 2025 20:56:55 +0700 Subject: [PATCH 13/29] Update web/src/components/TaskForm.vue Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- web/src/components/TaskForm.vue | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/web/src/components/TaskForm.vue b/web/src/components/TaskForm.vue index 7ff41e8c1..dedc1e5a4 100644 --- a/web/src/components/TaskForm.vue +++ b/web/src/components/TaskForm.vue @@ -96,14 +96,16 @@ :multiple="v.type === 'select'" :chips="v.type === 'select'" > -