mirror of https://github.com/zulip/zulip.git
registration: Ask user how they found Zulip.
This commit is contained in:
parent
293992fe60
commit
d21f5c9b75
|
@ -30,6 +30,7 @@ from corporate.views.support import get_plan_type_string
|
||||||
from zerver.decorator import require_server_admin
|
from zerver.decorator import require_server_admin
|
||||||
from zerver.lib.request import has_request_variables
|
from zerver.lib.request import has_request_variables
|
||||||
from zerver.models import Realm
|
from zerver.models import Realm
|
||||||
|
from zerver.models.realm_audit_logs import RealmAuditLog
|
||||||
from zerver.models.realms import get_org_type_display_name
|
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(wau_table.value, 0) wau_count,
|
||||||
coalesce(dau_table.value, 0) dau_count,
|
coalesce(dau_table.value, 0) dau_count,
|
||||||
coalesce(user_count_table.value, 0) user_profile_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
|
FROM
|
||||||
zerver_realm as realm
|
zerver_realm as realm
|
||||||
LEFT OUTER JOIN (
|
LEFT OUTER JOIN (
|
||||||
|
@ -157,6 +159,15 @@ def realm_summary_table() -> str:
|
||||||
AND subgroup = 'true'
|
AND subgroup = 'true'
|
||||||
AND end_time = %(active_users_audit_end_time)s
|
AND end_time = %(active_users_audit_end_time)s
|
||||||
) as bot_count_table ON realm.id = bot_count_table.realm_id
|
) 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
|
WHERE
|
||||||
_14day_active_humans IS NOT NULL
|
_14day_active_humans IS NOT NULL
|
||||||
or realm.plan_type = 3
|
or realm.plan_type = 3
|
||||||
|
@ -178,6 +189,7 @@ def realm_summary_table() -> str:
|
||||||
"active_users_audit_end_time": COUNT_STATS[
|
"active_users_audit_end_time": COUNT_STATS[
|
||||||
"active_users_audit:is_bot:day"
|
"active_users_audit:is_bot:day"
|
||||||
].last_successful_fill(),
|
].last_successful_fill(),
|
||||||
|
"realm_creation_event_type": RealmAuditLog.REALM_CREATED,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
rows = dictfetchall(cursor)
|
rows = dictfetchall(cursor)
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
<th>Bots</th>
|
<th>Bots</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th colspan=8>Human messages sent, last 8 UTC days (today-so-far first)</th>
|
<th colspan=8>Human messages sent, last 8 UTC days (today-so-far first)</th>
|
||||||
|
<th>Referrer</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
|
@ -118,6 +119,10 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
<td colspan=8></td>
|
<td colspan=8></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<td>
|
||||||
|
{{ row.how_realm_creator_found_zulip }}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -196,6 +196,26 @@ Form is validated both client-side using jquery-validation (see signup.js) and s
|
||||||
<hr />
|
<hr />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if creating_new_realm %}
|
||||||
|
<div class="input-group input-box" id="how-realm-creator-found-zulip">
|
||||||
|
<label for="how_realm_creator_found_zulip">
|
||||||
|
{{ _('How did you first hear about Zulip?') }}
|
||||||
|
{% if not corporate_enabled %}
|
||||||
|
<i class="fa fa-question-circle-o" aria-hidden="true" data-tippy-content="{% trans %}This value is used only if you sign up for a plan, in which case it will be sent to the Zulip team.{% endtrans %}"></i>
|
||||||
|
{% endif %}
|
||||||
|
</label>
|
||||||
|
<select name="how_realm_creator_found_zulip" class="required">
|
||||||
|
<option value="" selected disabled>{{ _('Select an option') }}</option>
|
||||||
|
{% for option_id, option_name in how_realm_creator_found_zulip_options %}
|
||||||
|
<option value="{{ option_id }}">{{ option_name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<input id="how-realm-creator-found-zulip-other" type="text" placeholder="{{ _('Please describe') }}" name="how_realm_creator_found_zulip_other_text" maxlength="100"/>
|
||||||
|
<input id="how-realm-creator-found-zulip-where-ad" type="text" placeholder="{{ _('Where did you see the ad?') }}" name="how_realm_creator_found_zulip_where_ad" maxlength="100"/>
|
||||||
|
<input id="how-realm-creator-found-zulip-which-organization" type="text" placeholder="{{ _('Which organization?') }}" name="how_realm_creator_found_zulip_which_organization" maxlength="100"/>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="input-group margin terms-of-service">
|
<div class="input-group margin terms-of-service">
|
||||||
{% if terms_of_service %}
|
{% if terms_of_service %}
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
|
|
|
@ -59,6 +59,8 @@ async function realm_creation_tests(page: Page): Promise<void> {
|
||||||
full_name: "Alice",
|
full_name: "Alice",
|
||||||
password: "passwordwhichisnotreallycomplex",
|
password: "passwordwhichisnotreallycomplex",
|
||||||
terms: true,
|
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
|
// 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.
|
// so use page.$eval here to call the .click method in the browser.
|
||||||
|
|
|
@ -302,4 +302,40 @@ $(() => {
|
||||||
|
|
||||||
$(e.target).hide();
|
$(e.target).hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#how-realm-creator-found-zulip select").on("change", function () {
|
||||||
|
const elements: Record<string, string> = {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1458,3 +1458,14 @@ button#register_auth_button_gitlab {
|
||||||
max-width: 800px;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -115,11 +115,7 @@
|
||||||
],
|
],
|
||||||
"desktop-login": ["./src/bundles/portico", "./src/portico/desktop-login"],
|
"desktop-login": ["./src/bundles/portico", "./src/portico/desktop-login"],
|
||||||
"desktop-redirect": ["./src/bundles/portico", "./src/portico/desktop-redirect"],
|
"desktop-redirect": ["./src/bundles/portico", "./src/portico/desktop-redirect"],
|
||||||
"stats": [
|
"stats": ["./src/bundles/portico", "./styles/portico/stats.css", "./src/stats/stats"],
|
||||||
"./src/bundles/portico",
|
|
||||||
"./styles/portico/stats.css",
|
|
||||||
"./src/stats/stats"
|
|
||||||
],
|
|
||||||
"app": ["./src/bundles/app"],
|
"app": ["./src/bundles/app"],
|
||||||
"digest": ["./src/bundles/portico"]
|
"digest": ["./src/bundles/portico"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,6 +167,8 @@ def do_create_realm(
|
||||||
enable_read_receipts: Optional[bool] = None,
|
enable_read_receipts: Optional[bool] = None,
|
||||||
enable_spectator_access: Optional[bool] = None,
|
enable_spectator_access: Optional[bool] = None,
|
||||||
prereg_realm: Optional[PreregistrationRealm] = 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:
|
) -> Realm:
|
||||||
if string_id in [settings.SOCIAL_AUTH_SUBDOMAIN, settings.SELF_HOSTING_MANAGEMENT_SUBDOMAIN]:
|
if string_id in [settings.SOCIAL_AUTH_SUBDOMAIN, settings.SELF_HOSTING_MANAGEMENT_SUBDOMAIN]:
|
||||||
raise AssertionError(
|
raise AssertionError(
|
||||||
|
@ -243,6 +245,10 @@ def do_create_realm(
|
||||||
realm=realm,
|
realm=realm,
|
||||||
event_type=RealmAuditLog.REALM_CREATED,
|
event_type=RealmAuditLog.REALM_CREATED,
|
||||||
event_time=realm.date_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
|
realm_default_email_address_visibility = RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_EVERYONE
|
||||||
|
|
|
@ -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.subdomains import get_subdomain, is_root_domain_available
|
||||||
from zerver.lib.users import check_full_name
|
from zerver.lib.users import check_full_name
|
||||||
from zerver.models import Realm, UserProfile
|
from zerver.models import Realm, UserProfile
|
||||||
|
from zerver.models.realm_audit_logs import RealmAuditLog
|
||||||
from zerver.models.realms import (
|
from zerver.models.realms import (
|
||||||
DisposableEmailError,
|
DisposableEmailError,
|
||||||
DomainNotAllowedForRealmError,
|
DomainNotAllowedForRealmError,
|
||||||
|
@ -140,6 +141,8 @@ class RealmDetailsForm(forms.Form):
|
||||||
realm_name = forms.CharField(max_length=Realm.MAX_REALM_NAME_LENGTH)
|
realm_name = forms.CharField(max_length=Realm.MAX_REALM_NAME_LENGTH)
|
||||||
|
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
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"]
|
self.realm_creation = kwargs["realm_creation"]
|
||||||
del kwargs["realm_creation"]
|
del kwargs["realm_creation"]
|
||||||
|
|
||||||
|
@ -177,10 +180,6 @@ class RegistrationForm(RealmDetailsForm):
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
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)
|
super().__init__(*args, **kwargs)
|
||||||
if settings.TERMS_OF_SERVICE_VERSION is not None:
|
if settings.TERMS_OF_SERVICE_VERSION is not None:
|
||||||
self.fields["terms"] = forms.BooleanField(required=True)
|
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()],
|
choices=[(lang["code"], lang["name"]) for lang in get_language_list()],
|
||||||
required=self.realm_creation,
|
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:
|
def clean_full_name(self) -> str:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -842,6 +842,8 @@ Output:
|
||||||
"default_stream_group": default_stream_groups,
|
"default_stream_group": default_stream_groups,
|
||||||
"source_realm_id": source_realm_id,
|
"source_realm_id": source_realm_id,
|
||||||
"is_demo_organization": is_demo_organization,
|
"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:
|
if enable_marketing_emails is not None:
|
||||||
payload["enable_marketing_emails"] = enable_marketing_emails
|
payload["enable_marketing_emails"] = enable_marketing_emails
|
||||||
|
|
|
@ -162,6 +162,18 @@ class AbstractRealmAuditLog(models.Model):
|
||||||
REALM_IMPORTED,
|
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:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,8 @@ def register_development_realm(request: HttpRequest) -> HttpResponse:
|
||||||
password="test",
|
password="test",
|
||||||
realm_subdomain=realm_subdomain,
|
realm_subdomain=realm_subdomain,
|
||||||
terms="true",
|
terms="true",
|
||||||
|
how_realm_creator_found_zulip="ad",
|
||||||
|
how_realm_creator_found_zulip_extra_context="test",
|
||||||
)
|
)
|
||||||
|
|
||||||
return accounts_register(request)
|
return accounts_register(request)
|
||||||
|
@ -120,6 +122,8 @@ def register_demo_development_realm(request: HttpRequest) -> HttpResponse:
|
||||||
realm_subdomain=realm_subdomain,
|
realm_subdomain=realm_subdomain,
|
||||||
terms="true",
|
terms="true",
|
||||||
is_demo_organization="true",
|
is_demo_organization="true",
|
||||||
|
how_realm_creator_found_zulip="existing_user",
|
||||||
|
how_realm_creator_found_zulip_extra_context="test",
|
||||||
)
|
)
|
||||||
|
|
||||||
return accounts_register(request)
|
return accounts_register(request)
|
||||||
|
|
|
@ -82,6 +82,7 @@ from zerver.models import (
|
||||||
UserProfile,
|
UserProfile,
|
||||||
)
|
)
|
||||||
from zerver.models.constants import MAX_LANGUAGE_ID_LENGTH
|
from zerver.models.constants import MAX_LANGUAGE_ID_LENGTH
|
||||||
|
from zerver.models.realm_audit_logs import RealmAuditLog
|
||||||
from zerver.models.realms import (
|
from zerver.models.realms import (
|
||||||
DisposableEmailError,
|
DisposableEmailError,
|
||||||
DomainNotAllowedForRealmError,
|
DomainNotAllowedForRealmError,
|
||||||
|
@ -464,6 +465,32 @@ def registration_helper(
|
||||||
realm_type = form.cleaned_data["realm_type"]
|
realm_type = form.cleaned_data["realm_type"]
|
||||||
realm_default_language = form.cleaned_data["realm_default_language"]
|
realm_default_language = form.cleaned_data["realm_default_language"]
|
||||||
is_demo_organization = form.cleaned_data["is_demo_organization"]
|
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(
|
realm = do_create_realm(
|
||||||
string_id,
|
string_id,
|
||||||
realm_name,
|
realm_name,
|
||||||
|
@ -471,6 +498,8 @@ def registration_helper(
|
||||||
default_language=realm_default_language,
|
default_language=realm_default_language,
|
||||||
is_demo_organization=is_demo_organization,
|
is_demo_organization=is_demo_organization,
|
||||||
prereg_realm=prereg_realm,
|
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
|
assert realm is not None
|
||||||
|
|
||||||
|
@ -686,6 +715,7 @@ def registration_helper(
|
||||||
"email_address_visibility_moderators": RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_MODERATORS,
|
"email_address_visibility_moderators": RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_MODERATORS,
|
||||||
"email_address_visibility_nobody": RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_NOBODY,
|
"email_address_visibility_nobody": RealmUserDefault.EMAIL_ADDRESS_VISIBILITY_NOBODY,
|
||||||
"email_address_visibility_options_dict": UserProfile.EMAIL_ADDRESS_VISIBILITY_ID_TO_NAME_MAP,
|
"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.
|
# Add context for realm creation part of the form.
|
||||||
context.update(get_realm_create_form_context())
|
context.update(get_realm_create_form_context())
|
||||||
|
|
Loading…
Reference in New Issue