Hi Tim,
So that we can be on the same page and work through to a conclusion let me summarise my experience and where I think we may be. I'm more than happy to accept that I'm doing something wrong, but I really need to understand what that is. I am coming to this with decades of Genesys experience - but only a couple of years working with self taught python coding. So please bare this in mind.
I have 2 aims - based on the same methods..
- Create scripts to quickly build templated configurations - in this case queues.
- Create scripts that can restore/build objects from backups stored as json.
I am showing the 1st aim, as I need to resolve this issue - most likely where I'm going wrong to be able to achieve the 2nd aim.
----- The process I tried ---
- I called the get_routiing_queues() api and stored the results as JSON objects for queues we had built using the UI.
- I created a template using that exact format that was returned to be able to build additional queues with the same settings
- I built a script, with the ability to call that template but substitiute some values (e.g. name)
- running the script posted to the post_routing_queues() API with the SDK using the template above.
- Inspecting the built objects showed me that some settings did not reflect what was in the template
- I worked out elements that were not saving/being configured were one's which had 2 words in the element separated by an underscore
- Purely on a hunch (as no documentation suggested or supported this) .. I changed the elements in my template to be camel case.. and voila they worked.
Since then
- you suggested I follow the formatting in the python sdk.
- I confirmed that I followed the element naming in the same format
- I included snippets from my templates for the agent_owned_routing element of the queue (non working underscore format and working camelCase format)
- You've suggested there is something incorrect about the formatting in my templates - but I'm not exactly sure what.
I was trying to include as much information as necessary without overloading.
But as I haven't shown enough work - I'm posting as much as I can..
Firstly the relevant parts of the python sdk to get the element naming.
Here is the screenshot from the python sdk documentation for "Queue" showing the format for those elements.
Here is the screenshot for the Agent Owned Routing subseet.
Here is an example of the saved JSON from the get_routing_queues() API for a single queue. (but formatted with indentations to make it readable and id's removed )
This is what I used to format my "body" component to send.
{
"name": "SalesOutbound_DUAL",
"division": {
"name": "Home",
"self_uri": "/api/v2/authorization/divisions/"
},
"description": null,
"date_created": "2023-10-23T04:33:04.907000+00:00",
"date_modified": "2023-10-23T22:43:36.102000+00:00",
"modified_by": "74d0b20b-3b09-47b6-9583-091056829d02",
"created_by": "9f73e8dd-2030-449e-80c5-ff0b1e8584c2",
"member_count": 3,
"user_member_count": 3,
"joined_member_count": 3,
"media_settings": {
"call": {
"enable_auto_answer": null,
"alerting_timeout_seconds": 8,
"service_level": {
"percentage": 0.8,
"duration_ms": 20000
},
"auto_answer_alert_tone_seconds": null,
"manual_answer_alert_tone_seconds": null,
"sub_type_settings": null
},
"callback": {
"enable_auto_answer": null,
"alerting_timeout_seconds": 30,
"service_level": {
"percentage": 0.8,
"duration_ms": 20000
},
"auto_answer_alert_tone_seconds": null,
"manual_answer_alert_tone_seconds": null,
"sub_type_settings": null
},
"chat": {
"enable_auto_answer": null,
"alerting_timeout_seconds": 30,
"service_level": {
"percentage": 0.8,
"duration_ms": 20000
},
"auto_answer_alert_tone_seconds": null,
"manual_answer_alert_tone_seconds": null,
"sub_type_settings": null
},
"email": {
"enable_auto_answer": null,
"alerting_timeout_seconds": 300,
"service_level": {
"percentage": 0.8,
"duration_ms": 86400000
},
"auto_answer_alert_tone_seconds": null,
"manual_answer_alert_tone_seconds": null,
"sub_type_settings": null
},
"message": {
"enable_auto_answer": null,
"alerting_timeout_seconds": 30,
"service_level": {
"percentage": 0.8,
"duration_ms": 20000
},
"auto_answer_alert_tone_seconds": null,
"manual_answer_alert_tone_seconds": null,
"sub_type_settings": null
}
},
"routing_rules": [
{
"operator": "ANY",
"threshold": null,
"wait_seconds": 30.0
}
],
"conditional_group_routing": null,
"bullseye": null,
"scoring_method": "TimestampAndPriority",
"acw_settings": {
"wrapup_prompt": "OPTIONAL",
"timeout_ms": null
},
"skill_evaluation_method": "ALL",
"member_groups": null,
"queue_flow": null,
"email_in_queue_flow": null,
"message_in_queue_flow": null,
"whisper_prompt": null,
"on_hold_prompt": null,
"auto_answer_only": false,
"enable_transcription": null,
"enable_manual_assignment": null,
"agent_owned_routing": {
"enable_agent_owned_callbacks": true,
"max_owned_callback_hours": 168,
"max_owned_callback_delay_hours": 720
},
"direct_routing": null,
"calling_party_name": null,
"calling_party_number": null,
"default_scripts": {},
"outbound_messaging_addresses": null,
"outbound_email_address": null,
"peer_id": null,
"suppress_in_queue_call_recording": true,
"self_uri": "/api/v2/routing/queues/"
}
So that you can see how this was written. Here are the relevant functions I've used.
Where queue_list below is the return from get_routing_queues()
Which returns a list of dictionaries, each item in the list being a queue in dictionary form.
def get_queue_config(role,name=None):
#get the current config from the source env (function returns a list with one item)
queue_list=get_queue_details(role,name)
queue_names=[]
queue_number = len(queue_list)
for i in range (0,queue_number):
queue_names.append(queue_list[i].name)
return queue_names,queue_list,queue_number
get_queue_details is where I actually call the API
Ignore get_token_sdk - this is a separate function I've got for retrieving the token. Similarly 'role' is a construct I've created to be able to easily refer to different environments to enable migration of a configurations from non production to production environments, so isn't particularly relevant to my question.
def get_queue_details(role,name=None):
api_key = get_token_sdk(role)
if name==None:
name=""
api_instance = PureCloudPlatformClientV2.RoutingApi(api_key)
queue_list=[]
pages = api_instance.get_routing_queues(name=name).page_count
print ("Pages = ", pages)
for j in range(1,pages+1):
try:
page=j
print ("Page: ",j)
api_response=api_instance.get_routing_queues(name=name,page_number=page)
print ("Actual api response :",api_response.to_json())
queue_list.extend(api_response.entities)
except ApiException as e:
print("Exception when calling RoutingApi->get_routing_queues: %s\n" % e)
sys.exit(0)
print (queue_list)
return queue_list
Finally - how I'm writing the API response to file.
def export_configs(queue_names,queue_list,queue_number):
for i in range (0,queue_number):
q_name=queue_names[i]
q_filename=(path.join(repoFolder,q_name+".json")).replace(" ","_")
#print(q_filename)
q_json=queue_list[i].to_json()
#print(q_json)
with open(q_filename,"w") as outfile:
outfile.write(q_json)
Each queue getting a separate file and for easy reference. The file name has spaces replaced with underscores...
Here is my original template file.. (note Australian/UK spelling used for elements that aren't actually POSTED or for names)
{
"dialler": {
"WebCallback": {
"division": {
"name": "Home"
},
"description": null,
"media_settings": {
"call": {
"enable_auto_answer": null,
"alerting_timeout_seconds": 8,
"service_level": {
"percentage": 0.8,
"duration_ms": 20000
},
"auto_answer_alert_tone_seconds": null,
"manual_answer_alert_tone_seconds": null,
"sub_type_settings": null
},
"callback": {
"enable_auto_answer": null,
"alerting_timeout_seconds": 30,
"service_level": {
"percentage": 0.8,
"duration_ms": 20000
},
"auto_answer_alert_tone_seconds": null,
"manual_answer_alert_tone_seconds": null,
"sub_type_settings": null
},
"chat": {
"enable_auto_answer": null,
"alerting_timeout_seconds": 30,
"service_level": {
"percentage": 0.8,
"duration_ms": 20000
},
"auto_answer_alert_tone_seconds": null,
"manual_answer_alert_tone_seconds": null,
"sub_type_settings": null
},
"email": {
"enable_auto_answer": null,
"alerting_timeout_seconds": 300,
"service_level": {
"percentage": 0.8,
"duration_ms": 86400000
},
"auto_answer_alert_tone_seconds": null,
"manual_answer_alert_tone_seconds": null,
"sub_type_settings": null
},
"message": {
"enable_auto_answer": null,
"alerting_timeout_seconds": 30,
"service_level": {
"percentage": 0.8,
"duration_ms": 20000
},
"auto_answer_alert_tone_seconds": null,
"manual_answer_alert_tone_seconds": null,
"sub_type_settings": null
}
},
"routing_rules": [
{
"operator": "ANY",
"threshold": null,
"wait_seconds": 30.0
}
],
"conditional_group_routing": null,
"bullseye": null,
"scoring_method": "TimestampAndPriority",
"acw_settings": {
"wrapup_prompt": "MANDATORY",
"timeout_ms": null
},
"skill_evaluation_method": "NONE",
"member_groups": null,
"queue_flow": {
"name": "Red_NextGen_Unified_InQueue_Flow"
},
"email_in_queue_flow": null,
"message_in_queue_flow": null,
"whisper_prompt": null,
"on_hold_prompt": null,
"auto_answer_only": false,
"enable_transcription": true,
"enable_manual_assignment": null,
"agent_owned_routing": {
"enable_agent_owned_callbacks": true,
"max_owned_callback_hours": 1,
"max_owned_callback_delay_hours": 2
},
"direct_routing": null,
"calling_party_name": null,
"calling_party_number": null,
"default_scripts": {},
"outbound_messaging_addresses": null,
"outbound_email_address": null,
"peer_id": null,
"suppress_in_queue_call_recording": true
}
}
}
Now for the relevant code for preparing the body from the template.
def import_template():
#import the list template from the outbound.json file
#global g_temp,camp_elements
file= open ("templates/queues.json")
json_data = json.load(file)
#print(json_data)
#convert to python dictionaries
q_temp=json_data.get(q_function)
q_template=q_temp.get(template)
#check validity of dictionary entry
if type(q_temp)!=dict:
print("The template you have requested doesn't exist in queues.json file")
exit(2)
# get the list part of the template
file.close
return q_template
... I then add values where necessary, not all error trapping added yet ..
e.g.
def configure_queue(q_template):
q_config=q_template
##get configs from template names and environment
division_name=q_template['division']['name']
division_id = get_div_id(role,division_name)
if division_id == -1:
print ("Error: A division with the name ", division_name," doesn't exist in the environment")
q_flow_name=q_template['queue_flow']['name']
print(q_flow_name)
q_flow_id=get_unique_flow_id(role,q_flow_name)
print(q_flow_id)
voice_script_name=q_template["default_scripts"]["CALL"]["name"]
voice_script_id=get_unique_script_id(role,voice_script_name)
print ("Voice Script \n Name: ",voice_script_name,"\n ID: ", voice_script_id)
# assign configs to q_config
q_config["division"]["id"]=division_id
q_config["name"]=queue_name
q_config["default_scripts"]["CALL"]["id"]=voice_script_id
del q_config["default_scripts"]["CALL"]["name"]
q_config["queue_flow"]["id"]=q_flow_id
#print(q_config)
return q_config
The result of which gets submitted as the body via the following
def create_queue(role,body):
api_key = get_token_sdk(role)
api_instance = PureCloudPlatformClientV2.RoutingApi(api_key)
try:
# Create a queue
api_response = api_instance.post_routing_queues(body)
print (api_response)
return api_response.id
except ApiException as e:
print("Exception when calling RoutingApi->post_routing_queues: %s\n" % e)
sys.exit(0)
So now for the working template... I've only changed configs I'm particularly concerned with at this point so there are still many elements with the underscore naming
{
"dialler": {
"WebCallback": {
"division": {
"name": "Home"
},
"description": null,
"mediaSettings": {
"call": {
"enableAutoAnswer": true,
"alertingTimeoutSeconds": 8,
"serviceLevel": {
"percentage": 0.8,
"durationMs": 20000
},
"autoAnswerAlertToneSeconds": null,
"manualAnswerAlertToneSeconds": null,
"subTypeSettings": null
},
"callback": {
"enableAutoAnswer": null,
"alertingTimeoutSeconds": 30,
"serviceLevel": {
"percentage": 0.8,
"durationMs": 20000
},
"autoAnswerAlertToneSeconds": null,
"manualAnswerAlertToneSeconds": null,
"subTypeSettings": null
},
"chat": {
"enableAutoAnswer": null,
"alertingTimeoutSeconds": 30,
"serviceLevel": {
"percentage": 0.8,
"durationMs": 20000
},
"auto_answer_alert_tone_seconds": null,
"manual_answer_alert_tone_seconds": null,
"sub_type_settings": null
},
"email": {
"enableAutoAnswer": null,
"alertingTimeoutSeconds": 300,
"serviceLevel": {
"percentage": 0.8,
"durationMs": 86400000
},
"autoAnswerAlertToneSeconds": null,
"manualAnswerAlertToneSeconds": null,
"subTypeSettings": null
},
"message": {
"enableAutoAnswer": null,
"alertingTimeoutSeconds": 30,
"serviceLevel": {
"percentage": 0.8,
"durationMs": 20000
},
"autoAnswerAlertToneSeconds": null,
"manualAnswerAlertToneSeconds": null,
"subTypeSettings": null
}
},
"routingRules": [
{
"operator": "ANY",
"waitSeconds": 30.0
}
],
"conditional_group_routing": null,
"bullseye": null,
"scoringMethod": "TimestampAndPriority",
"acwSettings": {
"wrapupPrompt": "MANDATORY",
"timeout_ms": null
},
"skill_evaluation_method": "NONE",
"member_groups": null,
"queueFlow": {
"name": "RedEnergy_InQueue_Outbound_DUAL_V0.1"
},
"email_in_queue_flow": null,
"message_in_queue_flow": null,
"whisper_prompt": null,
"on_hold_prompt": null,
"auto_answer_only": false,
"enable_transcription": true,
"enable_manual_assignment": null,
"agentOwnedRouting": {
"enableAgentOwnedCallbacks": true,
"maxOwnedCallbackHours": 168,
"maxOwnedCallbackDelayHours": 720
},
"direct_routing": null,
"calling_party_name": null,
"calling_party_number": null,
"defaultScripts": {
"CALL":{
"name":"DefaultOutbound_RED2.1"
}
},
"outbound_messaging_addresses": null,
"outbound_email_address": null,
"peer_id": null,
"suppress_in_queue_call_recording": true
}
}
}
I've also changed the function that adds in the necessary data to match this format.
def configure_queue(q_template):
q_config=q_template
##get configs from template names and environment
division_name=q_template['division']['name']
division_id = get_div_id(role,division_name)
if division_id == -1:
print ("Error: A division with the name ", division_name," doesn't exist in the environment")
q_flow_name=q_template['queueFlow']['name']
print(q_flow_name)
q_flow_id=get_unique_flow_id(role,q_flow_name)
print(q_flow_id)
voice_script_name=q_template["defaultScripts"]["CALL"]["name"]
voice_script_id=get_unique_script_id(role,voice_script_name)
print ("Voice Script \n Name: ",voice_script_name,"\n ID: ", voice_script_id)
# assign configs to q_config
q_config["division"]["id"]=division_id
q_config["name"]=queue_name
q_config["defaultScripts"]["CALL"]["id"]=voice_script_id
del q_config["defaultScripts"]["CALL"]["name"]
q_config["queueFlow"]["id"]=q_flow_id
print(json.dumps(q_config))
return q_config
So, I'd much prefer to use the naming convention as per the sdk - but need to know what I'm doing wrong that is forcing me to use camel case.
I can't see what I may be doing wrong, but it does seem that I can't post a template in the same format that the GET requests are responding with. Hence why I suspected there may be a defect.