Step 3: Migrating the relationship types of legacy custom objects
This article describes in detail how to migrate the relationship types of your legacy custom objects to the new custom objects experience.
In database design, a relationship type describes how two or more tables are associated with each other. The relationship defines how data in one table relates to data in another. For example, a record in one table can be related to many records in a second table.
Similarly in custom objects, a relationship type defines how two object records are associated with each other. For example, a record of one object type can be related to many records of a second object type.
Relationship types don't actually create relationships between existing records. In legacy custom objects, a relationship between two existing records is created with a relationship record. The relationship records of legacy custom objects are migrated to the new custom objects experience separately. See Step 5: Migrating the relationship records of legacy custom objects.
Topics covered in this article:
- Understanding relationship types in legacy custom objects
- Understanding relationships in the new custom objects experience
- Limitations
- Migrating legacy relationship types
- Next steps
This is the third article in a series on migrating legacy custom objects:
- Step 1: Migrating the object types of legacy custom objects
- Step 2: Migrating the schemas of legacy custom objects
- Step 3: Migrating the relationship types of legacy custom objects
- Step 4: Migrating the records of legacy custom objects
- Step 5: Migrating the relationship records of legacy custom objects
Disclaimer: Zendesk provides this article for informational purposes only. Zendesk doesn't provide support for the code in this article, nor does Zendesk support third-party technologies such as Python.
Understanding relationship types in legacy custom objects
In legacy custom objects, a relationship type is defined by an object with a key
, source
, and target
property. Example:
{
"data": {
"key": "product_has_many_users",
"source": "product",
"target": ["zen:user"]
}
}
The source
and target
values can be any custom object or a supported standard Zendesk object such as "zen:ticket" or "zen:user".
The square brackets define the cardinality of the relationship -- one-to-one, one-to-many (or many-to-one), or many-to-many. The brackets can enclose the target
value for one-to-many relationships, each value for many-to-many relationships, or neither value for one-to-one relationships. For more information, see Legacy relationship types.
Understanding relationships in the new custom objects experience
In the new custom objects experience, a relationship is defined by a lookup relationship field.
A lookup relationship field always defines a "many-to-one" relationship:
- The "many" object is always the object that contains the lookup relationship field.
- The "one" object is always the object that populates the options in the lookup relationship field's drop-down menu. The options are records of the "one" object.
The reason lookup relationship fields define many-to-one relationships is that the field only allows you to pick one record from the lookup field for each record of the "many" object. Over time, more than one record of the "many" object can become related to a particular record of the "one" object. Or stated in a different way, a single record of the "one" object can become related to more than one records of the "many" object.
You can add one or more lookup relationship fields to the following pages in Zendesk:
- The ticket page or the support request form in your help center
- The user profile page
- The organization page
The objects that these pages represent -- the standard Zendesk ticket, user, and organization objects -- automatically become the "many" object of the relationship.
The following example shows the body of an API request that adds a lookup relationship field to the user profile page in Zendesk (which is indicated by the user_field
property). The Zendesk user object is the "many" object in the relationship and the "rental_property" custom object is the "one" object.
{
"user_field": {
"key": "users_who_booked_a_rental_property",
"title": "Users who booked a rental property",
"type": "lookup",
"relationship_target_type": "zen:custom_object:rental_property"
}
}
As well as adding lookup relationship fields to the pages of standard objects in Zendesk, you can also add lookup fields to custom objects. As the object that contains the lookup field, the custom object automatically becomes the "many" object.
In the following example, the lookup field was added to a "rental_property" custom object. The "many" object is the "rental_property" custom object and the "one" object is the Zendesk user object.
{
"custom_object_field": {
"key": "rental_properties_booked_by_user",
"title": "Rental properties booked by user",
"type": "lookup",
"relationship_target_type": "zen:user"
}
}
Limitations
Standard Zendesk objects
Lookup relationship fields don't support all the standard Zendesk objects that relationships in legacy custom objects do. If the source
or target
of a legacy relationship is a standard Zendesk object that's not supported in the new custom objects experience, then you won't be able to migrate the relationship.
The following standard objects are supported in both the legacy and new custom objects experiences:
- "zen:ticket"
- "zen:user"
- "zen:organization"
The following standard objects are supported in the legacy custom experience but are not supported in the new custom objects experience:
- "zen:group"
- "zen:article"
- "zen:chat"
- "zen:brand"
- "zen:lead"
- "zen:contact"
- "zen:deal"
Relationship cardinality
Lookup relationship fields only support many-to-one or one-to-many relationships.
However, you can create a many-to-many relationship using a junction object. For more information, see Creating many-to-many relationships in Zendesk using custom objects.
Migrating legacy relationship types
Migrating legacy relationship types consists of adding a lookup field to the "many" object of each relationship. The "many" object could be a standard Zendesk object or a migrated custom object.
For example, suppose the legacy relationship type defines the following one-to-many relationship:
{
"data": {
"key": "product_has_many_users",
"source": "product",
"target": ["zen:user"]
}
}
Add the lookup field to the specified by the target
object -- the Zendesk user object. Adding the field to the user object makes it the "many" object of the lookup field.
Specify the source
object of the relationship type as the relationship_target_type
of the lookup field. This makes the "rental_property" object the "one" object of the lookup field.
Example request body:
{
"user_field": {
"key": "product_has_many_users",
"title": "Product users",
"type": "lookup",
"relationship_target_type": "zen:custom_object:product"
}
}
If the "many" target
of a relationship type is a custom object, then add a lookup custom object field to the migrated custom object. In the following example, the target
object of the relationship type is a custom object:
{
"data": {
"key": "user_booked_many_rental_properties",
"source": "zen:user",
"target": ["rental_property"]
}
}
Add the lookup field to the migrated "rental_property" custom object and specify the user object as the relationship_target_type
of the lookup field. The "rental_property" object becomes the "many" object and the user object becomes the "one" object of the lookup field.
Example lookup field:
"custom_object_field": {
"key": "user_booked_many_rental_properties",
"title": "Rental properties booked by user",
"type": "lookup",
"relationship_target_type": "zen:user"
}
Example main function logic
The following example main()
function shows the general logic of migrating legacy relationship types to the new custom objects experience. The called functions are explained in the follow-up sections.
def main():
lookup_field_map = {}
relationship_types = get_relationship_types()
for relationship_type in relationship_types:
if relationship_type['target'][:4] == 'zen:':
add_lookup_field_to_zen_object(relationship_type, lookup_field_map)
else:
add_lookup_field_to_custom_object(relationship_type)
write_json_file('lookup_field_map.json', lookup_field_map)
# utility function
def write_json_file(file_path, file_data):
with open(file_path, mode='w', encoding='utf-8') as f:
json.dump(file_data, f, sort_keys=True, indent=2)
Example function for getting the relationship types
The following example Python function gets the relationships types and filters out any unsupported ones.
def get_relationship_types():
endpoint = f'api/sunshine/relationships/types'
url = f'https://{ZENDESK_SUBDOMAIN}.zendesk.com/{endpoint}'
auth = f'{ZENDESK_USER_EMAIL}/token', ZENDESK_API_TOKEN
response = requests.get(url, auth=auth)
relationship_types = response.json()['data']
supported_rel_types = []
for relationship_type in relationship_types:
if relationship_type_is_not_supported(relationship_type):
continue
supported_rel_types.append(relationship_type)
return supported_rel_types
def relationship_type_is_not_supported(relationship_type) -> bool:
"""
Checks for unsupported relationship cardinality or unsupported Zendesk standard objects
"""
# check for many-to-many relationship
if (isinstance(relationship_type['source'], list)
and isinstance(relationship_type['target'], list)):
return True
# check for one-to-one relationship
if (not isinstance(relationship_type['source'], list)
and not isinstance(relationship_type['target'], list)):
return True
# check for unsupported Zendesk standard objects
if (relationship_type['source'][:4] == 'zen:'
and relationship_type['source'] not in ['zen:ticket', 'zen:user', 'zen:organization']):
return True
if (relationship_type['target'][:4] == 'zen:'
and relationship_type['target'] not in ['zen:ticket', 'zen:user', 'zen:organization']):
return True
return False
Example function for adding a lookup field to a Zendesk object
The following example Python function adds a lookup relationship field to a Zendesk object.
The API endpoint the function uses to add the field depends on the Zendesk object:
- for the "zen:ticket" object, it uses the Create Ticket Field endpoint
- for the "zen:user" object, it uses the Create User Field endpoint
- for the "zen:organization object, it uses the Create Organization Field endpoint
def add_lookup_field_to_zen_object(relationship_type, lookup_field_map):
relationship_target_type = f'zen:custom_object:{relationship_type['source']}'
lookup_relationship_field = {
'title': relationship_type['key'].replace('_', ' ').capitalize(),
'type': 'lookup',
'relationship_target_type': relationship_target_type
}
standard_object_page = relationship_type['target'][0].split(':')[1]
data = {f'{standard_object_page}_field': lookup_relationship_field}
endpoint = f'api/v2/{standard_object_page}_fields'
# make the request
url = f'https://{ZENDESK_SUBDOMAIN}.zendesk.com/{endpoint}'
auth = f'{ZENDESK_USER_EMAIL}/token', ZENDESK_API_TOKEN
response = requests.post(url, json=data, auth=auth)
if response.status_code != 201:
print(f'-- {response.status_code}: {response.text}')
return {}
# update lookup field map
data = response.json()
lookup_field_id = data['ticket_field']['id']
lookup_field_map[relationship_type['key']] = lookup_field_id
return response
Example function for adding a lookup field to a custom object
The following example Python function adds a lookup field to a custom object. It uses the Create Custom Object Field endpoint to add the field.
def add_lookup_field_to_custom_object(relationship_type):
custom_object_field = {
'key': relationship_type['key'],
'title': relationship_type['key'].replace('_', ' ').capitalize(),
'type': 'lookup',
'relationship_target_type': relationship_type['source']
}
custom_object_key = relationship_type['target'][0]
data = {'custom_object_field': custom_object_field}
endpoint = f'api/v2/custom_objects/{custom_object_key}/fields'
# make the request
url = f'https://{ZENDESK_SUBDOMAIN}.zendesk.com/{endpoint}'
auth = f'{ZENDESK_USER_EMAIL}/token', ZENDESK_API_TOKEN
response = requests.post(url, json=data, auth=auth)
if response.status_code != 201:
print(f'-- {response.status_code}: {response.text}')
return {}
Next steps
After migrating your legacy custom object types, schemas, and relationship types, you can migrate the object records. See Step 4: Migrating the records of legacy custom objects.