-
Couldn't load subscription status.
- Fork 9.8k
Add support for concurrency cross channel behaviour #44750
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: main
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 |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| ```release-note:enhancement | ||
| resource/aws_connect_routing_profile: Add `media_concurrencies.cross_channel_behavior` argument | ||
| ``` | ||
|
|
||
| ```release-note:enhancement | ||
| data-source/aws_connect_routing_profile: Add `media_concurrencies.cross_channel_behavior` attribute | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -78,6 +78,21 @@ func resourceRoutingProfile() *schema.Resource { | |
| Required: true, | ||
| ValidateFunc: validation.IntBetween(1, 10), | ||
| }, | ||
| "cross_channel_behavior": { | ||
| Type: schema.TypeList, | ||
| Optional: true, | ||
| Computed: true, | ||
| MaxItems: 1, | ||
| Elem: &schema.Resource{ | ||
| Schema: map[string]*schema.Schema{ | ||
| "behavior_type": { | ||
| Type: schema.TypeString, | ||
| Required: true, | ||
| ValidateDiagFunc: enum.Validate[awstypes.BehaviorType](), | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
|
|
@@ -201,7 +216,7 @@ func resourceRoutingProfileRead(ctx context.Context, d *schema.ResourceData, met | |
| d.Set("default_outbound_queue_id", routingProfile.DefaultOutboundQueueId) | ||
| d.Set(names.AttrDescription, routingProfile.Description) | ||
| d.Set(names.AttrInstanceID, instanceID) | ||
| if err := d.Set("media_concurrencies", flattenMediaConcurrencies(routingProfile.MediaConcurrencies)); err != nil { | ||
| if err := d.Set("media_concurrencies", flattenMediaConcurrencies(routingProfile.MediaConcurrencies, d.Get("media_concurrencies").(*schema.Set).List())); err != nil { | ||
| return sdkdiag.AppendErrorf(diags, "setting media_concurrencies: %s", err) | ||
| } | ||
| d.Set(names.AttrName, routingProfile.Name) | ||
|
|
@@ -477,21 +492,51 @@ func expandMediaConcurrencies(tfList []any) []awstypes.MediaConcurrency { | |
| Channel: awstypes.Channel(tfMap["channel"].(string)), | ||
| Concurrency: aws.Int32(int32(tfMap["concurrency"].(int))), | ||
| } | ||
|
|
||
| if v, ok := tfMap["cross_channel_behavior"].([]any); ok && len(v) > 0 { | ||
| crossChannelBehaviorMap := v[0].(map[string]any) | ||
| apiObject.CrossChannelBehavior = &awstypes.CrossChannelBehavior{ | ||
| BehaviorType: awstypes.BehaviorType(crossChannelBehaviorMap["behavior_type"].(string)), | ||
| } | ||
| } | ||
|
|
||
| apiObjects = append(apiObjects, apiObject) | ||
| } | ||
|
|
||
| return apiObjects | ||
| } | ||
|
|
||
| func flattenMediaConcurrencies(apiObjects []awstypes.MediaConcurrency) []any { | ||
| func flattenMediaConcurrencies(apiObjects []awstypes.MediaConcurrency, configList ...[]any) []any { | ||
|
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. Is there a reason the configured values are now passed in here? I'm not quite following why it is being used below. |
||
| tfList := []any{} | ||
|
|
||
| configuredBehaviors := make(map[string]bool) | ||
| if len(configList) > 0 && configList[0] != nil { | ||
| for _, configRaw := range configList[0] { | ||
| configMap := configRaw.(map[string]any) | ||
| channel := configMap["channel"].(string) | ||
| if crossChannelBehaviorList, ok := configMap["cross_channel_behavior"].([]any); ok && len(crossChannelBehaviorList) > 0 { | ||
| configuredBehaviors[channel] = true | ||
| } | ||
| } | ||
| } | ||
|
|
||
| for _, apiObject := range apiObjects { | ||
| tfMap := map[string]any{ | ||
| "channel": apiObject.Channel, | ||
| "concurrency": aws.ToInt32(apiObject.Concurrency), | ||
| } | ||
|
|
||
| channel := string(apiObject.Channel) | ||
| if apiObject.CrossChannelBehavior != nil { | ||
| if len(configList) == 0 || configuredBehaviors[channel] { | ||
| tfMap["cross_channel_behavior"] = []map[string]any{ | ||
| { | ||
| "behavior_type": string(apiObject.CrossChannelBehavior.BehaviorType), | ||
| }, | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+529
to
+538
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. This can be a separate flattener. |
||
|
|
||
| tfList = append(tfList, tfMap) | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -335,6 +335,78 @@ func testAccRoutingProfile_updateQueues(t *testing.T) { | |
| }) | ||
| } | ||
|
|
||
| func testAccRoutingProfile_crossChannelBehavior(t *testing.T) { | ||
| ctx := acctest.Context(t) | ||
| var v awstypes.RoutingProfile | ||
| rName := sdkacctest.RandomWithPrefix("resource-test-terraform") | ||
| rName2 := sdkacctest.RandomWithPrefix("resource-test-terraform") | ||
| rName3 := sdkacctest.RandomWithPrefix("resource-test-terraform") | ||
| resourceName := "aws_connect_routing_profile.test" | ||
|
|
||
| resource.Test(t, resource.TestCase{ | ||
| PreCheck: func() { acctest.PreCheck(ctx, t) }, | ||
| ErrorCheck: acctest.ErrorCheck(t, names.ConnectServiceID), | ||
| ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, | ||
| CheckDestroy: testAccCheckRoutingProfileDestroy(ctx), | ||
| Steps: []resource.TestStep{ | ||
| { | ||
| Config: testAccRoutingProfileConfig_crossChannelBehaviorDefault(rName, rName2, rName3), | ||
| Check: resource.ComposeTestCheckFunc( | ||
| testAccCheckRoutingProfileExists(ctx, resourceName, &v), | ||
| resource.TestCheckResourceAttr(resourceName, "media_concurrencies.#", "2"), | ||
| resource.TestCheckTypeSetElemNestedAttrs(resourceName, "media_concurrencies.*", map[string]string{ | ||
| "channel": string(awstypes.ChannelVoice), | ||
| "concurrency": "1", | ||
| }), | ||
|
Comment on lines
+357
to
+360
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. This check should also verify the AWS server-side default for |
||
| resource.TestCheckTypeSetElemNestedAttrs(resourceName, "media_concurrencies.*", map[string]string{ | ||
| "channel": string(awstypes.ChannelChat), | ||
| "concurrency": "2", | ||
| }), | ||
| ), | ||
| }, | ||
| { | ||
| ResourceName: resourceName, | ||
| ImportState: true, | ||
| ImportStateVerify: true, | ||
| }, | ||
| { | ||
| Config: testAccRoutingProfileConfig_crossChannelBehaviorCurrentChannelOnly(rName, rName2, rName3), | ||
| Check: resource.ComposeTestCheckFunc( | ||
| testAccCheckRoutingProfileExists(ctx, resourceName, &v), | ||
| resource.TestCheckResourceAttr(resourceName, "media_concurrencies.#", "2"), | ||
| resource.TestCheckTypeSetElemNestedAttrs(resourceName, "media_concurrencies.*", map[string]string{ | ||
| "channel": string(awstypes.ChannelVoice), | ||
| "concurrency": "1", | ||
| "cross_channel_behavior.0.behavior_type": string(awstypes.BehaviorTypeRouteCurrentChannelOnly), | ||
| }), | ||
| resource.TestCheckTypeSetElemNestedAttrs(resourceName, "media_concurrencies.*", map[string]string{ | ||
| "channel": string(awstypes.ChannelChat), | ||
| "concurrency": "3", | ||
| "cross_channel_behavior.0.behavior_type": string(awstypes.BehaviorTypeRouteCurrentChannelOnly), | ||
| }), | ||
| ), | ||
| }, | ||
| { | ||
| Config: testAccRoutingProfileConfig_crossChannelBehaviorMixed(rName, rName2, rName3), | ||
| Check: resource.ComposeTestCheckFunc( | ||
| testAccCheckRoutingProfileExists(ctx, resourceName, &v), | ||
| resource.TestCheckResourceAttr(resourceName, "media_concurrencies.#", "2"), | ||
| resource.TestCheckTypeSetElemNestedAttrs(resourceName, "media_concurrencies.*", map[string]string{ | ||
| "channel": string(awstypes.ChannelVoice), | ||
| "concurrency": "1", | ||
| "cross_channel_behavior.0.behavior_type": string(awstypes.BehaviorTypeRouteAnyChannel), | ||
| }), | ||
| resource.TestCheckTypeSetElemNestedAttrs(resourceName, "media_concurrencies.*", map[string]string{ | ||
| "channel": string(awstypes.ChannelChat), | ||
| "concurrency": "3", | ||
| "cross_channel_behavior.0.behavior_type": string(awstypes.BehaviorTypeRouteCurrentChannelOnly), | ||
| }), | ||
| ), | ||
| }, | ||
| }, | ||
| }) | ||
| } | ||
|
|
||
| func testAccRoutingProfile_updateTags(t *testing.T) { | ||
| ctx := acctest.Context(t) | ||
| var v awstypes.RoutingProfile | ||
|
|
@@ -997,3 +1069,98 @@ resource "aws_connect_routing_profile" "test" { | |
| } | ||
| `, rName3)) | ||
| } | ||
|
|
||
| func testAccRoutingProfileConfig_crossChannelBehaviorDefault(rName, rName2, rName3 string) string { | ||
| return acctest.ConfigCompose( | ||
| testAccRoutingProfileConfig_base(rName, rName2), | ||
| fmt.Sprintf(` | ||
| resource "aws_connect_routing_profile" "test" { | ||
| instance_id = aws_connect_instance.test.id | ||
| name = %[1]q | ||
| default_outbound_queue_id = aws_connect_queue.default_outbound_queue.queue_id | ||
| description = "Test cross-channel behavior - default" | ||
|
|
||
| media_concurrencies { | ||
| channel = "VOICE" | ||
| concurrency = 1 | ||
| # behaviour uses AWS server-side default | ||
| } | ||
|
|
||
| media_concurrencies { | ||
| channel = "CHAT" | ||
| concurrency = 2 | ||
| # behaviour uses AWS server-side default | ||
| } | ||
|
|
||
| tags = { | ||
| "Name" = "Test Routing Profile Cross-Channel Behavior", | ||
| } | ||
| } | ||
| `, rName3)) | ||
| } | ||
|
|
||
| func testAccRoutingProfileConfig_crossChannelBehaviorCurrentChannelOnly(rName, rName2, rName3 string) string { | ||
| return acctest.ConfigCompose( | ||
| testAccRoutingProfileConfig_base(rName, rName2), | ||
| fmt.Sprintf(` | ||
| resource "aws_connect_routing_profile" "test" { | ||
| instance_id = aws_connect_instance.test.id | ||
| name = %[1]q | ||
| default_outbound_queue_id = aws_connect_queue.default_outbound_queue.queue_id | ||
| description = "Test cross-channel behavior - current channel only" | ||
|
|
||
| media_concurrencies { | ||
| channel = "VOICE" | ||
| concurrency = 1 | ||
| cross_channel_behavior { | ||
| behavior_type = "ROUTE_CURRENT_CHANNEL_ONLY" | ||
| } | ||
| } | ||
|
|
||
| media_concurrencies { | ||
| channel = "CHAT" | ||
| concurrency = 3 | ||
| cross_channel_behavior { | ||
| behavior_type = "ROUTE_CURRENT_CHANNEL_ONLY" | ||
| } | ||
| } | ||
|
|
||
| tags = { | ||
| "Name" = "Test Routing Profile Cross-Channel Behavior", | ||
| } | ||
| } | ||
| `, rName3)) | ||
| } | ||
|
|
||
| func testAccRoutingProfileConfig_crossChannelBehaviorMixed(rName, rName2, rName3 string) string { | ||
| return acctest.ConfigCompose( | ||
| testAccRoutingProfileConfig_base(rName, rName2), | ||
| fmt.Sprintf(` | ||
| resource "aws_connect_routing_profile" "test" { | ||
| instance_id = aws_connect_instance.test.id | ||
| name = %[1]q | ||
| default_outbound_queue_id = aws_connect_queue.default_outbound_queue.queue_id | ||
| description = "Test cross-channel behavior - mixed" | ||
|
|
||
| media_concurrencies { | ||
| channel = "VOICE" | ||
| concurrency = 1 | ||
| cross_channel_behavior { | ||
| behavior_type = "ROUTE_ANY_CHANNEL" | ||
| } | ||
| } | ||
|
|
||
| media_concurrencies { | ||
| channel = "CHAT" | ||
| concurrency = 3 | ||
| cross_channel_behavior { | ||
| behavior_type = "ROUTE_CURRENT_CHANNEL_ONLY" | ||
| } | ||
| } | ||
|
|
||
| tags = { | ||
| "Name" = "Test Routing Profile Cross-Channel Behavior", | ||
| } | ||
| } | ||
| `, rName3)) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,17 @@ resource "aws_connect_routing_profile" "example" { | |
| media_concurrencies { | ||
| channel = "VOICE" | ||
| concurrency = 1 | ||
| cross_channel_behavior { | ||
| behavior_type = "ROUTE_ANY_CHANNEL" | ||
| } | ||
| } | ||
|
|
||
| media_concurrencies { | ||
| channel = "CHAT" | ||
| concurrency = 3 | ||
| cross_channel_behavior { | ||
| behavior_type = "ROUTE_CURRENT_CHANNEL_ONLY" | ||
| } | ||
| } | ||
|
|
||
| queue_configs { | ||
|
|
@@ -38,6 +49,15 @@ resource "aws_connect_routing_profile" "example" { | |
| } | ||
| ``` | ||
|
|
||
| ### Cross-Channel Behavior | ||
|
|
||
| The `cross_channel_behavior` block in `media_concurrencies` controls how Amazon Connect routes contacts across different channels: | ||
|
|
||
| - `ROUTE_ANY_CHANNEL` (default): Allows agents to receive contacts from any channel, regardless of the channel they are currently handling. | ||
| - `ROUTE_CURRENT_CHANNEL_ONLY`: Restricts agents to receive contacts only from the channel they are currently handling. | ||
|
|
||
| This feature enables fine-grained control over agent workload distribution and helps optimize contact center operations. | ||
|
Comment on lines
+52
to
+59
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. This block seems redundant with the argument reference below. If any of the additional details are necessary they can be included in the argument descriptions instead. |
||
|
|
||
| ## Argument Reference | ||
|
|
||
| This resource supports the following arguments: | ||
|
|
@@ -56,6 +76,11 @@ A `media_concurrencies` block supports the following arguments: | |
|
|
||
| * `channel` - (Required) Specifies the channels that agents can handle in the Contact Control Panel (CCP). Valid values are `VOICE`, `CHAT`, `TASK`. | ||
| * `concurrency` - (Required) Specifies the number of contacts an agent can have on a channel simultaneously. Valid Range for `VOICE`: Minimum value of 1. Maximum value of 1. Valid Range for `CHAT`: Minimum value of 1. Maximum value of 10. Valid Range for `TASK`: Minimum value of 1. Maximum value of 10. | ||
| * `cross_channel_behavior` - (Optional) Configuration block for cross-channel behavior. If not specified, AWS will use the service default behavior. Documented below. | ||
|
|
||
| A `cross_channel_behavior` block supports the following arguments: | ||
|
|
||
| * `behavior_type` - (Required) Specifies the cross-channel behavior for routing contacts across multiple channels. Valid values are `ROUTE_CURRENT_CHANNEL_ONLY`, `ROUTE_ANY_CHANNEL`. | ||
|
|
||
| A `queue_configs` block supports the following arguments: | ||
|
|
||
|
|
||
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.
This can be moved into it's own, distinct expander
expandCrossChannelBehavior.Each flatten/expander is typically only responsible for one level of attributes. Because
cross_channel_behavioris a nested list, we can split that out into it's own expand function which gets called from here. This helps keep the function body of each individual flex function compact, even for large nested structures.