Skip to content

Commit

Permalink
Add A/B testing tab to page editor
Browse files Browse the repository at this point in the history
  • Loading branch information
kaedroho committed Dec 15, 2020
1 parent d548de9 commit f0c97b3
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 0 deletions.
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"devDependencies": {
"@svgr/webpack": "^5.4.0",
"@types/c3": "^0.7.5",
"@types/jquery": "^3.5.4",
"@types/react": "^16.8.19",
"@types/react-dom": "^16.8.19",
"@types/styled-components": "^5.1.2",
Expand Down
8 changes: 8 additions & 0 deletions wagtail_ab_testing/compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import os

if os.name == 'nt':
# Windows has a different strftime format for dates without leading 0
# https://stackoverflow.com/questions/904928/python-strftime-date-without-leading-0
DATE_FORMAT = '%#d %B %Y'
else:
DATE_FORMAT = '%-d %B %Y'
46 changes: 46 additions & 0 deletions wagtail_ab_testing/static_src/components/PageEditorTab/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { FunctionComponent } from 'react';
import ReactDOM from 'react-dom';

interface AbTest {
id: number;
name: string;
started_at: string;
status: string;
}

interface PageEditorTabProps {
tests: AbTest[];
}

const PageEditorTab: FunctionComponent<PageEditorTabProps> = ({ tests }) => {
return (
<div className="nice-padding">
<table className="listing">
<thead>
<tr>
<th className="title">{gettext('Started at')}</th>
<th>{gettext('Test name')}</th>
<th>{gettext('Status')}</th>
</tr>
</thead>
<tbody>
{tests.map(test => (
<tr key={test.id}>
<td className="title">{test.started_at}</td>
<td>{test.name}</td>
<td>
<span className="status-tag primary">
{test.status}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};

export function initPageEditorTab(element: HTMLElement, props: any) {
ReactDOM.render(<PageEditorTab {...props} />, element);
}
2 changes: 2 additions & 0 deletions wagtail_ab_testing/static_src/custom.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ declare module '*.svg' {
// Declare globals provided by Django's JavaScript Catalog
// For more information, see: https://docs.djangoproject.com/en/3.1/topics/i18n/translation/#module-django.views.i18n
declare global {
const abTestingTabProps: any | undefined;

// Wagtail globals

interface WagtailConfig {
Expand Down
19 changes: 19 additions & 0 deletions wagtail_ab_testing/static_src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import c3 from 'c3';

import { initGoalSelector } from './components/GoalSelector';
import { initPageEditorTab } from './components/PageEditorTab';
import './style/progress.scss';

import './styles/sections.scss';
import './styles/forms.scss';

document.addEventListener('DOMContentLoaded', () => {
// Goal selector on create new A/B test
initGoalSelector();

// Charts on A/B test progress
document.querySelectorAll('[component="chart"]').forEach(chartElement => {
if (
!(chartElement instanceof HTMLElement) ||
Expand Down Expand Up @@ -36,4 +39,20 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
});

// A/B testing tab on page edito
if (abTestingTabProps) {
$('ul.tab-nav').append(`<li role="tab" aria-controls="tab-abtesting">
<a href="#tab-abtesting" class="">${gettext('A/B testing')}</a>
</li>`);
$('div.tab-content').append(`
<section id="tab-abtesting" role="tabpanel" aria-labelledby="tab-label-abtesting">
</section>
`);

const abTestingTab = document.getElementById('tab-abtesting');
if (abTestingTab) {
initPageEditorTab(abTestingTab, abTestingTabProps);
}
}
});
34 changes: 34 additions & 0 deletions wagtail_ab_testing/wagtail_hooks.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import json

from django.shortcuts import redirect
from django.urls import path, include, reverse
from django.utils.html import format_html, escapejs
from django.utils.translation import gettext as _, gettext_lazy as __
from django.views.i18n import JavaScriptCatalog

from wagtail.admin.action_menu import ActionMenuItem
from wagtail.admin.menu import MenuItem
from wagtail.admin.staticfiles import versioned_static
from wagtail.core import hooks

from . import views
from .compat import DATE_FORMAT
from .models import AbTest
from .utils import request_is_trackable

Expand Down Expand Up @@ -49,6 +54,35 @@ def register_create_abtest_action_menu_item():
return CreateAbTestActionMenuItem(order=100)


# This is the only way to inject custom JS into the editor with knowledge of the page being edited
class AbTestingTabActionMenuItem(ActionMenuItem):
def render_html(self, request, context):
if 'page' in context:
return format_html(
'<script src="{}"></script><script src="{}"></script><script>window.abTestingTabProps = JSON.parse("{}");</script>',
reverse('wagtail_ab_testing:javascript_catalog'),
versioned_static('wagtail_ab_testing/js/wagtail-ab-testing.js'),
escapejs(json.dumps({
'tests': [
{
'id': ab_test.id,
'name': ab_test.name,
'started_at': ab_test.first_started_at.strftime(DATE_FORMAT) if ab_test.first_started_at else _("Not started"),
'status': ab_test.get_status_description(),
}
for ab_test in AbTest.objects.filter(page=context['page']).order_by('-id')
]
}))
)

return ''


@hooks.register('register_page_action_menu_item')
def register_ab_testing_tab_action_menu_item():
return AbTestingTabActionMenuItem()


@hooks.register('after_edit_page')
def redirect_to_create_ab_test(request, page):
if 'create-ab-test' in request.POST:
Expand Down

0 comments on commit f0c97b3

Please sign in to comment.