Skip to content

Commit

Permalink
Merge pull request #554 from labd/541-allow-usage-of-custom-for-resou…
Browse files Browse the repository at this point in the history
…rce-business_unit_company-and-business_unit_division

feat: added custom fields for all new resources
  • Loading branch information
demeyerthom authored Jan 10, 2025
2 parents e669a42 + 534d9fc commit 97d93d2
Show file tree
Hide file tree
Showing 31 changed files with 1,201 additions and 121 deletions.
3 changes: 3 additions & 0 deletions .changes/unreleased/Added-20241231-120809.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Added
body: Added generic custom field support to all newer resources
time: 2024-12-31T12:08:09.386099205+01:00
66 changes: 38 additions & 28 deletions commercetools/custom_fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func CreateCustomFieldDraft(ctx context.Context, client *platform.ByProjectKeyRe
return nil, err
}

t, err := getTypeResource(ctx, client, d)
t, err := getTypeResourceFromResourceData(ctx, client, d)
if err != nil {
return nil, err
}
Expand All @@ -58,7 +58,10 @@ type SetCustomTypeAction interface {
platform.ShippingMethodSetCustomTypeAction |
platform.CustomerGroupSetCustomTypeAction |
platform.DiscountCodeSetCustomTypeAction |
platform.CartDiscountSetCustomTypeAction
platform.CartDiscountSetCustomTypeAction |
platform.AssociateRoleSetCustomTypeAction |
platform.ProductSelectionSetCustomTypeAction |
platform.BusinessUnitSetCustomTypeAction
}

