Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Devtooling-991] Added voicemail timeout to user #1510

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/resources/user.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ resource "genesyscloud_user" "example_user" {
- `routing_utilization` (List of Object) The routing utilization settings for this user. If empty list, the org default settings are used. If not set, this resource will not manage the users's utilization settings. (see [below for nested schema](#nestedatt--routing_utilization))
- `state` (String) User's state (active | inactive). Default is 'active'. Defaults to `active`.
- `title` (String) User's title.
- `voicemail_userpolicies` (Block List, Max: 1) User's voicemail policies. (see [below for nested schema](#nestedblock--voicemail_userpolicies))
fergalmcl marked this conversation as resolved.
Show resolved Hide resolved

### Read-Only

Expand Down Expand Up @@ -275,3 +276,12 @@ Optional:
- `interruptible_media_types` (Set of String)
- `maximum_capacity` (Number)



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

Optional:

- `alert_timeout_seconds` (Number) The number of seconds to ring the user's phone before a call is transferred to voicemail.

83 changes: 55 additions & 28 deletions genesyscloud/user/genesyscloud_user_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type deleteUserFunc func(ctx context.Context, p *userProxy, id string) (*interfa
type patchUserWithStateFunc func(ctx context.Context, p *userProxy, id string, updateUser *platformclientv2.Updateuser) (*platformclientv2.User, *platformclientv2.APIResponse, error)
type hydrateUserCacheFunc func(ctx context.Context, p *userProxy, pageSize int, pageNum int) (*platformclientv2.Userentitylisting, *platformclientv2.APIResponse, error)
type getUserByNameFunc func(ctx context.Context, p *userProxy, searchUser platformclientv2.Usersearchrequest) (*platformclientv2.Userssearchresponse, *platformclientv2.APIResponse, error)
type updateVoicemailUserpoliciesFunc func(ctx context.Context, p *userProxy, id string, policy *platformclientv2.Voicemailuserpolicy) (*platformclientv2.Voicemailuserpolicy, *platformclientv2.APIResponse, error)
type getVoicemailUserpoliciesByIdFunc func(ctx context.Context, p *userProxy, id string) (*platformclientv2.Voicemailuserpolicy, *platformclientv2.APIResponse, error)

/*
The userProxy struct holds all the methods responsible for making calls to
Expand All @@ -42,19 +44,22 @@ enabling this terraform provider software to perform tasks like retrieving data,
or triggering actions within the Genesys Cloud environment.
*/
type userProxy struct {
clientConfig *platformclientv2.Configuration
userApi *platformclientv2.UsersApi
routingApi *platformclientv2.RoutingApi
createUserAttr createUserFunc
getAllUserAttr getAllUserFunc
getUserIdByNameAttr getUserIdByNameFunc
getUserByIdAttr getUserByIdFunc
updateUserAttr updateUserFunc
deleteUserAttr deleteUserFunc
patchUserWithStateAttr patchUserWithStateFunc
hydrateUserCacheAttr hydrateUserCacheFunc
getUserByNameAttr getUserByNameFunc
userCache rc.CacheInterface[platformclientv2.User] //Define the cache for user resource
clientConfig *platformclientv2.Configuration
userApi *platformclientv2.UsersApi
routingApi *platformclientv2.RoutingApi
voicemailApi *platformclientv2.VoicemailApi
createUserAttr createUserFunc
getAllUserAttr getAllUserFunc
getUserIdByNameAttr getUserIdByNameFunc
getUserByIdAttr getUserByIdFunc
updateUserAttr updateUserFunc
deleteUserAttr deleteUserFunc
patchUserWithStateAttr patchUserWithStateFunc
hydrateUserCacheAttr hydrateUserCacheFunc
getUserByNameAttr getUserByNameFunc
updateVoicemailUserpoliciesAttr updateVoicemailUserpoliciesFunc
getVoicemailUserpolicicesByIdAttr getVoicemailUserpoliciesByIdFunc
userCache rc.CacheInterface[platformclientv2.User] //Define the cache for user resource
}

/*
Expand All @@ -66,21 +71,25 @@ seamlessly with the Genesys Cloud platform.
func newUserProxy(clientConfig *platformclientv2.Configuration) *userProxy {
userApi := platformclientv2.NewUsersApiWithConfig(clientConfig) // NewUsersApiWithConfig creates an Genesyc Cloud API instance using the provided configuration
routingApi := platformclientv2.NewRoutingApiWithConfig(clientConfig) // NewRoutingApiWithConfig creates an Genesyc Cloud API instance using the provided configuration
userCache := rc.NewResourceCache[platformclientv2.User]() // Create Cache for User resource
voicemailApi := platformclientv2.NewVoicemailApiWithConfig(clientConfig)
userCache := rc.NewResourceCache[platformclientv2.User]() // Create Cache for User resource
return &userProxy{
clientConfig: clientConfig,
userApi: userApi,
routingApi: routingApi,
userCache: userCache,
createUserAttr: createUserFn,
getAllUserAttr: getAllUserFn,
getUserIdByNameAttr: getUserIdByNameFn,
getUserByIdAttr: getUserByIdFn,
updateUserAttr: updateUserFn,
deleteUserAttr: deleteUserFn,
patchUserWithStateAttr: patchUserWithStateFn,
hydrateUserCacheAttr: hydrateUserCacheFn,
getUserByNameAttr: getUserByNameFn,
clientConfig: clientConfig,
userApi: userApi,
routingApi: routingApi,
voicemailApi: voicemailApi,
userCache: userCache,
createUserAttr: createUserFn,
getAllUserAttr: getAllUserFn,
getUserIdByNameAttr: getUserIdByNameFn,
getUserByIdAttr: getUserByIdFn,
updateUserAttr: updateUserFn,
deleteUserAttr: deleteUserFn,
patchUserWithStateAttr: patchUserWithStateFn,
hydrateUserCacheAttr: hydrateUserCacheFn,
updateVoicemailUserpoliciesAttr: updateVoicemailUserpoliciesFn,
getUserByNameAttr: getUserByNameFn,
getVoicemailUserpolicicesByIdAttr: getVoicemailUserpoliciesByUserIdFn,
}
}

Expand Down Expand Up @@ -146,6 +155,16 @@ func (p *userProxy) getUserByName(ctx context.Context, searchUser platformclient
return p.getUserByNameAttr(ctx, p, searchUser)
}

// updateVoicemailUserpolicies
func (p *userProxy) updateVoicemailUserpolicies(ctx context.Context, userId string, updatePolicy *platformclientv2.Voicemailuserpolicy) (*platformclientv2.Voicemailuserpolicy, *platformclientv2.APIResponse, error) {
return p.updateVoicemailUserpoliciesAttr(ctx, p, userId, updatePolicy)
}

// getVoicemailUserpoliciesById
func (p *userProxy) getVoicemailUserpoliciesById(ctx context.Context, id string) (*platformclientv2.Voicemailuserpolicy, *platformclientv2.APIResponse, error) {
return p.getVoicemailUserpolicicesByIdAttr(ctx, p, id)
}

// createUserFn is an implementation function for creating a Genesys Cloud user
func createUserFn(ctx context.Context, p *userProxy, createUser *platformclientv2.Createuser) (*platformclientv2.User, *platformclientv2.APIResponse, error) {
return p.userApi.PostUsers(*createUser)
Expand Down Expand Up @@ -270,5 +289,13 @@ func getUserIdByNameFn(ctx context.Context, p *userProxy, name string) (id strin
}
}

return "", true, apiResponse, fmt.Errorf("Unable to find user wiht name %s", name)
return "", true, apiResponse, fmt.Errorf("Unable to find user with name %s", name)
}

func updateVoicemailUserpoliciesFn(ctx context.Context, p *userProxy, userId string, updatePolicy *platformclientv2.Voicemailuserpolicy) (*platformclientv2.Voicemailuserpolicy, *platformclientv2.APIResponse, error) {
return p.voicemailApi.PatchVoicemailUserpolicy(userId, *updatePolicy)
}

func getVoicemailUserpoliciesByUserIdFn(ctx context.Context, p *userProxy, id string) (*platformclientv2.Voicemailuserpolicy, *platformclientv2.APIResponse, error) {
return p.voicemailApi.GetVoicemailUserpolicy(id)
}
8 changes: 8 additions & 0 deletions genesyscloud/user/resource_genesyscloud_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,14 @@ func readUser(ctx context.Context, d *schema.ResourceData, meta interface{}) dia
d.Set("certifications", flattenUserData(currentUser.Certifications))
d.Set("employer_info", flattenUserEmployerInfo(currentUser.EmployerInfo))

//Get attributes from Voicemail/Userpolicies resource
currentVoicemailUserpolicies, resp, err := proxy.getVoicemailUserpoliciesById(ctx, d.Id())
if err != nil {
return retry.RetryableError(util.BuildWithRetriesApiDiagnosticError(ResourceType, fmt.Sprintf("Failed to read voicemail userpolicies %s error: %s", d.Id(), err), resp))
}

_ = d.Set("voicemail_userpolicies", flattenVoicemailUserpolicies(d, currentVoicemailUserpolicies))

if diagErr := readUserRoutingUtilization(d, proxy); diagErr != nil {
return retry.NonRetryableError(fmt.Errorf("%v", diagErr))
}
Expand Down
24 changes: 21 additions & 3 deletions genesyscloud/user/resource_genesyscloud_user_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,15 @@ var (
},
},
}
voicemailUserpoliciesResource = &schema.Resource{
fergalmcl marked this conversation as resolved.
Show resolved Hide resolved
Schema: map[string]*schema.Schema{
"alert_timeout_seconds": {
Description: "The number of seconds to ring the user's phone before a call is transferred to voicemail.",
Type: schema.TypeInt,
Optional: true,
},
},
}
)

func ResourceUser() *schema.Resource {
Expand Down Expand Up @@ -388,6 +397,14 @@ func ResourceUser() *schema.Resource {
},
},
},
"voicemail_userpolicies": {
Description: "User's voicemail policies.",
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this meant to be computed? For example, if the user doesn't set this, does the API set some default? Otherwise, if it is nil on the API, we probably shouldn't mark this as Computed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API does set a default value but only for one of the fields 'send_email_notifications' so i've just changed that to be computed instead

Elem: voicemailUserpoliciesResource,
},
},
}
}
Expand Down Expand Up @@ -422,9 +439,10 @@ func UserExporter() *resourceExporter.ResourceExporter {
"locations.location_id": {RefType: "genesyscloud_location"},
},
RemoveIfMissing: map[string][]string{
"routing_skills": {"skill_id"},
"routing_languages": {"language_id"},
"locations": {"location_id"},
"routing_skills": {"skill_id"},
"routing_languages": {"language_id"},
"locations": {"location_id"},
"voicemail_userpolicies": {"alert_timeout_seconds"},
fergalmcl marked this conversation as resolved.
Show resolved Hide resolved
},
AllowZeroValues: []string{"routing_skills.proficiency", "routing_languages.proficiency"},
}
Expand Down
56 changes: 56 additions & 0 deletions genesyscloud/user/resource_genesyscloud_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,62 @@ func TestAccResourceUserBasic(t *testing.T) {
})
}

func TestAccResourceUserVoicemailUserpolicies(t *testing.T) {
var (
userResourceLabel1 = "test-user1"
email1 = "terraform-" + uuid.NewString() + "@user.com"
email2 = "terraform-" + uuid.NewString() + "@user.com"
userName1 = "John Terraform"
userName2 = "Jim Terraform"
timeoutSeconds = 550
timeoutSeconds2 = 450
)

resource.Test(t, resource.TestCase{
PreCheck: func() { util.TestAccPreCheck(t) },
ProviderFactories: provider.GetProviderFactories(providerResources, providerDataSources),
Steps: []resource.TestStep{
{
// Create
Config: generateUserWithCustomAttrs(
userResourceLabel1,
email1,
userName1,
GenerateVoicemailUserpolicies(timeoutSeconds),
),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(ResourceType+"."+userResourceLabel1, "email", email1),
resource.TestCheckResourceAttr(ResourceType+"."+userResourceLabel1, "name", userName1),
resource.TestCheckResourceAttr(ResourceType+"."+userResourceLabel1, "voicemail_userpolicies.0.alert_timeout_seconds", strconv.Itoa(timeoutSeconds)),
provider.TestDefaultHomeDivision(ResourceType+"."+userResourceLabel1),
),
},
{
// Update
Config: generateUserWithCustomAttrs(
userResourceLabel1,
email2,
userName2,
GenerateVoicemailUserpolicies(timeoutSeconds2),
),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(ResourceType+"."+userResourceLabel1, "email", email2),
resource.TestCheckResourceAttr(ResourceType+"."+userResourceLabel1, "name", userName2),
resource.TestCheckResourceAttr(ResourceType+"."+userResourceLabel1, "voicemail_userpolicies.0.alert_timeout_seconds", strconv.Itoa(timeoutSeconds2)),
provider.TestDefaultHomeDivision(ResourceType+"."+userResourceLabel1),
),
},
{
// Import/Read
ResourceName: ResourceType + "." + userResourceLabel1,
ImportState: true,
ImportStateVerify: true,
},
},
CheckDestroy: testVerifyUsersDestroyed,
})
}

func generateUserWithCustomAttrs(resourceLabel string, email string, name string, attrs ...string) string {
return fmt.Sprintf(`resource "%s" "%s" {
email = "%s"
Expand Down
59 changes: 59 additions & 0 deletions genesyscloud/user/resource_genesyscloud_user_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ func executeAllUpdates(d *schema.ResourceData, proxy *userProxy, sdkConfig *plat
return diagErr
}

diagErr = updateUserVoicemailPolicies(d, proxy)
if diagErr != nil {
return diagErr
}

return nil
}

Expand Down Expand Up @@ -236,6 +241,32 @@ func updateUserProfileSkills(d *schema.ResourceData, proxy *userProxy) diag.Diag
return nil
}

func updateUserVoicemailPolicies(d *schema.ResourceData, proxy *userProxy) diag.Diagnostics {
if d.HasChange("voicemail_userpolicies") {
if voicemailUserpolicies := d.Get("voicemail_userpolicies").([]interface{}); voicemailUserpolicies != nil {
fergalmcl marked this conversation as resolved.
Show resolved Hide resolved
if len(voicemailUserpolicies) > 0 {
if extractMap, ok := voicemailUserpolicies[0].(map[string]interface{}); ok {
reqBody := buildVoicemailUserpoliciesRequest(extractMap)
diagErr := util.RetryWhen(util.IsVersionMismatch, func() (*platformclientv2.APIResponse, diag.Diagnostics) {

_, proxyPutResponse, putErr := proxy.voicemailApi.PatchVoicemailUserpolicy(d.Id(), reqBody)
if putErr != nil {
return proxyPutResponse, util.BuildAPIDiagnosticError(ResourceType, fmt.Sprintf("Failed to update voicemail userpolicices for user %s error: %s", d.Id(), putErr), proxyPutResponse)
}

return nil, nil
})
if diagErr != nil {
return diagErr
}
}

}
}
}
return nil
}

func updateUserRoutingUtilization(d *schema.ResourceData, proxy *userProxy) diag.Diagnostics {
if d.HasChange("routing_utilization") {
if utilConfig := d.Get("routing_utilization").([]interface{}); utilConfig != nil {
Expand Down Expand Up @@ -871,6 +902,27 @@ func getSdkUtilizationTypes() []string {
return types
}

func buildVoicemailUserpoliciesRequest(voicemailUserpolicyMap map[string]interface{}) platformclientv2.Voicemailuserpolicy {
alertTimeoutSeconds := voicemailUserpolicyMap["alert_timeout_seconds"].(int)

return platformclientv2.Voicemailuserpolicy{
AlertTimeoutSeconds: &alertTimeoutSeconds,
}
}

func flattenVoicemailUserpolicies(d *schema.ResourceData, voicemail *platformclientv2.Voicemailuserpolicy) []interface{} {
if voicemail == nil {
fergalmcl marked this conversation as resolved.
Show resolved Hide resolved
return nil
}

voicemailUserpolicy := make(map[string]interface{})
if voicemail.AlertTimeoutSeconds != nil {
voicemailUserpolicy["alert_timeout_seconds"] = *voicemail.AlertTimeoutSeconds
}

return []interface{}{voicemailUserpolicy}
}

func generateRoutingUtilMediaType(
mediaType string,
maxCapacity string,
Expand Down Expand Up @@ -935,3 +987,10 @@ func GenerateUserResource(resourceLabel string, email string, name string, state
}
`, ResourceType, resourceLabel, email, name, state, title, department, manager, acdAutoAnswer, profileSkills, certifications)
}

func GenerateVoicemailUserpolicies(timeout int) string {
return fmt.Sprintf(`voicemail_userpolicies {
alert_timeout_seconds = %d
}
`, timeout)
}