-
Notifications
You must be signed in to change notification settings - Fork 117
[DO NOT MERGE] Alternative fix for timezone issue [Issue 2094] #2180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,9 @@ package outbound_contact_list | |
| // @description: Manages outbound campaign operations including automated voice dialing, SMS/email messaging campaigns, contact list management, and campaign rules for proactive customer outreach. | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
|
|
||
| "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/provider" | ||
| resourceExporter "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/resource_exporter" | ||
| "github.com/mypurecloud/terraform-provider-genesyscloud/genesyscloud/validators" | ||
|
|
@@ -14,6 +17,84 @@ import ( | |
| "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" | ||
| ) | ||
|
|
||
| func normalizeOutboundContactListTimeColumnFields(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { | ||
| // Normalize plan-time values so the deprecated `*_time_column` and new `*_time_column_name` | ||
| // attributes don't cause TypeSet element mismatches/diffs during migration. | ||
| if v := diff.Get("phone_columns"); v != nil { | ||
| if s, ok := v.(*schema.Set); ok && s.Len() > 0 { | ||
| newSet := schema.NewSet(hashOutboundContactListPhoneColumn, []interface{}{}) | ||
| for _, item := range s.List() { | ||
| m, ok := item.(map[string]interface{}) | ||
| if !ok { | ||
| continue | ||
| } | ||
| if newName, _ := m["callable_time_column_name"].(string); newName == "" { | ||
| if oldName, _ := m["callable_time_column"].(string); oldName != "" { | ||
| m["callable_time_column_name"] = oldName | ||
| } | ||
| } | ||
| newSet.Add(m) | ||
| } | ||
| _ = diff.SetNew("phone_columns", newSet) | ||
| } | ||
| } | ||
|
|
||
| if v := diff.Get("email_columns"); v != nil { | ||
| if s, ok := v.(*schema.Set); ok && s.Len() > 0 { | ||
| newSet := schema.NewSet(hashOutboundContactListEmailColumn, []interface{}{}) | ||
| for _, item := range s.List() { | ||
| m, ok := item.(map[string]interface{}) | ||
| if !ok { | ||
| continue | ||
| } | ||
| if newName, _ := m["contactable_time_column_name"].(string); newName == "" { | ||
| if oldName, _ := m["contactable_time_column"].(string); oldName != "" { | ||
| m["contactable_time_column_name"] = oldName | ||
| } | ||
| } | ||
| newSet.Add(m) | ||
| } | ||
| _ = diff.SetNew("email_columns", newSet) | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func hashOutboundContactListPhoneColumn(v interface{}) int { | ||
| m, ok := v.(map[string]interface{}) | ||
| if !ok { | ||
| return 0 | ||
| } | ||
|
|
||
| columnName, _ := m["column_name"].(string) | ||
| colType, _ := m["type"].(string) | ||
|
|
||
| timeColName, _ := m["callable_time_column_name"].(string) | ||
| if timeColName == "" { | ||
| timeColName, _ = m["callable_time_column"].(string) | ||
| } | ||
|
|
||
| return schema.HashString(fmt.Sprintf("%s|%s|%s", columnName, colType, timeColName)) | ||
| } | ||
|
|
||
| func hashOutboundContactListEmailColumn(v interface{}) int { | ||
| m, ok := v.(map[string]interface{}) | ||
| if !ok { | ||
| return 0 | ||
| } | ||
|
|
||
| columnName, _ := m["column_name"].(string) | ||
| colType, _ := m["type"].(string) | ||
|
|
||
| timeColName, _ := m["contactable_time_column_name"].(string) | ||
| if timeColName == "" { | ||
| timeColName, _ = m["contactable_time_column"].(string) | ||
| } | ||
|
|
||
| return schema.HashString(fmt.Sprintf("%s|%s|%s", columnName, colType, timeColName)) | ||
| } | ||
|
|
||
| /* | ||
| resource_genesycloud_outbound_contact_list_schema.go holds three functions within it: | ||
|
|
||
|
|
@@ -36,9 +117,24 @@ var ( | |
| Type: schema.TypeString, | ||
| }, | ||
| `callable_time_column`: { | ||
| Description: `A column that indicates the timezone to use for a given contact when checking callable times. Not allowed if 'automaticTimeZoneMapping' is set to true.`, | ||
| Optional: true, | ||
| Type: schema.TypeString, | ||
| Description: `A column that indicates the timezone to use for a given contact when checking callable times. Not allowed if 'automaticTimeZoneMapping' is set to true.`, | ||
| Deprecated: "Use `callable_time_column_name` instead.", | ||
| Optional: true, | ||
| Type: schema.TypeString, | ||
| DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { | ||
| // When automatic timezone mapping is enabled, the API may drop callable time columns. | ||
| // Suppress diffs to prevent perpetual drift. | ||
| return d.Get("automatic_time_zone_mapping").(bool) | ||
| }, | ||
| }, | ||
| `callable_time_column_name`: { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, I don't know if its absolutely necessary to add a new field to the schema. Basically, there are two approaches you can take:
You should check in with your team leads on which approach is desired.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After discussing with Hemanth we determined that Approach 1 was desired: I've added the new _time_column_name fields, deprecated the old _time_column fields, and provided a migration path via StateUpgraders using the routing_queue resource as reference.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes this wont break anything for the end user immediately, (thanks to the deprecation) and we can remove this in the later releases. api fields 1:1 aligning with the terraform schema will make sure of consistency in the long run. |
||
| Description: `A column name that indicates the timezone to use for a given contact when checking callable times.`, | ||
| Optional: true, | ||
| Computed: true, | ||
| Type: schema.TypeString, | ||
| DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { | ||
| return d.Get("automatic_time_zone_mapping").(bool) | ||
| }, | ||
| }, | ||
| }, | ||
| } | ||
|
|
@@ -56,9 +152,16 @@ var ( | |
| Type: schema.TypeString, | ||
| }, | ||
| `contactable_time_column`: { | ||
| Description: `A column that indicates the timezone to use for a given contact when checking contactable times.`, | ||
| Optional: true, | ||
| Type: schema.TypeString, | ||
| Description: `A column that indicates the timezone to use for a given contact when checking contactable times.`, | ||
| Deprecated: "Use `contactable_time_column_name` instead.", | ||
| Optional: true, | ||
| Type: schema.TypeString, | ||
| }, | ||
| `contactable_time_column_name`: { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above comment for this field. |
||
| Description: `A column name that indicates the timezone to use for a given contact when checking contactable times.`, | ||
| Optional: true, | ||
| Computed: true, | ||
| Type: schema.TypeString, | ||
| }, | ||
| }, | ||
| } | ||
|
|
@@ -107,10 +210,18 @@ func ResourceOutboundContactList() *schema.Resource { | |
| Importer: &schema.ResourceImporter{ | ||
| StateContext: schema.ImportStatePassthroughContext, | ||
| }, | ||
| SchemaVersion: 2, | ||
| SchemaVersion: 3, | ||
| StateUpgraders: []schema.StateUpgrader{ | ||
| { | ||
| Version: 2, | ||
| Type: resourceOutboundContactListV2().CoreConfigSchema().ImpliedType(), | ||
| Upgrade: stateUpgraderOutboundContactListV2ToV3, | ||
| }, | ||
| }, | ||
| CustomizeDiff: customdiff.All( | ||
| customdiff.ComputedIf("contacts_file_content_hash", validators.ValidateFileContentHashChanged("contacts_filepath", "contacts_file_content_hash", S3Enabled)), | ||
| validators.ValidateCSVWithColumns("contacts_filepath", "column_names"), | ||
| normalizeOutboundContactListTimeColumnFields, | ||
| ), | ||
| Schema: map[string]*schema.Schema{ | ||
| `name`: { | ||
|
|
@@ -136,13 +247,15 @@ func ResourceOutboundContactList() *schema.Resource { | |
| Optional: true, | ||
| ForceNew: true, | ||
| Type: schema.TypeSet, | ||
| Set: hashOutboundContactListPhoneColumn, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you hash these values, how is the provider supposed to read the values back?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed set behavior so hashing is only for stable TypeSet identity, and ensured flatten/build keep state consistent:
|
||
| Elem: outboundContactListContactPhoneNumberColumnResource, | ||
| }, | ||
| `email_columns`: { | ||
| Description: `Indicates which columns are email addresses. Changing the email_columns attribute will cause the outbound_contact_list object to be dropped and recreated with a new ID. Required if phone_columns is empty`, | ||
| Optional: true, | ||
| ForceNew: true, | ||
| Type: schema.TypeSet, | ||
| Set: hashOutboundContactListEmailColumn, | ||
| Elem: outboundContactListEmailColumnResource, | ||
| }, | ||
| `preview_mode_column_name`: { | ||
|
|
@@ -251,3 +364,158 @@ func DataSourceOutboundContactList() *schema.Resource { | |
| }, | ||
| } | ||
| } | ||
|
|
||
| func resourceOutboundContactListV2() *schema.Resource { | ||
| outboundContactListContactPhoneNumberColumnResourceV2 := &schema.Resource{ | ||
| Schema: map[string]*schema.Schema{ | ||
| `column_name`: { | ||
| Required: true, | ||
| Type: schema.TypeString, | ||
| }, | ||
| `type`: { | ||
| Required: true, | ||
| Type: schema.TypeString, | ||
| }, | ||
| `callable_time_column`: { | ||
| Optional: true, | ||
| Type: schema.TypeString, | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| outboundContactListEmailColumnResourceV2 := &schema.Resource{ | ||
| Schema: map[string]*schema.Schema{ | ||
| `column_name`: { | ||
| Required: true, | ||
| Type: schema.TypeString, | ||
| }, | ||
| `type`: { | ||
| Required: true, | ||
| Type: schema.TypeString, | ||
| }, | ||
| `contactable_time_column`: { | ||
| Optional: true, | ||
| Type: schema.TypeString, | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| return &schema.Resource{ | ||
| SchemaVersion: 2, | ||
| Schema: map[string]*schema.Schema{ | ||
| `name`: { | ||
| Required: true, | ||
| Type: schema.TypeString, | ||
| }, | ||
| `division_id`: { | ||
| Optional: true, | ||
| Computed: true, | ||
| Type: schema.TypeString, | ||
| }, | ||
| `column_names`: { | ||
| Required: true, | ||
| ForceNew: true, | ||
| Type: schema.TypeList, | ||
| Elem: &schema.Schema{Type: schema.TypeString}, | ||
| }, | ||
| `phone_columns`: { | ||
| Optional: true, | ||
| ForceNew: true, | ||
| Type: schema.TypeSet, | ||
| Elem: outboundContactListContactPhoneNumberColumnResourceV2, | ||
| }, | ||
| `email_columns`: { | ||
| Optional: true, | ||
| ForceNew: true, | ||
| Type: schema.TypeSet, | ||
| Elem: outboundContactListEmailColumnResourceV2, | ||
| }, | ||
| `preview_mode_column_name`: { | ||
| Optional: true, | ||
| Type: schema.TypeString, | ||
| }, | ||
| `preview_mode_accepted_values`: { | ||
| Optional: true, | ||
| Type: schema.TypeList, | ||
| Elem: &schema.Schema{Type: schema.TypeString}, | ||
| }, | ||
| `attempt_limit_id`: { | ||
| Optional: true, | ||
| Type: schema.TypeString, | ||
| }, | ||
| `automatic_time_zone_mapping`: { | ||
| Optional: true, | ||
| ForceNew: true, | ||
| Type: schema.TypeBool, | ||
| }, | ||
| `zip_code_column_name`: { | ||
| Optional: true, | ||
| ForceNew: true, | ||
| Type: schema.TypeString, | ||
| }, | ||
| `column_data_type_specifications`: { | ||
| Optional: true, | ||
| ForceNew: true, | ||
| Type: schema.TypeList, | ||
| Elem: outboundContactListColumnDataTypeSpecification, | ||
| }, | ||
| `trim_whitespace`: { | ||
| Optional: true, | ||
| Type: schema.TypeBool, | ||
| }, | ||
| `contacts_filepath`: { | ||
| Optional: true, | ||
| ForceNew: false, | ||
| Type: schema.TypeString, | ||
| ValidateFunc: validators.ValidatePath, | ||
| RequiredWith: []string{"contacts_filepath", "contacts_id_name"}, | ||
| }, | ||
| `contacts_id_name`: { | ||
| Optional: true, | ||
| ForceNew: false, | ||
| Type: schema.TypeString, | ||
| RequiredWith: []string{"contacts_id_name", "contacts_filepath"}, | ||
| }, | ||
| `contacts_file_content_hash`: { | ||
| Computed: true, | ||
| Type: schema.TypeString, | ||
| }, | ||
| `contacts_record_count`: { | ||
| Computed: true, | ||
| Type: schema.TypeInt, | ||
| }, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func stateUpgraderOutboundContactListV2ToV3(_ context.Context, rawState map[string]interface{}, _ interface{}) (map[string]interface{}, error) { | ||
| migrateSet := func(v interface{}, legacyKey, nameKey string) { | ||
| list, ok := v.([]interface{}) | ||
| if !ok { | ||
| return | ||
| } | ||
| for _, item := range list { | ||
| m, ok := item.(map[string]interface{}) | ||
| if !ok { | ||
| continue | ||
| } | ||
| legacy, _ := m[legacyKey].(string) | ||
| name, _ := m[nameKey].(string) | ||
| if name == "" && legacy != "" { | ||
| m[nameKey] = legacy | ||
| } | ||
| if legacy == "" && name != "" { | ||
| m[legacyKey] = name | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if v, ok := rawState["phone_columns"]; ok { | ||
| migrateSet(v, "callable_time_column", "callable_time_column_name") | ||
| } | ||
| if v, ok := rawState["email_columns"]; ok { | ||
| migrateSet(v, "contactable_time_column", "contactable_time_column_name") | ||
| } | ||
|
|
||
| return rawState, nil | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should probably include in this comment a link or JIRA to the corresponding fix for pulling this field into the SDK with instructions to remove this code after the SDK has been upgraded with support for the new field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've now added a TODO with a link to the Go SDK repo to remove this once the SDK models include TimeColumnName fields.