type SetCustomFieldAction interface {
Expand All @@ -68,10 +71,13 @@ type SetCustomFieldAction interface {
platform.ShippingMethodSetCustomFieldAction |
platform.CustomerGroupSetCustomFieldAction |
platform.DiscountCodeSetCustomFieldAction |
platform.CartDiscountSetCustomFieldAction
platform.CartDiscountSetCustomFieldAction |
platform.AssociateRoleSetCustomFieldAction |
platform.ProductSelectionSetCustomFieldAction |
platform.BusinessUnitSetCustomFieldAction
}

func customFieldEncodeType(t *platform.Type, name string, value any) (any, error) {
func CustomFieldEncodeType(t *platform.Type, name string, value any) (any, error) {
// Suboptimal to do this everytime, however performance is not that important here and impact is negligible
fieldTypes := map[string]platform.FieldType{}
for _, field := range t.FieldDefinitions {
Expand All @@ -82,10 +88,10 @@ func customFieldEncodeType(t *platform.Type, name string, value any) (any, error
if !ok {
return nil, fmt.Errorf("no field '%s' defined in type %s (%s)", name, t.Key, t.ID)
}
return customFieldEncodeValue(fieldType, name, value)
return CustomFieldEncodeValue(fieldType, name, value)
}

func customFieldEncodeValue(t platform.FieldType, name string, value any) (any, error) {
func CustomFieldEncodeValue(t platform.FieldType, name string, value any) (any, error) {
switch v := t.(type) {
case platform.CustomFieldLocalizedStringType:
result := platform.LocalizedString{}
Expand Down Expand Up @@ -129,7 +135,7 @@ func customFieldEncodeValue(t platform.FieldType, name string, value any) (any,
}
element = string(marshalledValue)
}
itemValue, err := customFieldEncodeValue(v.ElementType, name, element)
itemValue, err := CustomFieldEncodeValue(v.ElementType, name, element)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -198,7 +204,7 @@ func CreateCustomFieldDraftRaw(data map[string]any, t *platform.Type) (*platform
if raw, ok := data["fields"].(map[string]any); ok {
container := platform.FieldContainer{}
for key, value := range raw {
enc, err := customFieldEncodeType(t, key, value)
enc, err := CustomFieldEncodeType(t, key, value)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -234,36 +240,40 @@ func flattenCustomFields(c *platform.CustomFields) []map[string]any {
return []map[string]any{result}
}

// getTypeResource returns the platform.Type for the type_id in the custom
// field. The type_id is cached to minimize API calls when multiple resource
// use the same type
func getTypeResource(ctx context.Context, client *platform.ByProjectKeyRequestBuilder, d *schema.ResourceData) (*platform.Type, error) {
func getTypeResourceFromResourceData(ctx context.Context, client *platform.ByProjectKeyRequestBuilder, d *schema.ResourceData) (*platform.Type, error) {
custom := d.Get("custom")
data := firstElementFromSlice(custom.([]any))
if data == nil {
return nil, nil
}

if typeId, ok := data["type_id"].(string); ok {
if cacheTypes == nil {
cacheTypes = make(map[string]*platform.Type)
}
if t, exists := cacheTypes[typeId]; exists {
if t == nil {
return nil, fmt.Errorf("type %s not in cache due to previous error", typeId)
}
return t, nil
}

t, err := client.Types().WithId(typeId).Get().Execute(ctx)
cacheTypes[typeId] = t
return t, err
return GetTypeResource(ctx, client, typeId)
}
return nil, fmt.Errorf("missing type_id for custom fields")
}

// GetTypeResource returns the platform.Type for the type_id in the custom
// field. The type_id is cached to minimize API calls when multiple resource
// use the same type
func GetTypeResource(ctx context.Context, client *platform.ByProjectKeyRequestBuilder, typeId string) (*platform.Type, error) {
if cacheTypes == nil {
cacheTypes = make(map[string]*platform.Type)
}
if t, exists := cacheTypes[typeId]; exists {
if t == nil {
return nil, fmt.Errorf("type %s not in cache due to previous error", typeId)
}
return t, nil
}

t, err := client.Types().WithId(typeId).Get().Execute(ctx)
cacheTypes[typeId] = t
return t, err
}

func CustomFieldUpdateActions[T SetCustomTypeAction, F SetCustomFieldAction](ctx context.Context, client *platform.ByProjectKeyRequestBuilder, d *schema.ResourceData) ([]any, error) {
t, err := getTypeResource(ctx, client, d)
t, err := getTypeResourceFromResourceData(ctx, client, d)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -294,7 +304,7 @@ func CustomFieldUpdateActions[T SetCustomTypeAction, F SetCustomFieldAction](ctx
return []any{action}, nil
}

changes := diffSlices(
changes := DiffSlices(
oldData["fields"].(map[string]any),
newData["fields"].(map[string]any))

Expand All @@ -306,7 +316,7 @@ func CustomFieldUpdateActions[T SetCustomTypeAction, F SetCustomFieldAction](ctx
Value: nil,
})
} else {
val, err := customFieldEncodeType(t, key, changes[key])
val, err := CustomFieldEncodeType(t, key, changes[key])
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion commercetools/custom_fields_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ var customFieldEncodeValueTests = []struct {
func TestCustomFieldEncodeValue(t *testing.T) {
for _, tt := range customFieldEncodeValueTests {
t.Run("TestCustomFieldEncodeValue", func(t *testing.T) {
encodedValue, err := customFieldEncodeValue(tt.typ, "some_field", tt.value)
encodedValue, err := CustomFieldEncodeValue(tt.typ, "some_field", tt.value)
if tt.hasError {
assert.Error(t, err)
} else {
Expand Down
4 changes: 2 additions & 2 deletions commercetools/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,9 +380,9 @@ func removeValueFromSlice(items []string, value string) []string {
return items
}

// diffSlices does a diff on two slices and returns the changes. If a field is
// DiffSlices does a diff on two slices and returns the changes. If a field is
// no longer available then nil is returned.
func diffSlices(old, new map[string]any) map[string]any {
func DiffSlices(old, new map[string]any) map[string]any {
result := map[string]any{}
seen := map[string]bool{}

Expand Down
43 changes: 40 additions & 3 deletions docs/resources/associate_role.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,31 @@ See also the [Associate Role API Documentation](https://docs.commercetools.com/a
## Example Usage

```terraform
resource "commercetools_associate_role" "regional_manager" {
key = "regional-manager-europe"
resource "commercetools_type" "my-type" {
key = "my-type"
name = {
en = "My type"
nl = "Mijn type"
}
resource_type_ids = ["associate-role"]
field {
name = "my-field"
label = {
en = "My field"
nl = "Mijn veld"
}
type {
name = "String"
}
}
}
resource "commercetools_associate_role" "my-role" {
key = "my-role"
buyer_assignable = false
name = "Regional Manager - Europe"
name = "My Role"
permissions = [
"AddChildUnits",
"UpdateAssociates",
Expand Down Expand Up @@ -61,6 +82,13 @@ resource "commercetools_associate_role" "regional_manager" {
"UpdateApprovalRules",
"UpdateApprovalFlows",
]
custom {
type_id = commercetools_type.my-type.id
fields = {
my_field = "My value"
}
}
}
```

Expand All @@ -75,9 +103,18 @@ resource "commercetools_associate_role" "regional_manager" {
### Optional

- `buyer_assignable` (Boolean) Whether the associate role can be assigned to an associate by a buyer. If false, the associate role can only be assigned using the general endpoint. Defaults to true.
- `custom` (Block, Optional) Custom fields for this resource. (see [below for nested schema](#nestedblock--custom))
- `name` (String) Name of the associate role.

### Read-Only

- `id` (String) Unique identifier of the associate role.
- `version` (Number) Current version of the associate role.

<a id="nestedblock--custom"></a>
### Nested Schema for `custom`

Optional:

- `fields` (Map of String) CustomValue fields for this resource. Note that the values need to be provided as JSON encoded strings: `my-value = jsonencode({"key": "value"})`
- `type_id` (String) The ID of the custom type to use for this resource.
38 changes: 38 additions & 0 deletions docs/resources/business_unit_company.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,27 @@ resource "commercetools_store" "my-store" {
languages = ["en-GB"]
}
resource "commercetools_type" "my-type" {
key = "my-type"
name = {
en = "My type"
nl = "Mijn type"
}
resource_type_ids = ["business-unit"]
field {
name = "my-field"
label = {
en = "My field"
nl = "Mijn veld"
}
type {
name = "String"
}
}
}
resource "commercetools_business_unit_company" "my-company" {
key = "my-company"
name = "My company"
Expand Down Expand Up @@ -60,6 +81,13 @@ resource "commercetools_business_unit_company" "my-company" {
shipping_address_keys = ["my-company-address-1", "my-company-address-2"]
default_billing_address_key = "my-company-address-1"
default_shipping_address_key = "my-company-address-1"
custom {
type_id = commercetools_type.my-type.id
fields = {
my_field = "My value"
}
}
}
```

Expand All @@ -76,6 +104,7 @@ resource "commercetools_business_unit_company" "my-company" {
- `address` (Block List) Addresses used by the Business Unit. (see [below for nested schema](#nestedblock--address))
- `billing_address_keys` (Set of String) Indexes of entries in addresses to set as billing addresses. The billingAddressIds of the [Customer](https://docs.commercetools.com/api/projects/customers) will be replaced by these addresses.
- `contact_email` (String) The email address of the company.
- `custom` (Block, Optional) Custom fields for this resource. (see [below for nested schema](#nestedblock--custom))
- `default_billing_address_key` (String) Index of the entry in addresses to set as the default billing address.
- `default_shipping_address_key` (String) Index of the entry in addresses to set as the default shipping address.
- `shipping_address_keys` (Set of String) Indexes of entries in addresses to set as shipping addresses. The shippingAddressIds of the [Customer](https://docs.commercetools.com/api/projects/customers) will be replaced by these addresses.
Expand Down Expand Up @@ -129,6 +158,15 @@ Read-Only:
- `id` (String) Unique identifier of the Address


<a id="nestedblock--custom"></a>
### Nested Schema for `custom`

Optional:

- `fields` (Map of String) CustomValue fields for this resource. Note that the values need to be provided as JSON encoded strings: `my-value = jsonencode({"key": "value"})`
- `type_id` (String) The ID of the custom type to use for this resource.


<a id="nestedblock--store"></a>
### Nested Schema for `store`

Expand Down
38 changes: 38 additions & 0 deletions docs/resources/business_unit_division.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,27 @@ resource "commercetools_store" "my-store" {
languages = ["en-GB"]
}
resource "commercetools_type" "my-type" {
key = "my-type"
name = {
en = "My type"
nl = "Mijn type"
}
resource_type_ids = ["business-unit"]
field {
name = "my-field"
label = {
en = "My field"
nl = "Mijn veld"
}
type {
name = "String"
}
}
}
resource "commercetools_business_unit_company" "my-company" {
key = "my-company"
name = "My company"
Expand Down Expand Up @@ -72,6 +93,13 @@ resource "commercetools_business_unit_division" "my-division" {
shipping_address_keys = ["my-div-address-1", "my-div-address-2"]
default_billing_address_key = "my-div-address-1"
default_shipping_address_key = "my-div-address-1"
custom {
type_id = commercetools_type.my-type.id
fields = {
my_field = "My value"
}
}
}
```

Expand All @@ -90,6 +118,7 @@ resource "commercetools_business_unit_division" "my-division" {
- `associate_mode` (String) Determines whether the business unit can inherit Associates from a parent. Defaults to `ExplicitAndFromParent`.
- `billing_address_keys` (List of String) List of the billing addresses used by the division.
- `contact_email` (String) The email address of the division.
- `custom` (Block, Optional) Custom fields for this resource. (see [below for nested schema](#nestedblock--custom))
- `default_billing_address_key` (String) Key of the default billing Address.
- `default_shipping_address_key` (String) Key of the default shipping Address.
- `parent_unit` (Block, Optional) Reference to a parent business unit by its key or id. One of either is required. (see [below for nested schema](#nestedblock--parent_unit))
Expand Down Expand Up @@ -145,6 +174,15 @@ Read-Only:
- `id` (String) Unique identifier of the Address


<a id="nestedblock--custom"></a>
### Nested Schema for `custom`

Optional:

- `fields` (Map of String) CustomValue fields for this resource. Note that the values need to be provided as JSON encoded strings: `my-value = jsonencode({"key": "value"})`
- `type_id` (String) The ID of the custom type to use for this resource.


<a id="nestedblock--parent_unit"></a>
### Nested Schema for `parent_unit`

Expand Down
Loading

0 comments on commit 97d93d2

Please sign in to comment.