diff --git a/corporate/views/installation_activity.py b/corporate/views/installation_activity.py
index 482abe48e1..d8c7e6681e 100644
--- a/corporate/views/installation_activity.py
+++ b/corporate/views/installation_activity.py
@@ -30,6 +30,7 @@ from corporate.views.support import get_plan_type_string
from zerver.decorator import require_server_admin
from zerver.lib.request import has_request_variables
from zerver.models import Realm
+from zerver.models.realm_audit_logs import RealmAuditLog
from zerver.models.realms import get_org_type_display_name
@@ -102,7 +103,8 @@ def realm_summary_table() -> str:
coalesce(wau_table.value, 0) wau_count,
coalesce(dau_table.value, 0) dau_count,
coalesce(user_count_table.value, 0) user_profile_count,
- coalesce(bot_count_table.value, 0) bot_count
+ coalesce(bot_count_table.value, 0) bot_count,
+ coalesce(realm_audit_log_table.how_realm_creator_found_zulip, '') how_realm_creator_found_zulip
FROM
zerver_realm as realm
LEFT OUTER JOIN (
@@ -157,6 +159,15 @@ def realm_summary_table() -> str:
AND subgroup = 'true'
AND end_time = %(active_users_audit_end_time)s
) as bot_count_table ON realm.id = bot_count_table.realm_id
+ LEFT OUTER JOIN (
+ SELECT
+ extra_data->>'how_realm_creator_found_zulip' as how_realm_creator_found_zulip,
+ realm_id
+ from
+ zerver_realmauditlog
+ WHERE
+ event_type = %(realm_creation_event_type)s
+ ) as realm_audit_log_table ON realm.id = realm_audit_log_table.realm_id
WHERE
_14day_active_humans IS NOT NULL
or realm.plan_type = 3
@@ -178,6 +189,7 @@ def realm_summary_table() -> str:
"active_users_audit_end_time": COUNT_STATS[
"active_users_audit:is_bot:day"
].last_successful_fill(),
+ "realm_creation_event_type": RealmAuditLog.REALM_CREATED,
},
)
rows = dictfetchall(cursor)
diff --git a/templates/corporate/activity/installation_activity_table.html b/templates/corporate/activity/installation_activity_table.html
index 54db2f311a..8c0b00f61e 100644
--- a/templates/corporate/activity/installation_activity_table.html
+++ b/templates/corporate/activity/installation_activity_table.html
@@ -48,6 +48,7 @@
{% if terms_of_service %}
diff --git a/web/e2e-tests/realm-creation.test.ts b/web/e2e-tests/realm-creation.test.ts
index 5ca0777281..ee2d4253c0 100644
--- a/web/e2e-tests/realm-creation.test.ts
+++ b/web/e2e-tests/realm-creation.test.ts
@@ -59,6 +59,8 @@ async function realm_creation_tests(page: Page): Promise {
full_name: "Alice",
password: "passwordwhichisnotreallycomplex",
terms: true,
+ how_realm_creator_found_zulip: "other",
+ how_realm_creator_found_zulip_other_text: "test",
};
// For some reason, page.click() does not work this for particular checkbox
// so use page.$eval here to call the .click method in the browser.
diff --git a/web/src/portico/signup.ts b/web/src/portico/signup.ts
index 210b8a8618..7a2256de63 100644
--- a/web/src/portico/signup.ts
+++ b/web/src/portico/signup.ts
@@ -302,4 +302,40 @@ $(() => {
$(e.target).hide();
});
+
+ $("#how-realm-creator-found-zulip select").on("change", function () {
+ const elements: Record = {
+ Other: "how-realm-creator-found-zulip-other",
+ Advertisement: "how-realm-creator-found-zulip-where-ad",
+ "At an organization that's using it":
+ "how-realm-creator-found-zulip-which-organization",
+ };
+
+ const hideElement = (element: string): void => {
+ const $element = $(`#${element}`);
+ $element.hide();
+ $element.removeAttr("required");
+ $(`#${element}-error`).hide();
+ };
+
+ const showElement = (element: string): void => {
+ const $element = $(`#${element}`);
+ $element.show();
+ $element.attr("required", "required");
+ };
+
+ // Reset state
+ for (const element of Object.values(elements)) {
+ if (element) {
+ hideElement(element);
+ }
+ }
+
+ // Show the additional input box if needed.
+ const selected_option = $("option:selected", this).text();
+ const selected_element = elements[selected_option];
+ if (selected_element) {
+ showElement(selected_element);
+ }
+ });
});
diff --git a/web/styles/portico/portico_signin.css b/web/styles/portico/portico_signin.css
index 6f227d5121..6bdb198d07 100644
--- a/web/styles/portico/portico_signin.css
+++ b/web/styles/portico/portico_signin.css
@@ -1458,3 +1458,14 @@ button#register_auth_button_gitlab {
max-width: 800px;
}
}
+
+#registration #how-realm-creator-found-zulip label {
+ top: -5px;
+}
+
+#how-realm-creator-found-zulip-where-ad,
+#how-realm-creator-found-zulip-other,
+#how-realm-creator-found-zulip-which-organization {
+ margin-top: 5px;
+ display: none;
+}
diff --git a/web/webpack.assets.json b/web/webpack.assets.json
index a826cef354..01936537dd 100644
--- a/web/webpack.assets.json
+++ b/web/webpack.assets.json
@@ -115,11 +115,7 @@
],
"desktop-login": ["./src/bundles/portico", "./src/portico/desktop-login"],
"desktop-redirect": ["./src/bundles/portico", "./src/portico/desktop-redirect"],
- "stats": [
- "./src/bundles/portico",
- "./styles/portico/stats.css",
- "./src/stats/stats"
- ],
+ "stats": ["./src/bundles/portico", "./styles/portico/stats.css", "./src/stats/stats"],
"app": ["./src/bundles/app"],
"digest": ["./src/bundles/portico"]
}
diff --git a/zerver/actions/create_realm.py b/zerver/actions/create_realm.py
index affb079190..176613f498 100644
--- a/zerver/actions/create_realm.py
+++ b/zerver/actions/create_realm.py
@@ -167,6 +167,8 @@ def do_create_realm(
enable_read_receipts: Optional[bool] = None,
enable_spectator_access: Optional[bool] = None,
prereg_realm: Optional[PreregistrationRealm] = None,
+ how_realm_creator_found_zulip: Optional[str] = None,
+ how_realm_creator_found_zulip_extra_context: Optional[str] = None,
) -> Realm:
if string_id in [settings.SOCIAL_AUTH_SUBDOMAIN, settings.SELF_HOSTING_MANAGEMENT_SUBDOMAIN]:
raise AssertionError(
@@ -243,6 +245,10 @@ def do_create_realm(
realm=realm,
event_type=RealmAuditLog.REALM_CREATED,
event_time=realm.date_created,
+ extra_data={
+ "how_realm_creator_found_zulip": how_realm_creator_found_zulip,
+ "how_realm_creator_found_zulip_extra_context": how_realm_creator_found_zulip_extra_context,
+ },
)
realm_default_email_address_visibility = RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_EVERYONE
diff --git a/zerver/forms.py b/zerver/forms.py
index 27181eb8f5..ce56796561 100644
--- a/zerver/forms.py
+++ b/zerver/forms.py
@@ -36,6 +36,7 @@ from zerver.lib.soft_deactivation import queue_soft_reactivation
from zerver.lib.subdomains import get_subdomain, is_root_domain_available
from zerver.lib.users import check_full_name
from zerver.models import Realm, UserProfile
+from zerver.models.realm_audit_logs import RealmAuditLog
from zerver.models.realms import (
DisposableEmailError,
DomainNotAllowedForRealmError,
@@ -140,6 +141,8 @@ class RealmDetailsForm(forms.Form):
realm_name = forms.CharField(max_length=Realm.MAX_REALM_NAME_LENGTH)
def __init__(self, *args: Any, **kwargs: Any) -> None:
+ # Since the superclass doesn't accept random extra kwargs, we
+ # remove it from the kwargs dict before initializing.
self.realm_creation = kwargs["realm_creation"]
del kwargs["realm_creation"]
@@ -177,10 +180,6 @@ class RegistrationForm(RealmDetailsForm):
)
def __init__(self, *args: Any, **kwargs: Any) -> None:
- # Since the superclass doesn't except random extra kwargs, we
- # remove it from the kwargs dict before initializing.
- self.realm_creation = kwargs["realm_creation"]
-
super().__init__(*args, **kwargs)
if settings.TERMS_OF_SERVICE_VERSION is not None:
self.fields["terms"] = forms.BooleanField(required=True)
@@ -196,6 +195,19 @@ class RegistrationForm(RealmDetailsForm):
choices=[(lang["code"], lang["name"]) for lang in get_language_list()],
required=self.realm_creation,
)
+ self.fields["how_realm_creator_found_zulip"] = forms.ChoiceField(
+ choices=RealmAuditLog.HOW_REALM_CREATOR_FOUND_ZULIP_OPTIONS.items(),
+ required=self.realm_creation,
+ )
+ self.fields["how_realm_creator_found_zulip_other_text"] = forms.CharField(
+ max_length=100, required=False
+ )
+ self.fields["how_realm_creator_found_zulip_where_ad"] = forms.CharField(
+ max_length=100, required=False
+ )
+ self.fields["how_realm_creator_found_zulip_which_organization"] = forms.CharField(
+ max_length=100, required=False
+ )
def clean_full_name(self) -> str:
try:
diff --git a/zerver/lib/test_classes.py b/zerver/lib/test_classes.py
index 461c4f0703..d897f5a6db 100644
--- a/zerver/lib/test_classes.py
+++ b/zerver/lib/test_classes.py
@@ -842,6 +842,8 @@ Output:
"default_stream_group": default_stream_groups,
"source_realm_id": source_realm_id,
"is_demo_organization": is_demo_organization,
+ "how_realm_creator_found_zulip": "other",
+ "how_realm_creator_found_zulip_extra_context": "I found it on the internet.",
}
if enable_marketing_emails is not None:
payload["enable_marketing_emails"] = enable_marketing_emails
diff --git a/zerver/models/realm_audit_logs.py b/zerver/models/realm_audit_logs.py
index 1a161dfcb9..8b4a54d18d 100644
--- a/zerver/models/realm_audit_logs.py
+++ b/zerver/models/realm_audit_logs.py
@@ -162,6 +162,18 @@ class AbstractRealmAuditLog(models.Model):
REALM_IMPORTED,
]
+ HOW_REALM_CREATOR_FOUND_ZULIP_OPTIONS = {
+ "existing_user": "At an organization that's using it",
+ "search_engine": "Search engine",
+ "review_site": "Review site",
+ "personal_recommendation": "Personal recommendation",
+ "hacker_news": "Hacker News",
+ "ad": "Advertisement",
+ "other": "Other",
+ "forgot": "Don't remember",
+ "refuse_to_answer": "Prefer not to say",
+ }
+
class Meta:
abstract = True
diff --git a/zerver/views/development/registration.py b/zerver/views/development/registration.py
index 08a47ba4e5..0cf5884832 100644
--- a/zerver/views/development/registration.py
+++ b/zerver/views/development/registration.py
@@ -85,6 +85,8 @@ def register_development_realm(request: HttpRequest) -> HttpResponse:
password="test",
realm_subdomain=realm_subdomain,
terms="true",
+ how_realm_creator_found_zulip="ad",
+ how_realm_creator_found_zulip_extra_context="test",
)
return accounts_register(request)
@@ -120,6 +122,8 @@ def register_demo_development_realm(request: HttpRequest) -> HttpResponse:
realm_subdomain=realm_subdomain,
terms="true",
is_demo_organization="true",
+ how_realm_creator_found_zulip="existing_user",
+ how_realm_creator_found_zulip_extra_context="test",
)
return accounts_register(request)
diff --git a/zerver/views/registration.py b/zerver/views/registration.py
index 06ce6fee45..436fb82b7d 100644
--- a/zerver/views/registration.py
+++ b/zerver/views/registration.py
@@ -82,6 +82,7 @@ from zerver.models import (
UserProfile,
)
from zerver.models.constants import MAX_LANGUAGE_ID_LENGTH
+from zerver.models.realm_audit_logs import RealmAuditLog
from zerver.models.realms import (
DisposableEmailError,
DomainNotAllowedForRealmError,
@@ -464,6 +465,32 @@ def registration_helper(
realm_type = form.cleaned_data["realm_type"]
realm_default_language = form.cleaned_data["realm_default_language"]
is_demo_organization = form.cleaned_data["is_demo_organization"]
+ how_realm_creator_found_zulip = RealmAuditLog.HOW_REALM_CREATOR_FOUND_ZULIP_OPTIONS[
+ form.cleaned_data["how_realm_creator_found_zulip"]
+ ]
+ how_realm_creator_found_zulip_extra_context = ""
+ if (
+ how_realm_creator_found_zulip
+ == RealmAuditLog.HOW_REALM_CREATOR_FOUND_ZULIP_OPTIONS["other"]
+ ):
+ how_realm_creator_found_zulip_extra_context = form.cleaned_data[
+ "how_realm_creator_found_zulip_other_text"
+ ]
+ elif (
+ how_realm_creator_found_zulip
+ == RealmAuditLog.HOW_REALM_CREATOR_FOUND_ZULIP_OPTIONS["ad"]
+ ):
+ how_realm_creator_found_zulip_extra_context = form.cleaned_data[
+ "how_realm_creator_found_zulip_where_ad"
+ ]
+ elif (
+ how_realm_creator_found_zulip
+ == RealmAuditLog.HOW_REALM_CREATOR_FOUND_ZULIP_OPTIONS["existing_user"]
+ ):
+ how_realm_creator_found_zulip_extra_context = form.cleaned_data[
+ "how_realm_creator_found_zulip_which_organization"
+ ]
+
realm = do_create_realm(
string_id,
realm_name,
@@ -471,6 +498,8 @@ def registration_helper(
default_language=realm_default_language,
is_demo_organization=is_demo_organization,
prereg_realm=prereg_realm,
+ how_realm_creator_found_zulip=how_realm_creator_found_zulip,
+ how_realm_creator_found_zulip_extra_context=how_realm_creator_found_zulip_extra_context,
)
assert realm is not None
@@ -686,6 +715,7 @@ def registration_helper(
"email_address_visibility_moderators": RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_MODERATORS,
"email_address_visibility_nobody": RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_NOBODY,
"email_address_visibility_options_dict": UserProfile.EMAIL_ADDRESS_VISIBILITY_ID_TO_NAME_MAP,
+ "how_realm_creator_found_zulip_options": RealmAuditLog.HOW_REALM_CREATOR_FOUND_ZULIP_OPTIONS.items(),
}
# Add context for realm creation part of the form.
context.update(get_realm_create_form_context())