Skip to content
Open
Changes from all commits
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
103 changes: 82 additions & 21 deletions scripts/clone_organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from closeio_api import APIError

from scripts.CloseApiWrapper import CloseApiWrapper
from CloseApiWrapper import CloseApiWrapper

arg_parser = argparse.ArgumentParser(
description="Clone one organization to another"
Expand Down Expand Up @@ -93,6 +93,11 @@
arg_parser.add_argument(
"--all", "-a", action="store_true", help="Copy all settings"
)
arg_parser.add_argument(
"--clear-lead-and-status",
action="store_true",
help="Remove leads and lead statuses from destination org",
)
args = arg_parser.parse_args()

from_api = CloseApiWrapper(args.from_api_key)
Expand All @@ -101,13 +106,49 @@
from_organization = from_api.get("me")["organizations"][0]
to_organization = to_api.get("me")["organizations"][0]

# ask user to confirm changes
message = f"Cloning `{from_organization['name']}` ({from_organization['id']}) organization to `{to_organization['name']}` ({to_organization['id']})..."
message += '\nData from source organization will be added to the destination organization. No data will be deleted.\n\nContinue?'
if not args.clear_lead_and_status:
message += '\nData from source organization will be added to the destination organization. No data will be deleted.\n\nContinue?'
else:
message += '\nData from source organization will be added to the destination organization.\n\nContinue?'

confirmed = input(f"{message} (y/n)\n")
if confirmed not in ["yes", "y"]:
exit()

# Clear destination leads and lead statuses
if args.clear_lead_and_status:
delete_message = 'Existing leads and lead statuses will be deleted from the destination organization. \n Continue?'
delete_confirmation = input(f"{delete_message} (y/n)")
if delete_confirmation in ["yes", "y"]:
print("\nDeleting existing leads in destination org")
to_leads = to_api.get_all_items('lead', params={'_fields': 'id,name'})
for lead in to_leads:
try:
to_api.delete(f"lead/{lead['id']}/")
print(f"Removed lead {lead['name']}")
except APIError as e:
print(f"Couldn't remove lead `{lead['name']}` because {str(e)}")

print("\nDeleting lead statuses in destination org")
to_lead_statuses = to_api.get_lead_statuses()
# at least one status must exist so we can rename the first one to have the label match the id for now
first_status_to = to_lead_statuses[0]
del to_lead_statuses[0]
first_status_to['label'] = first_status_to['id']
try:
to_api.put(f"status/lead/{first_status_to['id']}", data=first_status_to)
except APIError as e:
print(f"Couldn't remove `{first_status_to['label']}` because {str(e)}")
for status in to_lead_statuses:
try:
to_api.delete(f"status/lead/{status['id']}")
print(f"Removed lead status {status['label']}")
except APIError as e:
print(f"Couldn't remove `{status['label']}` because {str(e)}")

# Copy lead statuses
if args.lead_statuses or args.statuses or args.all:
print("\nCopying Lead Statuses")

Expand All @@ -120,7 +161,15 @@
print(f'Added lead status `{status["label"]}`')
except APIError as e:
print(f"Couldn't add `{status['label']}` because {str(e)}")
if args.clear_lead_and_status and delete_confirmation in ["yes", "y"]:
# if original statuses in destination org need to be removed, this removes the one remaining
try:
to_api.delete(f"status/lead/{first_status_to['id']}")
print(f"Removed old default lead status {first_status_to['label']}")
except APIError as e:
print(f"Couldn't remove `{first_status_to['label']}` because {str(e)}")

# Copy pipelines and associated opportunity statuses
if args.opportunity_statuses or args.statuses or args.all:
print("\nCopying Opportunity Statuses")
to_pipelines = to_api.get_opportunity_pipelines()
Expand Down Expand Up @@ -175,6 +224,7 @@ def copy_custom_fields(custom_field_type):

try:
if from_cf['is_shared']:
# check to see if shared custom field already exists
to_cf = next(
(
x
Expand All @@ -183,7 +233,7 @@ def copy_custom_fields(custom_field_type):
),
None,
)

# add shared custom fields that do not already exist
if not to_cf:
to_cf = to_api.post(f"custom_field/shared", data=from_cf)
print(f'Created `{from_cf["name"]}` shared custom field')
Expand All @@ -207,19 +257,22 @@ def copy_custom_fields(custom_field_type):
except APIError as e:
print(f"Couldn't add `{from_cf['name']}` because {str(e)}")


# copy lead custom fields
if args.lead_custom_fields or args.custom_fields or args.all:
print("\nCopying Lead Custom Fields")
copy_custom_fields('lead')

# copy opportunity custom fields
if args.opportunity_custom_fields or args.custom_fields or args.all:
print("\nCopying Opportunity Custom Fields")
copy_custom_fields('opportunity')

# copy contact custom fields
if args.contact_custom_fields or args.custom_fields or args.all:
print("\nCopying Contact Custom Fields")
copy_custom_fields('contact')

# copy integration links
if args.integration_links or args.all:
print("\nCopying Integration Links")
integration_links = from_api.get_all_items('integration_link')
Expand All @@ -233,6 +286,7 @@ def copy_custom_fields(custom_field_type):
except APIError as e:
print(f"Couldn't add `{link['name']}` because {str(e)}")

# copy roles
if args.roles or args.all:
BUILT_IN_ROLES = [
"Admin",
Expand All @@ -244,6 +298,7 @@ def copy_custom_fields(custom_field_type):
print("\nCopying Roles")
roles = from_api.get_all_items('role')
for role in roles:
# skip built in roles
if role["name"] in BUILT_IN_ROLES:
continue

Expand All @@ -256,38 +311,36 @@ def copy_custom_fields(custom_field_type):
except APIError as e:
print(f"Couldn't add `{role['name']}` because {str(e)}")

if args.templates or args.email_templates or args.all:
print("\nCopying Email Templates")
templates = from_api.get_all_items('email_template')
# function to copy sms or email templates
def copy_templates(template_type):
templates = from_api.get_all_items(template_type)
for template in templates:
del template["id"]
del template["organization_id"]

try:
to_api.post("email_template", data=template)
to_api.post(template_type, data=template)
print(f'Added `{template["name"]}`')
except APIError as e:
print(f"Couldn't add `{template['name']}` because {str(e)}")

# Copy email templates
if args.templates or args.email_templates or args.all:
print("\nCopying Email Templates")
copy_templates('email_template')

# copy sms templates
if args.templates or args.sms_templates or args.all:
print("\nCopying SMS Templates")
templates = from_api.get_all_items('sms_template')
for template in templates:
del template["id"]
del template["organization_id"]

try:
to_api.post("sms_template", data=template)
print(f'Added `{template["name"]}`')
except APIError as e:
print(f"Couldn't add `{template['name']}` because {str(e)}")
copy_templates('sms_template')

# Assumes all the sequence steps (templates) were already transferred over
if args.sequences or args.all:
print("\nCopying Sequences")

# get existing templates in new org
to_email_templates = to_api.get_all_items('email_template')
to_sms_templates = to_api.get_all_items('sms_template')
# get sequences in old org
from_sequences = from_api.get_all_items('sequence')
for sequence in from_sequences:
del sequence["id"]
Expand Down Expand Up @@ -327,6 +380,7 @@ def copy_custom_fields(custom_field_type):
except APIError as e:
print(f"Couldn't add `{sequence['name']}` because {str(e)}")

# Copy custom activities
if args.custom_activities or args.all:
print("\nCopying Custom Activities")

Expand Down Expand Up @@ -380,6 +434,7 @@ def copy_custom_fields(custom_field_type):
from_field.pop('organization_id', None)

if field["is_shared"]:
# check if field already exists
to_field = next(
(
x
Expand Down Expand Up @@ -421,6 +476,7 @@ def copy_custom_fields(custom_field_type):
from_field["custom_activity_type_id"] = new_activity_type["id"]
to_api.post("custom_field/activity/", data=from_field)

# copy groups
if args.groups or args.groups_with_members or args.all:
print("\nCopying Groups")
groups = from_api.get('group')['data']
Expand All @@ -431,6 +487,7 @@ def copy_custom_fields(custom_field_type):
new_group = to_api.post('group', data={'name': group['name']})

if args.groups_with_members:
# copy group members
for member in group['members']:
try:
to_api.post(f'group/{new_group["id"]}/member', data={'user_id': member['user_id']})
Expand All @@ -442,6 +499,7 @@ def copy_custom_fields(custom_field_type):
except APIError as e:
print(f"Couldn't add `{group['name']}` because {str(e)}")

# copy smart views
if args.smart_views or args.all:

def structured_replace(value, replacement_dictionary):
Expand Down Expand Up @@ -477,6 +535,7 @@ def textual_replace(value, replacement_dictionary):


def get_id_mappings():
# create mapping dictionary of all custom objects accessible by smart views
map_from_to_id = {}

# Custom Activity Types
Expand Down Expand Up @@ -611,7 +670,7 @@ def get_smartviews(api):
print("\nCopying Smart Views")
from_smart_views = get_smartviews(from_api)

# Filter our Smart Views that already exist (by name)
# Filter out Smart Views that already exist (by name)
to_smart_views = get_smartviews(to_api)
to_smart_view_names = [x['name'] for x in to_smart_views]
from_smart_views = [x for x in from_smart_views if x['name'] not in to_smart_view_names]
Expand All @@ -632,7 +691,7 @@ def get_smartviews(api):
def get_memberships(api, organization):
resp = api.get(f"organization/{organization['id']}", params={"_fields": "memberships,inactive_memberships"})
return resp["memberships"] + resp["inactive_memberships"]

# map user access from old org to new org
from_memberships = get_memberships(from_api, from_organization)
to_memberships = get_memberships(to_api, to_organization)
from_to_membership_id = {}
Expand Down Expand Up @@ -700,6 +759,7 @@ def get_memberships(api, organization):
if smart_view['s_query'] != s_query or smart_view['query'] != query:
to_api.put(f"saved_search/{smart_view['id']}", data=smart_view)

# copy webhooks
if args.webhooks:
print("\nCopying Webhooks")
webhooks = from_api.get_all_items('webhook')
Expand All @@ -711,3 +771,4 @@ def get_memberships(api, organization):
print(f'Added `{webhook["url"]}`')
except APIError as e:
print(f"Couldn't add `{webhook['url']}` because {str(e)}")