diff --git a/examples/resources/storyblok_component/resource.tf b/examples/resources/storyblok_component/resource.tf index 183c3b4..ee30012 100644 --- a/examples/resources/storyblok_component/resource.tf +++ b/examples/resources/storyblok_component/resource.tf @@ -24,62 +24,126 @@ resource "storyblok_component" "banner" { // advanced example resource "storyblok_component" "advanced_component" { - name = "advanced-component" - space_id = "" - is_root = true + name = "advanced-component" + space_id = "" + is_root = true is_nestable = false schema = { title = { - type = "text" - position = 1 - required = true // The field is required. Default is false. - max_length = 200 // Set the max length of the input string + type = "text" + position = 1 + required = true // The field is required. Default is false. + max_length = 200 // Set the max length of the input string description = "Title of the component" // Description shown in the editor interface } introduction = { - type = "rich_text" - position = 2 + type = "rich_text" + position = 2 rich_markdown = true // Enable rich markdown view by default - description = "Introduction text with rich text editor" + description = "Introduction text with rich text editor" } image = { - type = "image" - position = 3 - asset_folder_id = 1 // Default asset folder numeric id to store uploaded image of that field - add_https = true // Prepends https: to stop usage of relative protocol - image_crop = true // Activate force crop for images + type = "image" + position = 3 + asset_folder_id = 1 // Default asset folder numeric id to store uploaded image of that field + add_https = true // Prepends https: to stop usage of relative protocol + image_crop = true // Activate force crop for images } release_date = { - type = "date" - position = 4 + type = "date" + position = 4 disable_time = true // Disables time selection from date picker - description = "Release date of the content" + description = "Release date of the content" } tags = { - type = "multi_option" - position = 5 + type = "multi_option" + position = 5 datasource_slug = "tags" // Define selectable datasources string - description = "Tags for the component" + description = "Tags for the component" } rating = { - type = "number" - position = 6 - description = "Rating of the content" + type = "number" + position = 6 + description = "Rating of the content" default_value = "3" // Default value for the field } content = { - type = "bloks" - position = 7 + type = "bloks" + position = 7 component_whitelist = ["text", "image", "video"] // Array of component/content type names - maximum = 10 // Maximum amount of added bloks in this blok field - description = "Content blocks" + maximum = 10 // Maximum amount of added bloks in this blok field + description = "Content blocks" } } -} \ No newline at end of file +} + +// conditional content +resource "storyblok_component" "conditional_settings_new" { + name = "conditional settings component" + space_id = "" + is_root = false + is_nestable = true + + + schema = { + content = { + position = 0 + translatable = true + display_name = "Content" + required = true + type = "text" + } + + more_content = { + position = 1 + translatable = true + display_name = "more content" + required = true + type = "text" + } + + conditionalContent = { + position = 2 + display_name = "conditinal content" + required = true + type = "text" + + conditional_settings = [ + { + modifications = [ + { + required = false + } + ] + + // make "conditional content" optional of either: + // 1. content is empty + // 2. more content equals "test" + rule_match = "any" + rule_conditions = [ + { + validation = "empty" + validated_object = { + field_key = "content" + } + }, + { + value = "test" + validation = "equals" + validated_object = { + field_key = "more_content" + } + } + ] + } + ] + } + } +} diff --git a/internal/component_model.go b/internal/component_model.go index 3f994a0..cf2d472 100644 --- a/internal/component_model.go +++ b/internal/component_model.go @@ -83,9 +83,13 @@ type conditionalSettingsModel struct { } type ruleConditionModel struct { - Validation types.String `tfsdk:"validation"` - Value types.String `tfsdk:"value"` - FieldKey types.String `tfsdk:"field_key"` + Validation types.String `tfsdk:"validation"` + Value types.String `tfsdk:"value"` + ValidatedObject validatedObjectModel `tfsdk:"validated_object"` +} + +type validatedObjectModel struct { + FieldKey types.String `tfsdk:"field_key"` } type modificationModel struct { @@ -169,7 +173,7 @@ func toFieldInput(item fieldModel) sbmgmt.FieldInput { AssetFolderId: item.AssetFolderId.ValueInt64Pointer(), CanSync: item.CanSync.ValueBoolPointer(), ComponentWhitelist: utils.ConvertToPointerStringSlice(item.ComponentWhitelist), - ConditionalSettings: deserializeConditionalSettingsModel(item.ConditionalSettings), + ConditionalSettings: deserializeConditionalSettings(item.ConditionalSettings), CustomizeToolbar: item.CustomizeToolbar.ValueBoolPointer(), DatasourceSlug: item.DatasourceSlug.ValueStringPointer(), DefaultValue: item.DefaultValue.ValueStringPointer(), @@ -285,6 +289,7 @@ func toFieldModel(field sbmgmt.FieldInput) fieldModel { Tooltip: types.BoolPointerValue(field.Tooltip), Translatable: types.BoolPointerValue(field.Translatable), UseUuid: types.BoolPointerValue(field.UseUuid), + ConditionalSettings: serializeConditionalSettings(field.ConditionalSettings), } } @@ -388,7 +393,62 @@ func deserializeOptionsModel(options []optionModel) *[]sbmgmt.FieldOption { return &optionModels } -func deserializeConditionalSettingsModel(conditionalSettings []conditionalSettingsModel) *[]sbmgmt.ConditionalSettings { +func serializeConditionalSettings(conditionalSettings *[]sbmgmt.ConditionalSettings) []conditionalSettingsModel { + if conditionalSettings == nil { + return nil + } + + serializedConditionalSettings := make([]conditionalSettingsModel, len(*conditionalSettings)) + + for i, conditionalSetting := range *conditionalSettings { + serializedConditionalSettings[i] = conditionalSettingsModel{ + RuleMatch: types.StringPointerValue((*string)(conditionalSetting.RuleMatch)), + Modifications: serializeModifications(conditionalSetting.Modifications), + RuleConditions: serializeRuleConditions(conditionalSetting.RuleConditions), + } + } + + return serializedConditionalSettings +} + +func serializeModifications(modifications *[]sbmgmt.Modification) []modificationModel { + if modifications == nil { + return nil + } + + serializedModifications := make([]modificationModel, len(*modifications)) + + for i, modification := range *modifications { + serializedModifications[i] = modificationModel{ + Required: types.BoolPointerValue(modification.Required), + Display: types.StringPointerValue((*string)(modification.Display)), + } + } + + return serializedModifications +} +func serializeRuleConditions(ruleConditions *[]sbmgmt.RuleCondition) []ruleConditionModel { + + if ruleConditions == nil { + return nil + } + + serializedRuleConditions := make([]ruleConditionModel, len(*ruleConditions)) + + for i, ruleCondition := range *ruleConditions { + serializedRuleConditions[i] = ruleConditionModel{ + Value: types.StringPointerValue(ruleCondition.Value), + Validation: types.StringPointerValue((*string)(ruleCondition.Validation)), + ValidatedObject: validatedObjectModel{ + FieldKey: types.StringPointerValue(ruleCondition.ValidatedObject.FieldKey), + }, + } + } + + return serializedRuleConditions +} + +func deserializeConditionalSettings(conditionalSettings []conditionalSettingsModel) *[]sbmgmt.ConditionalSettings { if conditionalSettings == nil { return nil } @@ -411,14 +471,9 @@ func deserializeConditionalSettingsModificationsModel(conditionalSettingsModific deserializedModifications := make([]sbmgmt.Modification, len(conditionalSettingsModifications)) for i, modification := range conditionalSettingsModifications { - if !modification.Display.IsNull() { - deserializedModifications[i] = sbmgmt.Modification{ - Display: (*sbmgmt.ModificationDisplay)(modification.Display.ValueStringPointer()), - } - } else { - deserializedModifications[i] = sbmgmt.Modification{ - Required: modification.Required.ValueBoolPointer(), - } + deserializedModifications[i] = sbmgmt.Modification{ + Required: modification.Required.ValueBoolPointer(), + Display: (*sbmgmt.ModificationDisplay)(modification.Display.ValueStringPointer()), } } @@ -434,9 +489,9 @@ func deserializeRuleConditions(ruleConditions []ruleConditionModel) *[]sbmgmt.Ru for i, ruleCondition := range ruleConditions { deserializedRuleConditions[i] = sbmgmt.RuleCondition{ Validation: (*sbmgmt.RuleConditionValidation)(ruleCondition.Validation.ValueStringPointer()), - Value: ruleCondition.Validation.ValueStringPointer(), + Value: ruleCondition.Value.ValueStringPointer(), ValidatedObject: &sbmgmt.ValidatedObject{ - FieldKey: ruleCondition.FieldKey.ValueStringPointer(), + FieldKey: ruleCondition.ValidatedObject.FieldKey.ValueStringPointer(), FieldAttr: &validatedObjectFieldAttrValue, Type: &validatedValidatedObjectType, }, diff --git a/internal/component_resource.go b/internal/component_resource.go index 2b4403f..4722d6e 100644 --- a/internal/component_resource.go +++ b/internal/component_resource.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" @@ -174,7 +175,7 @@ func (r *componentResource) Schema(_ context.Context, _ resource.SchemaRequest, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "modifications": schema.ListNestedAttribute{ - Optional: false, + Required: true, Description: "List of modifications to be applied to the field. Only 1 modification can be applied at a time (display OR required)", NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ @@ -192,24 +193,30 @@ func (r *componentResource) Schema(_ context.Context, _ resource.SchemaRequest, }, "rule_match": schema.StringAttribute{ Description: "Define if all or any of the conditions should be met to apply the modifications", - Optional: false, + Required: true, Validators: []validator.String{stringvalidator.OneOf("any", "all")}, }, "rule_conditions": schema.ListNestedAttribute{ Description: "Conditional rules to be applied to the target field", - Optional: false, + Required: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "validation": schema.StringAttribute{ - Optional: false, + Required: true, Validators: []validator.String{stringvalidator.OneOf("empty", "not_empty", "equals", "not_equals")}, }, "value": schema.StringAttribute{ Optional: true, - Default: nil, + Computed: true, + Default: stringdefault.StaticString("empty"), }, - "field_key": schema.StringAttribute{ - Optional: false, + "validated_object": schema.SingleNestedAttribute{ + Required: true, + Attributes: map[string]schema.Attribute{ + "field_key": schema.StringAttribute{ + Required: true, + }, + }, }, }, },