portico: Add setting to put Google Analytics on selected portico pages.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg
2020-05-07 21:37:58 -07:00
committed by Tim Abbott
parent 8fb1f2af58
commit 4362cceffb
14 changed files with 106 additions and 34 deletions

View File

@@ -31,6 +31,7 @@
"file-loader": "^6.0.0", "file-loader": "^6.0.0",
"flatpickr": "^4.5.7", "flatpickr": "^4.5.7",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"ga-gtag": "^1.0.1",
"handlebars": "^4.7.2", "handlebars": "^4.7.2",
"handlebars-loader": "^1.7.1", "handlebars-loader": "^1.7.1",
"html-webpack-plugin": "^4.0.0-beta.8", "html-webpack-plugin": "^4.0.0-beta.8",

View File

@@ -1,4 +1,5 @@
import "./common.js"; import "./common.js";
import "../i18n.js"; import "../i18n.js";
import "../portico/header.js"; import "../portico/header.js";
import "../portico/google-analytics.js";
import "../../styles/portico/portico-styles.scss"; import "../../styles/portico/portico-styles.scss";

View File

@@ -0,0 +1,10 @@
import { gtag, install } from "ga-gtag";
export let config;
if (page_params.google_analytics_id !== undefined) {
install(page_params.google_analytics_id);
config = (info) => gtag("config", page_params.google_analytics_id, info);
} else {
config = () => {};
}

View File

@@ -1,3 +1,4 @@
import * as google_analytics from './google-analytics.js';
import SimpleBar from 'simplebar'; import SimpleBar from 'simplebar';
import {activate_correct_tab} from './tabbed-instructions.js'; import {activate_correct_tab} from './tabbed-instructions.js';
@@ -93,6 +94,7 @@ const update_page = function (html_map, path) {
scrollToHash(markdownSB); scrollToHash(markdownSB);
}); });
} }
google_analytics.config({page_path: path});
}; };
new SimpleBar($(".sidebar")[0]); new SimpleBar($(".sidebar")[0]);

View File

