Commit 0c5718c7 by Martyn James

Merge pull request #13 from edx-solutions/mjevtic/analytics-fixes

Analytics fixes + alt description for google images
parents 31a81191 15728ae4
......@@ -48,11 +48,11 @@ Access it at [http://localhost:8000/](http://localhost:8000).
Running tests
-------------
From the xblock-google-drive repository root, run the tests with the
From google-drive directory, run the tests with the
following command:
```bash
$ DJANGO_SETTINGS_MODULE="settings" nosetests --with-django
$ DJANGO_SETTINGS_MODULE="settings" nosetests --with-django tests/*
```
If you want to run only the integration or the unit tests, append the directory to the command. You can also run separate modules in this manner.
......@@ -84,6 +84,24 @@ Analogically, validation takes place for embedded code of Google Drive File.
Since error status codes start with 400, it's assumed that each status code that's larger than or equal to 400 states that file is invalid.
If for any reason exception occurs while getting an HTTP response, error code is returned, thus overriding default signalization that is invoked by edx platform when the 500 status code is reported.
a11y
----
For users with a visual impairment:
1. Iframes in which Google calendars and Google Drive files (except images) are shown now have title attribute with alternative text content which describes what the iframe contains.
2. Images have alt attribute which contains alternative text that has the same purpose as the title attribute of an iframe has
Analytics
---------
For analytics purposes, each time an image or iframe containing a calendar or Google Drive file is loaded, an event will be triggered.
There are two types of events:
1. edx.googlecomponent.calendar.displayed (if an iframe containing a Google calendar is loaded)
2. edx.googlecomponent.document.displayed (if an image or an iframe containing a Google Drive File is loaded)
License
-------
......
......@@ -42,7 +42,7 @@ class GoogleCalendarBlock(XBlock, PublishEventMixin):
views = [(0, 'Week'), (1, 'Month'), (2, 'Agenda')]
def student_view(self, context={}):
def student_view(self, context):
"""
Player view, displayed to the student
"""
......@@ -53,11 +53,10 @@ class GoogleCalendarBlock(XBlock, PublishEventMixin):
iframe = '<iframe src="https://www.google.com/calendar/embed?mode={}&amp;src={}&amp;showCalendars=0" title="{}"></iframe>'.format(view, self.calendar_id, self.display_name)
context.update({
fragment.add_content(loader.render_template('/templates/html/google_calendar.html', {
"self": self,
"iframe": iframe
})
fragment.add_content(loader.render_template('/templates/html/google_calendar.html', context))
}))
fragment.add_css(loader.load_unicode('public/css/google_calendar.css'))
fragment.add_javascript(loader.load_unicode('public/js/google_calendar.js'))
......
......@@ -43,17 +43,21 @@ class GoogleDocumentBlock(XBlock, PublishEventMixin):
</iframe>
"""))
def student_view(self, context={}):
alt_text = String(
display_name="Alternative Text",
help="In situations where image is not available to the reader, the alternative text ensures that no information or functionality is lost.",
scope=Scope.settings,
default=""
)
def student_view(self, context):
"""
Player view, displayed to the student
"""
fragment = Fragment()
context.update({
"self": self
})
fragment.add_content(loader.render_template('/templates/html/google_docs.html', context))
fragment.add_content(loader.render_template('/templates/html/google_docs.html', {"self": self}))
fragment.add_css(loader.load_unicode('public/css/google_docs.css'))
fragment.add_javascript(loader.load_unicode('public/js/google_docs.js'))
......@@ -82,6 +86,7 @@ class GoogleDocumentBlock(XBlock, PublishEventMixin):
self.display_name = submissions['display_name']
self.embed_code = submissions['embed_code']
self.alt_text = submissions['alt_text']
return {
'result': 'success',
......
......@@ -11,7 +11,7 @@
max-height: 200px;
}
.google-edit-wrapper .user-inputs-and-validation .validation_alert.covered {
.google-edit-wrapper .user-inputs-and-validation .validation_alert.covered, .google-edit-wrapper .xblock-inputs li.comp-setting-entry.covered {
display: none;
visibility: hidden;
}
......
......@@ -6,7 +6,13 @@ function GoogleCalendarBlock(runtime, element) {
$.ajax({
type: "POST",
url: runtime.handlerUrl(element, 'publish_event'),
data: JSON.stringify({url: iframe_url, displayedin: 'iframe', event_type: 'edx.googlecomponent.calendar.displayed'})
data: JSON.stringify({
url: iframe_url,
displayed_in: 'iframe', event_type: 'edx.googlecomponent.calendar.displayed'
}),
success: function(){
$('.load_event_complete', element).val("I've published the event that indicates that the load has completed");
}
});
});
}
......@@ -3,7 +3,9 @@ function GoogleDocumentBlock(runtime, element) {
var iframe = $('iframe', element);
var image = $('img', element);
var display_name = $('.google-docs-xblock-wrapper', element).attr('data-display-name');
var xblock_wrapper = $('.google-docs-xblock-wrapper', element);
var display_name = xblock_wrapper.attr('data-display-name');
var alt_text = xblock_wrapper.attr('data-alt-text');
if(iframe.length > 0){
var iframe_src = iframe.attr('src');
......@@ -16,7 +18,7 @@ function GoogleDocumentBlock(runtime, element) {
iframe.attr('title', display_name);
}else if(image.length > 0){
image.attr('title', display_name);
image.attr('alt', alt_text);
}
function SignalDocumentLoaded(ev, presented_within){
......@@ -26,11 +28,15 @@ function GoogleDocumentBlock(runtime, element) {
url: runtime.handlerUrl(element, 'publish_event'),
data: JSON.stringify({
url: document_url,
displayedin: presented_within,
event_type: 'edx.googlecomponent.document.displayed',
})
displayed_in: presented_within,
event_type: 'edx.googlecomponent.document.displayed'
}),
success: function(){
$('.load_event_complete', element).val("I've published the event that indicates that the load has completed");
}
});
}
iframe.load(function(e){SignalDocumentLoaded(e, 'iframe');});
image.load(function(e){SignalDocumentLoaded(e, 'img');});
......
......@@ -8,6 +8,8 @@ function GoogleDocumentEditBlock(runtime, element) {
var edit_display_name_input = $('#edit_display_name', element);
var error_message_div = $('.xblock-editor-error-message', element);
var defaultName = edit_display_name_input.attr('data-default-value');
var edit_alt_text_input = $('#edit_alt_text', element);
var alt_text_item = $('li#alt_text_item', element);
ToggleClearDefaultName();
IsUrlValid();
......@@ -44,6 +46,7 @@ function GoogleDocumentEditBlock(runtime, element) {
var data = {
'display_name': edit_display_name_input.val(),
'embed_code': embed_code_textbox.val(),
'alt_text': edit_alt_text_input.val(),
};
error_message_div.html();
......@@ -59,6 +62,11 @@ function GoogleDocumentEditBlock(runtime, element) {
});
}
function HideAltTextInput(){
edit_alt_text_input.val('');
alt_text_item.addClass('covered');
}
function IsUrlValid(){
var embed_html = embed_code_textbox.val();
......@@ -75,6 +83,7 @@ function GoogleDocumentEditBlock(runtime, element) {
validation_alert.removeClass('covered');
embed_code_textbox.addClass('error');
xblock_inputs_wrapper.addClass('alerted');
HideAltTextInput();
} else {
validation_alert.addClass('covered');
save_button.removeClass('disabled');
......@@ -82,6 +91,16 @@ function GoogleDocumentEditBlock(runtime, element) {
xblock_inputs_wrapper.removeClass('alerted');
save_button.bind('click', SaveEditing);
if (embed_html.toLowerCase().indexOf("<img") >= 0){
if (alt_text_item.hasClass('covered')){
alt_text_item.removeClass('covered');
}
} else {
if (!alt_text_item.hasClass('covered')){
HideAltTextInput();
}
}
}
},
error: function(result) {
......
<div class="google-calendar-xblock-wrapper">
{{ iframe|safe }}
<input type="hidden" class="load_event_complete">
{{ iframe|safe }}
</div>
<div class="google-docs-xblock-wrapper" data-display-name="{{self.display_name}}">
{{ self.embed_code|safe }}
<div class="google-docs-xblock-wrapper" data-display-name="{{self.display_name}}" data-alt-text="{{self.alt_text}}">
<input type="hidden" class="load_event_complete">
{{ self.embed_code|safe }}
</div>
......@@ -30,6 +30,13 @@
</div>
<span class="tip setting-help">{% trans "Google provides an embed code for Drive documents. In the Google Drive document, from the File menu, select Publish to the Web. Modify settings as needed, click Publish, and copy the embed code into this field." %}</span>
</li>
<li class="field comp-setting-entry is-set covered" id="alt_text_item">
<div class="wrapper-comp-setting">
<label class="label setting-label" for="edit_alt_text">{% trans "Alternative text" %}</label>
<input class="input setting-input edit-alt-text" id="edit_alt_text" value="{{ self.alt_text }}" type="text">
</div>
<span class="tip setting-help">{% trans "In situations where image is not available to the reader, the alternative text ensures that no information or functionality is lost." %}</span>
</li>
</ul>
</div>
</div>
......
from xblockutils.base_test import SeleniumBaseTest
class GoogleCalendarBaseTest(SeleniumBaseTest):
module_name = __name__
default_css_selector = 'div.google-calendar-xblock-wrapper'
def test_calendar_publish_event(self):
calendar = self.go_to_page('Calendar')
load_event_complete = calendar.find_element_by_css_selector('.load_event_complete')
self.assertEqual(load_event_complete.get_attribute('value'), "I've published the event that indicates that the load has completed")
from xblockutils.base_test import SeleniumBaseTest
class GoogleDocumentBaseTest(SeleniumBaseTest):
module_name = __name__
default_css_selector = 'div.google-docs-xblock-wrapper'
def test_document_publish_event(self):
document = self.go_to_page('Document')
load_event_complete = document.find_element_by_css_selector('.load_event_complete')
self.assertEqual(load_event_complete.get_attribute('value'), "I've published the event that indicates that the load has completed")
def test_image_publish_event(self):
image = self.go_to_page('Image')
load_event_complete = image.find_element_by_css_selector('.load_event_complete')
self.assertEqual(load_event_complete.get_attribute('value'), "I've published the event that indicates that the load has completed")
<vertical_demo>
<google-calendar/>
</vertical_demo>
<vertical_demo>
<google-document/>
</vertical_demo>
<vertical_demo>
<google-document embed_code="&lt;img src='https://docs.google.com/drawings/d/1lmmxboBM5c_0WCTjhAxBdkpqQb3T8VSwtuG0TRR1ODQ/pub?w=960&amp;h=720'&gt;"/>
</vertical_demo>
......@@ -36,8 +36,8 @@ def test_document_templates_contents():
student_fragment = block.render('student_view', Mock())
assert_in('<div class="google-docs-xblock-wrapper"', student_fragment.content)
assert_in('&#39;self.display_name&#39;', student_fragment.content)
assert_in('&#39;self.embed_code&#39;', student_fragment.content)
assert_in('Google Document', student_fragment.content)
assert_in('https://docs.google.com/presentation/d/1x2ZuzqHsMoh1epK8VsGAlanSo7r9z55ualwQlj-ofBQ/embed?start=true&loop=true&delayms=10000"\n frameborder="0"\n width="960"\n height="569"\n allowfullscreen="true"', student_fragment.content)
studio_fragment = block.render('studio_view', Mock())
assert_in('<div class="wrapper-comp-settings is-active editor-with-buttons google-edit-wrapper" id="settings-tab">', studio_fragment.content)
......@@ -50,7 +50,8 @@ def test_calendar_templates_contents():
student_fragment = block.render('student_view', Mock())
assert_in('<div class="google-calendar-xblock-wrapper">', student_fragment.content)
assert_in('&#39;iframe&#39;', student_fragment.content)
assert_in('https://www.google.com/calendar/embed?mode=Month&amp;src=edx.org_lom804qe3ttspplj1bgeu1l3ak@group.calendar.google.com&amp;showCalendars=0', student_fragment.content)
assert_in('Google Calendar', student_fragment.content)
studio_fragment = block.render('studio_view', Mock())
assert_in('<div class="wrapper-comp-settings is-active editor-with-buttons google-edit-wrapper" id="settings-tab">', studio_fragment.content)
......@@ -63,7 +64,8 @@ def test_studio_document_submit():
body = json.dumps({
'display_name': "Google Document",
'embed_code': "<iframe>"
'embed_code': "<iframe>",
'alt_text': "This is alt text",
})
res = block.handle('studio_submit', make_request(body))
......@@ -71,6 +73,7 @@ def test_studio_document_submit():
assert_equals(block.display_name, "Google Document")
assert_equals(block.embed_code, "<iframe>")
assert_equals(block.alt_text, "This is alt text")
def test_calendar_document_submit():
block = make_calendar_block()
......@@ -112,4 +115,51 @@ def test_check_document_url():
assert_equals(json.loads(res.body), {'status_code': 404})
def test_document_publish_event():
block = make_document_block()
body = json.dumps({
'url': 'https://docs.google.com/presentation/d/1x2ZuzqHsMoh1epK8VsdsadfGAlanSo7r9z55ualwQlj-ofBQ/embed?start=true&loop=true&delayms=10000',
'displayed_in': 'iframe',
'event_type': 'edx.googlecomponent.document.displayed',
})
res = block.handle('publish_event', make_request(body))
assert_equals(json.loads(res.body), {'result': 'success'})
body = json.dumps({
'url': 'https://docs.google.com/drawings/d/1LHGzCTLRb--CDvFFjoYp62TiIN5KgsE7QOy9Sift_eg/pub?w=882&amp;h=657',
'displayed_in': 'img',
'event_type': 'edx.googlecomponent.document.displayed',
})
res = block.handle('publish_event', make_request(body))
assert_equals(json.loads(res.body), {'result': 'success'})
body = json.dumps({
'url': 'https://docs.google.com/presentation/d/1x2ZuzqHsMoh1epK8VsdsadfGAlanSo7r9z55ualwQlj-ofBQ/embed?start=true&loop=true&delayms=10000',
'displayed_in': 'iframe',
})
res = block.handle('publish_event', make_request(body))
assert_equals(json.loads(res.body), {'result': 'error', 'message': 'Missing event_type in JSON data'})
def test_calendar_publish_event():
block = make_calendar_block()
body = json.dumps({
'url': 'https://www.google.com/calendar/embed?mode=Month&src=edx.org_lom804qe3ttspplj1bgeu1l3ak@group.calendar.google.com&showCalendars=0',
'displayed_in': 'iframe',
'event_type': 'edx.googlecomponent.calendar.displayed'
})
res = block.handle('publish_event', make_request(body))
assert_equals(json.loads(res.body), {'result': 'success'})
body = json.dumps({
'url': 'https://www.google.com/calendar/embed?mode=Month&src=edx.org_lom804qe3ttspplj1bgeu1l3ak@group.calendar.google.com&showCalendars=0',
'displayed_in': 'iframe',
})
res = block.handle('publish_event', make_request(body))
assert_equals(json.loads(res.body), {'result': 'error', 'message': 'Missing event_type in JSON data'})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment