Skip to content

Commit 507b504

Browse files
Holmusclaude
andcommitted
fix(Integrations): Let HubSpot handle company creation and association
The HubSpot lead tracking integration was creating and updating companies from Flagsmith org data, overwriting enriched company names with user-entered org names. HubSpot already handles company creation and contact association automatically from email domains. Simplify create_lead to only create the contact. Remove domain filtering from should_track since HubSpot handles that natively. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 123b18d commit 507b504

2 files changed

Lines changed: 12 additions & 112 deletions

File tree

api/integrations/lead_tracking/hubspot/lead_tracker.py

Lines changed: 10 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,35 +17,11 @@
1717

1818
logger = logging.getLogger(__name__)
1919

20-
try:
21-
import re2 as re # type: ignore[import-untyped]
22-
23-
logger.info("Using re2 library for regex.")
24-
except ImportError:
25-
logger.warning("Unable to import re2. Falling back to re.")
26-
import re
27-
2820

2921
class HubspotLeadTracker(LeadTracker):
3022
@staticmethod
3123
def should_track(user: FFAdminUser) -> bool:
32-
if not settings.ENABLE_HUBSPOT_LEAD_TRACKING:
33-
return False
34-
35-
domain = user.email_domain
36-
37-
if settings.HUBSPOT_IGNORE_DOMAINS_REGEX and re.match(
38-
settings.HUBSPOT_IGNORE_DOMAINS_REGEX, domain
39-
):
40-
return False
41-
42-
if (
43-
settings.HUBSPOT_IGNORE_DOMAINS
44-
and domain in settings.HUBSPOT_IGNORE_DOMAINS
45-
):
46-
return False
47-
48-
return True
24+
return settings.ENABLE_HUBSPOT_LEAD_TRACKING
4925

