Skip to content

Commit

Permalink
Cleanup/Use sdk to deal with utilization instead of direct public-api…
Browse files Browse the repository at this point in the history
… calls (#1123)
  • Loading branch information
tsantos-genesys authored Jul 1, 2024
1 parent 83762bb commit 23ffa1d
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 75 deletions.
2 changes: 1 addition & 1 deletion genesyscloud/resource_genesyscloud_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -1088,7 +1088,7 @@ func readUserRoutingUtilization(d *schema.ResourceData, sdkConfig *platformclien
originalLabelUtilizations := originalSettings["label_utilizations"].([]interface{})

// Only add to the state the configured labels, in the configured order, but not any extras, to help terraform with matching new and old state.
filteredLabelUtilizations := routingUtilization.FilterAndFlattenLabelUtilizations(agentUtilization.LabelUtilizations, originalLabelUtilizations)
filteredLabelUtilizations := routingUtilization.FilterAndFlattenLabelUtilizationsInternal(agentUtilization.LabelUtilizations, originalLabelUtilizations)

allSettings["label_utilizations"] = filteredLabelUtilizations
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,22 @@ package routing_utilization

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2"
)

var internalProxy *routingUtilizationProxy

type getRoutingUtilizationFunc func(ctx context.Context, p *routingUtilizationProxy) (*platformclientv2.APIResponse, error)
type getRoutingUtilizationFunc func(ctx context.Context, p *routingUtilizationProxy) (*platformclientv2.Utilizationresponse, *platformclientv2.APIResponse, error)
type updateRoutingUtilizationFunc func(ctx context.Context, p *routingUtilizationProxy, request *platformclientv2.Utilizationrequest) (*platformclientv2.Utilizationresponse, *platformclientv2.APIResponse, error)
type deleteRoutingUtilizationFunc func(ctx context.Context, p *routingUtilizationProxy) (*platformclientv2.APIResponse, error)

type updateDirectlyFunc func(ctx context.Context, p *routingUtilizationProxy, d *schema.ResourceData, utilizationRequest []interface{}) (*platformclientv2.APIResponse, error)

type routingUtilizationProxy struct {
clientConfig *platformclientv2.Configuration
routingApi *platformclientv2.RoutingApi
getRoutingUtilizationAttr getRoutingUtilizationFunc
updateRoutingUtilizationAttr updateRoutingUtilizationFunc
deleteRoutingUtilizationAttr deleteRoutingUtilizationFunc

updateDirectlyAttr updateDirectlyFunc
}

func newRoutingUtilizationProxy(clientConfig *platformclientv2.Configuration) *routingUtilizationProxy {
Expand All @@ -34,8 +28,6 @@ func newRoutingUtilizationProxy(clientConfig *platformclientv2.Configuration) *r
getRoutingUtilizationAttr: getRoutingUtilizationFn,
updateRoutingUtilizationAttr: updateRoutingUtilizationFn,
deleteRoutingUtilizationAttr: deleteRoutingUtilizationFn,

updateDirectlyAttr: updateDirectlyFn,
}
}

Expand All @@ -46,31 +38,20 @@ func getRoutingUtilizationProxy(clientConfig *platformclientv2.Configuration) *r
return internalProxy
}

