Skip to content

Commit 547139a

Browse files
feat: use-create-lead-form-for-self-hosted (#6117)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent b135023 commit 547139a

15 files changed

Lines changed: 81 additions & 171 deletions

File tree

api/integrations/lead_tracking/hubspot/client.py

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
)
1717

1818
from integrations.lead_tracking.hubspot.constants import (
19-
HUBSPOT_API_LEAD_SOURCE_SELF_HOSTED,
20-
HUBSPOT_FORM_ID,
19+
HUBSPOT_FORM_ID_SAAS,
2120
HUBSPOT_PORTAL_ID,
2221
HUBSPOT_ROOT_FORM_URL,
2322
)
@@ -61,6 +60,7 @@ def create_lead_form(
6160
user: "FFAdminUser",
6261
hubspot_cookie: str | None = None,
6362
utm_data: dict[str, str] | None = None,
63+
form_id: str = HUBSPOT_FORM_ID_SAAS,
6464
) -> dict[str, Any]:
6565
utm_data = utm_data or {}
6666
logger.info(
@@ -105,7 +105,7 @@ def create_lead_form(
105105
headers = {
106106
"Content-Type": "application/json",
107107
}
108-
url = f"{HUBSPOT_ROOT_FORM_URL}/{HUBSPOT_PORTAL_ID}/{HUBSPOT_FORM_ID}"
108+
url = f"{HUBSPOT_ROOT_FORM_URL}/{HUBSPOT_PORTAL_ID}/{form_id}"
109109

110110
response = requests.post(url, headers=headers, data=json.dumps(payload))
111111

@@ -131,38 +131,6 @@ def associate_contact_to_company(self, contact_id: str, company_id: str) -> None
131131
association_spec=association_spec,
132132
)
133133

134-
def create_self_hosted_contact(
135-
self, email: str, first_name: str, last_name: str, hubspot_company_id: str
136-
) -> None:
137-
properties = {
138-
"email": email,
139-
"firstname": first_name,
140-
"lastname": last_name,
141-
"api_lead_source": HUBSPOT_API_LEAD_SOURCE_SELF_HOSTED,
142-
}
143-
144-
create_params = {
145-
"simple_public_object_input_for_create": SimplePublicObjectInputForCreate(
146-
properties=properties,
147-
)
148-
}
149-
150-
if hubspot_company_id:
151-
create_params["simple_public_object_input_for_create"].associations = [
152-
{
153-
"types": [
154-
{
155-
"associationCategory": "HUBSPOT_DEFINED",
156-
"associationTypeId": 1,
157-
}
158-
],
159-
"to": {"id": hubspot_company_id},
160-
}
161-
]
162-
163-
response = self.client.crm.contacts.basic_api.create(**create_params)
164-
return response.to_dict() # type: ignore[no-any-return]
165-
166134
def get_company_by_domain(self, domain: str) -> dict[str, Any] | None:
167135
"""
168136
Domain should be unique in Hubspot by design, so we should only ever have
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
HUBSPOT_COOKIE_NAME = "hubspotutk"
22
HUBSPOT_PORTAL_ID = "143451822"
3-
HUBSPOT_FORM_ID = "562ee023-fb3f-4645-a217-4d8c9b4e45be"
3+
HUBSPOT_FORM_ID_SAAS = "562ee023-fb3f-4645-a217-4d8c9b4e45be"
4+
HUBSPOT_FORM_ID_SELF_HOSTED = "e79b8eca-a727-4188-a5ed-8f71c707ac50"
45
HUBSPOT_ROOT_FORM_URL = "https://api.hsforms.com/submissions/v3/integration/submit"
56
HUBSPOT_API_LEAD_SOURCE_SELF_HOSTED = "self-hosted"
67
HUBSPOT_ACTIVE_SUBSCRIPTION_SELF_HOSTED = "free-opensource"

api/integrations/lead_tracking/hubspot/lead_tracker.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from users.models import FFAdminUser, HubspotLead, HubspotTracker
1414

1515
from .client import HubspotClient
16+
from .constants import HUBSPOT_FORM_ID_SAAS
1617

1718
logger = logging.getLogger(__name__)
1819

@@ -67,7 +68,10 @@ def update_company_active_subscription(
6768

6869
def create_user_hubspot_contact(self, user: FFAdminUser) -> str | None:
6970
tracker = HubspotTracker.objects.filter(user=user).first()
70-
create_lead_form_kwargs: dict[str, Any] = {"user": user}
71+
create_lead_form_kwargs: dict[str, Any] = {
72+
"user": user,
73+
"form_id": HUBSPOT_FORM_ID_SAAS,
74+
}
7175
if tracker:
7276
create_lead_form_kwargs.update(
7377
hubspot_cookie=tracker.hubspot_cookie,

api/integrations/lead_tracking/hubspot/services.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import logging
2+
from typing import Any
23

34
from django.conf import settings
45
from rest_framework.request import Request
56

67
from integrations.lead_tracking.hubspot.client import HubspotClient
78
from integrations.lead_tracking.hubspot.constants import (
8-
HUBSPOT_ACTIVE_SUBSCRIPTION_SELF_HOSTED,
99
HUBSPOT_COOKIE_NAME,
10+
HUBSPOT_FORM_ID_SELF_HOSTED,
1011
)
1112
from integrations.lead_tracking.hubspot.tasks import (
1213
create_hubspot_contact_for_user,
@@ -67,18 +68,18 @@ def register_hubspot_tracker(
6768

6869

6970
def create_self_hosted_onboarding_lead(
70-
email: str, first_name: str, last_name: str, organisation_name: str
71+
email: str,
72+
first_name: str,
73+
last_name: str,
74+
hubspot_cookie: str = "",
7175
) -> None:
72-
email_domain = email.split("@")[1]
7376
hubspot_client = HubspotClient()
74-
company = hubspot_client.get_company_by_domain(email_domain)
75-
if not company:
76-
company = hubspot_client.create_company(
77-
name=organisation_name,
78-
domain=email_domain,
79-
active_subscription=HUBSPOT_ACTIVE_SUBSCRIPTION_SELF_HOSTED,
80-
)
81-
82-
company_id = company["id"]
77+
user = FFAdminUser(email=email, first_name=first_name, last_name=last_name)
78+
create_lead_form_kwargs: dict[str, Any] = {
79+
"user": user,
80+
"form_id": HUBSPOT_FORM_ID_SELF_HOSTED,
81+
}
82+
if hubspot_cookie:
83+
create_lead_form_kwargs.update(hubspot_cookie=hubspot_cookie)
8384

84-
hubspot_client.create_self_hosted_contact(email, first_name, last_name, company_id)
85+
hubspot_client.create_lead_form(**create_lead_form_kwargs)

api/integrations/lead_tracking/hubspot/tasks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def update_hubspot_active_subscription(subscription_id: int) -> None:
6666

6767
@register_task_handler()
6868
def create_self_hosted_onboarding_lead_task(
69-
email: str, first_name: str, last_name: str, organisation_name: str
69+
email: str, first_name: str, last_name: str, hubspot_cookie: str = ""
7070
) -> None:
7171
# Avoid circular imports.
7272
from integrations.lead_tracking.hubspot.services import (
@@ -77,5 +77,5 @@ def create_self_hosted_onboarding_lead_task(
7777
first_name=first_name,
7878
last_name=last_name,
7979
email=email,
80-
organisation_name=organisation_name,
80+
hubspot_cookie=hubspot_cookie,
8181
)

api/onboarding/serializers.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
from rest_framework import serializers
22

33

4-
class SelfHostedOnboardingSupportSerializer(serializers.Serializer): # type: ignore[type-arg]
5-
organisation_name = serializers.CharField()
4+
class SelfHostedOnboardingSupportSendRequestSerializer(serializers.Serializer): # type: ignore[type-arg]
5+
hubspotutk = serializers.CharField(required=False, allow_blank=True)
6+
7+
8+
class SelfHostedOnboardingReceiveSupportSerializer(serializers.Serializer): # type: ignore[type-arg]
69
first_name = serializers.CharField()
710
last_name = serializers.CharField()
811
email = serializers.EmailField()
12+
hubspot_cookie = serializers.CharField(required=False, allow_blank=True)

api/onboarding/tasks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88

99
@register_task_handler()
1010
def send_onboarding_request_to_saas_flagsmith_task(
11-
first_name: str, last_name: str, email: str, organisation_name: str
11+
first_name: str, last_name: str, email: str, hubspot_cookie: str
1212
) -> None:
1313
data = {
1414
"first_name": first_name,
1515
"last_name": last_name,
1616
"email": email,
17-
"organisation_name": organisation_name,
17+
"hubspot_cookie": hubspot_cookie,
1818
}
1919
response = requests.post(SEND_SUPPORT_REQUEST_URL, data=data, timeout=30)
2020
response.raise_for_status()

api/onboarding/views.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22

33
from django.conf import settings
4-
from drf_yasg.utils import no_body, swagger_auto_schema # type: ignore[import-untyped]
4+
from drf_yasg.utils import swagger_auto_schema # type: ignore[import-untyped]
55
from rest_framework import status
66
from rest_framework.decorators import api_view, permission_classes
77
from rest_framework.generics import GenericAPIView
@@ -13,7 +13,10 @@
1313
from integrations.lead_tracking.hubspot.tasks import (
1414
create_self_hosted_onboarding_lead_task,
1515
)
16-
from onboarding.serializers import SelfHostedOnboardingSupportSerializer
16+
from onboarding.serializers import (
17+
SelfHostedOnboardingReceiveSupportSerializer,
18+
SelfHostedOnboardingSupportSendRequestSerializer,
19+
)
1720
from onboarding.tasks import send_onboarding_request_to_saas_flagsmith_task
1821
from onboarding.throttling import OnboardingRequestThrottle
1922
from users.models import FFAdminUser
@@ -23,7 +26,7 @@
2326

2427
@swagger_auto_schema(
2528
method="post",
26-
request_body=no_body,
29+
request_body=SelfHostedOnboardingSupportSendRequestSerializer,
2730
responses={204: "No Content", 400: ErrorSerializer()},
2831
) # type: ignore[misc]
2932
@api_view(["POST"])
@@ -42,20 +45,20 @@ def send_onboarding_request_to_saas_flagsmith_view(request: Request) -> Response
4245
"first_name": admin_user.first_name,
4346
"last_name": admin_user.last_name,
4447
"email": admin_user.email,
45-
"organisation_name": organisation.name,
48+
"hubspot_cookie": request.data.get("hubspotutk"),
4649
}
4750
)
4851
return Response(status=status.HTTP_204_NO_CONTENT)
4952

5053

5154
class ReceiveSupportRequestFromSelfHosted(GenericAPIView): # type: ignore[type-arg]
52-
serializer_class = SelfHostedOnboardingSupportSerializer
55+
serializer_class = SelfHostedOnboardingReceiveSupportSerializer
5356
authentication_classes = ()
5457
permission_classes = ()
5558
throttle_classes = [OnboardingRequestThrottle]
5659

5760
@swagger_auto_schema(
58-
request_body=SelfHostedOnboardingSupportSerializer,
61+
request_body=SelfHostedOnboardingReceiveSupportSerializer,
5962
responses={204: "No Content", 400: ErrorSerializer()},
6063
) # type: ignore[misc]
6164
def post(self, request: Request) -> Response:
Lines changed: 17 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,42 @@
1+
import pytest
12
from pytest_mock import MockerFixture
23

34
from integrations.lead_tracking.hubspot.constants import (
4-
HUBSPOT_ACTIVE_SUBSCRIPTION_SELF_HOSTED,
5+
HUBSPOT_FORM_ID_SELF_HOSTED,
56
)
67
from integrations.lead_tracking.hubspot.services import (
78
create_self_hosted_onboarding_lead,
89
)
910

1011

12+
@pytest.mark.parametrize(
13+
"hubspot_cookie, expected_hubspot_cookie", [("", False), ("test_cookie", True)]
14+
)
1115
def test_create_self_hosted_onboarding_lead_with_existing_company(
1216
mocker: MockerFixture,
17+
hubspot_cookie: str,
18+
expected_hubspot_cookie: bool,
1319
) -> None:
1420
# Given
1521
mocked_hubspot_client = mocker.patch(
1622
"integrations.lead_tracking.hubspot.services.HubspotClient", autospec=True
1723
)
1824
email = "user@flagsmith.com"
19-
organisation_name = "Flagsmith"
2025
first_name = "user"
2126
last_name = "test"
2227

23-
company_domain = "flagsmith.com"
24-
company_id = "111"
25-
26-
mocked_hubspot_client().get_company_by_domain.return_value = {"id": company_id}
27-
2828
# When
29-
create_self_hosted_onboarding_lead(email, first_name, last_name, organisation_name)
29+
create_self_hosted_onboarding_lead(email, first_name, last_name, hubspot_cookie)
3030

3131
# Then
32-
mocked_hubspot_client().get_company_by_domain.assert_called_once_with(
33-
company_domain
34-
)
35-
36-
mocked_hubspot_client().create_company.assert_not_called()
37-
38-
mocked_hubspot_client().create_self_hosted_contact.assert_called_once_with(
39-
email, first_name, last_name, company_id
40-
)
32+
mocked_hubspot_client().create_lead_form.assert_called_once()
4133

34+
call_args = mocked_hubspot_client().create_lead_form.call_args
35+
user = call_args.kwargs["user"]
36+
form_id = call_args.kwargs["form_id"]
4237

43-
def test_create_self_hosted_onboarding_lead_with_new_company(
44-
mocker: MockerFixture,
45-
) -> None:
46-
# Given
47-
mocked_hubspot_client = mocker.patch(
48-
"integrations.lead_tracking.hubspot.services.HubspotClient", autospec=True
49-
)
50-
email = "user@flagsmith.com"
51-
organisation_name = "Flagsmith"
52-
first_name = "user"
53-
last_name = "test"
54-
55-
company_domain = "flagsmith.com"
56-
company_id = "111"
57-
58-
mocked_hubspot_client().get_company_by_domain.return_value = None
59-
mocked_hubspot_client().create_company.return_value = {"id": company_id}
60-
61-
# When
62-
create_self_hosted_onboarding_lead(email, first_name, last_name, organisation_name)
63-
64-
# Then
65-
mocked_hubspot_client().get_company_by_domain.assert_called_once_with(
66-
company_domain
67-
)
68-
69-
mocked_hubspot_client().create_company.assert_called_once_with(
70-
name=organisation_name,
71-
domain=company_domain,
72-
active_subscription=HUBSPOT_ACTIVE_SUBSCRIPTION_SELF_HOSTED,
73-
)
74-
75-
mocked_hubspot_client().create_self_hosted_contact.assert_called_once_with(
76-
email, first_name, last_name, company_id
77-
)
38+
assert user.email == email
39+
assert user.first_name == first_name
40+
assert user.last_name == last_name
41+
assert form_id == HUBSPOT_FORM_ID_SELF_HOSTED
42+
assert ("hubspot_cookie" in call_args.kwargs) is expected_hubspot_cookie

0 commit comments

Comments
 (0)