@@ -1,3 +1,4 @@
import * as google_analytics from './google-analytics.js';
import blueslip from './../blueslip'; import blueslip from './../blueslip';
import { path_parts } from './landing-page'; import { path_parts } from './landing-page';
@@ -72,6 +73,7 @@ function update_path() {
} }
window.history.pushState(state, '', next_path); window.history.pushState(state, '', next_path);
google_analytics.config({page_path: next_path});
} }
function update_categories() { function update_categories() {
@@ -306,6 +308,7 @@ function dispatch(action, payload) {
case 'LOAD_PATH': case 'LOAD_PATH':
render(get_state_from_path()); render(get_state_from_path());
google_analytics.config({page_path: window.location.pathname});
break; break;
default: default:

View File

@@ -1,3 +1,4 @@
import * as google_analytics from './google-analytics.js';
import { detect_user_os } from './tabbed-instructions.js'; import { detect_user_os } from './tabbed-instructions.js';
import render_tabs from './team.js'; import render_tabs from './team.js';
@@ -126,6 +127,7 @@ const apps_events = function () {
version = get_version_from_path(); version = get_version_from_path();
update_page(); update_page();
$("body").animate({ scrollTop: 0 }, 200); $("body").animate({ scrollTop: 0 }, 200);
google_analytics.config({page_path: window.location.pathname});
}); });
$(".apps a .icon").click(function (e) { $(".apps a .icon").click(function (e) {
@@ -137,6 +139,7 @@ const apps_events = function () {
update_path(); update_path();
update_page(); update_page();
$("body").animate({ scrollTop: 0 }, 200); $("body").animate({ scrollTop: 0 }, 200);
google_analytics.config({page_path: window.location.pathname});
return false; return false;
}); });

View File

@@ -44,4 +44,4 @@ API_FEATURE_LEVEL = 4
# historical commits sharing the same major version, in which case a # historical commits sharing the same major version, in which case a
# minor version bump suffices. # minor version bump suffices.
PROVISION_VERSION = '82.1' PROVISION_VERSION = '82.2'

View File

@@ -5006,6 +5006,11 @@ functional-red-black-tree@^1.0.0, functional-red-black-tree@^1.0.1:
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
ga-gtag@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ga-gtag/-/ga-gtag-1.0.1.tgz#c23d9b0619cee1eec7670f6db26dd071d614a6f4"
integrity sha512-wWPLuVwmfMfiK5KI7w59fN5kTONRnsSmnejoDo7+6tOulxxKgTSkB4kJcgBa3VfKAunvjopAOGp/5L/BTYZftw==
gamma@^0.1.0: gamma@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/gamma/-/gamma-0.1.0.tgz#3315643403bf27906ca80ab37c36ece9440ef330" resolved "https://registry.yarnpkg.com/gamma/-/gamma-0.1.0.tgz#3315643403bf27906ca80ab37c36ece9440ef330"

View File

@@ -16,6 +16,7 @@ from django.shortcuts import resolve_url
from django.utils.decorators import available_attrs from django.utils.decorators import available_attrs
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
from django.conf import settings from django.conf import settings
from django.template.response import SimpleTemplateResponse
from zerver.lib.exceptions import UnexpectedWebhookEventType from zerver.lib.exceptions import UnexpectedWebhookEventType
from zerver.lib.queue import queue_json_publish from zerver.lib.queue import queue_json_publish
@@ -825,3 +826,20 @@ def zulip_otp_required(view: Any=None,
redirect_field_name=redirect_field_name) redirect_field_name=redirect_field_name)
return decorator if (view is None) else decorator(view) return decorator if (view is None) else decorator(view)
def add_google_analytics_context(context: Dict[str, Any]) -> None:
if settings.GOOGLE_ANALYTICS_ID is not None: # nocoverage
context.setdefault("page_params", {})["google_analytics_id"] = settings.GOOGLE_ANALYTICS_ID
def add_google_analytics(view_func: ViewFuncT) -> ViewFuncT:
@wraps(view_func)
def _wrapped_view_func(request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
response = view_func(request, *args, **kwargs)
if isinstance(response, SimpleTemplateResponse):
if response.context_data is None:
response.context_data = {}
add_google_analytics_context(response.context_data)
elif response.status_code == 200: # nocoverage
raise TypeError("add_google_analytics requires a TemplateResponse")
return response
return _wrapped_view_func # type: ignore[return-value] # https://github.com/python/mypy/issues/1927

View File

@@ -9,6 +9,7 @@ import os
import random import random
import re import re
from zerver.decorator import add_google_analytics_context
from zerver.lib.integrations import CATEGORIES, INTEGRATIONS, HubotIntegration, \ from zerver.lib.integrations import CATEGORIES, INTEGRATIONS, HubotIntegration, \
WebhookIntegration WebhookIntegration
from zerver.lib.request import has_request_variables, REQ from zerver.lib.request import has_request_variables, REQ
@@ -123,6 +124,7 @@ class MarkdownDirectoryView(ApiURLView):
add_api_uri_context(api_uri_context, self.request) add_api_uri_context(api_uri_context, self.request)
api_uri_context["run_content_validators"] = True api_uri_context["run_content_validators"] = True
context["api_uri_context"] = api_uri_context context["api_uri_context"] = api_uri_context
add_google_analytics_context(context)
return context return context
def get(self, request: HttpRequest, article: str="") -> HttpResponse: def get(self, request: HttpRequest, article: str="") -> HttpResponse:
@@ -170,6 +172,7 @@ class IntegrationView(ApiURLView):
context: Dict[str, Any] = super().get_context_data(**kwargs) context: Dict[str, Any] = super().get_context_data(**kwargs)
add_integrations_context(context) add_integrations_context(context)
add_integrations_open_graph_context(context, self.request) add_integrations_open_graph_context(context, self.request)
add_google_analytics_context(context)
return context return context

View File

@@ -7,7 +7,6 @@ from django.shortcuts import redirect, render
from django.utils import translation from django.utils import translation
from django.utils.cache import patch_cache_control from django.utils.cache import patch_cache_control
from zerver.context_processors import latest_info_context
from zerver.decorator import zulip_login_required from zerver.decorator import zulip_login_required
from zerver.forms import ToSForm from zerver.forms import ToSForm
from zerver.models import Message, Stream, UserProfile, \ from zerver.models import Message, Stream, UserProfile, \
@@ -25,6 +24,7 @@ from zerver.lib.utils import statsd, generate_random_token
from zerver.views.compatibility import is_outdated_desktop_app, \ from zerver.views.compatibility import is_outdated_desktop_app, \
is_unsupported_browser is_unsupported_browser
from zerver.views.messages import get_latest_update_message_flag_activity from zerver.views.messages import get_latest_update_message_flag_activity
from zerver.views.portico import hello_view
from two_factor.utils import default_device from two_factor.utils import default_device
import calendar import calendar
@@ -145,7 +145,7 @@ def home(request: HttpRequest) -> HttpResponse:
if subdomain != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN: if subdomain != Realm.SUBDOMAIN_FOR_ROOT_DOMAIN:
return home_real(request) return home_real(request)
return render(request, 'zerver/hello.html', latest_info_context()) return hello_view(request)
@zulip_login_required @zulip_login_required
def home_real(request: HttpRequest) -> HttpResponse: def home_real(request: HttpRequest) -> HttpResponse:

View File

@@ -1,23 +1,28 @@
from django.conf import settings from django.conf import settings
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.shortcuts import render from django.template.response import TemplateResponse
import ujson import ujson
from zerver.context_processors import get_realm_from_request from zerver.context_processors import get_realm_from_request, latest_info_context
from zerver.decorator import redirect_to_login from zerver.decorator import add_google_analytics, redirect_to_login
from zerver.models import Realm from zerver.models import Realm
from version import LATEST_DESKTOP_VERSION from version import LATEST_DESKTOP_VERSION
@add_google_analytics
def apps_view(request: HttpRequest, _: str) -> HttpResponse: def apps_view(request: HttpRequest, _: str) -> HttpResponse:
if settings.ZILENCER_ENABLED: if settings.ZILENCER_ENABLED:
return render(request, 'zerver/apps.html', return TemplateResponse(
request,
'zerver/apps.html',
context={ context={
"page_params": { "page_params": {
'electron_app_version': LATEST_DESKTOP_VERSION, 'electron_app_version': LATEST_DESKTOP_VERSION,
} }
}) }
)
return HttpResponseRedirect('https://zulipchat.com/apps/', status=301) return HttpResponseRedirect('https://zulipchat.com/apps/', status=301)
@add_google_analytics
def plans_view(request: HttpRequest) -> HttpResponse: def plans_view(request: HttpRequest) -> HttpResponse:
realm = get_realm_from_request(request) realm = get_realm_from_request(request)
realm_plan_type = 0 realm_plan_type = 0
@@ -28,9 +33,13 @@ def plans_view(request: HttpRequest) -> HttpResponse:
return HttpResponseRedirect('https://zulipchat.com/plans') return HttpResponseRedirect('https://zulipchat.com/plans')
if not request.user.is_authenticated: if not request.user.is_authenticated:
return redirect_to_login(next="plans") return redirect_to_login(next="plans")
return render(request, "zerver/plans.html", return TemplateResponse(
context={"realm_plan_type": realm_plan_type, 'free_trial_months': free_trial_months}) request,
"zerver/plans.html",
context={"realm_plan_type": realm_plan_type, 'free_trial_months': free_trial_months},
)
@add_google_analytics
def team_view(request: HttpRequest) -> HttpResponse: def team_view(request: HttpRequest) -> HttpResponse:
if not settings.ZILENCER_ENABLED: if not settings.ZILENCER_ENABLED:
return HttpResponseRedirect('https://zulipchat.com/team/', status=301) return HttpResponseRedirect('https://zulipchat.com/team/', status=301)
@@ -41,7 +50,7 @@ def team_view(request: HttpRequest) -> HttpResponse:
except FileNotFoundError: except FileNotFoundError:
data = {'contrib': {}, 'date': "Never ran."} data = {'contrib': {}, 'date': "Never ran."}
return render( return TemplateResponse(
request, request,
'zerver/team.html', 'zerver/team.html',
context={ context={
@@ -56,10 +65,24 @@ def get_isolated_page(request: HttpRequest) -> bool:
'''Accept a GET param `?nav=no` to render an isolated, navless page.''' '''Accept a GET param `?nav=no` to render an isolated, navless page.'''
return request.GET.get('nav') == 'no' return request.GET.get('nav') == 'no'
def terms_view(request: HttpRequest) -> HttpResponse: @add_google_analytics
return render(request, 'zerver/terms.html', def landing_view(request: HttpRequest, template_name: str) -> HttpResponse:
context={'isolated_page': get_isolated_page(request)}) return TemplateResponse(request, template_name)
@add_google_analytics
def hello_view(request: HttpRequest) -> HttpResponse:
return TemplateResponse(request, 'zerver/hello.html', latest_info_context())
@add_google_analytics
def terms_view(request: HttpRequest) -> HttpResponse:
return TemplateResponse(
request, 'zerver/terms.html',
context={'isolated_page': get_isolated_page(request)},
)
@add_google_analytics
def privacy_view(request: HttpRequest) -> HttpResponse: def privacy_view(request: HttpRequest) -> HttpResponse:
return render(request, 'zerver/privacy.html', return TemplateResponse(
context={'isolated_page': get_isolated_page(request)}) request, 'zerver/privacy.html',
context={'isolated_page': get_isolated_page(request)},
)

View File

@@ -370,3 +370,6 @@ FREE_TRIAL_MONTHS = None
# users, and you would like to save some disk space. Soft-deactivated # users, and you would like to save some disk space. Soft-deactivated
# returning users would still be caught-up normally. # returning users would still be caught-up normally.
AUTO_CATCH_UP_SOFT_DEACTIVATED_USERS = True AUTO_CATCH_UP_SOFT_DEACTIVATED_USERS = True
# Enables Google Analytics on selected portico pages.
GOOGLE_ANALYTICS_ID: Optional[str] = None

View File

@@ -35,7 +35,6 @@ import zerver.views.streams
import zerver.views.realm import zerver.views.realm
import zerver.views.digest import zerver.views.digest
import zerver.views.messages import zerver.views.messages
from zerver.context_processors import latest_info_context
import zerver.views.realm_export import zerver.views.realm_export
import zerver.views.upload import zerver.views.upload
@@ -548,23 +547,24 @@ i18n_urls = [
url(r'^integrations/(.*)$', IntegrationView.as_view()), url(r'^integrations/(.*)$', IntegrationView.as_view()),
# Landing page, features pages, signup form, etc. # Landing page, features pages, signup form, etc.
url(r'^hello/$', TemplateView.as_view(template_name='zerver/hello.html', url(r'^hello/$', zerver.views.portico.hello_view, name='landing-page'),
get_context_data=latest_info_context),
name='landing-page'),
url(r'^new-user/$', RedirectView.as_view(url='/hello', permanent=True)), url(r'^new-user/$', RedirectView.as_view(url='/hello', permanent=True)),
url(r'^features/$', TemplateView.as_view(template_name='zerver/features.html')), url(r'^features/$', zerver.views.portico.landing_view, {'template_name': 'zerver/features.html'}),
url(r'^plans/$', zerver.views.portico.plans_view, name='plans'), url(r'^plans/$', zerver.views.portico.plans_view, name='plans'),
url(r'^apps/(.*)$', zerver.views.portico.apps_view, name='zerver.views.home.apps_view'), url(r'^apps/(.*)$', zerver.views.portico.apps_view, name='zerver.views.home.apps_view'),
url(r'^team/$', zerver.views.portico.team_view), url(r'^team/$', zerver.views.portico.team_view),
url(r'^history/$', TemplateView.as_view(template_name='zerver/history.html')), url(r'^history/$', zerver.views.portico.landing_view, {'template_name': 'zerver/history.html'}),
url(r'^why-zulip/$', TemplateView.as_view(template_name='zerver/why-zulip.html')), url(r'^why-zulip/$', zerver.views.portico.landing_view, {'template_name': 'zerver/why-zulip.html'}),
url(r'^for/open-source/$', TemplateView.as_view(template_name='zerver/for-open-source.html')), url(r'^for/open-source/$', zerver.views.portico.landing_view,
url(r'^for/companies/$', TemplateView.as_view(template_name='zerver/for-companies.html')), {'template_name': 'zerver/for-open-source.html'}),
url(r'^for/working-groups-and-communities/$', url(r'^for/companies/$', zerver.views.portico.landing_view,
TemplateView.as_view(template_name='zerver/for-working-groups-and-communities.html')), {'template_name': 'zerver/for-companies.html'}),
url(r'^for/mystery-hunt/$', TemplateView.as_view(template_name='zerver/for-mystery-hunt.html')), url(r'^for/working-groups-and-communities/$', zerver.views.portico.landing_view,
url(r'^security/$', TemplateView.as_view(template_name='zerver/security.html')), {'template_name': 'zerver/for-working-groups-and-communities.html'}),
url(r'^atlassian/$', TemplateView.as_view(template_name='zerver/atlassian.html')), url(r'^for/mystery-hunt/$', zerver.views.portico.landing_view,
{'template_name': 'zerver/for-mystery-hunt.html'}),
url(r'^security/$', zerver.views.portico.landing_view, {'template_name': 'zerver/security.html'}),
url(r'^atlassian/$', zerver.views.portico.landing_view, {'template_name': 'zerver/atlassian.html'}),
# Terms of Service and privacy pages. # Terms of Service and privacy pages.
url(r'^terms/$', zerver.views.portico.terms_view, name='terms'), url(r'^terms/$', zerver.views.portico.terms_view, name='terms'),