func (p *routingUtilizationProxy) getRoutingUtilization(ctx context.Context) (*platformclientv2.APIResponse, error) {
func (p *routingUtilizationProxy) getRoutingUtilization(ctx context.Context) (*platformclientv2.Utilizationresponse, *platformclientv2.APIResponse, error) {
return p.getRoutingUtilizationAttr(ctx, p)
}

func (p *routingUtilizationProxy) updateRoutingUtilization(ctx context.Context, request *platformclientv2.Utilizationrequest) (*platformclientv2.Utilizationresponse, *platformclientv2.APIResponse, error) {
return p.updateRoutingUtilizationAttr(ctx, p, request)
}

func (p *routingUtilizationProxy) deleteRoutingUtilization(ctx context.Context) (*platformclientv2.APIResponse, error) {
return p.deleteRoutingUtilizationAttr(ctx, p)
}

func (p *routingUtilizationProxy) updateDirectly(ctx context.Context, d *schema.ResourceData, utilizationRequest []interface{}) (*platformclientv2.APIResponse, error) {
return p.updateDirectlyAttr(ctx, p, d, utilizationRequest)
}

// Calling the Utilization API directly while the label feature is not available.
// Once it is, this code can go back to using platformclientv2's RoutingApi to make the call.
func getRoutingUtilizationFn(ctx context.Context, p *routingUtilizationProxy) (*platformclientv2.APIResponse, error) {
apiClient := &p.routingApi.Configuration.APIClient
path := fmt.Sprintf("%s/api/v2/routing/utilization", p.routingApi.Configuration.BasePath)
headerParams := buildHeaderParams(p.routingApi)
resp, err := apiClient.CallAPI(path, "GET", nil, headerParams, nil, nil, "", nil)
if err != nil {
return resp, fmt.Errorf("failed to get routing utilization %s ", err)
}
return resp, nil
func getRoutingUtilizationFn(ctx context.Context, p *routingUtilizationProxy) (*platformclientv2.Utilizationresponse, *platformclientv2.APIResponse, error) {
return p.routingApi.GetRoutingUtilization()
}

func updateRoutingUtilizationFn(ctx context.Context, p *routingUtilizationProxy, utilizationRequest *platformclientv2.Utilizationrequest) (*platformclientv2.Utilizationresponse, *platformclientv2.APIResponse, error) {
Expand All @@ -80,21 +61,3 @@ func updateRoutingUtilizationFn(ctx context.Context, p *routingUtilizationProxy,
func deleteRoutingUtilizationFn(ctx context.Context, p *routingUtilizationProxy) (*platformclientv2.APIResponse, error) {
return p.routingApi.DeleteRoutingUtilization()
}

// If the resource has label(s), calls the Utilization API directly.
// This code can go back to using platformclientv2's RoutingApi to make the call once label utilization is available in platformclientv2's RoutingApi
func updateDirectlyFn(ctx context.Context, p *routingUtilizationProxy, d *schema.ResourceData, utilizationRequest []interface{}) (*platformclientv2.APIResponse, error) {
apiClient := &p.routingApi.Configuration.APIClient

path := fmt.Sprintf("%s/api/v2/routing/utilization", p.routingApi.Configuration.BasePath)
headerParams := buildHeaderParams(p.routingApi)
requestPayload := make(map[string]interface{})
requestPayload["utilization"] = buildSdkMediaUtilizations(d)
requestPayload["labelUtilizations"] = BuildLabelUtilizationsRequest(utilizationRequest)

resp, err := apiClient.CallAPI(path, "PUT", requestPayload, headerParams, nil, nil, "", nil)
if err != nil {
return resp, fmt.Errorf("error updating directly %s", err)
}
return resp, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package routing_utilization

import (
"context"
"encoding/json"
"fmt"
"log"
"terraform-provider-genesyscloud/genesyscloud/provider"
Expand Down Expand Up @@ -34,30 +33,25 @@ func createRoutingUtilization(ctx context.Context, d *schema.ResourceData, meta
}

func readRoutingUtilization(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
// Calling the Utilization API directly while the label feature is not available.
// Once it is, this code can go back to using platformclientv2's RoutingApi to make the call.
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRoutingUtilizationProxy(sdkConfig)
cc := consistency_checker.NewConsistencyCheck(ctx, d, meta, ResourceRoutingUtilization(), constants.DefaultConsistencyChecks, resourceName)
orgUtilization := &OrgUtilizationWithLabels{}

log.Printf("Reading Routing Utilization")

return util.WithRetriesForRead(ctx, d, func() *retry.RetryError {
resp, err := proxy.getRoutingUtilization(ctx)
orgUtilization, resp, err := proxy.getRoutingUtilization(ctx)
if err != nil {
if util.IsStatus404(resp) {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Routing Utilization: %s", err), resp))
}
return retry.NonRetryableError(util.BuildWithRetriesApiDiagnosticError(resourceName, fmt.Sprintf("Failed to read Routing Utilization: %s", err), resp))
}

err = json.Unmarshal(resp.RawBody, &orgUtilization)

if orgUtilization.Utilization != nil {
for sdkType, schemaType := range UtilizationMediaTypes {
if mediaSettings, ok := orgUtilization.Utilization[sdkType]; ok {
_ = d.Set(schemaType, FlattenUtilizationSetting(mediaSettings))
if mediaSettings, ok := (*orgUtilization.Utilization)[sdkType]; ok {
_ = d.Set(schemaType, FlattenMediaUtilization(mediaSettings))
} else {
_ = d.Set(schemaType, nil)
}
Expand All @@ -66,8 +60,8 @@ func readRoutingUtilization(ctx context.Context, d *schema.ResourceData, meta in

if orgUtilization.LabelUtilizations != nil {
originalLabelUtilizations := d.Get("label_utilizations").([]interface{})
// Only add to the state the configured labels, in the configured order, but not any extras, to help terraform with matching new and old state.
flattenedLabelUtilizations := FilterAndFlattenLabelUtilizations(orgUtilization.LabelUtilizations, originalLabelUtilizations)
// Only add the configured labels to the state, in the configured order, but not any extras, to help terraform with matching new and old state.
flattenedLabelUtilizations := FilterAndFlattenLabelUtilizations(*orgUtilization.LabelUtilizations, originalLabelUtilizations)
_ = d.Set("label_utilizations", flattenedLabelUtilizations)
}

Expand All @@ -80,32 +74,21 @@ func updateRoutingUtilization(ctx context.Context, d *schema.ResourceData, meta
sdkConfig := meta.(*provider.ProviderMeta).ClientConfig
proxy := getRoutingUtilizationProxy(sdkConfig)

labelUtilizations := d.Get("label_utilizations").([]interface{})
var resp *platformclientv2.APIResponse
var err error

log.Printf("Updating Routing Utilization")

// Retrying on 409s because if a label is created immediately before the utilization update, it can lead to a conflict while the utilization is being updated to handle the new label.
diagErr := util.RetryWhen(util.IsStatus409, func() (*platformclientv2.APIResponse, diag.Diagnostics) {
// If the resource has label(s), calls the Utilization API directly.
// This code can go back to using platformclientv2's RoutingApi to make the call once label utilization is available in platformclientv2's RoutingApi.
if labelUtilizations != nil && len(labelUtilizations) > 0 {
resp, err := proxy.updateDirectly(ctx, d, labelUtilizations)
if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, "Failed to update routing utilization directly", resp)
}
} else {
_, resp, err = proxy.updateRoutingUtilization(ctx, &platformclientv2.Utilizationrequest{
Utilization: buildSdkMediaUtilizations(d),
})
}
_, resp, err := proxy.updateRoutingUtilization(ctx, &platformclientv2.Utilizationrequest{
Utilization: BuildSdkMediaUtilizations(d),
LabelUtilizations: BuildSdkLabelUtilizations(d),
})

if err != nil {
return resp, util.BuildAPIDiagnosticError(resourceName, fmt.Sprintf("Failed to update Routing Utilization %s error: %s", d.Id(), err), resp)
}
return resp, nil
})

if diagErr != nil {
return diagErr
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/mypurecloud/platform-client-sdk-go/v133/platformclientv2"
)

func buildSdkMediaUtilizations(d *schema.ResourceData) *map[string]platformclientv2.Mediautilization {
func BuildSdkMediaUtilizations(d *schema.ResourceData) *map[string]platformclientv2.Mediautilization {
settings := make(map[string]platformclientv2.Mediautilization)

for sdkType, schemaType := range UtilizationMediaTypes {
Expand Down Expand Up @@ -42,6 +42,24 @@ func BuildSdkMediaUtilization(settings []interface{}) platformclientv2.Mediautil
}
}

func BuildSdkLabelUtilizations(d *schema.ResourceData) *map[string]platformclientv2.Labelutilizationrequest {
labelUtilizations := d.Get("label_utilizations").([]interface{})
request := make(map[string]platformclientv2.Labelutilizationrequest)

for _, labelUtilization := range labelUtilizations {
labelUtilizationMap := labelUtilization.(map[string]interface{})
maxCapacity := labelUtilizationMap["maximum_capacity"].(int)
interruptingLabelIds := lists.SetToStringList(labelUtilizationMap["interrupting_label_ids"].(*schema.Set))

request[labelUtilizationMap["label_id"].(string)] = platformclientv2.Labelutilizationrequest{
MaximumCapacity: &maxCapacity,
InterruptingLabelIds: interruptingLabelIds,
}
}

return &request
}

func BuildLabelUtilizationsRequest(labelUtilizations []interface{}) map[string]LabelUtilization {
request := make(map[string]LabelUtilization)
for _, labelUtilization := range labelUtilizations {
Expand All @@ -56,6 +74,49 @@ func BuildLabelUtilizationsRequest(labelUtilizations []interface{}) map[string]L
return request
}

func FlattenMediaUtilization(mediaUtilization platformclientv2.Mediautilization) []interface{} {
settingsMap := make(map[string]interface{})

settingsMap["maximum_capacity"] = mediaUtilization.MaximumCapacity
settingsMap["include_non_acd"] = mediaUtilization.IncludeNonAcd
if mediaUtilization.InterruptableMediaTypes != nil {
settingsMap["interruptible_media_types"] = lists.StringListToSet(*mediaUtilization.InterruptableMediaTypes)
}

return []interface{}{settingsMap}
}

func FilterAndFlattenLabelUtilizations(labelUtilizations map[string]platformclientv2.Labelutilizationresponse, originalLabelUtilizations []interface{}) []interface{} {
flattenedLabelUtilizations := make([]interface{}, 0)

for _, originalLabelUtilization := range originalLabelUtilizations {
originalLabelId := (originalLabelUtilization.(map[string]interface{}))["label_id"].(string)

for currentLabelId, currentLabelUtilization := range labelUtilizations {
if currentLabelId == originalLabelId {
flattenedLabelUtilizations = append(flattenedLabelUtilizations, flattenLabelUtilization(currentLabelId, currentLabelUtilization))
delete(labelUtilizations, currentLabelId)
break
}
}
}

return flattenedLabelUtilizations
}

func flattenLabelUtilization(labelId string, labelUtilization platformclientv2.Labelutilizationresponse) map[string]interface{} {
utilizationMap := make(map[string]interface{})

utilizationMap["label_id"] = labelId
utilizationMap["maximum_capacity"] = labelUtilization.MaximumCapacity
if labelUtilization.InterruptingLabelIds != nil {
utilizationMap["interrupting_label_ids"] = lists.StringListToSet(*labelUtilization.InterruptingLabelIds)
}

return utilizationMap
}

// To be removed once resource_user is refactored to use FlattenMediaUtilization instead
func FlattenUtilizationSetting(settings MediaUtilization) []interface{} {
settingsMap := make(map[string]interface{})

Expand All @@ -68,15 +129,16 @@ func FlattenUtilizationSetting(settings MediaUtilization) []interface{} {
return []interface{}{settingsMap}
}

func FilterAndFlattenLabelUtilizations(labelUtilizations map[string]LabelUtilization, originalLabelUtilizations []interface{}) []interface{} {
// To be removed once resource_user is refactored to use FilterAndFlattenLabelUtilizations instead
func FilterAndFlattenLabelUtilizationsInternal(labelUtilizations map[string]LabelUtilization, originalLabelUtilizations []interface{}) []interface{} {
flattenedLabelUtilizations := make([]interface{}, 0)

for _, originalLabelUtilization := range originalLabelUtilizations {
originalLabelId := (originalLabelUtilization.(map[string]interface{}))["label_id"].(string)

for currentLabelId, currentLabelUtilization := range labelUtilizations {
if currentLabelId == originalLabelId {
flattenedLabelUtilizations = append(flattenedLabelUtilizations, flattenLabelUtilization(currentLabelId, currentLabelUtilization))
flattenedLabelUtilizations = append(flattenedLabelUtilizations, flattenLabelUtilizationInternal(currentLabelId, currentLabelUtilization))
delete(labelUtilizations, currentLabelId)
break
}
Expand All @@ -86,7 +148,8 @@ func FilterAndFlattenLabelUtilizations(labelUtilizations map[string]LabelUtiliza
return flattenedLabelUtilizations
}

func flattenLabelUtilization(labelId string, labelUtilization LabelUtilization) map[string]interface{} {
// To be removed once resource_user is refactored to use flattenLabelUtilization instead
func flattenLabelUtilizationInternal(labelId string, labelUtilization LabelUtilization) map[string]interface{} {
utilizationMap := make(map[string]interface{})

utilizationMap["label_id"] = labelId
Expand Down

0 comments on commit 23ffa1d

Please sign in to comment.