diff --git a/docs/resources/routing_queue.md b/docs/resources/routing_queue.md index 5a1178cbf..734f1bb9b 100644 --- a/docs/resources/routing_queue.md +++ b/docs/resources/routing_queue.md @@ -109,12 +109,16 @@ resource "genesyscloud_routing_queue" "example_queue" { - `message_in_queue_flow_id` (String) The in-queue flow ID to use for message conversations waiting in queue. - `on_hold_prompt_id` (String) The audio to be played when calls on this queue are on hold. If not configured, the default on-hold music will play. - `outbound_email_address` (Block List, Max: 1, Deprecated) The outbound email address settings for this queue. (see [below for nested schema](#nestedblock--outbound_email_address)) +- `outbound_messaging_open_messaging_recipient_id` (String) The unique ID of the outbound messaging open messaging recipient for the queue. - `outbound_messaging_sms_address_id` (String) The unique ID of the outbound messaging SMS address for the queue. +- `outbound_messaging_whatsapp_recipient_id` (String) The unique ID of the outbound messaging whatsapp recipient for the queue. +- `peer_id` (String) The ID of an associated external queue - `queue_flow_id` (String) The in-queue flow ID to use for call conversations waiting in queue. - `routing_rules` (Block List, Max: 6) The routing rules for the queue, used for routing to known or preferred agents. (see [below for nested schema](#nestedblock--routing_rules)) - `scoring_method` (String) The Scoring Method for the queue. Defaults to TimestampAndPriority. Defaults to `TimestampAndPriority`. - `skill_evaluation_method` (String) The skill evaluation method to use when routing conversations (NONE | BEST | ALL). Defaults to `ALL`. - `skill_groups` (Set of String) List of skill group ids assigned to the queue. +- `source_queue_id` (String) The id of an existing queue to copy the settings (does not include GPR settings) from when creating a new queue. - `suppress_in_queue_call_recording` (Boolean) Indicates whether recording in-queue calls is suppressed for this queue. Defaults to `true`. - `teams` (Set of String) List of ids assigned to the queue - `whisper_prompt_id` (String) The prompt ID used for whisper on the queue, if configured. @@ -127,7 +131,7 @@ resource "genesyscloud_routing_queue" "example_queue" { ### Nested Schema for `agent_owned_routing` -Required: +Optional: - `enable_agent_owned_callbacks` (Boolean) Enable Agent Owned Callbacks - `max_owned_callback_delay_hours` (Number) Max Owned Call Back Delay Hours >= 7 @@ -161,13 +165,13 @@ Required: Required: -- `condition_value` (Number) The limit value, beyond which a rule evaluates as true. - `groups` (Block List, Min: 1) The group(s) to activate if the rule evaluates as true. (see [below for nested schema](#nestedblock--conditional_group_routing_rules--groups)) -- `operator` (String) The operator that compares the actual value against the condition value. Valid values: GreaterThan, GreaterThanOrEqualTo, LessThan, LessThanOrEqualTo. Optional: +- `condition_value` (Number) The limit value, beyond which a rule evaluates as true. - `metric` (String) The queue metric being evaluated. Valid values: EstimatedWaitTime, ServiceLevel Defaults to `EstimatedWaitTime`. +- `operator` (String) The operator that compares the actual value against the condition value. Valid values: GreaterThan, GreaterThanOrEqualTo, LessThan, LessThanOrEqualTo. - `queue_id` (String) The ID of the queue being evaluated for this rule. For rule 1, this is always be the current queue, so no queue id should be specified for the first rule. - `wait_seconds` (Number) The number of seconds to wait in this rule, if it evaluates as true, before evaluating the next rule. For the final rule, this is ignored, so need not be specified. Defaults to `2`. @@ -197,86 +201,71 @@ Optional: ### Nested Schema for `media_settings_call` -Required: - -- `alerting_timeout_sec` (Number) Alerting timeout in seconds. Must be >= 7 -- `service_level_duration_ms` (Number) Service Level target in milliseconds. Must be >= 1000 -- `service_level_percentage` (Number) The desired Service Level. A float value between 0 and 1. - Optional: +- `alerting_timeout_sec` (Number) Alerting timeout in seconds. Must be >= 7 - `auto_dial_delay_seconds` (Number) Auto Dial Delay Seconds. - `auto_end_delay_seconds` (Number) Auto End Delay Seconds. - `enable_auto_answer` (Boolean) Auto-Answer for digital channels(Email, Message) Defaults to `false`. - `enable_auto_dial_and_end` (Boolean) Auto Dail and End Defaults to `false`. +- `service_level_duration_ms` (Number) Service Level target in milliseconds. Must be >= 1000 +- `service_level_percentage` (Number) The desired Service Level. A float value between 0 and 1. ### Nested Schema for `media_settings_callback` -Required: - -- `alerting_timeout_sec` (Number) Alerting timeout in seconds. Must be >= 7 -- `service_level_duration_ms` (Number) Service Level target in milliseconds. Must be >= 1000 -- `service_level_percentage` (Number) The desired Service Level. A float value between 0 and 1. - Optional: +- `alerting_timeout_sec` (Number) Alerting timeout in seconds. Must be >= 7 - `auto_dial_delay_seconds` (Number) Auto Dial Delay Seconds. - `auto_end_delay_seconds` (Number) Auto End Delay Seconds. - `enable_auto_answer` (Boolean) Auto-Answer for digital channels(Email, Message) Defaults to `false`. - `enable_auto_dial_and_end` (Boolean) Auto Dail and End Defaults to `false`. +- `service_level_duration_ms` (Number) Service Level target in milliseconds. Must be >= 1000 +- `service_level_percentage` (Number) The desired Service Level. A float value between 0 and 1. ### Nested Schema for `media_settings_chat` -Required: - -- `alerting_timeout_sec` (Number) Alerting timeout in seconds. Must be >= 7 -- `service_level_duration_ms` (Number) Service Level target in milliseconds. Must be >= 1000 -- `service_level_percentage` (Number) The desired Service Level. A float value between 0 and 1. - Optional: +- `alerting_timeout_sec` (Number) Alerting timeout in seconds. Must be >= 7 - `auto_dial_delay_seconds` (Number) Auto Dial Delay Seconds. - `auto_end_delay_seconds` (Number) Auto End Delay Seconds. - `enable_auto_answer` (Boolean) Auto-Answer for digital channels(Email, Message) Defaults to `false`. - `enable_auto_dial_and_end` (Boolean) Auto Dail and End Defaults to `false`. +- `service_level_duration_ms` (Number) Service Level target in milliseconds. Must be >= 1000 +- `service_level_percentage` (Number) The desired Service Level. A float value between 0 and 1. ### Nested Schema for `media_settings_email` -Required: - -- `alerting_timeout_sec` (Number) Alerting timeout in seconds. Must be >= 7 -- `service_level_duration_ms` (Number) Service Level target in milliseconds. Must be >= 1000 -- `service_level_percentage` (Number) The desired Service Level. A float value between 0 and 1. - Optional: +- `alerting_timeout_sec` (Number) Alerting timeout in seconds. Must be >= 7 - `auto_dial_delay_seconds` (Number) Auto Dial Delay Seconds. - `auto_end_delay_seconds` (Number) Auto End Delay Seconds. - `enable_auto_answer` (Boolean) Auto-Answer for digital channels(Email, Message) Defaults to `false`. - `enable_auto_dial_and_end` (Boolean) Auto Dail and End Defaults to `false`. +- `service_level_duration_ms` (Number) Service Level target in milliseconds. Must be >= 1000 +- `service_level_percentage` (Number) The desired Service Level. A float value between 0 and 1. ### Nested Schema for `media_settings_message` -Required: - -- `alerting_timeout_sec` (Number) Alerting timeout in seconds. Must be >= 7 -- `service_level_duration_ms` (Number) Service Level target in milliseconds. Must be >= 1000 -- `service_level_percentage` (Number) The desired Service Level. A float value between 0 and 1. - Optional: +- `alerting_timeout_sec` (Number) Alerting timeout in seconds. Must be >= 7 - `auto_dial_delay_seconds` (Number) Auto Dial Delay Seconds. - `auto_end_delay_seconds` (Number) Auto End Delay Seconds. - `enable_auto_answer` (Boolean) Auto-Answer for digital channels(Email, Message) Defaults to `false`. - `enable_auto_dial_and_end` (Boolean) Auto Dail and End Defaults to `false`. +- `service_level_duration_ms` (Number) Service Level target in milliseconds. Must be >= 1000 +- `service_level_percentage` (Number) The desired Service Level. A float value between 0 and 1. diff --git a/examples/resources/genesyscloud_flow/inboundcall_flow_example.yaml b/examples/resources/genesyscloud_flow/inboundcall_flow_example.yaml index 9c390be4a..12ffba09a 100644 --- a/examples/resources/genesyscloud_flow/inboundcall_flow_example.yaml +++ b/examples/resources/genesyscloud_flow/inboundcall_flow_example.yaml @@ -1,5 +1,5 @@ inboundCall: - name: Terraform Flow Test-a8bf377f-3900-42a2-aa43-cec584f14366 + name: Terraform Flow Test-b066d719-c330-41c3-8c0f-9f880f6cdfb5 defaultLanguage: en-us startUpRef: ./menus/menu[mainMenu] initialGreeting: diff --git a/examples/resources/genesyscloud_flow/inboundcall_flow_example2.yaml b/examples/resources/genesyscloud_flow/inboundcall_flow_example2.yaml index 28dca8215..1975e5da7 100644 --- a/examples/resources/genesyscloud_flow/inboundcall_flow_example2.yaml +++ b/examples/resources/genesyscloud_flow/inboundcall_flow_example2.yaml @@ -1,5 +1,5 @@ inboundEmail: - name: Terraform Flow Test-c93ae359-ace8-4e15-932b-18f242a33b09 + name: Terraform Flow Test-88aecd38-8ab8-45cf-bf74-7f9fa01da2ac division: New Home startUpRef: "/inboundEmail/states/state[Initial State_10]" defaultLanguage: en-us diff --git a/examples/resources/genesyscloud_flow/inboundcall_flow_example3.yaml b/examples/resources/genesyscloud_flow/inboundcall_flow_example3.yaml index 99d08d155..7ca9669f5 100644 --- a/examples/resources/genesyscloud_flow/inboundcall_flow_example3.yaml +++ b/examples/resources/genesyscloud_flow/inboundcall_flow_example3.yaml @@ -1,5 +1,5 @@ inboundCall: - name: Terraform Flow Test-7fbe9a86-3aca-4bdc-a88f-ab89b891793e + name: Terraform Flow Test-bbe3c4a2-faa9-48c7-a54d-6117ae2eabc1 defaultLanguage: en-us startUpRef: ./menus/menu[mainMenu] initialGreeting: diff --git a/genesyscloud/routing_queue/data_source_genesyscloud_routing_queue.go b/genesyscloud/routing_queue/data_source_genesyscloud_routing_queue.go index 7ed966b21..8eaba9f7c 100644 --- a/genesyscloud/routing_queue/data_source_genesyscloud_routing_queue.go +++ b/genesyscloud/routing_queue/data_source_genesyscloud_routing_queue.go @@ -32,7 +32,6 @@ func dataSourceRoutingQueueRead(ctx context.Context, d *schema.ResourceData, m i } queueId, err := rc.RetrieveId(dataSourceRoutingQueueCache, resourceName, key, ctx) - if err != nil { return err } @@ -48,19 +47,23 @@ func normalizeQueueName(queueName string) string { // hydrateRoutingQueueCacheFn for hydrating the cache with Genesys Cloud routing queues using the SDK func hydrateRoutingQueueCacheFn(c *rc.DataSourceCache, ctx context.Context) error { - log.Printf("hydrating cache for data source genesyscloud_routing_queues") proxy := GetRoutingQueueProxy(c.ClientConfig) + log.Printf("hydrating cache for data source genesyscloud_routing_queues") + // Newly created resources often aren't returned unless there's a delay time.Sleep(5 * time.Second) - queues, _, err := proxy.GetAllRoutingQueues(ctx, "") + allQueues, resp, err := proxy.GetAllRoutingQueues(ctx, "") if err != nil { - return err + return fmt.Errorf("failed to get routing queues %s %s", err, resp) } - // Add ids to cache - for _, queue := range *queues { + if allQueues == nil || len(*allQueues) == 0 { + return nil + } + + for _, queue := range *allQueues { c.Cache[normalizeQueueName(*queue.Name)] = *queue.Id } @@ -75,16 +78,16 @@ func getQueueByNameFn(c *rc.DataSourceCache, name string, ctx context.Context) ( queueId := "" diag := util.WithRetries(ctx, 15*time.Second, func() *retry.RetryError { - queue, resp, retryable, getErr := proxy.getRoutingQueueByName(ctx, name) + + queueID, resp, retryable, getErr := proxy.getRoutingQueueByName(ctx, name) if getErr != nil && !retryable { return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting queue %s | error %s", name, getErr), resp)) } - if retryable { - return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("no routing queue found with name %s", name), resp)) + return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("error requesting queue %s | error %s", name, getErr), resp)) } - queueId = queue + queueId = queueID return nil }) diff --git a/genesyscloud/routing_queue/data_source_genesyscloud_routing_queue_test.go b/genesyscloud/routing_queue/data_source_genesyscloud_routing_queue_test.go index 99f73f055..b0e7c65df 100644 --- a/genesyscloud/routing_queue/data_source_genesyscloud_routing_queue_test.go +++ b/genesyscloud/routing_queue/data_source_genesyscloud_routing_queue_test.go @@ -42,6 +42,8 @@ func TestAccDataSourceRoutingQueueBasic(t *testing.T) { util.NullValue, //suppressCall_record_false util.NullValue, // enable_transcription false strconv.Quote("TimestampAndPriority"), + util.NullValue, + util.NullValue, ) + generateRoutingQueueDataSource( queueDataSource, "genesyscloud_routing_queue."+queueResource+".name", diff --git a/genesyscloud/routing_queue/genesyscloud_routing_queue_proxy.go b/genesyscloud/routing_queue/genesyscloud_routing_queue_proxy.go index 0a2c31698..f7567d77a 100644 --- a/genesyscloud/routing_queue/genesyscloud_routing_queue_proxy.go +++ b/genesyscloud/routing_queue/genesyscloud_routing_queue_proxy.go @@ -18,36 +18,69 @@ out during testing. // internalProxy holds a proxy instance that can be used throughout the package var internalProxy *RoutingQueueProxy -// Type definitions for each func on our proxy so we can easily mock them out later -type getAllRoutingQueuesFunc func(ctx context.Context, p *RoutingQueueProxy, queueName string) (*[]platformclientv2.Queue, *platformclientv2.APIResponse, error) -type getRoutingQueueByIdFunc func(ctx context.Context, p *RoutingQueueProxy, queueId string) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) -type getRoutingQueueByNameFunc func(ctx context.Context, p *RoutingQueueProxy, queueName string) (string, *platformclientv2.APIResponse, bool, error) -type getRoutingQueueWrapupCodeIdsFunc func(ctx context.Context, p *RoutingQueueProxy, queueId string) ([]string, *platformclientv2.APIResponse, error) +type GetAllRoutingQueuesFunc func(ctx context.Context, p *RoutingQueueProxy, name string) (*[]platformclientv2.Queue, *platformclientv2.APIResponse, error) +type createRoutingQueueFunc func(ctx context.Context, p *RoutingQueueProxy, createReq *platformclientv2.Createqueuerequest) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) +type getRoutingQueueByIdFunc func(ctx context.Context, p *RoutingQueueProxy, queueId string, checkCache bool) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) +type getRoutingQueueByNameFunc func(ctx context.Context, p *RoutingQueueProxy, name string) (string, *platformclientv2.APIResponse, bool, error) +type updateRoutingQueueFunc func(ctx context.Context, p *RoutingQueueProxy, queueId string, updateReq *platformclientv2.Queuerequest) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) +type deleteRoutingQueueFunc func(ctx context.Context, p *RoutingQueueProxy, queueId string, forceDelete bool) (*platformclientv2.APIResponse, error) + +type getAllRoutingQueueWrapupCodesFunc func(ctx context.Context, p *RoutingQueueProxy, queueId string) (*[]platformclientv2.Wrapupcode, *platformclientv2.APIResponse, error) +type createRoutingQueueWrapupCodeFunc func(ctx context.Context, p *RoutingQueueProxy, queueId string, body []platformclientv2.Wrapupcodereference) ([]platformclientv2.Wrapupcode, *platformclientv2.APIResponse, error) +type deleteRoutingQueueWrapupCodeFunc func(ctx context.Context, p *RoutingQueueProxy, queueId, codeId string) (*platformclientv2.APIResponse, error) + +type addOrRemoveMembersFunc func(ctx context.Context, p *RoutingQueueProxy, queueId string, body []platformclientv2.Writableentity, delete bool) (*platformclientv2.APIResponse, error) +type updateRoutingQueueMemberFunc func(ctx context.Context, p *RoutingQueueProxy, queueId, userId string, body platformclientv2.Queuemember) (*platformclientv2.APIResponse, error) // RoutingQueueProxy contains all the methods that call genesys cloud APIs. type RoutingQueueProxy struct { - clientConfig *platformclientv2.Configuration - routingApi *platformclientv2.RoutingApi - getAllRoutingQueuesAttr getAllRoutingQueuesFunc - getRoutingQueueByIdAttr getRoutingQueueByIdFunc - getRoutingQueueByNameAttr getRoutingQueueByNameFunc - getRoutingQueueWrapupCodeIdsAttr getRoutingQueueWrapupCodeIdsFunc - RoutingQueueCache rc.CacheInterface[platformclientv2.Queue] + clientConfig *platformclientv2.Configuration + routingApi *platformclientv2.RoutingApi + + GetAllRoutingQueuesAttr GetAllRoutingQueuesFunc + createRoutingQueueAttr createRoutingQueueFunc + getRoutingQueueByIdAttr getRoutingQueueByIdFunc + getRoutingQueueByNameAttr getRoutingQueueByNameFunc + updateRoutingQueueAttr updateRoutingQueueFunc + deleteRoutingQueueAttr deleteRoutingQueueFunc + + getAllRoutingQueueWrapupCodesAttr getAllRoutingQueueWrapupCodesFunc + createRoutingQueueWrapupCodeAttr createRoutingQueueWrapupCodeFunc + deleteRoutingQueueWrapupCodeAttr deleteRoutingQueueWrapupCodeFunc + + addOrRemoveMembersAttr addOrRemoveMembersFunc + updateRoutingQueueMemberAttr updateRoutingQueueMemberFunc + + RoutingQueueCache rc.CacheInterface[platformclientv2.Queue] + wrapupCodeCache rc.CacheInterface[platformclientv2.Wrapupcode] } // newRoutingQueuesProxy initializes the routing queue proxy with all the data needed to communicate with Genesys Cloud func newRoutingQueuesProxy(clientConfig *platformclientv2.Configuration) *RoutingQueueProxy { api := platformclientv2.NewRoutingApiWithConfig(clientConfig) routingQueueCache := rc.NewResourceCache[platformclientv2.Queue]() + wrapupCodeCache := rc.NewResourceCache[platformclientv2.Wrapupcode]() return &RoutingQueueProxy{ - clientConfig: clientConfig, - routingApi: api, - getAllRoutingQueuesAttr: getAllRoutingQueuesFn, - getRoutingQueueByIdAttr: getRoutingQueueByIdFn, - getRoutingQueueByNameAttr: getRoutingQueueByNameFn, - getRoutingQueueWrapupCodeIdsAttr: getRoutingQueueWrapupCodeIdsFn, - RoutingQueueCache: routingQueueCache, + clientConfig: clientConfig, + routingApi: api, + + GetAllRoutingQueuesAttr: GetAllRoutingQueuesFn, + createRoutingQueueAttr: createRoutingQueueFn, + getRoutingQueueByIdAttr: getRoutingQueueByIdFn, + getRoutingQueueByNameAttr: getRoutingQueueByNameFn, + updateRoutingQueueAttr: updateRoutingQueueFn, + deleteRoutingQueueAttr: deleteRoutingQueueFn, + + getAllRoutingQueueWrapupCodesAttr: getAllRoutingQueueWrapupCodesFn, + createRoutingQueueWrapupCodeAttr: createRoutingQueueWrapupCodeFn, + deleteRoutingQueueWrapupCodeAttr: deleteRoutingQueueWrapupCodeFn, + + addOrRemoveMembersAttr: addOrRemoveMembersFn, + updateRoutingQueueMemberAttr: updateRoutingQueueMemberFn, + + RoutingQueueCache: routingQueueCache, + wrapupCodeCache: wrapupCodeCache, } } @@ -60,32 +93,56 @@ func GetRoutingQueueProxy(clientConfig *platformclientv2.Configuration) *Routing return internalProxy } -// GetAllRoutingQueues retrieves all Genesys Cloud routing queues -func (p *RoutingQueueProxy) GetAllRoutingQueues(ctx context.Context, queueName string) (*[]platformclientv2.Queue, *platformclientv2.APIResponse, error) { - return p.getAllRoutingQueuesAttr(ctx, p, queueName) +func (p *RoutingQueueProxy) GetAllRoutingQueues(ctx context.Context, name string) (*[]platformclientv2.Queue, *platformclientv2.APIResponse, error) { + return p.GetAllRoutingQueuesAttr(ctx, p, name) +} + +func (p *RoutingQueueProxy) createRoutingQueue(ctx context.Context, createReq *platformclientv2.Createqueuerequest) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) { + return p.createRoutingQueueAttr(ctx, p, createReq) +} + +func (p *RoutingQueueProxy) getRoutingQueueById(ctx context.Context, queueId string, checkCache bool) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) { + return p.getRoutingQueueByIdAttr(ctx, p, queueId, checkCache) } -// getRoutingQueueById returns a single Genesys Cloud Routing Queue by ID -func (p *RoutingQueueProxy) getRoutingQueueById(ctx context.Context, queueId string) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) { - return p.getRoutingQueueByIdAttr(ctx, p, queueId) +func (p *RoutingQueueProxy) getRoutingQueueByName(ctx context.Context, name string) (string, *platformclientv2.APIResponse, bool, error) { + return p.getRoutingQueueByNameAttr(ctx, p, name) } -// getRoutingQueueByName returns a single Genesys Cloud Routing Queue by Name -func (p *RoutingQueueProxy) getRoutingQueueByName(ctx context.Context, queueName string) (string, *platformclientv2.APIResponse, bool, error) { - return p.getRoutingQueueByNameAttr(ctx, p, queueName) +func (p *RoutingQueueProxy) updateRoutingQueue(ctx context.Context, queueId string, updateReq *platformclientv2.Queuerequest) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) { + return p.updateRoutingQueueAttr(ctx, p, queueId, updateReq) } -// getRoutingQueueWrapupCodeIds returns a list of routing queue wrapup code ids -func (p *RoutingQueueProxy) getRoutingQueueWrapupCodeIds(ctx context.Context, queueId string) ([]string, *platformclientv2.APIResponse, error) { - return p.getRoutingQueueWrapupCodeIdsAttr(ctx, p, queueId) +func (p *RoutingQueueProxy) deleteRoutingQueue(ctx context.Context, queueId string, forceDelete bool) (*platformclientv2.APIResponse, error) { + return p.deleteRoutingQueueAttr(ctx, p, queueId, forceDelete) } -// getAllRoutingQueuesFn is the implementation for retrieving all routing queues in Genesys Cloud -func getAllRoutingQueuesFn(ctx context.Context, p *RoutingQueueProxy, queueName string) (*[]platformclientv2.Queue, *platformclientv2.APIResponse, error) { +func (p *RoutingQueueProxy) getAllRoutingQueueWrapupCodes(ctx context.Context, queueId string) (*[]platformclientv2.Wrapupcode, *platformclientv2.APIResponse, error) { + return p.getAllRoutingQueueWrapupCodesAttr(ctx, p, queueId) +} + +func (p *RoutingQueueProxy) createRoutingQueueWrapupCode(ctx context.Context, queueId string, body []platformclientv2.Wrapupcodereference) ([]platformclientv2.Wrapupcode, *platformclientv2.APIResponse, error) { + return p.createRoutingQueueWrapupCodeAttr(ctx, p, queueId, body) +} + +func (p *RoutingQueueProxy) deleteRoutingQueueWrapupCode(ctx context.Context, queueId, codeId string) (*platformclientv2.APIResponse, error) { + return p.deleteRoutingQueueWrapupCodeAttr(ctx, p, queueId, codeId) +} + +func (p *RoutingQueueProxy) addOrRemoveMembers(ctx context.Context, queueId string, body []platformclientv2.Writableentity, delete bool) (*platformclientv2.APIResponse, error) { + return p.addOrRemoveMembersAttr(ctx, p, queueId, body, delete) +} + +func (p *RoutingQueueProxy) updateRoutingQueueMember(ctx context.Context, queueId, userId string, body platformclientv2.Queuemember) (*platformclientv2.APIResponse, error) { + return p.updateRoutingQueueMemberAttr(ctx, p, queueId, userId, body) +} + +// GetAllRoutingQueuesFn is the implementation for retrieving all routing queues in Genesys Cloud +func GetAllRoutingQueuesFn(ctx context.Context, p *RoutingQueueProxy, name string) (*[]platformclientv2.Queue, *platformclientv2.APIResponse, error) { var allQueues []platformclientv2.Queue const pageSize = 100 - queues, resp, getErr := p.routingApi.GetRoutingQueues(1, pageSize, "", queueName, nil, nil, nil, "", false) + queues, resp, getErr := p.routingApi.GetRoutingQueues(1, pageSize, "", name, nil, nil, nil, "", false) if getErr != nil { return nil, resp, fmt.Errorf("failed to get first page of queues: %v", getErr) } @@ -125,71 +182,109 @@ func getAllRoutingQueuesFn(ctx context.Context, p *RoutingQueueProxy, queueName return &allQueues, resp, nil } -// getRoutingQueueByIdFn is the implementation for retrieving a routing queues in Genesys Cloud -func getRoutingQueueByIdFn(ctx context.Context, p *RoutingQueueProxy, queueId string) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) { - queue := rc.GetCacheItem(p.RoutingQueueCache, queueId) - if queue != nil { - return queue, nil, nil - } +func createRoutingQueueFn(ctx context.Context, p *RoutingQueueProxy, createReq *platformclientv2.Createqueuerequest) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) { + return p.routingApi.PostRoutingQueues(*createReq) +} - queue, resp, err := p.routingApi.GetRoutingQueue(queueId) - if err != nil { - return nil, resp, fmt.Errorf("failed to retrieve routing queue by id %s: %s", queueId, err) +// getRoutingQueueByIdFn is the implementation for retrieving a routing queues in Genesys Cloud +func getRoutingQueueByIdFn(ctx context.Context, p *RoutingQueueProxy, queueId string, checkCache bool) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) { + if checkCache { + queue := rc.GetCacheItem(p.RoutingQueueCache, queueId) + if queue != nil { + return queue, nil, nil + } } - return queue, resp, nil + return p.routingApi.GetRoutingQueue(queueId) } -// getRoutingQueueByNameFn is the implementation for retrieving a routing queues in Genesys Cloud -func getRoutingQueueByNameFn(ctx context.Context, p *RoutingQueueProxy, queueName string) (string, *platformclientv2.APIResponse, bool, error) { - - queues, resp, err := getAllRoutingQueuesFn(ctx, p, queueName) - +func getRoutingQueueByNameFn(ctx context.Context, p *RoutingQueueProxy, name string) (string, *platformclientv2.APIResponse, bool, error) { + queues, resp, err := GetAllRoutingQueuesFn(ctx, p, name) if err != nil { return "", resp, false, err } if queues == nil || len(*queues) == 0 { - return "", resp, true, err + return "", resp, true, fmt.Errorf("no routing queue found with name %s", name) } for _, queue := range *queues { - if *queue.Name == queueName { - log.Printf("Retrieved the routing skill id %s by name %s", *queue.Id, queueName) + if normalizeQueueName(*queue.Name) == name { + log.Printf("Retrieved the routing queue id %s by name %s", *queue.Id, name) return *queue.Id, resp, false, nil } } - return "", resp, true, fmt.Errorf("unable to find routing skill with name %s", queueName) + return "", resp, true, fmt.Errorf("unable to find routing queue with name %s", name) +} + +func updateRoutingQueueFn(ctx context.Context, p *RoutingQueueProxy, queueId string, updateReq *platformclientv2.Queuerequest) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) { + return p.routingApi.PutRoutingQueue(queueId, *updateReq) +} + +func deleteRoutingQueueFn(ctx context.Context, p *RoutingQueueProxy, queueID string, forceDelete bool) (*platformclientv2.APIResponse, error) { + resp, err := p.routingApi.DeleteRoutingQueue(queueID, forceDelete) + if err != nil { + return resp, err + } + rc.DeleteCacheItem(p.RoutingQueueCache, queueID) + return resp, nil } -func getRoutingQueueWrapupCodeIdsFn(ctx context.Context, p *RoutingQueueProxy, queueId string) ([]string, *platformclientv2.APIResponse, error) { - var codeIds []string +func getAllRoutingQueueWrapupCodesFn(ctx context.Context, p *RoutingQueueProxy, queueId string) (*[]platformclientv2.Wrapupcode, *platformclientv2.APIResponse, error) { + var allWrapupcodes []platformclientv2.Wrapupcode const pageSize = 100 - codes, resp, err := p.routingApi.GetRoutingQueueWrapupcodes(queueId, pageSize, 1) + wrapupcodes, apiResponse, err := p.routingApi.GetRoutingQueueWrapupcodes(queueId, pageSize, 1) if err != nil { - return nil, resp, fmt.Errorf("failed to first page of wrapup codes for queue %s: %s", queueId, err) + return nil, apiResponse, fmt.Errorf("failed to get routing wrapupcode : %v", err) } - if codes == nil || codes.Entities == nil || len(*codes.Entities) == 0 { - return codeIds, resp, nil + + if rc.GetCacheSize(p.wrapupCodeCache) == *wrapupcodes.Total && rc.GetCacheSize(p.wrapupCodeCache) != 0 { + return rc.GetCache(p.wrapupCodeCache), nil, nil + } else if rc.GetCacheSize(p.wrapupCodeCache) != *wrapupcodes.Total && rc.GetCacheSize(p.wrapupCodeCache) != 0 { + // The cache is populated but not with the right data, clear the cache so it can be re populated + p.wrapupCodeCache = rc.NewResourceCache[platformclientv2.Wrapupcode]() } - for _, code := range *codes.Entities { - codeIds = append(codeIds, *code.Id) + if wrapupcodes == nil || wrapupcodes.Entities == nil || len(*wrapupcodes.Entities) == 0 { + return &allWrapupcodes, apiResponse, nil } - for pageNum := 2; pageNum <= *codes.PageCount; pageNum++ { - codes, resp, err := p.routingApi.GetRoutingQueueWrapupcodes(queueId, pageSize, pageNum) + allWrapupcodes = append(allWrapupcodes, *wrapupcodes.Entities...) + + for pageNum := 2; pageNum <= *wrapupcodes.PageCount; pageNum++ { + wrapupcodes, apiResponse, err := p.routingApi.GetRoutingQueueWrapupcodes(queueId, pageSize, pageNum) if err != nil { - return nil, resp, fmt.Errorf("failed to page of wrapup codes for queue %s: %s", queueId, err) + return nil, apiResponse, fmt.Errorf("failed to get routing wrapupcode : %v", err) } - if codes == nil || codes.Entities != nil || len(*codes.Entities) == 0 { + + if wrapupcodes == nil || wrapupcodes.Entities == nil || len(*wrapupcodes.Entities) == 0 { break } - for _, code := range *codes.Entities { - codeIds = append(codeIds, *code.Id) - } + + allWrapupcodes = append(allWrapupcodes, *wrapupcodes.Entities...) } - return codeIds, resp, nil + // Cache the routing wrapupcodes resource into the p.routingWrapupcodesCache for later use + for _, wrapupcode := range allWrapupcodes { + rc.SetCache(p.wrapupCodeCache, *wrapupcode.Id, wrapupcode) + } + + return &allWrapupcodes, apiResponse, nil +} + +func createRoutingQueueWrapupCodeFn(ctx context.Context, p *RoutingQueueProxy, queueId string, body []platformclientv2.Wrapupcodereference) ([]platformclientv2.Wrapupcode, *platformclientv2.APIResponse, error) { + return p.routingApi.PostRoutingQueueWrapupcodes(queueId, body) +} + +func deleteRoutingQueueWrapupCodeFn(ctx context.Context, p *RoutingQueueProxy, queueId, codeId string) (*platformclientv2.APIResponse, error) { + return p.routingApi.DeleteRoutingQueueWrapupcode(queueId, codeId) +} + +func addOrRemoveMembersFn(ctx context.Context, p *RoutingQueueProxy, queueId string, body []platformclientv2.Writableentity, delete bool) (*platformclientv2.APIResponse, error) { + return p.routingApi.PostRoutingQueueMembers(queueId, body, delete) +} + +func updateRoutingQueueMemberFn(ctx context.Context, p *RoutingQueueProxy, queueId, userId string, body platformclientv2.Queuemember) (*platformclientv2.APIResponse, error) { + return p.routingApi.PatchRoutingQueueMember(queueId, userId, body) } diff --git a/genesyscloud/routing_queue/resource_genesyscloud_routing_queue.go b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue.go index 61413ffc8..ac42d0312 100644 --- a/genesyscloud/routing_queue/resource_genesyscloud_routing_queue.go +++ b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue.go @@ -2,12 +2,9 @@ package routing_queue import ( "context" - "encoding/json" "fmt" "log" "net/http" - "net/url" - "strings" "terraform-provider-genesyscloud/genesyscloud/consistency_checker" "terraform-provider-genesyscloud/genesyscloud/provider" "terraform-provider-genesyscloud/genesyscloud/util" @@ -42,6 +39,10 @@ func getAllRoutingQueues(ctx context.Context, clientConfig *platformclientv2.Con return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to get routing queues: %s", err), resp) } + if queues == nil || len(*queues) == 0 { + return resources, nil + } + for _, queue := range *queues { resources[*queue.Id] = &resourceExporter.ResourceMeta{Name: *queue.Name} } @@ -49,12 +50,14 @@ func getAllRoutingQueues(ctx context.Context, clientConfig *platformclientv2.Con return resources, nil } -func createQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func createRoutingQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig) + proxy := GetRoutingQueueProxy(sdkConfig) divisionID := d.Get("division_id").(string) scoringMethod := d.Get("scoring_method").(string) + peerId := d.Get("peer_id").(string) + sourceQueueId := d.Get("source_queue_id").(string) skillGroups := buildMemberGroupList(d, "skill_groups", "SKILLGROUP") groups := buildMemberGroupList(d, "groups", "GROUP") teams := buildMemberGroupList(d, "teams", "TEAM") @@ -107,16 +110,22 @@ func createQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) if divisionID != "" { createQueue.Division = &platformclientv2.Writabledivision{Id: &divisionID} } - if scoringMethod != "" { createQueue.ScoringMethod = &scoringMethod } - queue, resp, err := routingAPI.PostRoutingQueues(createQueue) - if err != nil { - log.Printf("error while trying to create queue: %s. Err %s", *createQueue.Name, err) - return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create queue %s error: %s", *createQueue.Name, err), resp) + if peerId != "" { + createQueue.PeerId = &peerId + } + if sourceQueueId != "" { + createQueue.SourceQueueId = &sourceQueueId } + log.Printf("Creating Routing Queue %s", *createQueue.Name) + + queue, resp, err := proxy.createRoutingQueue(ctx, &createQueue) + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create queue %s | error: %s", *createQueue.Name, err), resp) + } if resp.StatusCode != http.StatusOK { return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to create queue %s with error: %s, status code %v", *createQueue.Name, err, resp.StatusCode), resp) } @@ -128,22 +137,24 @@ func createQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) return diagErr } - diagErr = updateQueueWrapupCodes(d, routingAPI) + diagErr = updateQueueWrapupCodes(d, sdkConfig) if diagErr != nil { return diagErr } - return readQueue(ctx, d, meta) + log.Printf("Created Routing Queue %s", d.Id()) + return readRoutingQueue(ctx, d, meta) } -func readQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func readRoutingQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { sdkConfig := meta.(*provider.ProviderMeta).ClientConfig proxy := GetRoutingQueueProxy(sdkConfig) cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingQueue(), constants.DefaultConsistencyChecks, resourceName) log.Printf("Reading queue %s", d.Id()) + return util.WithRetriesForRead(ctx, d, func() *retry.RetryError { - currentQueue, resp, getErr := proxy.getRoutingQueueById(ctx, d.Id()) + currentQueue, resp, getErr := proxy.getRoutingQueueById(ctx, d.Id(), true) if getErr != nil { if util.IsStatus404(resp) { return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read queue %s | error: %s", d.Id(), getErr), resp)) @@ -154,7 +165,6 @@ func readQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) di resourcedata.SetNillableValue(d, "name", currentQueue.Name) resourcedata.SetNillableValue(d, "description", currentQueue.Description) resourcedata.SetNillableValue(d, "skill_evaluation_method", currentQueue.SkillEvaluationMethod) - resourcedata.SetNillableReferenceDivision(d, "division_id", currentQueue.Division) _ = d.Set("acw_wrapup_prompt", nil) @@ -180,16 +190,31 @@ func readQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) di resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "media_settings_message", currentQueue.MediaSettings.Message, flattenMediaSetting) } + _ = d.Set("outbound_messaging_sms_address_id", nil) + _ = d.Set("outbound_messaging_whatsapp_recipient_id", nil) + _ = d.Set("outbound_messaging_open_messaging_recipient_id", nil) + + if currentQueue.OutboundMessagingAddresses != nil { + if currentQueue.OutboundMessagingAddresses.SmsAddress != nil { + _ = d.Set("outbound_messaging_sms_address_id", *currentQueue.OutboundMessagingAddresses.SmsAddress.Id) + } + if currentQueue.OutboundMessagingAddresses.WhatsAppRecipient != nil { + _ = d.Set("outbound_messaging_whatsapp_recipient_id", *currentQueue.OutboundMessagingAddresses.WhatsAppRecipient.Id) + } + if currentQueue.OutboundMessagingAddresses.OpenMessagingRecipient != nil { + _ = d.Set("outbound_messaging_open_messaging_recipient_id", *currentQueue.OutboundMessagingAddresses.OpenMessagingRecipient.Id) + } + } + if currentQueue.AgentOwnedRouting != nil { resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "agent_owned_routing", currentQueue.AgentOwnedRouting, flattenAgentOwnedRouting) } - resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "routing_rules", currentQueue.RoutingRules, flattenRoutingRules) - if currentQueue.Bullseye != nil { resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "bullseye_rings", currentQueue.Bullseye.Rings, flattenBullseyeRings) } + resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "routing_rules", currentQueue.RoutingRules, flattenRoutingRules) resourcedata.SetNillableReference(d, "queue_flow_id", currentQueue.QueueFlow) resourcedata.SetNillableReference(d, "message_in_queue_flow_id", currentQueue.MessageInQueueFlow) resourcedata.SetNillableReference(d, "email_in_queue_flow_id", currentQueue.EmailInQueueFlow) @@ -203,6 +228,8 @@ func readQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) di resourcedata.SetNillableValue(d, "calling_party_name", currentQueue.CallingPartyName) resourcedata.SetNillableValue(d, "calling_party_number", currentQueue.CallingPartyNumber) resourcedata.SetNillableValue(d, "scoring_method", currentQueue.ScoringMethod) + resourcedata.SetNillableValue(d, "peer_id", currentQueue.PeerId) + resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "direct_routing", currentQueue.DirectRouting, flattenDirectRouting) if currentQueue.DefaultScripts != nil { _ = d.Set("default_script_ids", flattenDefaultScripts(*currentQueue.DefaultScripts)) @@ -210,14 +237,6 @@ func readQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) di _ = d.Set("default_script_ids", nil) } - if currentQueue.OutboundMessagingAddresses != nil && currentQueue.OutboundMessagingAddresses.SmsAddress != nil { - _ = d.Set("outbound_messaging_sms_address_id", *currentQueue.OutboundMessagingAddresses.SmsAddress.Id) - } else { - _ = d.Set("outbound_messaging_sms_address_id", nil) - } - - resourcedata.SetNillableValueWithInterfaceArrayWithFunc(d, "direct_routing", currentQueue.DirectRouting, flattenDirectRouting) - wrapupCodes, err := flattenQueueWrapupCodes(ctx, d.Id(), proxy) if err != nil { return retry.NonRetryableError(fmt.Errorf("%v", err)) @@ -255,20 +274,22 @@ func readQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) di log.Printf("%s is set, not reading outbound_email_address attribute in routing_queue %s resource", featureToggles.OEAToggleName(), d.Id()) } - log.Printf("Done reading queue %s %s", d.Id(), *currentQueue.Name) + log.Printf("Read queue %s %s", d.Id(), *currentQueue.Name) return cc.CheckState(d) }) } -func updateQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func updateRoutingQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig) + proxy := GetRoutingQueueProxy(sdkConfig) + scoringMethod := d.Get("scoring_method").(string) skillGroups := buildMemberGroupList(d, "skill_groups", "SKILLGROUP") groups := buildMemberGroupList(d, "groups", "GROUP") teams := buildMemberGroupList(d, "teams", "TEAM") memberGroups := append(*skillGroups, *groups...) memberGroups = append(memberGroups, *teams...) + peerId := d.Get("peer_id").(string) updateQueue := platformclientv2.Queuerequest{ Name: platformclientv2.String(d.Get("name").(string)), @@ -297,18 +318,21 @@ func updateQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) MemberGroups: &memberGroups, } - diagErr := addCGRAndOEA(routingAPI, d, &updateQueue) + diagErr := addCGRAndOEA(proxy, d, &updateQueue) if diagErr != nil { return diagErr } - log.Printf("Updating queue %s", *updateQueue.Name) - if scoringMethod != "" { updateQueue.ScoringMethod = &scoringMethod } + if peerId != "" { + updateQueue.PeerId = &peerId + } - _, resp, err := routingAPI.PutRoutingQueue(d.Id(), updateQueue) + log.Printf("Updating queue %s", *updateQueue.Name) + + _, resp, err := proxy.updateRoutingQueue(ctx, d.Id(), &updateQueue) if err != nil { return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update queue %s error: %s", *updateQueue.Name, err), resp) } @@ -323,13 +347,13 @@ func updateQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) return diagErr } - diagErr = updateQueueWrapupCodes(d, routingAPI) + diagErr = updateQueueWrapupCodes(d, sdkConfig) if diagErr != nil { return diagErr } - log.Printf("Finished updating queue %s", *updateQueue.Name) - return readQueue(ctx, d, meta) + log.Printf("Updated queue %s", *updateQueue.Name) + return readRoutingQueue(ctx, d, meta) } /* @@ -337,8 +361,8 @@ DEVTOOLING-751: If conditional group routing rules and outbound email address ar they are being removed when the parent queue is updated since the update body does not contain them. If the independent resources are enabled, pass in the current OEA and/or CGR to the update queue so they are not removed */ -func addCGRAndOEA(routingAPI *platformclientv2.RoutingApi, d *schema.ResourceData, queue *platformclientv2.Queuerequest) diag.Diagnostics { - currentQueue, resp, err := routingAPI.GetRoutingQueue(d.Id()) +func addCGRAndOEA(proxy *RoutingQueueProxy, d *schema.ResourceData, queue *platformclientv2.Queuerequest) diag.Diagnostics { + currentQueue, resp, err := proxy.getRoutingQueueById(ctx, d.Id(), true) if err != nil { return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to get queue %s for update, error: %s", *queue.Name, err), resp) } @@ -354,7 +378,7 @@ func addCGRAndOEA(routingAPI *platformclientv2.RoutingApi, d *schema.ResourceDat queue.ConditionalGroupRouting = currentQueue.ConditionalGroupRouting // remove queue_id from first CGR rule to avoid api error - if len(*queue.ConditionalGroupRouting.Rules) > 0 { + if queue.ConditionalGroupRouting != nil && len(*queue.ConditionalGroupRouting.Rules) > 0 { (*queue.ConditionalGroupRouting.Rules)[0].Queue = nil } } @@ -363,20 +387,22 @@ func addCGRAndOEA(routingAPI *platformclientv2.RoutingApi, d *schema.ResourceDat queue.OutboundEmailAddress = buildSdkQueueEmailAddress(d) } else { log.Printf("%s is set, not updating outbound_email_address attribute in routing_queue %s resource", featureToggles.OEAToggleName(), d.Id()) - queue.OutboundEmailAddress = *currentQueue.OutboundEmailAddress + + if currentQueue.OutboundEmailAddress != nil { + queue.OutboundEmailAddress = *currentQueue.OutboundEmailAddress + } } return nil } -func deleteQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func deleteRoutingQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { name := d.Get("name").(string) - sdkConfig := meta.(*provider.ProviderMeta).ClientConfig - routingAPI := platformclientv2.NewRoutingApiWithConfig(sdkConfig) + proxy := GetRoutingQueueProxy(sdkConfig) log.Printf("Deleting queue %s", name) - resp, err := routingAPI.DeleteRoutingQueue(d.Id(), true) + resp, err := proxy.deleteRoutingQueue(ctx, d.Id(), true) if err != nil { return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to delete queue %s error: %s", name, err), resp) } @@ -388,7 +414,7 @@ func deleteQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) //DEVTOOLING-238- Increasing this to a 120 seconds to see if we can temporarily mitigate a problem for a customer return util.WithRetries(ctx, 120*time.Second, func() *retry.RetryError { - _, resp, err := routingAPI.GetRoutingQueue(d.Id()) + _, resp, err := proxy.getRoutingQueueById(ctx, d.Id(), false) if err != nil { if util.IsStatus404(resp) { // Queue deleted @@ -401,578 +427,46 @@ func deleteQueue(ctx context.Context, d *schema.ResourceData, meta interface{}) }) } -func buildSdkMediaSettings(d *schema.ResourceData) *platformclientv2.Queuemediasettings { - queueMediaSettings := &platformclientv2.Queuemediasettings{} - - mediaSettingsCall := d.Get("media_settings_call").([]interface{}) - if mediaSettingsCall != nil && len(mediaSettingsCall) > 0 { - queueMediaSettings.Call = buildSdkMediaSetting(mediaSettingsCall) - } - - mediaSettingsCallback := d.Get("media_settings_callback").([]interface{}) - if mediaSettingsCallback != nil && len(mediaSettingsCallback) > 0 { - queueMediaSettings.Callback = buildSdkMediaSettingCallback(mediaSettingsCallback) - } - - mediaSettingsChat := d.Get("media_settings_chat").([]interface{}) - if mediaSettingsChat != nil && len(mediaSettingsChat) > 0 { - queueMediaSettings.Chat = buildSdkMediaSetting(mediaSettingsChat) - } - - mediaSettingsEmail := d.Get("media_settings_email").([]interface{}) - log.Printf("The media settings email #%v", mediaSettingsEmail) - if mediaSettingsEmail != nil && len(mediaSettingsEmail) > 0 { - queueMediaSettings.Email = buildSdkMediaSetting(mediaSettingsEmail) - } - - mediaSettingsMessage := d.Get("media_settings_message").([]interface{}) - if mediaSettingsMessage != nil && len(mediaSettingsMessage) > 0 { - queueMediaSettings.Message = buildSdkMediaSetting(mediaSettingsMessage) - } - - return queueMediaSettings -} - -func constructAgentOwnedRouting(d *schema.ResourceData) *platformclientv2.Agentownedrouting { - if agentOwnedRouting, ok := d.Get("agent_owned_routing").([]interface{}); ok { - if agentOwnedRouting != nil && len(agentOwnedRouting) > 0 { - return buildAgentOwnedRouting(agentOwnedRouting) - } - } - return nil -} - -func buildAgentOwnedRouting(routing []interface{}) *platformclientv2.Agentownedrouting { - settingsMap := routing[0].(map[string]interface{}) - return &platformclientv2.Agentownedrouting{ - EnableAgentOwnedCallbacks: platformclientv2.Bool(settingsMap["enable_agent_owned_callbacks"].(bool)), - MaxOwnedCallbackDelayHours: platformclientv2.Int(settingsMap["max_owned_callback_delay_hours"].(int)), - MaxOwnedCallbackHours: platformclientv2.Int(settingsMap["max_owned_callback_hours"].(int)), - } -} - -func buildSdkMediaSetting(settings []interface{}) *platformclientv2.Mediasettings { - settingsMap := settings[0].(map[string]interface{}) - - return &platformclientv2.Mediasettings{ - AlertingTimeoutSeconds: platformclientv2.Int(settingsMap["alerting_timeout_sec"].(int)), - EnableAutoAnswer: platformclientv2.Bool(settingsMap["enable_auto_answer"].(bool)), - ServiceLevel: &platformclientv2.Servicelevel{ - Percentage: platformclientv2.Float64(settingsMap["service_level_percentage"].(float64)), - DurationMs: platformclientv2.Int(settingsMap["service_level_duration_ms"].(int)), - }, - } -} -func buildSdkMediaSettingCallback(settings []interface{}) *platformclientv2.Callbackmediasettings { - settingsMap := settings[0].(map[string]interface{}) - - return &platformclientv2.Callbackmediasettings{ - AlertingTimeoutSeconds: platformclientv2.Int(settingsMap["alerting_timeout_sec"].(int)), - ServiceLevel: &platformclientv2.Servicelevel{ - Percentage: platformclientv2.Float64(settingsMap["service_level_percentage"].(float64)), - DurationMs: platformclientv2.Int(settingsMap["service_level_duration_ms"].(int)), - }, - EnableAutoAnswer: platformclientv2.Bool(settingsMap["enable_auto_answer"].(bool)), - AutoEndDelaySeconds: platformclientv2.Int(settingsMap["auto_end_delay_seconds"].(int)), - AutoDialDelaySeconds: platformclientv2.Int(settingsMap["auto_dial_delay_seconds"].(int)), - EnableAutoDialAndEnd: platformclientv2.Bool(settingsMap["enable_auto_dial_and_end"].(bool)), - } -} - -func flattenAgentOwnedRouting(settings *platformclientv2.Agentownedrouting) []interface{} { - settingsMap := make(map[string]interface{}) - - settingsMap["max_owned_callback_delay_hours"] = *settings.MaxOwnedCallbackDelayHours - resourcedata.SetMapValueIfNotNil(settingsMap, "enable_agent_owned_callbacks", settings.EnableAgentOwnedCallbacks) - settingsMap["max_owned_callback_hours"] = *settings.MaxOwnedCallbackHours - - return []interface{}{settingsMap} -} - -func flattenMediaSetting(settings *platformclientv2.Mediasettings) []interface{} { - settingsMap := make(map[string]interface{}) - - settingsMap["alerting_timeout_sec"] = *settings.AlertingTimeoutSeconds - resourcedata.SetMapValueIfNotNil(settingsMap, "enable_auto_answer", settings.EnableAutoAnswer) - settingsMap["service_level_percentage"] = *settings.ServiceLevel.Percentage - settingsMap["service_level_duration_ms"] = *settings.ServiceLevel.DurationMs - - return []interface{}{settingsMap} -} - -func flattenMediaSettingCallback(settings *platformclientv2.Callbackmediasettings) []interface{} { - settingsMap := make(map[string]interface{}) - - settingsMap["alerting_timeout_sec"] = *settings.AlertingTimeoutSeconds - settingsMap["service_level_percentage"] = *settings.ServiceLevel.Percentage - settingsMap["service_level_duration_ms"] = *settings.ServiceLevel.DurationMs - resourcedata.SetMapValueIfNotNil(settingsMap, "enable_auto_answer", settings.EnableAutoAnswer) - resourcedata.SetMapValueIfNotNil(settingsMap, "enable_auto_dial_and_end", settings.EnableAutoDialAndEnd) - settingsMap["auto_end_delay_seconds"] = *settings.AutoEndDelaySeconds - settingsMap["auto_dial_delay_seconds"] = *settings.AutoDialDelaySeconds - - return []interface{}{settingsMap} -} - -func buildMemberGroupList(d *schema.ResourceData, groupKey string, groupType string) *[]platformclientv2.Membergroup { - var memberGroups []platformclientv2.Membergroup - if mg, ok := d.GetOk(groupKey); ok { - - for _, mgId := range mg.(*schema.Set).List() { - id := mgId.(string) - - memberGroup := &platformclientv2.Membergroup{Id: &id, VarType: &groupType} - memberGroups = append(memberGroups, *memberGroup) - } - } - - return &memberGroups -} - -func buildSdkRoutingRules(d *schema.ResourceData) *[]platformclientv2.Routingrule { - var routingRules []platformclientv2.Routingrule - if configRoutingRules, ok := d.GetOk("routing_rules"); ok { - for _, configRule := range configRoutingRules.([]interface{}) { - ruleSettings, ok := configRule.(map[string]interface{}) - if !ok { - continue - } - var sdkRule platformclientv2.Routingrule - - resourcedata.BuildSDKStringValueIfNotNil(&sdkRule.Operator, ruleSettings, "operator") - if threshold, ok := ruleSettings["threshold"]; ok { - v := threshold.(int) - sdkRule.Threshold = &v - } - if waitSeconds, ok := ruleSettings["wait_seconds"].(float64); ok { - sdkRule.WaitSeconds = &waitSeconds - } - - routingRules = append(routingRules, sdkRule) - } - } - return &routingRules -} - -func flattenRoutingRules(sdkRoutingRules *[]platformclientv2.Routingrule) []interface{} { - rules := make([]interface{}, len(*sdkRoutingRules)) - for i, sdkRule := range *sdkRoutingRules { - ruleSettings := make(map[string]interface{}) - - resourcedata.SetMapValueIfNotNil(ruleSettings, "operator", sdkRule.Operator) - resourcedata.SetMapValueIfNotNil(ruleSettings, "threshold", sdkRule.Threshold) - resourcedata.SetMapValueIfNotNil(ruleSettings, "wait_seconds", sdkRule.WaitSeconds) - - rules[i] = ruleSettings - } - return rules -} - -func buildSdkBullseyeSettings(d *schema.ResourceData) *platformclientv2.Bullseye { - if configRings, ok := d.GetOk("bullseye_rings"); ok { - var sdkRings []platformclientv2.Ring - for _, configRing := range configRings.([]interface{}) { - ringSettings, ok := configRing.(map[string]interface{}) - if !ok { - continue - } - var sdkRing platformclientv2.Ring - - if waitSeconds, ok := ringSettings["expansion_timeout_seconds"].(float64); ok { - sdkRing.ExpansionCriteria = &[]platformclientv2.Expansioncriterium{ - { - VarType: &bullseyeExpansionTypeTimeout, - Threshold: &waitSeconds, - }, - } - } - - if skillsToRemove, ok := ringSettings["skills_to_remove"]; ok { - skillIds := skillsToRemove.(*schema.Set).List() - if len(skillIds) > 0 { - sdkSkillsToRemove := make([]platformclientv2.Skillstoremove, len(skillIds)) - for i, id := range skillIds { - skillID := id.(string) - sdkSkillsToRemove[i] = platformclientv2.Skillstoremove{ - Id: &skillID, - } - } - sdkRing.Actions = &platformclientv2.Actions{ - SkillsToRemove: &sdkSkillsToRemove, - } - } - } - - if memberGroups, ok := ringSettings["member_groups"]; ok { - memberGroupList := memberGroups.(*schema.Set).List() - if len(memberGroupList) > 0 { - - sdkMemberGroups := make([]platformclientv2.Membergroup, len(memberGroupList)) - for i, memberGroup := range memberGroupList { - settingsMap := memberGroup.(map[string]interface{}) - memberGroupID := settingsMap["member_group_id"].(string) - memberGroupType := settingsMap["member_group_type"].(string) - - sdkMemberGroups[i] = platformclientv2.Membergroup{ - Id: &memberGroupID, - VarType: &memberGroupType, - } - } - sdkRing.MemberGroups = &sdkMemberGroups - } - } - sdkRings = append(sdkRings, sdkRing) - } - - /* - The routing queues API is a little unusual. You can have up to six bullseye routing rings but the last one is always - a treated as the default ring. This means you can actually ony define a maximum of 5. So, I have changed the behavior of this - resource to only allow you to add 5 items and then the code always adds a 6 item (see the code below) with a default timeout of 2. - */ - var defaultSdkRing platformclientv2.Ring - defaultTimeoutInt := 2 - defaultTimeoutFloat := float64(defaultTimeoutInt) - defaultSdkRing.ExpansionCriteria = &[]platformclientv2.Expansioncriterium{ - { - VarType: &bullseyeExpansionTypeTimeout, - Threshold: &defaultTimeoutFloat, - }, - } - - sdkRings = append(sdkRings, defaultSdkRing) - - return &platformclientv2.Bullseye{Rings: &sdkRings} - } - return nil -} - -/* -The flattenBullseyeRings function maps the data retrieved from our SDK call over to the bullseye_ring attribute within the provider. -You might notice in the code that we are always mapping all but the last item in the list of rings retrieved by the API. The reason for this -is that when you submit a list of bullseye_rings to the API, the API will always take the last item in the list and use it to drive default behavior -This is a change from earlier parts of the API where you could define 6 bullseye rings and there would always be six. Now when you define bullseye rings, -the public API will take the list item in the list and make it the default and it will not show up on the screen. To get around this you needed -to always add a dumb bullseye ring block. Now, we automatically add one for you. We only except a maximum of 5 bullseyes_ring blocks, but we will always -remove the last block returned by the API. -*/ -func flattenBullseyeRings(sdkRings *[]platformclientv2.Ring) []interface{} { - rings := make([]interface{}, len(*sdkRings)-1) //Sizing the target array of Rings to account for us removing the default block - for i, sdkRing := range *sdkRings { - if i < len(*sdkRings)-1 { //Checking to make sure we are do nothing with the last item in the list by skipping processing if it is defined - ringSettings := make(map[string]interface{}) - if sdkRing.ExpansionCriteria != nil { - for _, criteria := range *sdkRing.ExpansionCriteria { - if *criteria.VarType == bullseyeExpansionTypeTimeout { - ringSettings["expansion_timeout_seconds"] = *criteria.Threshold - break - } - } - } - - if sdkRing.Actions != nil && sdkRing.Actions.SkillsToRemove != nil { - skillIds := make([]interface{}, len(*sdkRing.Actions.SkillsToRemove)) - for s, skill := range *sdkRing.Actions.SkillsToRemove { - skillIds[s] = *skill.Id - } - ringSettings["skills_to_remove"] = schema.NewSet(schema.HashString, skillIds) - } - - if sdkRing.MemberGroups != nil { - memberGroups := schema.NewSet(schema.HashResource(memberGroupResource), []interface{}{}) - - for _, memberGroup := range *sdkRing.MemberGroups { - memberGroupMap := make(map[string]interface{}) - memberGroupMap["member_group_id"] = *memberGroup.Id - memberGroupMap["member_group_type"] = *memberGroup.VarType - - memberGroups.Add(memberGroupMap) - } - - ringSettings["member_groups"] = memberGroups - } - rings[i] = ringSettings - } - } - return rings -} - -func buildSdkConditionalGroupRouting(d *schema.ResourceData) (*platformclientv2.Conditionalgrouprouting, diag.Diagnostics) { - if configRules, ok := d.GetOk("conditional_group_routing_rules"); ok { - var sdkCGRRules []platformclientv2.Conditionalgrouproutingrule - for i, configRules := range configRules.([]interface{}) { - ruleSettings, ok := configRules.(map[string]interface{}) - if !ok { - continue - } - var sdkCGRRule platformclientv2.Conditionalgrouproutingrule - - if waitSeconds, ok := ruleSettings["wait_seconds"].(int); ok { - sdkCGRRule.WaitSeconds = &waitSeconds - } - resourcedata.BuildSDKStringValueIfNotNil(&sdkCGRRule.Operator, ruleSettings, "operator") - if conditionValue, ok := ruleSettings["condition_value"].(float64); ok { - sdkCGRRule.ConditionValue = &conditionValue - } - resourcedata.BuildSDKStringValueIfNotNil(&sdkCGRRule.Metric, ruleSettings, "metric") - - if queueId, ok := ruleSettings["queue_id"].(string); ok && queueId != "" { - if i == 0 { - return nil, util.BuildDiagnosticError(resourceName, fmt.Sprintf("For rule 1, queue_id is always assumed to be the current queue, so queue id should not be specified"), fmt.Errorf("queue id is not nil")) - } - sdkCGRRule.Queue = &platformclientv2.Domainentityref{Id: &queueId} - - } - - if memberGroupList, ok := ruleSettings["groups"].([]interface{}); ok { - if len(memberGroupList) > 0 { - sdkMemberGroups := make([]platformclientv2.Membergroup, len(memberGroupList)) - for i, memberGroup := range memberGroupList { - settingsMap, ok := memberGroup.(map[string]interface{}) - if !ok { - continue - } - - sdkMemberGroups[i] = platformclientv2.Membergroup{ - Id: platformclientv2.String(settingsMap["member_group_id"].(string)), - VarType: platformclientv2.String(settingsMap["member_group_type"].(string)), - } - } - sdkCGRRule.Groups = &sdkMemberGroups - } - } - sdkCGRRules = append(sdkCGRRules, sdkCGRRule) - } - rules := &sdkCGRRules - return &platformclientv2.Conditionalgrouprouting{Rules: rules}, nil - } - return nil, nil -} - -func flattenConditionalGroupRoutingRules(queue *platformclientv2.Queue) []interface{} { - if queue.ConditionalGroupRouting == nil || len(*queue.ConditionalGroupRouting.Rules) == 0 { - return nil - } - - rules := make([]interface{}, len(*queue.ConditionalGroupRouting.Rules)) - for i, rule := range *queue.ConditionalGroupRouting.Rules { - ruleSettings := make(map[string]interface{}) - - resourcedata.SetMapValueIfNotNil(ruleSettings, "wait_seconds", rule.WaitSeconds) - resourcedata.SetMapValueIfNotNil(ruleSettings, "operator", rule.Operator) - resourcedata.SetMapValueIfNotNil(ruleSettings, "condition_value", rule.ConditionValue) - resourcedata.SetMapValueIfNotNil(ruleSettings, "metric", rule.Metric) - - // The first rule is assumed to apply to this queue, so queue_id should be omitted if the conditional grouping routing rule - //is the first one being looked at. - if rule.Queue != nil && i > 0 { - ruleSettings["queue_id"] = *rule.Queue.Id - } - - if rule.Groups != nil { - memberGroups := make([]interface{}, 0) - for _, group := range *rule.Groups { - memberGroupMap := make(map[string]interface{}) - - resourcedata.SetMapValueIfNotNil(memberGroupMap, "member_group_id", group.Id) - resourcedata.SetMapValueIfNotNil(memberGroupMap, "member_group_type", group.VarType) +func createRoutingQueueWrapupCodes(queueID string, codesToAdd []string, sdkConfig *platformclientv2.Configuration) diag.Diagnostics { + proxy := GetRoutingQueueProxy(sdkConfig) + // API restricts wrapup code adds to 100 per call + if len(codesToAdd) > 0 { + chunks := chunksProcess.ChunkItems(codesToAdd, platformWrapupCodeReferenceFunc, 100) - memberGroups = append(memberGroups, memberGroupMap) + chunkProcessor := func(chunk []platformclientv2.Wrapupcodereference) diag.Diagnostics { + _, resp, err := proxy.createRoutingQueueWrapupCode(ctx, queueID, chunk) + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update wrapup codes for queue %s error: %s", queueID, err), resp) } - ruleSettings["groups"] = memberGroups - } - - rules[i] = ruleSettings - } - - return rules -} - -func buildSdkAcwSettings(d *schema.ResourceData) *platformclientv2.Acwsettings { - acwWrapupPrompt := d.Get("acw_wrapup_prompt").(string) - - acwSettings := platformclientv2.Acwsettings{ - WrapupPrompt: &acwWrapupPrompt, // Set or default - } - - // Only set timeout for certain wrapup prompt types - if acwWrapupPrompt == "MANDATORY_TIMEOUT" || acwWrapupPrompt == "MANDATORY_FORCED_TIMEOUT" || acwWrapupPrompt == "AGENT_REQUESTED" { - acwTimeoutMs, hasTimeout := d.GetOk("acw_timeout_ms") - if hasTimeout { - timeout := acwTimeoutMs.(int) - acwSettings.TimeoutMs = &timeout - } - } - return &acwSettings -} - -func buildSdkDefaultScriptsMap(d *schema.ResourceData) *map[string]platformclientv2.Script { - if scriptIds, ok := d.GetOk("default_script_ids"); ok { - scriptMap := scriptIds.(map[string]interface{}) - - results := make(map[string]platformclientv2.Script) - for k, v := range scriptMap { - scriptID := v.(string) - results[k] = platformclientv2.Script{Id: &scriptID} - } - return &results - } - return nil -} - -func flattenDefaultScripts(sdkScripts map[string]platformclientv2.Script) map[string]interface{} { - if len(sdkScripts) == 0 { - return nil - } - - results := make(map[string]interface{}) - for k, v := range sdkScripts { - results[k] = *v.Id - } - return results -} - -func validateMapCommTypes(val interface{}, _ cty.Path) diag.Diagnostics { - if val == nil { - return nil - } - - commTypes := []string{"CALL", "CALLBACK", "CHAT", "COBROWSE", "EMAIL", "MESSAGE", "SOCIAL_EXPRESSION", "VIDEO", "SCREENSHARE"} - m := val.(map[string]interface{}) - for k := range m { - if !lists.ItemInSlice(k, commTypes) { - return util.BuildDiagnosticError(resourceName, fmt.Sprintf("%s is an invalid communication type key.", k), fmt.Errorf("invalid communication type key")) - } - } - return nil -} - -func buildSdkQueueMessagingAddresses(d *schema.ResourceData) *platformclientv2.Queuemessagingaddresses { - if _, ok := d.GetOk("outbound_messaging_sms_address_id"); ok { - return &platformclientv2.Queuemessagingaddresses{ - SmsAddress: util.BuildSdkDomainEntityRef(d, "outbound_messaging_sms_address_id"), - } - } - return nil -} - -func buildSdkQueueEmailAddress(d *schema.ResourceData) *platformclientv2.Queueemailaddress { - outboundEmailAddress := d.Get("outbound_email_address").([]interface{}) - if outboundEmailAddress != nil && len(outboundEmailAddress) > 0 { - settingsMap := outboundEmailAddress[0].(map[string]interface{}) - - inboundRoute := &platformclientv2.Inboundroute{ - Id: platformclientv2.String(settingsMap["route_id"].(string)), - } - return &platformclientv2.Queueemailaddress{ - Domain: &platformclientv2.Domainentityref{Id: platformclientv2.String(settingsMap["domain_id"].(string))}, - Route: &inboundRoute, + return nil } + return chunksProcess.ProcessChunks(chunks, chunkProcessor) } - return nil -} - -func FlattenQueueEmailAddress(settings platformclientv2.Queueemailaddress) map[string]interface{} { - settingsMap := make(map[string]interface{}) - resourcedata.SetMapReferenceValueIfNotNil(settingsMap, "domain_id", settings.Domain) - - if settings.Route != nil { - route := *settings.Route - settingsMap["route_id"] = *route.Id - } - - return settingsMap -} -func buildSdkDirectRouting(d *schema.ResourceData) *platformclientv2.Directrouting { - directRouting := d.Get("direct_routing").([]interface{}) - if directRouting != nil && len(directRouting) > 0 { - settingsMap := directRouting[0].(map[string]interface{}) - - agentWaitSeconds := settingsMap["agent_wait_seconds"].(int) - waitForAgent := settingsMap["wait_for_agent"].(bool) - - callUseAgentAddressOutbound := settingsMap["call_use_agent_address_outbound"].(bool) - callSettings := &platformclientv2.Directroutingmediasettings{ - UseAgentAddressOutbound: &callUseAgentAddressOutbound, - } - - emailUseAgentAddressOutbound := settingsMap["email_use_agent_address_outbound"].(bool) - emailSettings := &platformclientv2.Directroutingmediasettings{ - UseAgentAddressOutbound: &emailUseAgentAddressOutbound, - } - - messageUseAgentAddressOutbound := settingsMap["message_use_agent_address_outbound"].(bool) - messageSettings := &platformclientv2.Directroutingmediasettings{ - UseAgentAddressOutbound: &messageUseAgentAddressOutbound, - } - - sdkDirectRouting := &platformclientv2.Directrouting{ - CallMediaSettings: callSettings, - EmailMediaSettings: emailSettings, - MessageMediaSettings: messageSettings, - WaitForAgent: &waitForAgent, - AgentWaitSeconds: &agentWaitSeconds, - } - - if backUpQueueId, ok := settingsMap["backup_queue_id"].(string); ok && backUpQueueId != "" { - sdkDirectRouting.BackupQueueId = &backUpQueueId - } - - return sdkDirectRouting - } return nil } -func flattenDirectRouting(settings *platformclientv2.Directrouting) []interface{} { - settingsMap := make(map[string]interface{}) - - if settings.BackupQueueId != nil { - settingsMap["backup_queue_id"] = *settings.BackupQueueId - } - if settings.AgentWaitSeconds != nil { - settingsMap["agent_wait_seconds"] = *settings.AgentWaitSeconds - } - if settings.WaitForAgent != nil { - settingsMap["wait_for_agent"] = *settings.WaitForAgent - } - - if settings.CallMediaSettings != nil { - callSettings := *settings.CallMediaSettings - settingsMap["call_use_agent_address_outbound"] = *callSettings.UseAgentAddressOutbound - } - if settings.EmailMediaSettings != nil { - emailSettings := *settings.EmailMediaSettings - settingsMap["email_use_agent_address_outbound"] = *emailSettings.UseAgentAddressOutbound - } - if settings.MessageMediaSettings != nil { - messageSettings := *settings.MessageMediaSettings - settingsMap["message_use_agent_address_outbound"] = *messageSettings.UseAgentAddressOutbound - } - - return []interface{}{settingsMap} -} +func updateQueueWrapupCodes(d *schema.ResourceData, sdkConfig *platformclientv2.Configuration) diag.Diagnostics { + proxy := GetRoutingQueueProxy(sdkConfig) -func updateQueueWrapupCodes(d *schema.ResourceData, routingAPI *platformclientv2.RoutingApi) diag.Diagnostics { if d.HasChange("wrapup_codes") { + log.Printf("Updating Routing Queue WrapupCodes") + if codesConfig := d.Get("wrapup_codes"); codesConfig != nil { // Get existing codes - codes, err := getRoutingQueueWrapupCodes(d.Id(), routingAPI) + codes, resp, err := proxy.getAllRoutingQueueWrapupCodes(ctx, d.Id()) if err != nil { - return err + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to query wrapup codes for queue %s error: %s", d.Id(), err), resp) } - var existingCodes []string - if codes != nil { - for _, code := range codes { - existingCodes = append(existingCodes, *code.Id) - } - } + existingCodes := getWrapupCodeIds(codes) configCodes := *lists.SetToStringList(codesConfig.(*schema.Set)) - codesToRemove := lists.SliceDifference(existingCodes, configCodes) + + // Remove Wrapup Codes if len(codesToRemove) > 0 { for _, codeId := range codesToRemove { - resp, err := routingAPI.DeleteRoutingQueueWrapupcode(d.Id(), codeId) + resp, err := proxy.deleteRoutingQueueWrapupCode(ctx, d.Id(), codeId) if err != nil { if util.IsStatus404(resp) { // Ignore missing queue or wrapup code @@ -983,9 +477,10 @@ func updateQueueWrapupCodes(d *schema.ResourceData, routingAPI *platformclientv2 } } + // Add Wrapup Codes codesToAdd := lists.SliceDifference(configCodes, existingCodes) if len(codesToAdd) > 0 { - err := addWrapupCodesInChunks(d.Id(), codesToAdd, routingAPI) + err := createRoutingQueueWrapupCodes(d.Id(), codesToAdd, sdkConfig) if err != nil { return err } @@ -995,337 +490,35 @@ func updateQueueWrapupCodes(d *schema.ResourceData, routingAPI *platformclientv2 return nil } -func addWrapupCodesInChunks(queueID string, codesToAdd []string, api *platformclientv2.RoutingApi) diag.Diagnostics { - // API restricts wrapup code adds to 100 per call - const maxBatchSize = 100 - for i := 0; i < len(codesToAdd); i += maxBatchSize { - end := i + maxBatchSize - if end > len(codesToAdd) { - end = len(codesToAdd) - } - var updateChunk []platformclientv2.Wrapupcodereference - for j := i; j < end; j++ { - updateChunk = append(updateChunk, platformclientv2.Wrapupcodereference{Id: &codesToAdd[j]}) - } - - if len(updateChunk) > 0 { - _, resp, err := api.PostRoutingQueueWrapupcodes(queueID, updateChunk) - if err != nil { - return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update wrapup codes for queue %s error: %s", queueID, err), resp) - } - } - } - return nil -} - -func getRoutingQueueWrapupCodes(queueID string, api *platformclientv2.RoutingApi) ([]platformclientv2.Wrapupcode, diag.Diagnostics) { - const maxPageSize = 100 - - var codes []platformclientv2.Wrapupcode - for pageNum := 1; ; pageNum++ { - codeResult, resp, err := api.GetRoutingQueueWrapupcodes(queueID, maxPageSize, pageNum) - if err != nil { - return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to query wrapup codes for queue %s error: %s", queueID, err), resp) - } - if codeResult == nil || codeResult.Entities == nil || len(*codeResult.Entities) == 0 { - return codes, nil - } - for _, code := range *codeResult.Entities { - codes = append(codes, code) +func getWrapupCodeIds(codes *[]platformclientv2.Wrapupcode) []string { + var wrapupCodes []string + if codes != nil { + for _, code := range *codes { + wrapupCodes = append(wrapupCodes, *code.Id) } } + return wrapupCodes } -func updateQueueMembers(d *schema.ResourceData, sdkConfig *platformclientv2.Configuration) diag.Diagnostics { - if !d.HasChange("members") { - return nil - } - membersSet, ok := d.Get("members").(*schema.Set) - if !ok || membersSet.Len() == 0 { - if err := removeAllExistingUserMembersFromQueue(d.Id(), sdkConfig); err != nil { - return diag.FromErr(err) - } +func validateMapCommTypes(val interface{}, _ cty.Path) diag.Diagnostics { + if val == nil { return nil } - log.Printf("Updating members for Queue %s", d.Get("name")) - newUserRingNums := make(map[string]int) - memberList := membersSet.List() - newUserIds := make([]string, len(memberList)) - for i, member := range memberList { - memberMap := member.(map[string]interface{}) - newUserIds[i] = memberMap["user_id"].(string) - newUserRingNums[newUserIds[i]] = memberMap["ring_num"].(int) - } - - if len(newUserIds) > 0 { - log.Printf("Sleeping for 10 seconds") - time.Sleep(10 * time.Second) - - members, diagErr := getRoutingQueueMembers(d.Id(), "group", sdkConfig) - if diagErr != nil { - return diagErr - } - - for _, userId := range newUserIds { - if err := verifyUserIsNotGroupMemberOfQueue(d.Id(), userId, members); err != nil { - return util.BuildDiagnosticError(resourceName, "failed to update queue member: ", err) - } - } - } - - oldSdkUsers, err := getRoutingQueueMembers(d.Id(), "user", sdkConfig) - if err != nil { - return err - } - - oldUserIds := make([]string, len(oldSdkUsers)) - oldUserRingNums := make(map[string]int) - for i, user := range oldSdkUsers { - oldUserIds[i] = *user.Id - oldUserRingNums[oldUserIds[i]] = *user.RingNumber - } - - if len(oldUserIds) > 0 { - usersToRemove := lists.SliceDifference(oldUserIds, newUserIds) - err := updateMembersInChunks(d.Id(), usersToRemove, true, sdkConfig) - if err != nil { - return err - } - } - - if len(newUserIds) > 0 { - usersToAdd := lists.SliceDifference(newUserIds, oldUserIds) - err := updateMembersInChunks(d.Id(), usersToAdd, false, sdkConfig) - if err != nil { - return err - } - } - - // Check for ring numbers to update - for userID, newNum := range newUserRingNums { - if oldNum, found := oldUserRingNums[userID]; found { - if newNum != oldNum { - log.Printf("updating ring_num for user %s because it has updated. New: %v, Old: %v", userID, newNum, oldNum) - // Number changed. Update ring number - err := updateQueueUserRingNum(d.Id(), userID, newNum, sdkConfig) - if err != nil { - return err - } - } - } else if newNum != 1 { - // New queue member. Update ring num if not set to the default of 1 - log.Printf("updating user %s ring_num because it is not the default 1", userID) - err := updateQueueUserRingNum(d.Id(), userID, newNum, sdkConfig) - if err != nil { - return err - } - } - } - log.Printf("Members updated for Queue %s", d.Get("name")) - - return nil -} - -// removeAllExistingUserMembersFromQueue get all existing user members of a given queue and remove them from the queue -func removeAllExistingUserMembersFromQueue(queueId string, sdkConfig *platformclientv2.Configuration) error { - log.Printf("Reading user members of queue %s", queueId) - oldSdkUsers, err := getRoutingQueueMembers(queueId, "user", sdkConfig) - if err != nil { - return fmt.Errorf("%v", err) - } - log.Printf("Read user members of queue %s", queueId) - - var oldUserIds []string - for _, user := range oldSdkUsers { - oldUserIds = append(oldUserIds, *user.Id) - } - - if len(oldUserIds) > 0 { - log.Printf("Removing queue %s user members", queueId) - if err := updateMembersInChunks(queueId, oldUserIds, true, sdkConfig); err != nil { - return fmt.Errorf("%v", err) - } - log.Printf("Removing queue %s user members", queueId) - } - return nil -} - -// verifyUserIsNotGroupMemberOfQueue Search through queue group members to verify that a given user is not a group member -func verifyUserIsNotGroupMemberOfQueue(queueId, userId string, members []platformclientv2.Queuemember) error { - log.Printf("verifying that member '%s' is not assinged to the queue '%s' via a group", userId, queueId) - - for _, member := range members { - if *member.Id == userId { - return fmt.Errorf("member %s is already assigned to queue %s via a group, and therefore should not be assigned as a member", userId, queueId) - } - } - - log.Printf("User %s not found as group member in queue %s", userId, queueId) - return nil -} - -func updateMembersInChunks(queueID string, membersToUpdate []string, remove bool, sdkConfig *platformclientv2.Configuration) diag.Diagnostics { - api := platformclientv2.NewRoutingApiWithConfig(sdkConfig) - // API restricts member adds/removes to 100 per call - // Generic call to prepare chunks for the Update. Takes in three args - // 1. MemberstoUpdate 2. The Entity prepare func for the update 3. Chunk Size - if len(membersToUpdate) > 0 { - chunks := chunksProcess.ChunkItems(membersToUpdate, platformWritableEntityFunc, 100) - // Closure to process the chunks - chunkProcessor := func(chunk []platformclientv2.Writableentity) diag.Diagnostics { - resp, err := api.PostRoutingQueueMembers(queueID, chunk, remove) - if err != nil { - return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update members in queue %s error: %s", queueID, err), resp) - } - return nil + commTypes := []string{"CALL", "CALLBACK", "CHAT", "COBROWSE", "EMAIL", "MESSAGE", "SOCIAL_EXPRESSION", "VIDEO", "SCREENSHARE"} + m := val.(map[string]interface{}) + for k := range m { + if !lists.ItemInSlice(k, commTypes) { + return util.BuildDiagnosticError(resourceName, fmt.Sprintf("%s is an invalid communication type key.", k), fmt.Errorf("invalid communication type key")) } - // Generic Function call which takes in the chunks and the processing function - return chunksProcess.ProcessChunks(chunks, chunkProcessor) } return nil - } func platformWritableEntityFunc(val string) platformclientv2.Writableentity { return platformclientv2.Writableentity{Id: &val} } -func updateQueueUserRingNum(queueID string, userID string, ringNum int, sdkConfig *platformclientv2.Configuration) diag.Diagnostics { - api := platformclientv2.NewRoutingApiWithConfig(sdkConfig) - resp, err := api.PatchRoutingQueueMember(queueID, userID, platformclientv2.Queuemember{ - Id: &userID, - RingNumber: &ringNum, - }) - if err != nil { - return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update ring number for queue %s user %s error: %s", queueID, userID, err), resp) - } - return nil -} - -func getRoutingQueueMembers(queueID string, memberBy string, sdkConfig *platformclientv2.Configuration) ([]platformclientv2.Queuemember, diag.Diagnostics) { - var members []platformclientv2.Queuemember - const pageSize = 100 - api := platformclientv2.NewRoutingApiWithConfig(sdkConfig) - - // Need to call this method to find the member count for a queue. GetRoutingQueueMembers does not return a `total` property for us to use. - queue, resp, err := api.GetRoutingQueue(queueID) - if err != nil { - return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to find queue %s error: %s", queueID, err), resp) - } - queueMembers := *queue.MemberCount - log.Printf("%d members belong to queue %s", queueMembers, queueID) - - for pageNum := 1; ; pageNum++ { - users, resp, err := sdkGetRoutingQueueMembers(queueID, memberBy, pageNum, pageSize, api) - if err != nil || resp.StatusCode != http.StatusOK { - return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to query users for queue %s error: %s", queueID, err), resp) - } - if users == nil || users.Entities == nil || len(*users.Entities) == 0 { - membersFound := len(members) - log.Printf("%d queue members found for queue %s", membersFound, queueID) - - if membersFound != queueMembers { - log.Printf("Member count is not equal to queue member found for queue %s, Correlation Id: %s", queueID, resp.CorrelationID) - } - return members, nil - } - - members = append(members, *users.Entities...) - } -} - -func sdkGetRoutingQueueMembers(queueID, memberBy string, pageNumber, pageSize int, api *platformclientv2.RoutingApi) (*platformclientv2.Queuememberentitylisting, *platformclientv2.APIResponse, error) { - // SDK does not support nil values for boolean query params yet, so we must manually construct this HTTP request for now - apiClient := &api.Configuration.APIClient - - // create path and map variables - path := api.Configuration.BasePath + "/api/v2/routing/queues/{queueId}/members" - path = strings.Replace(path, "{queueId}", queueID, -1) - - headerParams := make(map[string]string) - queryParams := make(map[string]string) - formParams := url.Values{} - var postBody interface{} - var postFileName string - var fileBytes []byte - - // oauth required - if api.Configuration.AccessToken != "" { - headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken - } - // add default headers if any - for key := range api.Configuration.DefaultHeader { - headerParams[key] = api.Configuration.DefaultHeader[key] - } - - queryParams["pageSize"] = apiClient.ParameterToString(pageSize, "") - queryParams["pageNumber"] = apiClient.ParameterToString(pageNumber, "") - if memberBy != "" { - queryParams["memberBy"] = memberBy - } - - headerParams["Content-Type"] = "application/json" - headerParams["Accept"] = "application/json" - - var successPayload *platformclientv2.Queuememberentitylisting - response, err := apiClient.CallAPI(path, http.MethodGet, postBody, headerParams, queryParams, formParams, postFileName, fileBytes) - if err != nil { - // Nothing special to do here, but do avoid processing the response - } else if response.Error != nil { - err = fmt.Errorf(response.ErrorMessage) - } else { - err = json.Unmarshal([]byte(response.RawBody), &successPayload) - } - return successPayload, response, err -} - -func flattenQueueMembers(queueID string, memberBy string, sdkConfig *platformclientv2.Configuration) (*schema.Set, diag.Diagnostics) { - members, err := getRoutingQueueMembers(queueID, memberBy, sdkConfig) - if err != nil { - return nil, err - } - - memberSet := schema.NewSet(schema.HashResource(queueMemberResource), []interface{}{}) - for _, member := range members { - memberMap := make(map[string]interface{}) - memberMap["user_id"] = *member.Id - memberMap["ring_num"] = *member.RingNumber - memberSet.Add(memberMap) - } - - return memberSet, nil -} - -func flattenQueueWrapupCodes(ctx context.Context, queueID string, proxy *RoutingQueueProxy) (*schema.Set, diag.Diagnostics) { - codeIds, resp, err := proxy.getRoutingQueueWrapupCodeIds(ctx, queueID) - if err != nil { - return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to query wrapup codes for queue %s", queueID), resp) - } - - if codeIds != nil { - return lists.StringListToSet(codeIds), nil - } - - return nil, nil -} - -func flattenQueueMemberGroupsList(queue *platformclientv2.Queue, groupType *string) *schema.Set { - var groupIds []string - - if queue == nil || queue.MemberGroups == nil { - return nil - } - - for _, memberGroup := range *queue.MemberGroups { - if strings.Compare(*memberGroup.VarType, *groupType) == 0 { - groupIds = append(groupIds, *memberGroup.Id) - } - } - - if groupIds != nil { - return lists.StringListToSet(groupIds) - } - - return nil +func platformWrapupCodeReferenceFunc(val string) platformclientv2.Wrapupcodereference { + return platformclientv2.Wrapupcodereference{Id: &val} } diff --git a/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_members.go b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_members.go new file mode 100644 index 000000000..d3b62cd5e --- /dev/null +++ b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_members.go @@ -0,0 +1,310 @@ +package routing_queue + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "net/url" + "strings" + "terraform-provider-genesyscloud/genesyscloud/util" + chunksProcess "terraform-provider-genesyscloud/genesyscloud/util/chunks" + "terraform-provider-genesyscloud/genesyscloud/util/lists" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" +) + +var ctx = context.Background() + +// postRoutingQueueMembers allows up to 100 bulk Member addition/removal per call +func postRoutingQueueMembers(queueID string, membersToUpdate []string, remove bool, proxy *RoutingQueueProxy) diag.Diagnostics { + // Generic call to prepare chunks for the Update. Takes in three args + // 1. MemberstoUpdate 2. The Entity prepare func for the update 3. Chunk Size + if len(membersToUpdate) > 0 { + chunks := chunksProcess.ChunkItems(membersToUpdate, platformWritableEntityFunc, 100) + + chunkProcessor := func(chunk []platformclientv2.Writableentity) diag.Diagnostics { + resp, err := proxy.addOrRemoveMembers(ctx, queueID, chunk, remove) + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update members in queue %s error: %s", queueID, err), resp) + } + return nil + } + // Generic Function call which takes in the chunks and the processing function + return chunksProcess.ProcessChunks(chunks, chunkProcessor) + } + return nil +} + +func getRoutingQueueMembers(queueID string, memberBy string, sdkConfig *platformclientv2.Configuration) ([]platformclientv2.Queuemember, diag.Diagnostics) { + proxy := GetRoutingQueueProxy(sdkConfig) + var members []platformclientv2.Queuemember + + // Need to call this method to find the member count for a queue. GetRoutingQueueMembers does not return a `total` property for us to use. + queue, resp, err := proxy.getRoutingQueueById(ctx, queueID, true) + if err != nil { + return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to find queue %s error: %s", queueID, err), resp) + } + + if queue.MemberCount == nil { + log.Printf("no members belong to queue %s", queueID) + return members, nil + } + + queueMembers := *queue.MemberCount + log.Printf("%d members belong to queue %s", queueMembers, queueID) + + for pageNum := 1; ; pageNum++ { + users, resp, err := sdkGetRoutingQueueMembers(queueID, memberBy, pageNum, 100, sdkConfig) + if err != nil || resp.StatusCode != http.StatusOK { + return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to query users for queue %s error: %s", queueID, err), resp) + } + + if users == nil || users.Entities == nil || len(*users.Entities) == 0 { + membersFound := len(members) + log.Printf("%d queue members found for queue %s", membersFound, queueID) + + if membersFound != queueMembers { + log.Printf("Member count is not equal to queue member found for queue %s, Correlation Id: %s", queueID, resp.CorrelationID) + } + return members, nil + } + + members = append(members, *users.Entities...) + } +} + +func updateQueueMembers(d *schema.ResourceData, sdkConfig *platformclientv2.Configuration) diag.Diagnostics { + proxy := GetRoutingQueueProxy(sdkConfig) + + if !d.HasChange("members") { + return nil + } + + membersSet, ok := d.Get("members").(*schema.Set) + if !ok || membersSet.Len() == 0 { + if err := removeAllExistingUserMembersFromQueue(d.Id(), sdkConfig); err != nil { + return diag.FromErr(err) + } + return nil + } + + log.Printf("Updating members for Queue %s", d.Get("name")) + + // Get new and Existing users and ring nums + newUserIds, newUserRingNums := getNewUsersAndRingNums(membersSet) + oldUserIds, oldUserRingNums, err := getExistingUsersAndRingNums(d.Id(), sdkConfig) + if err != nil { + return err + } + + if diagErr := checkUserMembership(d.Id(), newUserIds, sdkConfig); diagErr != nil { + return util.BuildDiagnosticError(resourceName, "failed to update queue member: ", diagErr) + } + + // Check for members to add or remove + if diagErr := addOrRemoveMembers(d.Id(), oldUserIds, newUserIds, proxy); err != nil { + return diagErr + } + + // Check for ring numbers to update + if diagErr := updateRingNumbers(d.Id(), newUserRingNums, oldUserRingNums, sdkConfig); diagErr != nil { + return diagErr + } + + log.Printf("Members updated for Queue %s", d.Get("name")) + return nil +} + +// removeAllExistingUserMembersFromQueue get all existing user members of a given queue and remove them from the queue +func removeAllExistingUserMembersFromQueue(queueId string, sdkConfig *platformclientv2.Configuration) error { + proxy := GetRoutingQueueProxy(sdkConfig) + + log.Printf("Reading user members of queue %s", queueId) + + oldSdkUsers, err := getRoutingQueueMembers(queueId, "user", sdkConfig) + if err != nil { + return fmt.Errorf("%v", err) + } + + log.Printf("Read user members of queue %s", queueId) + + var oldUserIds []string + for _, user := range oldSdkUsers { + oldUserIds = append(oldUserIds, *user.Id) + } + + if len(oldUserIds) > 0 { + log.Printf("Removing queue %s user members", queueId) + if err := postRoutingQueueMembers(queueId, oldUserIds, true, proxy); err != nil { + return fmt.Errorf("%v", err) + } + log.Printf("Removed queue %s user members", queueId) + } + return nil +} + +func updateRingNumbers(queueID string, newUserRingNums, oldUserRingNums map[string]int, sdkConfig *platformclientv2.Configuration) diag.Diagnostics { + for userID, newNum := range newUserRingNums { + if oldNum, found := oldUserRingNums[userID]; found { + if newNum != oldNum { + log.Printf("updating ring_num for user %s because it has updated. New: %v, Old: %v", userID, newNum, oldNum) + if err := updateQueueUserRingNum(queueID, userID, newNum, sdkConfig); err != nil { + return err + } + } + } else if newNum != 1 { + log.Printf("updating user %s ring_num because it is not the default 1", userID) + if err := updateQueueUserRingNum(queueID, userID, newNum, sdkConfig); err != nil { + return err + } + } + } + return nil +} + +func updateQueueUserRingNum(queueID string, userID string, ringNum int, sdkConfig *platformclientv2.Configuration) diag.Diagnostics { + proxy := GetRoutingQueueProxy(sdkConfig) + + log.Printf("Updating ring number for queue %s user %s", queueID, userID) + resp, err := proxy.updateRoutingQueueMember(ctx, queueID, userID, platformclientv2.Queuemember{ + Id: &userID, + RingNumber: &ringNum, + }) + if err != nil { + return util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update ring number for queue %s user %s error: %s", queueID, userID, err), resp) + } + return nil +} + +func sdkGetRoutingQueueMembers(queueID, memberBy string, pageNumber, pageSize int, sdkConfig *platformclientv2.Configuration) (*platformclientv2.Queuememberentitylisting, *platformclientv2.APIResponse, error) { + api := platformclientv2.NewRoutingApiWithConfig(sdkConfig) + // SDK does not support nil values for boolean query params yet, so we must manually construct this HTTP request for now + apiClient := &api.Configuration.APIClient + + // create path and map variables + path := api.Configuration.BasePath + "/api/v2/routing/queues/{queueId}/members" + path = strings.Replace(path, "{queueId}", queueID, -1) + + headerParams := make(map[string]string) + queryParams := make(map[string]string) + formParams := url.Values{} + var postBody interface{} + var postFileName string + var fileBytes []byte + + // oauth required + if api.Configuration.AccessToken != "" { + headerParams["Authorization"] = "Bearer " + api.Configuration.AccessToken + } + // add default headers if any + for key := range api.Configuration.DefaultHeader { + headerParams[key] = api.Configuration.DefaultHeader[key] + } + + queryParams["pageSize"] = apiClient.ParameterToString(pageSize, "") + queryParams["pageNumber"] = apiClient.ParameterToString(pageNumber, "") + if memberBy != "" { + queryParams["memberBy"] = memberBy + } + + headerParams["Content-Type"] = "application/json" + headerParams["Accept"] = "application/json" + + var successPayload *platformclientv2.Queuememberentitylisting + response, err := apiClient.CallAPI(path, http.MethodGet, postBody, headerParams, queryParams, formParams, postFileName, fileBytes) + if err != nil { + // Nothing special to do here, but do avoid processing the response + } else if response.Error != nil { + err = fmt.Errorf(response.ErrorMessage) + } else { + err = json.Unmarshal([]byte(response.RawBody), &successPayload) + } + return successPayload, response, err +} + +func checkUserMembership(queueId string, newUserIds []string, sdkConfig *platformclientv2.Configuration) error { + if len(newUserIds) > 0 { + log.Printf("Sleeping for 10 seconds") + time.Sleep(10 * time.Second) + + members, diagErr := getRoutingQueueMembers(queueId, "group", sdkConfig) + if diagErr != nil { + return fmt.Errorf("%v", diagErr) + } + + for _, userId := range newUserIds { + if err := verifyUserIsNotGroupMemberOfQueue(queueId, userId, members); err != nil { + return err + } + } + } + return nil +} + +// verifyUserIsNotGroupMemberOfQueue Search through queue group members to verify that a given user is not a group member +func verifyUserIsNotGroupMemberOfQueue(queueId, userId string, members []platformclientv2.Queuemember) error { + log.Printf("verifying that member '%s' is not assigned to the queue '%s' via a group", userId, queueId) + + for _, member := range members { + if *member.Id == userId { + return fmt.Errorf("member %s is already assigned to queue %s via a group, and therefore should not be assigned as a member", userId, queueId) + } + } + + log.Printf("User %s not found as group member in queue %s", userId, queueId) + return nil +} + +func getNewUsersAndRingNums(membersSet *schema.Set) ([]string, map[string]int) { + newUserRingNums := make(map[string]int) + memberList := membersSet.List() + newUserIds := make([]string, len(memberList)) + + for i, member := range memberList { + memberMap := member.(map[string]interface{}) + newUserIds[i] = memberMap["user_id"].(string) + newUserRingNums[newUserIds[i]] = memberMap["ring_num"].(int) + } + return newUserIds, newUserRingNums +} + +func getExistingUsersAndRingNums(queueID string, sdkConfig *platformclientv2.Configuration) ([]string, map[string]int, diag.Diagnostics) { + oldSdkUsers, err := getRoutingQueueMembers(queueID, "user", sdkConfig) + if err != nil { + return nil, nil, err + } + + oldUserIds := make([]string, len(oldSdkUsers)) + oldUserRingNums := make(map[string]int) + + for i, user := range oldSdkUsers { + oldUserIds[i] = *user.Id + oldUserRingNums[oldUserIds[i]] = *user.RingNumber + } + + return oldUserIds, oldUserRingNums, nil +} + +func addOrRemoveMembers(queueId string, oldUserIds, newUserIds []string, proxy *RoutingQueueProxy) diag.Diagnostics { + // Remove From Queue + if len(oldUserIds) > 0 { + usersToRemove := lists.SliceDifference(oldUserIds, newUserIds) + if err := postRoutingQueueMembers(queueId, usersToRemove, true, proxy); err != nil { + return err + } + } + + // Add To Queue + if len(newUserIds) > 0 { + usersToAdd := lists.SliceDifference(newUserIds, oldUserIds) + if err := postRoutingQueueMembers(queueId, usersToAdd, false, proxy); err != nil { + return err + } + } + return nil +} diff --git a/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_schema.go b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_schema.go index 30386aa67..a893553b5 100644 --- a/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_schema.go +++ b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_schema.go @@ -39,17 +39,17 @@ var ( "enable_agent_owned_callbacks": { Description: "Enable Agent Owned Callbacks", Type: schema.TypeBool, - Required: true, + Optional: true, }, "max_owned_callback_hours": { Description: "Auto End Delay Seconds Must be >= 7", Type: schema.TypeInt, - Required: true, + Optional: true, }, "max_owned_callback_delay_hours": { Description: "Max Owned Call Back Delay Hours >= 7", Type: schema.TypeInt, - Required: true, + Optional: true, }, }, } @@ -59,7 +59,7 @@ var ( "alerting_timeout_sec": { Description: "Alerting timeout in seconds. Must be >= 7", Type: schema.TypeInt, - Required: true, + Optional: true, ValidateFunc: validation.IntAtLeast(7), }, "auto_end_delay_seconds": { @@ -87,13 +87,13 @@ var ( "service_level_percentage": { Description: "The desired Service Level. A float value between 0 and 1.", Type: schema.TypeFloat, - Required: true, + Optional: true, ValidateFunc: validation.FloatBetween(0, 1), }, "service_level_duration_ms": { Description: "Service Level target in milliseconds. Must be >= 1000", Type: schema.TypeInt, - Required: true, + Optional: true, ValidateFunc: validation.IntAtLeast(1000), }, }, @@ -162,10 +162,10 @@ func ResourceRoutingQueue() *schema.Resource { return &schema.Resource{ Description: "Genesys Cloud Routing Queue", - CreateContext: provider.CreateWithPooledClient(createQueue), - ReadContext: provider.ReadWithPooledClient(readQueue), - UpdateContext: provider.UpdateWithPooledClient(updateQueue), - DeleteContext: provider.DeleteWithPooledClient(deleteQueue), + CreateContext: provider.CreateWithPooledClient(createRoutingQueue), + ReadContext: provider.ReadWithPooledClient(readRoutingQueue), + UpdateContext: provider.UpdateWithPooledClient(updateRoutingQueue), + DeleteContext: provider.DeleteWithPooledClient(deleteRoutingQueue), Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -307,7 +307,7 @@ func ResourceRoutingQueue() *schema.Resource { "operator": { Description: "The operator that compares the actual value against the condition value. Valid values: GreaterThan, GreaterThanOrEqualTo, LessThan, LessThanOrEqualTo.", Type: schema.TypeString, - Required: true, + Optional: true, ValidateFunc: validation.StringInSlice([]string{"GreaterThan", "LessThan", "GreaterThanOrEqualTo", "LessThanOrEqualTo"}, false), }, "metric": { @@ -319,7 +319,7 @@ func ResourceRoutingQueue() *schema.Resource { "condition_value": { Description: "The limit value, beyond which a rule evaluates as true.", Type: schema.TypeFloat, - Required: true, + Optional: true, ValidateFunc: validation.FloatBetween(0, 259200), }, "wait_seconds": { @@ -408,6 +408,16 @@ func ResourceRoutingQueue() *schema.Resource { Type: schema.TypeBool, Optional: true, }, + "peer_id": { + Description: "The ID of an associated external queue", + Optional: true, + Type: schema.TypeString, + }, + "source_queue_id": { + Description: "The id of an existing queue to copy the settings (does not include GPR settings) from when creating a new queue.", + Optional: true, + Type: schema.TypeString, + }, "enable_manual_assignment": { Description: "Indicates whether manual assignment is enabled for this queue.", Type: schema.TypeBool, @@ -443,6 +453,16 @@ func ResourceRoutingQueue() *schema.Resource { Type: schema.TypeString, Optional: true, }, + "outbound_messaging_open_messaging_recipient_id": { + Description: "The unique ID of the outbound messaging open messaging recipient for the queue.", + Type: schema.TypeString, + Optional: true, + }, + "outbound_messaging_whatsapp_recipient_id": { + Description: "The unique ID of the outbound messaging whatsapp recipient for the queue.", + Type: schema.TypeString, + Optional: true, + }, "outbound_email_address": { Description: "The outbound email address settings for this queue.", Type: schema.TypeList, diff --git a/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_test.go b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_test.go index caffb5eb1..053afc1c9 100644 --- a/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_test.go +++ b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_test.go @@ -96,6 +96,8 @@ func TestAccResourceRoutingQueueBasic(t *testing.T) { util.NullValue, // enable_manual_assignment false util.NullValue, // enable_transcription false strconv.Quote(scoringMethod), // scoring Method + util.NullValue, + util.NullValue, GenerateAgentOwnedRouting("agent_owned_routing", util.TrueValue, callbackHours, callbackHours), GenerateMediaSettings("media_settings_call", alertTimeout1, util.FalseValue, slPercent1, slDuration1), GenerateMediaSettingsCallBack("media_settings_callback", alertTimeout1, util.FalseValue, slPercent1, slDuration1, util.TrueValue, slDuration1, slDuration1), @@ -153,6 +155,8 @@ func TestAccResourceRoutingQueueBasic(t *testing.T) { util.TrueValue, // enable_manual_assignment true util.TrueValue, // enable_transcription true strconv.Quote(scoringMethod), + util.NullValue, + util.NullValue, GenerateAgentOwnedRouting("agent_owned_routing", util.TrueValue, callbackHours2, callbackHours2), GenerateMediaSettings("media_settings_call", alertTimeout2, util.FalseValue, slPercent2, slDuration2), GenerateMediaSettings("media_settings_callback", alertTimeout2, util.TrueValue, slPercent2, slDuration2), @@ -275,6 +279,8 @@ func TestAccResourceRoutingQueueConditionalRouting(t *testing.T) { util.NullValue, // enable_audio_monitoring false util.NullValue, // enable_manual_assignment false strconv.Quote("TimestampAndPriority"), + util.NullValue, + util.NullValue, GenerateMediaSettings( "media_settings_call", alertTimeout1, @@ -350,16 +356,14 @@ func TestAccResourceRoutingQueueConditionalRouting(t *testing.T) { groupResourceId, groupName, group.GenerateGroupOwners("genesyscloud_user."+testUserResource+".id"), - ) + - generateRoutingQueueResourceBasic( - queueResource2, - queueName2, - ) + - routingSkillGroup.GenerateRoutingSkillGroupResourceBasic( - skillGroupResourceId, - skillGroupName, - "description", - ) + GenerateRoutingQueueResource( + ) + generateRoutingQueueResourceBasic( + queueResource2, + queueName2, + ) + routingSkillGroup.GenerateRoutingSkillGroupResourceBasic( + skillGroupResourceId, + skillGroupName, + "description", + ) + GenerateRoutingQueueResource( queueResource1, queueName1, queueDesc1, @@ -374,6 +378,8 @@ func TestAccResourceRoutingQueueConditionalRouting(t *testing.T) { util.NullValue, // enable_audio_monitoring false util.NullValue, // enable_manual_assignment false strconv.Quote("TimestampAndPriority"), + util.NullValue, + util.NullValue, GenerateMediaSettings("media_settings_call", alertTimeout1, util.FalseValue, slPercent1, slDuration1), GenerateMediaSettings("media_settings_callback", alertTimeout1, util.FalseValue, slPercent1, slDuration1), GenerateMediaSettings("media_settings_chat", alertTimeout1, util.FalseValue, slPercent1, slDuration1), @@ -437,7 +443,7 @@ func TestAccResourceRoutingQueueConditionalRouting(t *testing.T) { validateMediaSettings(queueResource1, "media_settings_email", alertTimeout1, util.FalseValue, slPercent1, slDuration1), validateMediaSettings(queueResource1, "media_settings_message", alertTimeout1, util.FalseValue, slPercent1, slDuration1), ), - Destroy: true, + PreventPostDestroyRefresh: true, }, { Config: GenerateRoutingQueueResource( @@ -455,6 +461,8 @@ func TestAccResourceRoutingQueueConditionalRouting(t *testing.T) { util.NullValue, // enable_audio_monitoring false util.NullValue, // enable_manual_assignment false strconv.Quote("TimestampAndPriority"), + util.NullValue, + util.NullValue, GenerateMediaSettings("media_settings_call", alertTimeout1, util.FalseValue, slPercent1, slDuration1), GenerateMediaSettings("media_settings_callback", alertTimeout1, util.FalseValue, slPercent1, slDuration1), GenerateMediaSettings("media_settings_chat", alertTimeout1, util.FalseValue, slPercent1, slDuration1), @@ -542,6 +550,8 @@ func TestAccResourceRoutingQueueParToCGR(t *testing.T) { util.NullValue, // enable_manual_assignment false strconv.Quote(scoringMethod), + util.NullValue, + util.NullValue, GenerateAgentOwnedRouting("agent_owned_routing", util.TrueValue, callbackHours, callbackHours), GenerateMediaSettings("media_settings_call", alertTimeout1, util.FalseValue, slPercent1, slDuration1), GenerateMediaSettings("media_settings_callback", alertTimeout1, util.FalseValue, slPercent1, slDuration1), diff --git a/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_unit_test.go b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_unit_test.go new file mode 100644 index 000000000..833760a84 --- /dev/null +++ b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_unit_test.go @@ -0,0 +1,526 @@ +package routing_queue + +import ( + "context" + "fmt" + "net/http" + "terraform-provider-genesyscloud/genesyscloud/provider" + "testing" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" + "github.com/stretchr/testify/assert" +) + +func TestUnitResourceRoutingQueueCreate(t *testing.T) { + tId := uuid.NewString() + tName := "unit test routing queue" + testRoutingQueue := generateRoutingQueueData(tId, tName) + + queueProxy := &RoutingQueueProxy{} + queueProxy.getRoutingQueueByIdAttr = func(ctx context.Context, p *RoutingQueueProxy, queueId string, checkCache bool) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) { + assert.Equal(t, tId, queueId) + routingQueue := &testRoutingQueue + + queue := convertCreateQueuetoQueue(*routingQueue) + apiResponse := &platformclientv2.APIResponse{StatusCode: http.StatusOK} + return queue, apiResponse, nil + } + + queueProxy.createRoutingQueueAttr = func(ctx context.Context, p *RoutingQueueProxy, routingQueue *platformclientv2.Createqueuerequest) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) { + routingQueue.Id = &tId + queue := convertCreateQueuetoQueue(*routingQueue) + + assert.Equal(t, testRoutingQueue.Id, routingQueue.Id, "ID Not Equal") + assert.Equal(t, testRoutingQueue.Name, routingQueue.Name, "Name Not Equal") + assert.Equal(t, testRoutingQueue.Description, routingQueue.Description, "Description Not Equal") + assert.Equal(t, testRoutingQueue.ScoringMethod, routingQueue.ScoringMethod, "Scoring Method Not Equal") + assert.Equal(t, testRoutingQueue.SkillEvaluationMethod, routingQueue.SkillEvaluationMethod, "Skill Evaluation Method Not Equal") + assert.Equal(t, testRoutingQueue.AutoAnswerOnly, routingQueue.AutoAnswerOnly, "Auto Answer Only Not Equal") + assert.Equal(t, testRoutingQueue.EnableTranscription, routingQueue.EnableTranscription, "Enable Transcription Not Equal") + assert.Equal(t, testRoutingQueue.EnableAudioMonitoring, routingQueue.EnableAudioMonitoring, "Enable Audio Monitoring Not Equal") + assert.Equal(t, testRoutingQueue.EnableManualAssignment, routingQueue.EnableManualAssignment, "Enable Manual Assignment Not Equal") + assert.Equal(t, testRoutingQueue.CallingPartyName, routingQueue.CallingPartyName, "Calling Party Name Not Equal") + assert.Equal(t, testRoutingQueue.CallingPartyNumber, routingQueue.CallingPartyNumber, "Calling Party Number Not Equal") + assert.Equal(t, testRoutingQueue.PeerId, routingQueue.PeerId, "Peer ID Not Equal") + assert.Equal(t, testRoutingQueue.AcwSettings, routingQueue.AcwSettings, "ACW Settings Not Equal") + assert.Equal(t, testRoutingQueue.OutboundMessagingAddresses, routingQueue.OutboundMessagingAddresses, "Outbound Messaging Addresses Not Equal") + assert.Equal(t, testRoutingQueue.SuppressInQueueCallRecording, routingQueue.SuppressInQueueCallRecording, "Suppress In-Queue Call Recording Not Equal") + assert.Equal(t, testRoutingQueue.DirectRouting, routingQueue.DirectRouting, "Direct Routing Not Equal") + assert.Equal(t, testRoutingQueue.AgentOwnedRouting, routingQueue.AgentOwnedRouting, "Agent Owned Routing Not Equal") + assert.Equal(t, testRoutingQueue.RoutingRules, routingQueue.RoutingRules, "Routing Rules Not Equal") + assert.Equal(t, testRoutingQueue.MediaSettings, routingQueue.MediaSettings, "Media Settings Not Equal") + assert.Equal(t, testRoutingQueue.QueueFlow, routingQueue.QueueFlow, "Queue Flow Not Equal") + assert.Equal(t, testRoutingQueue.EmailInQueueFlow, routingQueue.EmailInQueueFlow, "Email In Queue Flow Not Equal") + assert.Equal(t, testRoutingQueue.MessageInQueueFlow, routingQueue.MessageInQueueFlow, "Message In Queue Flow Not Equal") + assert.Equal(t, testRoutingQueue.WhisperPrompt, routingQueue.WhisperPrompt, "Whisper Prompt Not Equal") + assert.Equal(t, testRoutingQueue.OnHoldPrompt, routingQueue.OnHoldPrompt, "On Hold Prompt Not Equal") + assert.Equal(t, testRoutingQueue.DefaultScripts, routingQueue.DefaultScripts, "Default Scripts Not Equal") + + return queue, &platformclientv2.APIResponse{StatusCode: http.StatusOK}, nil + } + + queueProxy.getAllRoutingQueueWrapupCodesAttr = func(ctx context.Context, proxy *RoutingQueueProxy, queueId string) (*[]platformclientv2.Wrapupcode, *platformclientv2.APIResponse, error) { + wrapupCodes := []platformclientv2.Wrapupcode{ + { + Id: platformclientv2.String("wrapupCode1"), + Name: platformclientv2.String("Wrapup Code 1"), + }, + { + Id: platformclientv2.String("wrapupCode2"), + Name: platformclientv2.String("Wrapup Code 2"), + }, + } + + return &wrapupCodes, &platformclientv2.APIResponse{StatusCode: http.StatusOK}, nil + } + + internalProxy = queueProxy + defer func() { internalProxy = nil }() + + ctx := context.Background() + gcloud := &provider.ProviderMeta{ClientConfig: &platformclientv2.Configuration{}} + + resourceSchema := ResourceRoutingQueue().Schema + resourceDataMap := buildRoutingQueueResourceMap(tId, *testRoutingQueue.Name, testRoutingQueue) + + d := schema.TestResourceDataRaw(t, resourceSchema, resourceDataMap) + d.SetId(tId) + + diag := createRoutingQueue(ctx, d, gcloud) + assert.Equal(t, false, diag.HasError()) + assert.Equal(t, tId, d.Id()) +} + +func TestUnitResourceRoutingQueueRead(t *testing.T) { + tId := uuid.NewString() + tName := "unit test routing queue" + testRoutingQueue := generateRoutingQueueData(tId, tName) + + queueProxy := &RoutingQueueProxy{} + queueProxy.getRoutingQueueByIdAttr = func(ctx context.Context, proxy *RoutingQueueProxy, id string, checkCache bool) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) { + assert.Equal(t, tId, id) + routingQueue := &testRoutingQueue + + queue := convertCreateQueuetoQueue(*routingQueue) + apiResponse := &platformclientv2.APIResponse{StatusCode: http.StatusOK} + return queue, apiResponse, nil + } + + queueProxy.getAllRoutingQueueWrapupCodesAttr = func(ctx context.Context, proxy *RoutingQueueProxy, queueId string) (*[]platformclientv2.Wrapupcode, *platformclientv2.APIResponse, error) { + wrapupCodes := []platformclientv2.Wrapupcode{ + { + Id: platformclientv2.String("wrapupCode1"), + Name: platformclientv2.String("Wrapup Code 1"), + }, + { + Id: platformclientv2.String("wrapupCode2"), + Name: platformclientv2.String("Wrapup Code 2"), + }, + } + + return &wrapupCodes, &platformclientv2.APIResponse{StatusCode: http.StatusOK}, nil + } + + internalProxy = queueProxy + defer func() { internalProxy = nil }() + + ctx := context.Background() + gcloud := &provider.ProviderMeta{ClientConfig: &platformclientv2.Configuration{}} + + //Grab our defined schema + resourceSchema := ResourceRoutingQueue().Schema + //Setup a map of values + resourceDataMap := buildRoutingQueueResourceMap(tId, *testRoutingQueue.Name, testRoutingQueue) + + d := schema.TestResourceDataRaw(t, resourceSchema, resourceDataMap) + d.SetId(tId) + + diag := readRoutingQueue(ctx, d, gcloud) + assert.Equal(t, false, diag.HasError()) + assert.Equal(t, tId, d.Id()) + + routingQueue := getRoutingQueueFromResourceData(d) + routingQueue.Id = platformclientv2.String(d.Id()) + + assert.Equal(t, testRoutingQueue.Id, routingQueue.Id, "ID Not Equal") + assert.Equal(t, testRoutingQueue.Name, routingQueue.Name, "Name Not Equal") + assert.Equal(t, testRoutingQueue.Description, routingQueue.Description, "Description Not Equal") + assert.Equal(t, testRoutingQueue.ScoringMethod, routingQueue.ScoringMethod, "Scoring Method Not Equal") + assert.Equal(t, testRoutingQueue.SkillEvaluationMethod, routingQueue.SkillEvaluationMethod, "Skill Evaluation Method Not Equal") + assert.Equal(t, testRoutingQueue.AutoAnswerOnly, routingQueue.AutoAnswerOnly, "Auto Answer Only Not Equal") + assert.Equal(t, testRoutingQueue.EnableTranscription, routingQueue.EnableTranscription, "Enable Transcription Not Equal") + assert.Equal(t, testRoutingQueue.EnableAudioMonitoring, routingQueue.EnableAudioMonitoring, "Enable Audio Monitoring Not Equal") + assert.Equal(t, testRoutingQueue.EnableManualAssignment, routingQueue.EnableManualAssignment, "Enable Manual Assignment Not Equal") + assert.Equal(t, testRoutingQueue.CallingPartyName, routingQueue.CallingPartyName, "Calling Party Name Not Equal") + assert.Equal(t, testRoutingQueue.CallingPartyNumber, routingQueue.CallingPartyNumber, "Calling Party Number Not Equal") + assert.Equal(t, testRoutingQueue.PeerId, routingQueue.PeerId, "Peer ID Not Equal") + assert.Equal(t, testRoutingQueue.AcwSettings, routingQueue.AcwSettings, "ACW Settings Not Equal") + assert.Equal(t, testRoutingQueue.OutboundMessagingAddresses, routingQueue.OutboundMessagingAddresses, "Outbound Messaging Addresses Not Equal") + assert.Equal(t, testRoutingQueue.SuppressInQueueCallRecording, routingQueue.SuppressInQueueCallRecording, "Suppress In-Queue Call Recording Not Equal") + assert.Equal(t, testRoutingQueue.DirectRouting, routingQueue.DirectRouting, "Direct Routing Not Equal") + assert.Equal(t, testRoutingQueue.AgentOwnedRouting, routingQueue.AgentOwnedRouting, "Agent Owned Routing Not Equal") + assert.Equal(t, testRoutingQueue.RoutingRules, routingQueue.RoutingRules, "Routing Rules Not Equal") + assert.Equal(t, testRoutingQueue.MediaSettings, routingQueue.MediaSettings, "Media Settings Not Equal") + assert.Equal(t, testRoutingQueue.QueueFlow, routingQueue.QueueFlow, "Queue Flow Not Equal") + assert.Equal(t, testRoutingQueue.EmailInQueueFlow, routingQueue.EmailInQueueFlow, "Email In Queue Flow Not Equal") + assert.Equal(t, testRoutingQueue.MessageInQueueFlow, routingQueue.MessageInQueueFlow, "Message In Queue Flow Not Equal") + assert.Equal(t, testRoutingQueue.WhisperPrompt, routingQueue.WhisperPrompt, "Whisper Prompt Not Equal") + assert.Equal(t, testRoutingQueue.OnHoldPrompt, routingQueue.OnHoldPrompt, "On Hold Prompt Not Equal") + assert.Equal(t, testRoutingQueue.DefaultScripts, routingQueue.DefaultScripts, "Default Scripts Not Equal") +} + +func TestUnitResourceRoutingQueueUpdate(t *testing.T) { + tId := uuid.NewString() + tName := "updated queue name" + testRoutingQueue := generateRoutingQueueData(tId, tName) + + queueProxy := &RoutingQueueProxy{} + queueProxy.getRoutingQueueByIdAttr = func(ctx context.Context, proxy *RoutingQueueProxy, id string, checkCache bool) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) { + assert.Equal(t, tId, id) + routingQueue := &testRoutingQueue + + queue := convertCreateQueuetoQueue(*routingQueue) + + apiResponse := &platformclientv2.APIResponse{StatusCode: http.StatusOK} + return queue, apiResponse, nil + } + + queueProxy.updateRoutingQueueAttr = func(ctx context.Context, proxy *RoutingQueueProxy, id string, routingQueue *platformclientv2.Queuerequest) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) { + routingQueue.Id = &tId + + assert.Equal(t, testRoutingQueue.Id, routingQueue.Id, "ID Not Equal") + assert.Equal(t, testRoutingQueue.Name, routingQueue.Name, "Name Not Equal") + assert.Equal(t, testRoutingQueue.Description, routingQueue.Description, "Description Not Equal") + assert.Equal(t, testRoutingQueue.ScoringMethod, routingQueue.ScoringMethod, "Scoring Method Not Equal") + assert.Equal(t, testRoutingQueue.SkillEvaluationMethod, routingQueue.SkillEvaluationMethod, "Skill Evaluation Method Not Equal") + assert.Equal(t, testRoutingQueue.AutoAnswerOnly, routingQueue.AutoAnswerOnly, "Auto Answer Only Not Equal") + assert.Equal(t, testRoutingQueue.EnableTranscription, routingQueue.EnableTranscription, "Enable Transcription Not Equal") + assert.Equal(t, testRoutingQueue.EnableAudioMonitoring, routingQueue.EnableAudioMonitoring, "Enable Audio Monitoring Not Equal") + assert.Equal(t, testRoutingQueue.EnableManualAssignment, routingQueue.EnableManualAssignment, "Enable Manual Assignment Not Equal") + assert.Equal(t, testRoutingQueue.CallingPartyName, routingQueue.CallingPartyName, "Calling Party Name Not Equal") + assert.Equal(t, testRoutingQueue.CallingPartyNumber, routingQueue.CallingPartyNumber, "Calling Party Number Not Equal") + assert.Equal(t, testRoutingQueue.PeerId, routingQueue.PeerId, "Peer ID Not Equal") + assert.Equal(t, testRoutingQueue.AcwSettings, routingQueue.AcwSettings, "ACW Settings Not Equal") + assert.Equal(t, testRoutingQueue.OutboundMessagingAddresses, routingQueue.OutboundMessagingAddresses, "Outbound Messaging Addresses Not Equal") + assert.Equal(t, testRoutingQueue.SuppressInQueueCallRecording, routingQueue.SuppressInQueueCallRecording, "Suppress In-Queue Call Recording Not Equal") + assert.Equal(t, testRoutingQueue.DirectRouting, routingQueue.DirectRouting, "Direct Routing Not Equal") + assert.Equal(t, testRoutingQueue.AgentOwnedRouting, routingQueue.AgentOwnedRouting, "Agent Owned Routing Not Equal") + assert.Equal(t, testRoutingQueue.RoutingRules, routingQueue.RoutingRules, "Routing Rules Not Equal") + assert.Equal(t, testRoutingQueue.MediaSettings, routingQueue.MediaSettings, "Media Settings Not Equal") + assert.Equal(t, testRoutingQueue.QueueFlow, routingQueue.QueueFlow, "Queue Flow Not Equal") + assert.Equal(t, testRoutingQueue.EmailInQueueFlow, routingQueue.EmailInQueueFlow, "Email In Queue Flow Not Equal") + assert.Equal(t, testRoutingQueue.MessageInQueueFlow, routingQueue.MessageInQueueFlow, "Message In Queue Flow Not Equal") + assert.Equal(t, testRoutingQueue.WhisperPrompt, routingQueue.WhisperPrompt, "Whisper Prompt Not Equal") + assert.Equal(t, testRoutingQueue.OnHoldPrompt, routingQueue.OnHoldPrompt, "On Hold Prompt Not Equal") + assert.Equal(t, testRoutingQueue.DefaultScripts, routingQueue.DefaultScripts, "Default Scripts Not Equal") + + return nil, nil, nil + } + + queueProxy.getAllRoutingQueueWrapupCodesAttr = func(ctx context.Context, proxy *RoutingQueueProxy, queueId string) (*[]platformclientv2.Wrapupcode, *platformclientv2.APIResponse, error) { + wrapupCodes := []platformclientv2.Wrapupcode{ + { + Id: platformclientv2.String("wrapupCode1"), + Name: platformclientv2.String("Wrapup Code 1"), + }, + { + Id: platformclientv2.String("wrapupCode2"), + Name: platformclientv2.String("Wrapup Code 2"), + }, + } + + return &wrapupCodes, &platformclientv2.APIResponse{StatusCode: http.StatusOK}, nil + } + + internalProxy = queueProxy + defer func() { internalProxy = nil }() + + ctx := context.Background() + gcloud := &provider.ProviderMeta{ClientConfig: &platformclientv2.Configuration{}} + + //Grab our defined schema + resourceSchema := ResourceRoutingQueue().Schema + //Setup a map of values + resourceDataMap := buildRoutingQueueResourceMap(tId, *testRoutingQueue.Name, testRoutingQueue) + + d := schema.TestResourceDataRaw(t, resourceSchema, resourceDataMap) + d.SetId(tId) + + diag := updateRoutingQueue(ctx, d, gcloud) + assert.Equal(t, false, diag.HasError()) + assert.Equal(t, tId, d.Id()) + assert.Equal(t, *testRoutingQueue.Name, d.Get("name").(string)) +} + +func TestUnitResourceRoutingQueueDelete(t *testing.T) { + tId := uuid.NewString() + tName := "unit test routing queue" + testRoutingQueue := generateRoutingQueueData(tId, tName) + + queueProxy := &RoutingQueueProxy{} + queueProxy.deleteRoutingQueueAttr = func(ctx context.Context, p *RoutingQueueProxy, queueId string, forceDelete bool) (*platformclientv2.APIResponse, error) { + assert.Equal(t, tId, queueId) + + apiResponse := &platformclientv2.APIResponse{StatusCode: http.StatusOK} + return apiResponse, nil + } + + queueProxy.getRoutingQueueByIdAttr = func(ctx context.Context, proxy *RoutingQueueProxy, id string, checkCache bool) (*platformclientv2.Queue, *platformclientv2.APIResponse, error) { + assert.Equal(t, tId, id) + + apiResponse := &platformclientv2.APIResponse{StatusCode: http.StatusNotFound} + err := fmt.Errorf("Unable to find targeted queue: %s", id) + return nil, apiResponse, err + } + + internalProxy = queueProxy + defer func() { internalProxy = nil }() + + ctx := context.Background() + gcloud := &provider.ProviderMeta{ClientConfig: &platformclientv2.Configuration{}} + + //Grab our defined schema + resourceSchema := ResourceRoutingQueue().Schema + //Setup a map of values + resourceDataMap := buildRoutingQueueResourceMap(tId, *testRoutingQueue.Name, testRoutingQueue) + + d := schema.TestResourceDataRaw(t, resourceSchema, resourceDataMap) + d.SetId(tId) + + diag := deleteRoutingQueue(ctx, d, gcloud) + assert.Nil(t, diag) + assert.Equal(t, tId, d.Id()) +} + +func buildRoutingQueueResourceMap(tId string, tName string, testRoutingQueue platformclientv2.Createqueuerequest) map[string]interface{} { + resourceDataMap := map[string]interface{}{ + "id": tId, + "name": tName, + "description": *testRoutingQueue.Description, + "scoring_method": *testRoutingQueue.ScoringMethod, + "skill_evaluation_method": *testRoutingQueue.SkillEvaluationMethod, + "auto_answer_only": *testRoutingQueue.AutoAnswerOnly, + "enable_transcription": *testRoutingQueue.EnableTranscription, + "enable_audio_monitoring": *testRoutingQueue.EnableAudioMonitoring, + "enable_manual_assignment": *testRoutingQueue.EnableManualAssignment, + "calling_party_name": *testRoutingQueue.CallingPartyName, + "calling_party_number": *testRoutingQueue.CallingPartyNumber, + "peer_id": *testRoutingQueue.PeerId, + "source_queue_id": *testRoutingQueue.SourceQueueId, + "acw_timeout_ms": *testRoutingQueue.AcwSettings.TimeoutMs, + "acw_wrapup_prompt": *testRoutingQueue.AcwSettings.WrapupPrompt, + "outbound_messaging_sms_address_id": *testRoutingQueue.OutboundMessagingAddresses.SmsAddress.Id, + "outbound_messaging_open_messaging_recipient_id": *testRoutingQueue.OutboundMessagingAddresses.OpenMessagingRecipient.Id, + "outbound_messaging_whatsapp_recipient_id": *testRoutingQueue.OutboundMessagingAddresses.WhatsAppRecipient.Id, + "suppress_in_queue_call_recording": *testRoutingQueue.SuppressInQueueCallRecording, + "direct_routing": flattenDirectRouting(testRoutingQueue.DirectRouting), + "agent_owned_routing": flattenAgentOwnedRouting(testRoutingQueue.AgentOwnedRouting), + "routing_rules": flattenRoutingRules(testRoutingQueue.RoutingRules), + "media_settings_call": flattenMediaSetting(testRoutingQueue.MediaSettings.Call), + "media_settings_email": flattenMediaSetting(testRoutingQueue.MediaSettings.Email), + "media_settings_chat": flattenMediaSetting(testRoutingQueue.MediaSettings.Chat), + "media_settings_callback": flattenMediaSettingCallback(testRoutingQueue.MediaSettings.Callback), + "media_settings_message": flattenMediaSetting(testRoutingQueue.MediaSettings.Message), + "queue_flow_id": *testRoutingQueue.QueueFlow.Id, + "email_in_queue_flow_id": *testRoutingQueue.EmailInQueueFlow.Id, + "message_in_queue_flow_id": *testRoutingQueue.MessageInQueueFlow.Id, + "whisper_prompt_id": *testRoutingQueue.WhisperPrompt.Id, + "on_hold_prompt_id": *testRoutingQueue.OnHoldPrompt.Id, + "default_script_ids": flattenDefaultScripts(*testRoutingQueue.DefaultScripts), + } + return resourceDataMap +} + +func generateRoutingQueueData(id, name string) platformclientv2.Createqueuerequest { + var ( + description = "Unit Test Description" + scoringMethod = "TimestampAndPriority" + skillEvaluationMethod = "BEST" + callingPartyName = "Unit Test Inc." + callingPartyNumber = "123" + peerId = "5696a54c-4009-4e63-826c-311679deeb97" + sourceQueueId = "5696a54c-4009-4e63-826c-311679deeb97" + backupQueueId = "5696a54c-4009-4e63-826c-311679deeb97" + + acwWrapupPrompt = "MANDATORY_TIMEOUT" + acwTimeoutMs = 300000 + + queueFlow = generateRandomDomainEntityRef() + emailFlow = generateRandomDomainEntityRef() + messageFlow = generateRandomDomainEntityRef() + whisperPrompt = generateRandomDomainEntityRef() + onHoldPrompt = generateRandomDomainEntityRef() + + // ACW Settings + acwSettings = platformclientv2.Acwsettings{ + WrapupPrompt: &acwWrapupPrompt, + TimeoutMs: &acwTimeoutMs, + } + + // Outbound Messaging Addresses + smsAddress = generateRandomDomainEntityRef() + openMessagingRecipient = generateRandomDomainEntityRef() + whatsAppRecipient = generateRandomDomainEntityRef() + + messagingAddress = platformclientv2.Queuemessagingaddresses{ + SmsAddress: &smsAddress, + OpenMessagingRecipient: &openMessagingRecipient, + WhatsAppRecipient: &whatsAppRecipient, + } + + // Direct Routing + callMediaSettings = generateDirectRoutingMediaSettings() + emailMediaSettings = generateDirectRoutingMediaSettings() + messageMediaSettings = generateDirectRoutingMediaSettings() + + directRouting = platformclientv2.Directrouting{ + CallMediaSettings: &callMediaSettings, + EmailMediaSettings: &emailMediaSettings, + MessageMediaSettings: &messageMediaSettings, + WaitForAgent: platformclientv2.Bool(false), + AgentWaitSeconds: platformclientv2.Int(20), + BackupQueueId: &backupQueueId, + } + + // Agent Owned Routing + agentOwnedRouting = platformclientv2.Agentownedrouting{ + EnableAgentOwnedCallbacks: platformclientv2.Bool(true), + MaxOwnedCallbackHours: platformclientv2.Int(1), + MaxOwnedCallbackDelayHours: platformclientv2.Int(2), + } + + // Routing Rules + rules = []platformclientv2.Routingrule{ + { + Operator: platformclientv2.String("MEETS_THRESHOLD"), + Threshold: platformclientv2.Int(9), + WaitSeconds: platformclientv2.Float64(300), + }, + } + + // Media Settings + call = generateMediaSettings() + callback = generateCallbackMediaSettings() + chat = generateMediaSettings() + email = generateMediaSettings() + message = generateMediaSettings() + + mediaSettings = platformclientv2.Queuemediasettings{ + Call: &call, + Callback: &callback, + Chat: &chat, + Email: &email, + Message: &message, + } + + // Default Scripts + sId = uuid.NewString() + + script = platformclientv2.Script{ + Id: &sId, + } + + defaultScripts = map[string]platformclientv2.Script{ + "script1": script, + } + ) + + return platformclientv2.Createqueuerequest{ + Id: &id, + Name: &name, + Description: &description, + ScoringMethod: &scoringMethod, + SkillEvaluationMethod: &skillEvaluationMethod, + AutoAnswerOnly: platformclientv2.Bool(true), + EnableTranscription: platformclientv2.Bool(true), + EnableAudioMonitoring: platformclientv2.Bool(true), + EnableManualAssignment: platformclientv2.Bool(true), + CallingPartyName: &callingPartyName, + CallingPartyNumber: &callingPartyNumber, + PeerId: &peerId, + SourceQueueId: &sourceQueueId, + AcwSettings: &acwSettings, + SuppressInQueueCallRecording: platformclientv2.Bool(true), + DirectRouting: &directRouting, + AgentOwnedRouting: &agentOwnedRouting, + RoutingRules: &rules, + MediaSettings: &mediaSettings, + QueueFlow: &queueFlow, + EmailInQueueFlow: &emailFlow, + MessageInQueueFlow: &messageFlow, + WhisperPrompt: &whisperPrompt, + OnHoldPrompt: &onHoldPrompt, + DefaultScripts: &defaultScripts, + OutboundMessagingAddresses: &messagingAddress, + } +} + +func convertCreateQueuetoQueue(req platformclientv2.Createqueuerequest) *platformclientv2.Queue { + return &platformclientv2.Queue{ + Id: req.Id, + Name: req.Name, + Description: req.Description, + ScoringMethod: req.ScoringMethod, + SkillEvaluationMethod: req.SkillEvaluationMethod, + AutoAnswerOnly: req.AutoAnswerOnly, + EnableTranscription: req.EnableTranscription, + EnableAudioMonitoring: req.EnableAudioMonitoring, + EnableManualAssignment: req.EnableManualAssignment, + CallingPartyName: req.CallingPartyName, + CallingPartyNumber: req.CallingPartyNumber, + PeerId: req.PeerId, + AcwSettings: req.AcwSettings, + OutboundMessagingAddresses: req.OutboundMessagingAddresses, + SuppressInQueueCallRecording: req.SuppressInQueueCallRecording, + DirectRouting: req.DirectRouting, + AgentOwnedRouting: req.AgentOwnedRouting, + RoutingRules: req.RoutingRules, + MediaSettings: req.MediaSettings, + QueueFlow: req.QueueFlow, + EmailInQueueFlow: req.EmailInQueueFlow, + MessageInQueueFlow: req.MessageInQueueFlow, + WhisperPrompt: req.WhisperPrompt, + OnHoldPrompt: req.OnHoldPrompt, + DefaultScripts: req.DefaultScripts, + } +} + +func generateMediaSettings() platformclientv2.Mediasettings { + return platformclientv2.Mediasettings{ + EnableAutoAnswer: platformclientv2.Bool(true), + AlertingTimeoutSeconds: platformclientv2.Int(20), + ServiceLevel: &platformclientv2.Servicelevel{ + Percentage: platformclientv2.Float64(0.7), + DurationMs: platformclientv2.Int(10000), + }, + } +} + +func generateCallbackMediaSettings() platformclientv2.Callbackmediasettings { + return platformclientv2.Callbackmediasettings{ + EnableAutoAnswer: platformclientv2.Bool(true), + AlertingTimeoutSeconds: platformclientv2.Int(20), + ServiceLevel: &platformclientv2.Servicelevel{ + Percentage: platformclientv2.Float64(0.7), + DurationMs: platformclientv2.Int(10000), + }, + EnableAutoDialAndEnd: platformclientv2.Bool(true), + AutoDialDelaySeconds: platformclientv2.Int(10), + AutoEndDelaySeconds: platformclientv2.Int(10), + } +} + +func generateDirectRoutingMediaSettings() platformclientv2.Directroutingmediasettings { + return platformclientv2.Directroutingmediasettings{ + UseAgentAddressOutbound: platformclientv2.Bool(false), + } +} + +func generateRandomDomainEntityRef() platformclientv2.Domainentityref { + id := uuid.NewString() + return platformclientv2.Domainentityref{ + Id: &id, + } +} diff --git a/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_utils.go b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_utils.go index fdef12d42..5b07e0070 100644 --- a/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_utils.go +++ b/genesyscloud/routing_queue/resource_genesyscloud_routing_queue_utils.go @@ -1,10 +1,635 @@ package routing_queue import ( + "context" "fmt" "strings" + "terraform-provider-genesyscloud/genesyscloud/util" + "terraform-provider-genesyscloud/genesyscloud/util/lists" + "terraform-provider-genesyscloud/genesyscloud/util/resourcedata" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2" ) +// Build Functions + +func buildSdkMediaSettings(d *schema.ResourceData) *platformclientv2.Queuemediasettings { + queueMediaSettings := &platformclientv2.Queuemediasettings{} + + mediaSettingsCall := d.Get("media_settings_call").([]interface{}) + if mediaSettingsCall != nil && len(mediaSettingsCall) > 0 { + queueMediaSettings.Call = buildSdkMediaSetting(mediaSettingsCall) + } + + mediaSettingsCallback := d.Get("media_settings_callback").([]interface{}) + if mediaSettingsCallback != nil && len(mediaSettingsCallback) > 0 { + queueMediaSettings.Callback = buildSdkMediaSettingCallback(mediaSettingsCallback) + } + + mediaSettingsChat := d.Get("media_settings_chat").([]interface{}) + if mediaSettingsChat != nil && len(mediaSettingsChat) > 0 { + queueMediaSettings.Chat = buildSdkMediaSetting(mediaSettingsChat) + } + + mediaSettingsEmail := d.Get("media_settings_email").([]interface{}) + if mediaSettingsEmail != nil && len(mediaSettingsEmail) > 0 { + queueMediaSettings.Email = buildSdkMediaSetting(mediaSettingsEmail) + } + + mediaSettingsMessage := d.Get("media_settings_message").([]interface{}) + if mediaSettingsMessage != nil && len(mediaSettingsMessage) > 0 { + queueMediaSettings.Message = buildSdkMediaSetting(mediaSettingsMessage) + } + + return queueMediaSettings +} + +func buildSdkAcwSettings(d *schema.ResourceData) *platformclientv2.Acwsettings { + acwWrapupPrompt := d.Get("acw_wrapup_prompt").(string) + + acwSettings := platformclientv2.Acwsettings{ + WrapupPrompt: &acwWrapupPrompt, // Set or default + } + + // Only set timeout for certain wrapup prompt types + if acwWrapupPrompt == "MANDATORY_TIMEOUT" || acwWrapupPrompt == "MANDATORY_FORCED_TIMEOUT" || acwWrapupPrompt == "AGENT_REQUESTED" { + acwTimeoutMs, hasTimeout := d.GetOk("acw_timeout_ms") + if hasTimeout { + timeout := acwTimeoutMs.(int) + acwSettings.TimeoutMs = &timeout + } + } + return &acwSettings +} + +func buildSdkDefaultScriptsMap(d *schema.ResourceData) *map[string]platformclientv2.Script { + if scriptIds, ok := d.GetOk("default_script_ids"); ok { + scriptMap := scriptIds.(map[string]interface{}) + + results := make(map[string]platformclientv2.Script) + for k, v := range scriptMap { + scriptID := v.(string) + results[k] = platformclientv2.Script{Id: &scriptID} + } + return &results + } + return nil +} + +func buildSdkDirectRouting(d *schema.ResourceData) *platformclientv2.Directrouting { + directRouting := d.Get("direct_routing").([]interface{}) + if directRouting != nil && len(directRouting) > 0 { + settingsMap := directRouting[0].(map[string]interface{}) + + agentWaitSeconds := settingsMap["agent_wait_seconds"].(int) + waitForAgent := settingsMap["wait_for_agent"].(bool) + + callUseAgentAddressOutbound := settingsMap["call_use_agent_address_outbound"].(bool) + callSettings := &platformclientv2.Directroutingmediasettings{ + UseAgentAddressOutbound: &callUseAgentAddressOutbound, + } + + emailUseAgentAddressOutbound := settingsMap["email_use_agent_address_outbound"].(bool) + emailSettings := &platformclientv2.Directroutingmediasettings{ + UseAgentAddressOutbound: &emailUseAgentAddressOutbound, + } + + messageUseAgentAddressOutbound := settingsMap["message_use_agent_address_outbound"].(bool) + messageSettings := &platformclientv2.Directroutingmediasettings{ + UseAgentAddressOutbound: &messageUseAgentAddressOutbound, + } + + sdkDirectRouting := &platformclientv2.Directrouting{ + CallMediaSettings: callSettings, + EmailMediaSettings: emailSettings, + MessageMediaSettings: messageSettings, + WaitForAgent: &waitForAgent, + AgentWaitSeconds: &agentWaitSeconds, + } + + if backUpQueueId, ok := settingsMap["backup_queue_id"].(string); ok && backUpQueueId != "" { + sdkDirectRouting.BackupQueueId = &backUpQueueId + } + + return sdkDirectRouting + } + return nil +} + +func buildAgentOwnedRouting(routing []interface{}) *platformclientv2.Agentownedrouting { + settingsMap := routing[0].(map[string]interface{}) + return &platformclientv2.Agentownedrouting{ + EnableAgentOwnedCallbacks: platformclientv2.Bool(settingsMap["enable_agent_owned_callbacks"].(bool)), + MaxOwnedCallbackDelayHours: platformclientv2.Int(settingsMap["max_owned_callback_delay_hours"].(int)), + MaxOwnedCallbackHours: platformclientv2.Int(settingsMap["max_owned_callback_hours"].(int)), + } +} + +func buildSdkMediaSetting(settings []interface{}) *platformclientv2.Mediasettings { + settingsMap := settings[0].(map[string]interface{}) + + return &platformclientv2.Mediasettings{ + AlertingTimeoutSeconds: platformclientv2.Int(settingsMap["alerting_timeout_sec"].(int)), + EnableAutoAnswer: platformclientv2.Bool(settingsMap["enable_auto_answer"].(bool)), + ServiceLevel: &platformclientv2.Servicelevel{ + Percentage: platformclientv2.Float64(settingsMap["service_level_percentage"].(float64)), + DurationMs: platformclientv2.Int(settingsMap["service_level_duration_ms"].(int)), + }, + } +} + +func buildSdkMediaSettingCallback(settings []interface{}) *platformclientv2.Callbackmediasettings { + settingsMap := settings[0].(map[string]interface{}) + + return &platformclientv2.Callbackmediasettings{ + AlertingTimeoutSeconds: platformclientv2.Int(settingsMap["alerting_timeout_sec"].(int)), + ServiceLevel: &platformclientv2.Servicelevel{ + Percentage: platformclientv2.Float64(settingsMap["service_level_percentage"].(float64)), + DurationMs: platformclientv2.Int(settingsMap["service_level_duration_ms"].(int)), + }, + EnableAutoAnswer: platformclientv2.Bool(settingsMap["enable_auto_answer"].(bool)), + AutoEndDelaySeconds: platformclientv2.Int(settingsMap["auto_end_delay_seconds"].(int)), + AutoDialDelaySeconds: platformclientv2.Int(settingsMap["auto_dial_delay_seconds"].(int)), + EnableAutoDialAndEnd: platformclientv2.Bool(settingsMap["enable_auto_dial_and_end"].(bool)), + } +} + +func buildSdkRoutingRules(d *schema.ResourceData) *[]platformclientv2.Routingrule { + var routingRules []platformclientv2.Routingrule + if configRoutingRules, ok := d.GetOk("routing_rules"); ok { + for _, configRule := range configRoutingRules.([]interface{}) { + ruleSettings, ok := configRule.(map[string]interface{}) + if !ok { + continue + } + var sdkRule platformclientv2.Routingrule + + resourcedata.BuildSDKStringValueIfNotNil(&sdkRule.Operator, ruleSettings, "operator") + if threshold, ok := ruleSettings["threshold"]; ok { + v := threshold.(int) + sdkRule.Threshold = &v + } + if waitSeconds, ok := ruleSettings["wait_seconds"].(float64); ok { + sdkRule.WaitSeconds = &waitSeconds + } + + routingRules = append(routingRules, sdkRule) + } + } + return &routingRules +} + +func buildSdkConditionalGroupRouting(d *schema.ResourceData) (*platformclientv2.Conditionalgrouprouting, diag.Diagnostics) { + if configRules, ok := d.GetOk("conditional_group_routing_rules"); ok { + var sdkCGRRules []platformclientv2.Conditionalgrouproutingrule + + for i, configRules := range configRules.([]interface{}) { + ruleSettings, ok := configRules.(map[string]interface{}) + if !ok { + continue + } + var sdkCGRRule platformclientv2.Conditionalgrouproutingrule + + if waitSeconds, ok := ruleSettings["wait_seconds"].(int); ok { + sdkCGRRule.WaitSeconds = &waitSeconds + } + + resourcedata.BuildSDKStringValueIfNotNil(&sdkCGRRule.Operator, ruleSettings, "operator") + resourcedata.BuildSDKStringValueIfNotNil(&sdkCGRRule.Metric, ruleSettings, "metric") + + if conditionValue, ok := ruleSettings["condition_value"].(float64); ok { + sdkCGRRule.ConditionValue = &conditionValue + } + + if queueId, ok := ruleSettings["queue_id"].(string); ok && queueId != "" { + if i == 0 { + return nil, util.BuildDiagnosticError(resourceName, "For rule 1, queue_id is always assumed to be the current queue, so queue id should not be specified", fmt.Errorf("queue id is not nil")) + } + sdkCGRRule.Queue = &platformclientv2.Domainentityref{Id: &queueId} + } + + if memberGroupList, ok := ruleSettings["groups"].([]interface{}); ok { + if len(memberGroupList) > 0 { + sdkMemberGroups := make([]platformclientv2.Membergroup, len(memberGroupList)) + + for i, memberGroup := range memberGroupList { + settingsMap, ok := memberGroup.(map[string]interface{}) + if !ok { + continue + } + + sdkMemberGroups[i] = platformclientv2.Membergroup{ + Id: platformclientv2.String(settingsMap["member_group_id"].(string)), + VarType: platformclientv2.String(settingsMap["member_group_type"].(string)), + } + } + sdkCGRRule.Groups = &sdkMemberGroups + } + } + sdkCGRRules = append(sdkCGRRules, sdkCGRRule) + } + rules := &sdkCGRRules + return &platformclientv2.Conditionalgrouprouting{Rules: rules}, nil + } + return nil, nil +} + +func buildMemberGroupList(d *schema.ResourceData, groupKey string, groupType string) *[]platformclientv2.Membergroup { + var memberGroups []platformclientv2.Membergroup + if mg, ok := d.GetOk(groupKey); ok { + + for _, mgId := range mg.(*schema.Set).List() { + id := mgId.(string) + + memberGroup := &platformclientv2.Membergroup{Id: &id, VarType: &groupType} + memberGroups = append(memberGroups, *memberGroup) + } + } + + return &memberGroups +} + +func buildSdkBullseyeSettings(d *schema.ResourceData) *platformclientv2.Bullseye { + if configRings, ok := d.GetOk("bullseye_rings"); ok { + var sdkRings []platformclientv2.Ring + + for _, configRing := range configRings.([]interface{}) { + ringSettings, ok := configRing.(map[string]interface{}) + if !ok { + continue + } + var sdkRing platformclientv2.Ring + + if waitSeconds, ok := ringSettings["expansion_timeout_seconds"].(float64); ok { + sdkRing.ExpansionCriteria = &[]platformclientv2.Expansioncriterium{ + { + VarType: &bullseyeExpansionTypeTimeout, + Threshold: &waitSeconds, + }, + } + } + + if skillsToRemove, ok := ringSettings["skills_to_remove"]; ok { + skillIds := skillsToRemove.(*schema.Set).List() + if len(skillIds) > 0 { + sdkSkillsToRemove := make([]platformclientv2.Skillstoremove, len(skillIds)) + for i, id := range skillIds { + skillID := id.(string) + sdkSkillsToRemove[i] = platformclientv2.Skillstoremove{ + Id: &skillID, + } + } + sdkRing.Actions = &platformclientv2.Actions{ + SkillsToRemove: &sdkSkillsToRemove, + } + } + } + + if memberGroups, ok := ringSettings["member_groups"]; ok { + memberGroupList := memberGroups.(*schema.Set).List() + if len(memberGroupList) > 0 { + + sdkMemberGroups := make([]platformclientv2.Membergroup, len(memberGroupList)) + for i, memberGroup := range memberGroupList { + settingsMap := memberGroup.(map[string]interface{}) + memberGroupID := settingsMap["member_group_id"].(string) + memberGroupType := settingsMap["member_group_type"].(string) + + sdkMemberGroups[i] = platformclientv2.Membergroup{ + Id: &memberGroupID, + VarType: &memberGroupType, + } + } + sdkRing.MemberGroups = &sdkMemberGroups + } + } + sdkRings = append(sdkRings, sdkRing) + } + + /* + The routing queues API is a little unusual. You can have up to six bullseye routing rings but the last one is always + a treated as the default ring. This means you can actually ony define a maximum of 5. So, I have changed the behavior of this + resource to only allow you to add 5 items and then the code always adds a 6 item (see the code below) with a default timeout of 2. + */ + var defaultSdkRing platformclientv2.Ring + defaultTimeoutInt := 2 + defaultTimeoutFloat := float64(defaultTimeoutInt) + defaultSdkRing.ExpansionCriteria = &[]platformclientv2.Expansioncriterium{ + { + VarType: &bullseyeExpansionTypeTimeout, + Threshold: &defaultTimeoutFloat, + }, + } + + sdkRings = append(sdkRings, defaultSdkRing) + return &platformclientv2.Bullseye{Rings: &sdkRings} + } + return nil +} + +func buildSdkQueueMessagingAddresses(d *schema.ResourceData) *platformclientv2.Queuemessagingaddresses { + var messagingAddresses platformclientv2.Queuemessagingaddresses + + if _, ok := d.GetOk("outbound_messaging_sms_address_id"); ok { + messagingAddresses.SmsAddress = util.BuildSdkDomainEntityRef(d, "outbound_messaging_sms_address_id") + } + + if _, ok := d.GetOk("outbound_messaging_whatsapp_recipient_id"); ok { + messagingAddresses.WhatsAppRecipient = util.BuildSdkDomainEntityRef(d, "outbound_messaging_whatsapp_recipient_id") + + } + if _, ok := d.GetOk("outbound_messaging_open_messaging_recipient_id"); ok { + messagingAddresses.OpenMessagingRecipient = util.BuildSdkDomainEntityRef(d, "outbound_messaging_open_messaging_recipient_id") + } + + return &messagingAddresses +} + +func buildSdkQueueEmailAddress(d *schema.ResourceData) *platformclientv2.Queueemailaddress { + outboundEmailAddress := d.Get("outbound_email_address").([]interface{}) + if outboundEmailAddress != nil && len(outboundEmailAddress) > 0 { + settingsMap := outboundEmailAddress[0].(map[string]interface{}) + + inboundRoute := &platformclientv2.Inboundroute{ + Id: platformclientv2.String(settingsMap["route_id"].(string)), + } + return &platformclientv2.Queueemailaddress{ + Domain: &platformclientv2.Domainentityref{Id: platformclientv2.String(settingsMap["domain_id"].(string))}, + Route: &inboundRoute, + } + } + return nil +} + +func constructAgentOwnedRouting(d *schema.ResourceData) *platformclientv2.Agentownedrouting { + if agentOwnedRouting, ok := d.Get("agent_owned_routing").([]interface{}); ok { + if len(agentOwnedRouting) > 0 { + return buildAgentOwnedRouting(agentOwnedRouting) + } + } + return nil +} + +// Flatten Functions + +func flattenMediaSetting(settings *platformclientv2.Mediasettings) []interface{} { + settingsMap := make(map[string]interface{}) + + settingsMap["alerting_timeout_sec"] = *settings.AlertingTimeoutSeconds + resourcedata.SetMapValueIfNotNil(settingsMap, "enable_auto_answer", settings.EnableAutoAnswer) + settingsMap["service_level_percentage"] = *settings.ServiceLevel.Percentage + settingsMap["service_level_duration_ms"] = *settings.ServiceLevel.DurationMs + + return []interface{}{settingsMap} +} + +func flattenDefaultScripts(sdkScripts map[string]platformclientv2.Script) map[string]interface{} { + if len(sdkScripts) == 0 { + return nil + } + + results := make(map[string]interface{}) + for k, v := range sdkScripts { + results[k] = *v.Id + } + return results +} + +func flattenDirectRouting(settings *platformclientv2.Directrouting) []interface{} { + settingsMap := make(map[string]interface{}) + + if settings.BackupQueueId != nil { + settingsMap["backup_queue_id"] = *settings.BackupQueueId + } + if settings.AgentWaitSeconds != nil { + settingsMap["agent_wait_seconds"] = *settings.AgentWaitSeconds + } + if settings.WaitForAgent != nil { + settingsMap["wait_for_agent"] = *settings.WaitForAgent + } + + if settings.CallMediaSettings != nil { + callSettings := *settings.CallMediaSettings + settingsMap["call_use_agent_address_outbound"] = *callSettings.UseAgentAddressOutbound + } + if settings.EmailMediaSettings != nil { + emailSettings := *settings.EmailMediaSettings + settingsMap["email_use_agent_address_outbound"] = *emailSettings.UseAgentAddressOutbound + } + if settings.MessageMediaSettings != nil { + messageSettings := *settings.MessageMediaSettings + settingsMap["message_use_agent_address_outbound"] = *messageSettings.UseAgentAddressOutbound + } + + return []interface{}{settingsMap} +} + +func flattenAgentOwnedRouting(settings *platformclientv2.Agentownedrouting) []interface{} { + settingsMap := make(map[string]interface{}) + + settingsMap["max_owned_callback_delay_hours"] = *settings.MaxOwnedCallbackDelayHours + resourcedata.SetMapValueIfNotNil(settingsMap, "enable_agent_owned_callbacks", settings.EnableAgentOwnedCallbacks) + settingsMap["max_owned_callback_hours"] = *settings.MaxOwnedCallbackHours + + return []interface{}{settingsMap} +} + +func flattenMediaSettingCallback(settings *platformclientv2.Callbackmediasettings) []interface{} { + settingsMap := make(map[string]interface{}) + + settingsMap["alerting_timeout_sec"] = *settings.AlertingTimeoutSeconds + settingsMap["service_level_percentage"] = *settings.ServiceLevel.Percentage + settingsMap["service_level_duration_ms"] = *settings.ServiceLevel.DurationMs + resourcedata.SetMapValueIfNotNil(settingsMap, "enable_auto_answer", settings.EnableAutoAnswer) + resourcedata.SetMapValueIfNotNil(settingsMap, "enable_auto_dial_and_end", settings.EnableAutoDialAndEnd) + settingsMap["auto_end_delay_seconds"] = *settings.AutoEndDelaySeconds + settingsMap["auto_dial_delay_seconds"] = *settings.AutoDialDelaySeconds + + return []interface{}{settingsMap} +} + +func flattenRoutingRules(sdkRoutingRules *[]platformclientv2.Routingrule) []interface{} { + rules := make([]interface{}, len(*sdkRoutingRules)) + for i, sdkRule := range *sdkRoutingRules { + ruleSettings := make(map[string]interface{}) + + resourcedata.SetMapValueIfNotNil(ruleSettings, "operator", sdkRule.Operator) + resourcedata.SetMapValueIfNotNil(ruleSettings, "threshold", sdkRule.Threshold) + resourcedata.SetMapValueIfNotNil(ruleSettings, "wait_seconds", sdkRule.WaitSeconds) + + rules[i] = ruleSettings + } + return rules +} + +func flattenConditionalGroupRoutingRules(queue *platformclientv2.Queue) []interface{} { + if queue.ConditionalGroupRouting == nil || len(*queue.ConditionalGroupRouting.Rules) == 0 { + return nil + } + + rules := make([]interface{}, len(*queue.ConditionalGroupRouting.Rules)) + for i, rule := range *queue.ConditionalGroupRouting.Rules { + ruleSettings := make(map[string]interface{}) + + resourcedata.SetMapValueIfNotNil(ruleSettings, "wait_seconds", rule.WaitSeconds) + resourcedata.SetMapValueIfNotNil(ruleSettings, "operator", rule.Operator) + resourcedata.SetMapValueIfNotNil(ruleSettings, "condition_value", rule.ConditionValue) + resourcedata.SetMapValueIfNotNil(ruleSettings, "metric", rule.Metric) + + // The first rule is assumed to apply to this queue, so queue_id should be omitted if the conditional grouping routing rule + //is the first one being looked at. + if rule.Queue != nil && i > 0 { + ruleSettings["queue_id"] = *rule.Queue.Id + } + + if rule.Groups != nil { + memberGroups := make([]interface{}, 0) + for _, group := range *rule.Groups { + memberGroupMap := make(map[string]interface{}) + + resourcedata.SetMapValueIfNotNil(memberGroupMap, "member_group_id", group.Id) + resourcedata.SetMapValueIfNotNil(memberGroupMap, "member_group_type", group.VarType) + + memberGroups = append(memberGroups, memberGroupMap) + } + ruleSettings["groups"] = memberGroups + } + + rules[i] = ruleSettings + } + + return rules +} + +func flattenQueueMemberGroupsList(queue *platformclientv2.Queue, groupType *string) *schema.Set { + var groupIds []string + + if queue == nil || queue.MemberGroups == nil { + return nil + } + + for _, memberGroup := range *queue.MemberGroups { + if strings.Compare(*memberGroup.VarType, *groupType) == 0 { + groupIds = append(groupIds, *memberGroup.Id) + } + } + + if groupIds != nil { + return lists.StringListToSet(groupIds) + } + + return nil +} + +/* +The flattenBullseyeRings function maps the data retrieved from our SDK call over to the bullseye_ring attribute within the provider. +You might notice in the code that we are always mapping all but the last item in the list of rings retrieved by the API. The reason for this +is that when you submit a list of bullseye_rings to the API, the API will always take the last item in the list and use it to drive default behavior +This is a change from earlier parts of the API where you could define 6 bullseye rings and there would always be six. Now when you define bullseye rings, +the public API will take the list item in the list and make it the default and it will not show up on the screen. To get around this you needed +to always add a dumb bullseye ring block. Now, we automatically add one for you. We only except a maximum of 5 bullseyes_ring blocks, but we will always +remove the last block returned by the API. +*/ +func flattenBullseyeRings(sdkRings *[]platformclientv2.Ring) []interface{} { + rings := make([]interface{}, len(*sdkRings)-1) //Sizing the target array of Rings to account for us removing the default block + for i, sdkRing := range *sdkRings { + if i < len(*sdkRings)-1 { //Checking to make sure we are do nothing with the last item in the list by skipping processing if it is defined + ringSettings := make(map[string]interface{}) + if sdkRing.ExpansionCriteria != nil { + for _, criteria := range *sdkRing.ExpansionCriteria { + if *criteria.VarType == bullseyeExpansionTypeTimeout { + ringSettings["expansion_timeout_seconds"] = *criteria.Threshold + break + } + } + } + + if sdkRing.Actions != nil && sdkRing.Actions.SkillsToRemove != nil { + skillIds := make([]interface{}, len(*sdkRing.Actions.SkillsToRemove)) + for s, skill := range *sdkRing.Actions.SkillsToRemove { + skillIds[s] = *skill.Id + } + ringSettings["skills_to_remove"] = schema.NewSet(schema.HashString, skillIds) + } + + if sdkRing.MemberGroups != nil { + memberGroups := schema.NewSet(schema.HashResource(memberGroupResource), []interface{}{}) + + for _, memberGroup := range *sdkRing.MemberGroups { + memberGroupMap := make(map[string]interface{}) + memberGroupMap["member_group_id"] = *memberGroup.Id + memberGroupMap["member_group_type"] = *memberGroup.VarType + + memberGroups.Add(memberGroupMap) + } + + ringSettings["member_groups"] = memberGroups + } + rings[i] = ringSettings + } + } + return rings +} + +func FlattenQueueEmailAddress(settings platformclientv2.Queueemailaddress) map[string]interface{} { + settingsMap := make(map[string]interface{}) + + if settings.Domain != nil { + settingsMap["domain_id"] = *settings.Domain.Id + } + + if settings.Route != nil { + route := *settings.Route + settingsMap["route_id"] = *route.Id + } + + return settingsMap +} + +func flattenQueueMembers(queueID string, memberBy string, sdkConfig *platformclientv2.Configuration) (*schema.Set, diag.Diagnostics) { + members, err := getRoutingQueueMembers(queueID, memberBy, sdkConfig) + if err != nil { + return nil, err + } + + memberSet := schema.NewSet(schema.HashResource(queueMemberResource), []interface{}{}) + for _, member := range members { + memberMap := make(map[string]interface{}) + memberMap["user_id"] = *member.Id + memberMap["ring_num"] = *member.RingNumber + memberSet.Add(memberMap) + } + + return memberSet, nil +} + +func flattenQueueWrapupCodes(ctx context.Context, queueID string, proxy *RoutingQueueProxy) (*schema.Set, diag.Diagnostics) { + codes, resp, err := proxy.getAllRoutingQueueWrapupCodes(ctx, queueID) + codeIds := getWrapupCodeIds(codes) + + if err != nil { + return nil, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("failed to query wrapup codes for queue %s", queueID), resp) + } + + if codeIds != nil { + return lists.StringListToSet(codeIds), nil + } + + return nil, nil +} + +// Generate Functions + +func GenerateRoutingQueueResourceBasic(resourceID string, name string, nestedBlocks ...string) string { + return fmt.Sprintf(`resource "genesyscloud_routing_queue" "%s" { + name = "%s" + %s + } + `, resourceID, name, strings.Join(nestedBlocks, "\n")) +} + func GenerateRoutingQueueResource( resourceID string, name string, @@ -20,6 +645,8 @@ func GenerateRoutingQueueResource( enableAudioMonitoring string, enableManualAssignment string, scoringMethod string, + peerId string, + sourceQueueId string, nestedBlocks ...string) string { return fmt.Sprintf(`resource "genesyscloud_routing_queue" "%s" { name = "%s" @@ -32,6 +659,8 @@ func GenerateRoutingQueueResource( calling_party_number = %s enable_transcription = %s scoring_method = %s + peer_id = %s + source_queue_id = %s suppress_in_queue_call_recording = %s enable_audio_monitoring = %s enable_manual_assignment = %s @@ -48,20 +677,14 @@ func GenerateRoutingQueueResource( callingPartyNumber, enableTranscription, scoringMethod, + peerId, + sourceQueueId, suppressInQueueCallRecording, enableAudioMonitoring, enableManualAssignment, strings.Join(nestedBlocks, "\n")) } -func GenerateRoutingQueueResourceBasic(resourceID string, name string, nestedBlocks ...string) string { - return fmt.Sprintf(`resource "genesyscloud_routing_queue" "%s" { - name = "%s" - %s - } - `, resourceID, name, strings.Join(nestedBlocks, "\n")) -} - // GenerateRoutingQueueResourceBasicWithDepends Used when testing skills group dependencies. func GenerateRoutingQueueResourceBasicWithDepends(resourceID string, dependsOn string, name string, nestedBlocks ...string) string { return fmt.Sprintf(`resource "genesyscloud_routing_queue" "%s" { @@ -173,3 +796,44 @@ func GenerateQueueWrapupCodes(wrapupCodes ...string) string { wrapup_codes = [%s] `, strings.Join(wrapupCodes, ", ")) } + +func getRoutingQueueFromResourceData(d *schema.ResourceData) platformclientv2.Queue { + skillGroups := buildMemberGroupList(d, "skill_groups", "SKILLGROUP") + groups := buildMemberGroupList(d, "groups", "GROUP") + teams := buildMemberGroupList(d, "teams", "TEAM") + memberGroups := append(*skillGroups, *groups...) + memberGroups = append(memberGroups, *teams...) + division := d.Get("division_id").(string) + + return platformclientv2.Queue{ + Name: platformclientv2.String(d.Get("name").(string)), + Description: platformclientv2.String(d.Get("description").(string)), + Division: &platformclientv2.Division{ + Id: &division, + }, + MediaSettings: buildSdkMediaSettings(d), + RoutingRules: buildSdkRoutingRules(d), + Bullseye: buildSdkBullseyeSettings(d), + AcwSettings: buildSdkAcwSettings(d), + AgentOwnedRouting: constructAgentOwnedRouting(d), + SkillEvaluationMethod: platformclientv2.String(d.Get("skill_evaluation_method").(string)), + QueueFlow: util.BuildSdkDomainEntityRef(d, "queue_flow_id"), + EmailInQueueFlow: util.BuildSdkDomainEntityRef(d, "email_in_queue_flow_id"), + MessageInQueueFlow: util.BuildSdkDomainEntityRef(d, "message_in_queue_flow_id"), + WhisperPrompt: util.BuildSdkDomainEntityRef(d, "whisper_prompt_id"), + OnHoldPrompt: util.BuildSdkDomainEntityRef(d, "on_hold_prompt_id"), + AutoAnswerOnly: platformclientv2.Bool(d.Get("auto_answer_only").(bool)), + CallingPartyName: platformclientv2.String(d.Get("calling_party_name").(string)), + CallingPartyNumber: platformclientv2.String(d.Get("calling_party_number").(string)), + DefaultScripts: buildSdkDefaultScriptsMap(d), + OutboundMessagingAddresses: buildSdkQueueMessagingAddresses(d), + EnableTranscription: platformclientv2.Bool(d.Get("enable_transcription").(bool)), + SuppressInQueueCallRecording: platformclientv2.Bool(d.Get("suppress_in_queue_call_recording").(bool)), + EnableAudioMonitoring: platformclientv2.Bool(d.Get("enable_audio_monitoring").(bool)), + EnableManualAssignment: platformclientv2.Bool(d.Get("enable_manual_assignment").(bool)), + DirectRouting: buildSdkDirectRouting(d), + MemberGroups: &memberGroups, + PeerId: platformclientv2.String(d.Get("peer_id").(string)), + ScoringMethod: platformclientv2.String(d.Get("scoring_method").(string)), + } +} diff --git a/genesyscloud/tfexporter/resource_genesyscloud_tf_export_test.go b/genesyscloud/tfexporter/resource_genesyscloud_tf_export_test.go index cb367c218..65aae1bb6 100644 --- a/genesyscloud/tfexporter/resource_genesyscloud_tf_export_test.go +++ b/genesyscloud/tfexporter/resource_genesyscloud_tf_export_test.go @@ -130,7 +130,7 @@ func TestAccResourceTfExportIncludeFilterResourcesByRegEx(t *testing.T) { }) } -// TestAccResourceTfExportIncludeFilterResourcesByRegExAndSanitizedNames will create 3 queues (two with foo bar, one to be excluded). +// TestAccResourceTfExportIncludeFilterResourcesByRegExAndSanitizedNames will create 3 queues (twoc with foo bar, one to be excluded). // The test ensures that resources can be exported directly by their actual name or their sanitized names. func TestAccResourceTfExportIncludeFilterResourcesByRegExAndSanitizedNames(t *testing.T) { var ( @@ -668,6 +668,8 @@ func TestAccResourceTfExportByName(t *testing.T) { util.FalseValue, // suppressCall_record_false util.NullValue, // enable_transcription false strconv.Quote("TimestampAndPriority"), + util.NullValue, + util.NullValue, ) + generateTfExportByName( exportResource1, exportTestDir, @@ -711,6 +713,8 @@ func TestAccResourceTfExportByName(t *testing.T) { util.FalseValue, // suppressCall_record_false util.NullValue, // enable_transcription false strconv.Quote("TimestampAndPriority"), + util.NullValue, + util.NullValue, ) + generateTfExportByName( exportResource1, exportTestDir, @@ -767,6 +771,8 @@ func TestAccResourceTfExportByName(t *testing.T) { util.FalseValue, // suppressCall_record_false util.NullValue, // enable_transcription false strconv.Quote("TimestampAndPriority"), + util.NullValue, + util.NullValue, ) + generateTfExportByName( exportResource1, exportTestDir, @@ -1118,6 +1124,8 @@ func TestAccResourceTfExportQueueAsHCL(t *testing.T) { util.TrueValue, util.FalseValue, strconv.Quote("TimestampAndPriority"), + util.NullValue, + util.NullValue, routingQueue.GenerateMediaSettings("media_settings_call", alertTimeoutSec, util.FalseValue, slPercentage, slDurationMs), routingQueue.GenerateRoutingRules(rrOperator, rrThreshold, rrWaitSeconds), routingQueue.GenerateDefaultScriptIDs(chatScriptID, emailScriptID), @@ -2823,6 +2831,8 @@ func buildQueueResources(queueExports []QueueExport) string { util.NullValue, //suppressCall_record_false util.NullValue, // enable_transcription false strconv.Quote("TimestampAndPriority"), + util.NullValue, + util.NullValue, ) }