5026
def update_company_active_subscription(
5127
self, subscription: Subscription
@@ -96,17 +72,9 @@ def create_user_hubspot_contact(self, user: FFAdminUser) -> str | None:
9672
return hubspot_contact_id
9773

9874
def create_lead(self, user: FFAdminUser, organisation: Organisation) -> None:
99-
hubspot_contact_id = self._get_or_create_user_hubspot_id(user)
100-
if not hubspot_contact_id:
101-
return
102-
hubspot_org_id = self._get_organisation_hubspot_id(user, organisation)
103-
if not hubspot_org_id:
104-
return
105-
106-
self.client.associate_contact_to_company(
107-
contact_id=hubspot_contact_id,
108-
company_id=hubspot_org_id,
109-
)
75+
# Only create the contact. HubSpot handles company creation and
76+
# association automatically from the contact's email domain.
77+
self._get_or_create_user_hubspot_id(user)
11078

11179
def _get_new_contact_with_retry(
11280
self, user: FFAdminUser, max_retries: int = 3
@@ -158,19 +126,18 @@ def _get_organisation_hubspot_id(
158126
company_kwargs["organisation_id"] = organisation.id
159127
company_kwargs["active_subscription"] = organisation.subscription.plan
160128

161-
# HubSpot auto-creates companies from contact email domains and may
162-
# enrich them with the correct company name. We look up the company
163-
# by domain and only set the name if HubSpot doesn't already have one,
164-
# using the email domain as a fallback rather than the Flagsmith org
165-
# name (which is often a placeholder like "test" or "ew").
129+
# As Hubspot creates/associates companies automatically based on contact domain
130+
# we need to get the hubspot id when this user creates the company for the first time
131+
# and update the company name
166132
company = self._get_hubspot_company_by_domain(domain)
167133
if not company:
168134
return None
169135
org_hubspot_id: str = company["id"]
170136

171-
existing_name = company.get("properties", {}).get("name")
137+
# Update the company in Hubspot with the name of the created
138+
# organisation in Flagsmith, and its numeric ID.
172139
self.client.update_company(
173-
name=domain if not existing_name else None,
140+
name=organisation.name,
174141
hubspot_company_id=org_hubspot_id,
175142
flagsmith_organisation_id=organisation.id,
176143
)

api/tests/unit/integrations/lead_tracking/hubspot/test_unit_hubspot_lead_tracking.py

Lines changed: 2 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,7 @@ def test_track_hubspot_lead_v2__new_user_added_to_org__creates_associations(
170170
assert organisation.hubspot_organisation.hubspot_id == HUBSPOT_COMPANY_ID
171171

172172
mock_client_existing_contact.create_company.assert_not_called()
173-
174-
mock_client_existing_contact.associate_contact_to_company.assert_called_once_with(
175-
contact_id=HUBSPOT_USER_ID,
176-
company_id=HUBSPOT_COMPANY_ID,
177-
)
173+
mock_client_existing_contact.associate_contact_to_company.assert_not_called()
178174
mock_client_existing_contact.create_lead_form.assert_not_called()
179175
mock_client_existing_contact.get_contact.assert_called_once_with(user)
180176

@@ -253,38 +249,7 @@ def test_create_lead__existing_hubspot_org__creates_contact_and_associates(
253249
mock_client.create_lead_form.assert_called_once_with(
254250
user=user, form_id=HUBSPOT_FORM_ID_SAAS
255251
)
256-
mock_client.associate_contact_to_company.assert_called_once_with(
257-
contact_id=HUBSPOT_USER_ID,
258-
company_id=HUBSPOT_COMPANY_ID,
259-
)
260-
261-
262-
def test_create_lead__filtered_domain__skips_company_creation(
263-
organisation: Organisation,
264-
settings: SettingsWrapper,
265-
mock_client_existing_contact: MagicMock,
266-
enable_hubspot: None,
267-
mocker: MockerFixture,
268-
) -> None:
269-
# Given
270-
settings.HUBSPOT_IGNORE_ORGANISATION_DOMAINS = ["example.com"]
271-
272-
user = FFAdminUser.objects.create(
273-
email="new.user@example.com",
274-
first_name="Frank",
275-
last_name="Louis",
276-
marketing_consent_given=True,
277-
)
278-
279-
# When
280-
tracker = HubspotLeadTracker()
281-
tracker.create_lead(user=user, organisation=organisation)
282-
283-
# Then
284-
assert HubspotLead.objects.filter(user=user, hubspot_id=HUBSPOT_USER_ID).exists()
285-
mock_client_existing_contact.get_contact.assert_called_once_with(user)
286-
mock_client_existing_contact.create_company.assert_not_called()
287-
mock_client_existing_contact.associate_contact_to_company.assert_not_called()
252+
mock_client.associate_contact_to_company.assert_not_called()
288253

289254

290255
def test_update_company_active_subscription__valid_subscription__calls_update_company(
@@ -390,38 +355,6 @@ def test_create_user_hubspot_contact__get_contact_retries__returns_expected_id(
390355
assert mock_client.get_contact.call_count == expected_call_count
391356

392357

393-
@pytest.mark.parametrize(
394-
"hubspot_contact_id, hubspot_org_id",
395-
[
396-
(None, "org_123"),
397-
("contact_123", None),
398-
],
399-
)
400-
def test_create_lead__missing_contact_or_org_id__skips_association(
401-
mocker: MockerFixture,
402-
hubspot_contact_id: str | None,
403-
hubspot_org_id: str | None,
404-
staff_user: FFAdminUser,
405-
organisation: Organisation,
406-
) -> None:
407-
# Given
408-
mock_client = mocker.MagicMock()
409-
tracker = HubspotLeadTracker()
410-
411-
mocker.patch.object(
412-
tracker, "_get_or_create_user_hubspot_id", return_value=hubspot_contact_id
413-
)
414-
mocker.patch.object(
415-
tracker, "_get_organisation_hubspot_id", return_value=hubspot_org_id
416-
)
417-
418-
# When
419-
tracker.create_lead(staff_user, organisation)
420-
421-
# Then
422-
mock_client.associate_contact_to_company.assert_not_called()
423-
424-
425358
def test_register_hubspot_tracker_and_track_user__no_explicit_user__falls_back_to_request_user(
426359
mocker: MockerFixture, staff_user: FFAdminUser, settings: SettingsWrapper
427360
) -> None:

0 commit comments

Comments
 (0)