Merge pull request #1185 from edx/alex/lti_new_window
New feature for lti module. Open in new window.
This commit is contained in:
@@ -7,6 +7,8 @@ the top. Include a label indicating the component affected.
|
||||
|
||||
Blades: Add possibility to use multiple LTI tools per page.
|
||||
|
||||
Blades: LTI module can now load external content in a new window.
|
||||
|
||||
LMS: Disable data download buttons on the instructor dashboard for large courses
|
||||
|
||||
LMS: Ported bulk emailing to the beta instructor dashboard.
|
||||
|
||||
1
cms/static/sass/_mixins-inherited.scss
Symbolic link
1
cms/static/sass/_mixins-inherited.scss
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../common/static/sass/_mixins-inherited.scss
|
||||
@@ -197,6 +197,10 @@ $lightBluishGrey: rgb(197, 207, 223);
|
||||
$lightBluishGrey2: rgb(213, 220, 228);
|
||||
$error-red: rgb(253, 87, 87);
|
||||
|
||||
|
||||
//carryover from LMS for xmodules
|
||||
$sidebar-color: rgb(246, 246, 246);
|
||||
|
||||
// type
|
||||
$sans-serif: $f-sans-serif;
|
||||
$body-line-height: golden-ratio(.875em, 1);
|
||||
|
||||
@@ -2,8 +2,23 @@ div.lti {
|
||||
// align center
|
||||
margin: 0 auto;
|
||||
|
||||
h3.error_message {
|
||||
display: block;
|
||||
.wrapper-lti-link {
|
||||
@include font-size(14);
|
||||
position: relative;
|
||||
background-color: $sidebar-color;
|
||||
padding: ($baseline*1.8) ($baseline*1.5) ($baseline*1.1) $baseline;
|
||||
|
||||
.lti-link {
|
||||
position: absolute;
|
||||
top: ($baseline*1.8);
|
||||
right: $baseline;
|
||||
|
||||
.link_lti_new_window {
|
||||
@extend .gray-button;
|
||||
@include font-size(13);
|
||||
@include line-height(14);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
form.ltiLaunchForm {
|
||||
@@ -13,18 +28,8 @@ div.lti {
|
||||
iframe.ltiLaunchFrame {
|
||||
width: 100%;
|
||||
height: 800px;
|
||||
display: none;
|
||||
display: block;
|
||||
border: 0px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
&.rendered {
|
||||
iframe.ltiLaunchFrame {
|
||||
display: block;
|
||||
}
|
||||
|
||||
h3.error_message {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,31 @@
|
||||
<div id="lti_id" class="lti">
|
||||
<div class="lti-wrapper">
|
||||
<div id="lti_id" class="lti" data-open_in_a_new_page="true">
|
||||
|
||||
<form
|
||||
action="http://www.example.com"
|
||||
name="ltiLaunchForm"
|
||||
class="ltiLaunchForm"
|
||||
method="post"
|
||||
target="ltiLaunchFrame"
|
||||
enctype="application/x-www-form-urlencoded"
|
||||
>
|
||||
<input name="launch_presentation_return_url" value="" />
|
||||
<input name="lti_version" value="LTI-1p0" />
|
||||
<input name="user_id" value="student" />
|
||||
<input name="oauth_nonce" value="28347958723982798572" />
|
||||
<input name="oauth_timestamp" value="2389479832" />
|
||||
<input name="oauth_consumer_key" value="" />
|
||||
<input name="lis_result_sourcedid" value="" />
|
||||
<input name="oauth_signature_method" value="HMAC-SHA1" />
|
||||
<input name="oauth_version" value="1.0" />
|
||||
<input name="role" value="student" />
|
||||
<input name="lis_outcome_service_url" value="" />
|
||||
<input name="oauth_signature" value="89ru3289r3ry283y3r82ryr38yr" />
|
||||
<input name="lti_message_type" value="basic-lti-launch-request" />
|
||||
<input name="oauth_callback" value="about:blank" />
|
||||
<form
|
||||
action=""
|
||||
name="ltiLaunchForm"
|
||||
class="ltiLaunchForm"
|
||||
method="post"
|
||||
target="_blank"
|
||||
enctype="application/x-www-form-urlencoded"
|
||||
>
|
||||
<input name="launch_presentation_return_url" value="" />
|
||||
<input name="lti_version" value="LTI-1p0" />
|
||||
<input name="user_id" value="student" />
|
||||
<input name="oauth_nonce" value="28347958723982798572" />
|
||||
<input name="oauth_timestamp" value="2389479832" />
|
||||
<input name="oauth_consumer_key" value="" />
|
||||
<input name="lis_result_sourcedid" value="" />
|
||||
<input name="oauth_signature_method" value="HMAC-SHA1" />
|
||||
<input name="oauth_version" value="1.0" />
|
||||
<input name="role" value="student" />
|
||||
<input name="lis_outcome_service_url" value="" />
|
||||
<input name="oauth_signature" value="89ru3289r3ry283y3r82ryr38yr" />
|
||||
<input name="lti_message_type" value="basic-lti-launch-request" />
|
||||
<input name="oauth_callback" value="about:blank" />
|
||||
|
||||
<input type="submit" value="Press to Launch" />
|
||||
</form>
|
||||
|
||||
<h3 class="error_message">
|
||||
Please provide launch_url. Click "Edit", and fill in the
|
||||
required fields.
|
||||
</h3>
|
||||
|
||||
<iframe name="ltiLaunchFrame" class="ltiLaunchFrame" src=""></iframe>
|
||||
<input type="submit" value="Press to Launch" />
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,77 +5,158 @@
|
||||
*
|
||||
*
|
||||
* The front-end part of the LTI module is really simple. If an action
|
||||
* is set for the hidden LTI form, then it is submited, and the results are
|
||||
* redirected to an iframe.
|
||||
* is set for the hidden LTI form, then it is submitted, and the results are
|
||||
* redirected to an iframe or to a new window (based on the
|
||||
* "open_in_a_new_page" attribute).
|
||||
*
|
||||
* We will test that the form is only submited when the action is set (i.e.
|
||||
* not empty).
|
||||
* We will test that the form is only submitted when the action is set (i.e.
|
||||
* not empty, and not the default one).
|
||||
*
|
||||
* Other aspects of LTI module will be covered by Python unit tests and
|
||||
* acceptance tests.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* "Hence that general is skilful in attack whose opponent does not know what
|
||||
* to defend; and he is skilful in defense whose opponent does not know what
|
||||
* "Hence that general is skillful in attack whose opponent does not know what
|
||||
* to defend; and he is skillful in defense whose opponent does not know what
|
||||
* to attack."
|
||||
*
|
||||
* ~ Sun Tzu
|
||||
*/
|
||||
|
||||
(function () {
|
||||
var element, container, form, link,
|
||||
IN_NEW_WINDOW = 'true',
|
||||
IN_IFRAME = 'false',
|
||||
EMPTY_URL = '',
|
||||
DEFAULT_URL = 'http://www.example.com',
|
||||
NEW_URL = 'http://www.example.com/some_book';
|
||||
|
||||
function initialize(target, action) {
|
||||
var tempEl;
|
||||
|
||||
loadFixtures('lti.html');
|
||||
|
||||
element = $('.lti-wrapper');
|
||||
container = element.find('.lti');
|
||||
form = container.find('.ltiLaunchForm');
|
||||
|
||||
if (target === IN_IFRAME) {
|
||||
container.data('open_in_a_new_page', 'false');
|
||||
form.attr('target', 'ltiLaunchFrame');
|
||||
}
|
||||
|
||||
form.attr('action', action);
|
||||
|
||||
// If we have a new proper action (non-default), we create either
|
||||
// a link that will submit the form, or an iframe that will contain
|
||||
// the answer of auto submitted form.
|
||||
if (action !== EMPTY_URL && action !== DEFAULT_URL) {
|
||||
if (target === IN_NEW_WINDOW) {
|
||||
$('<a />', {
|
||||
href: '#',
|
||||
class: 'link_lti_new_window'
|
||||
}).appendTo(container);
|
||||
|
||||
link = container.find('.link_lti_new_window');
|
||||
} else {
|
||||
$('<iframe />', {
|
||||
name: 'ltiLaunchFrame',
|
||||
class: 'ltiLaunchFrame',
|
||||
src: ''
|
||||
}).appendTo(container);
|
||||
}
|
||||
}
|
||||
|
||||
spyOnEvent(form, 'submit');
|
||||
|
||||
LTI(element);
|
||||
}
|
||||
|
||||
describe('LTI', function () {
|
||||
describe('constructor', function () {
|
||||
describe('before settings were filled in', function () {
|
||||
var element, errorMessage, frame;
|
||||
describe('initialize', function () {
|
||||
describe(
|
||||
'open_in_a_new_page is "true", launch URL is empty',
|
||||
function () {
|
||||
|
||||
// This function will be executed before each of the it() specs
|
||||
// in this suite.
|
||||
beforeEach(function () {
|
||||
loadFixtures('lti.html');
|
||||
|
||||
element = $('#lti_id');
|
||||
errorMessage = element.find('.error_message');
|
||||
form = element.find('.ltiLaunchForm');
|
||||
frame = element.find('.ltiLaunchFrame');
|
||||
|
||||
spyOnEvent(form, 'submit');
|
||||
|
||||
LTI(element);
|
||||
initialize(IN_NEW_WINDOW, EMPTY_URL);
|
||||
});
|
||||
|
||||
it(
|
||||
'when URL setting is not filled form is not submited',
|
||||
function () {
|
||||
|
||||
it('form is not submitted', function () {
|
||||
expect('submit').not.toHaveBeenTriggeredOn(form);
|
||||
});
|
||||
});
|
||||
|
||||
describe('After the settings were filled in', function () {
|
||||
var element, errorMessage, frame;
|
||||
describe(
|
||||
'open_in_a_new_page is "true", launch URL is default',
|
||||
function () {
|
||||
|
||||
// This function will be executed before each of the it() specs
|
||||
// in this suite.
|
||||
beforeEach(function () {
|
||||
loadFixtures('lti.html');
|
||||
|
||||
element = $('#lti_id');
|
||||
errorMessage = element.find('.error_message');
|
||||
form = element.find('.ltiLaunchForm');
|
||||
frame = element.find('.ltiLaunchFrame');
|
||||
|
||||
spyOnEvent(form, 'submit');
|
||||
|
||||
// The user "fills in" the necessary settings, and the
|
||||
// form will get an action URL.
|
||||
form.attr('action', 'http://www.example.com/test_submit');
|
||||
|
||||
LTI(element);
|
||||
initialize(IN_NEW_WINDOW, DEFAULT_URL);
|
||||
});
|
||||
|
||||
it('when URL setting is filled form is submited', function () {
|
||||
it('form is not submitted', function () {
|
||||
expect('submit').not.toHaveBeenTriggeredOn(form);
|
||||
});
|
||||
});
|
||||
|
||||
describe(
|
||||
'open_in_a_new_page is "true", launch URL is not empty, and ' +
|
||||
'not default',
|
||||
function () {
|
||||
|
||||
beforeEach(function () {
|
||||
initialize(IN_NEW_WINDOW, NEW_URL);
|
||||
});
|
||||
|
||||
it('form is not submitted', function () {
|
||||
expect('submit').not.toHaveBeenTriggeredOn(form);
|
||||
});
|
||||
|
||||
it('after link is clicked, form is submitted', function () {
|
||||
link.trigger('click');
|
||||
|
||||
expect('submit').toHaveBeenTriggeredOn(form);
|
||||
});
|
||||
});
|
||||
|
||||
describe(
|
||||
'open_in_a_new_page is "false", launch URL is empty',
|
||||
function () {
|
||||
|
||||
beforeEach(function () {
|
||||
initialize(IN_IFRAME, EMPTY_URL);
|
||||
});
|
||||
|
||||
it('form is not submitted', function () {
|
||||
expect('submit').not.toHaveBeenTriggeredOn(form);
|
||||
});
|
||||
});
|
||||
|
||||
describe(
|
||||
'open_in_a_new_page is "false", launch URL is default',
|
||||
function () {
|
||||
|
||||
beforeEach(function () {
|
||||
initialize(IN_IFRAME, DEFAULT_URL);
|
||||
});
|
||||
|
||||
it('form is not submitted', function () {
|
||||
expect('submit').not.toHaveBeenTriggeredOn(form);
|
||||
});
|
||||
});
|
||||
|
||||
describe(
|
||||
'open_in_a_new_page is "false", launch URL is not empty, ' +
|
||||
'and not default',
|
||||
function () {
|
||||
|
||||
beforeEach(function () {
|
||||
initialize(IN_IFRAME, NEW_URL);
|
||||
});
|
||||
|
||||
it('form is submitted', function () {
|
||||
expect('submit').toHaveBeenTriggeredOn(form);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,43 @@
|
||||
/**
|
||||
* File: lti.js
|
||||
*
|
||||
* Purpose: LTI module constructor. Given an LTI element, we process it.
|
||||
*
|
||||
*
|
||||
* Inside the element there is a form. If that form has a valid action
|
||||
* attribute, then we do one of:
|
||||
*
|
||||
* 1.) Submit the form. The results will be shown on the current page in an
|
||||
* iframe.
|
||||
* 2.) Attach a handler function to a link which will submit the form. The
|
||||
* results will be shown in a new window.
|
||||
*
|
||||
* The 'open_in_a_new_page' data attribute of the LTI element dictates which of
|
||||
* the two actions will be performed.
|
||||
*/
|
||||
|
||||
/*
|
||||
* So the thing to do when working on a motorcycle, as in any other task, is to
|
||||
* cultivate the peace of mind which does not separate one's self from one's
|
||||
* surroundings. When that is done successfully, then everything else follows
|
||||
* naturally. Peace of mind produces right values, right values produce right
|
||||
* thoughts. Right thoughts produce right actions and right actions produce
|
||||
* work which will be a material reflection for others to see of the serenity
|
||||
* at the center of it all.
|
||||
*
|
||||
* ~ Robert M. Pirsig
|
||||
*/
|
||||
|
||||
window.LTI = (function () {
|
||||
// Function initialize(element)
|
||||
//
|
||||
// Initialize the LTI iframe.
|
||||
// Initialize the LTI module.
|
||||
//
|
||||
// @param element DOM element, or jQuery element object.
|
||||
//
|
||||
// @return undefined
|
||||
function initialize(element) {
|
||||
var form;
|
||||
var form, openInANewPage, formAction;
|
||||
|
||||
// In cms (Studio) the element is already a jQuery object. In lms it is
|
||||
// a DOM object.
|
||||
@@ -13,12 +47,36 @@ window.LTI = (function () {
|
||||
element = $(element);
|
||||
|
||||
form = element.find('.ltiLaunchForm');
|
||||
formAction = form.attr('action');
|
||||
|
||||
// If action is empty string, or action is the default URL that should
|
||||
// not cause a form submit.
|
||||
if (!formAction || formAction === 'http://www.example.com') {
|
||||
|
||||
// Nothing to do - no valid action provided. Error message will be
|
||||
// displaced in browser (HTML).
|
||||
return;
|
||||
}
|
||||
|
||||
// We want a Boolean 'true' or 'false'. First we will retrieve the data
|
||||
// attribute, and then we will parse it via native JSON.parse().
|
||||
openInANewPage = element.find('.lti').data('open_in_a_new_page');
|
||||
openInANewPage = JSON.parse(openInANewPage);
|
||||
|
||||
// If the Form's action attribute is set (i.e. we can perform a normal
|
||||
// submit), then we submit the form and make the frame shown.
|
||||
if (form.attr('action') && form.attr('action') !== 'http://www.example.com') {
|
||||
// submit), then we (depending on instance settings) submit the form
|
||||
// when user will click on a link, or submit the form immediately.
|
||||
if (openInANewPage === true) {
|
||||
element.find('.link_lti_new_window').on('click', function () {
|
||||
form.submit();
|
||||
});
|
||||
} else {
|
||||
// At this stage the form exists on the page and has a valid
|
||||
// action. We are safe to submit it, even if `openInANewPage` is
|
||||
// set to some weird value.
|
||||
//
|
||||
// Best case scenario is that `openInANewPage` is set to `true`.
|
||||
form.submit();
|
||||
element.find('.lti').addClass('rendered');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,14 @@ http://www.imsglobal.org/LTI/v1p1p1/ltiIMGv1p1p1.html
|
||||
import logging
|
||||
import oauthlib.oauth1
|
||||
import urllib
|
||||
import json
|
||||
|
||||
from xmodule.editing_module import MetadataOnlyEditingDescriptor
|
||||
from xmodule.x_module import XModule
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
from pkg_resources import resource_string
|
||||
from xblock.core import String, Scope, List
|
||||
from xblock.fields import Boolean
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -45,6 +47,7 @@ class LTIFields(object):
|
||||
lti_id = String(help="Id of the tool", default='', scope=Scope.settings)
|
||||
launch_url = String(help="URL of the tool", default='http://www.example.com', scope=Scope.settings)
|
||||
custom_parameters = List(help="Custom parameters (vbid, book_location, etc..)", scope=Scope.settings)
|
||||
open_in_a_new_page = Boolean(help="Should LTI be opened in new page?", default=True, scope=Scope.settings)
|
||||
|
||||
|
||||
class LTIModule(LTIFields, XModule):
|
||||
@@ -169,14 +172,15 @@ class LTIModule(LTIFields, XModule):
|
||||
client_key,
|
||||
client_secret
|
||||
)
|
||||
|
||||
context = {
|
||||
'input_fields': input_fields,
|
||||
|
||||
# these params do not participate in oauth signing
|
||||
'launch_url': self.launch_url,
|
||||
'element_id': self.location.html_id(),
|
||||
'element_class': self.location.category,
|
||||
'element_class': self.category,
|
||||
'open_in_a_new_page': self.open_in_a_new_page,
|
||||
'display_name': self.display_name,
|
||||
}
|
||||
|
||||
return self.system.render_template('lti.html', context)
|
||||
|
||||
@@ -2,17 +2,27 @@
|
||||
Feature: LMS.LTI component
|
||||
As a student, I want to view LTI component in LMS.
|
||||
|
||||
Scenario: LTI component in LMS is not rendered
|
||||
Scenario: LTI component in LMS with no launch_url is not rendered
|
||||
Given the course has correct LTI credentials
|
||||
And the course has an LTI component with incorrect fields
|
||||
Then I view the LTI and it is not rendered
|
||||
And the course has an LTI component with no_launch_url fields, new_page is false
|
||||
Then I view the LTI and error is shown
|
||||
|
||||
Scenario: LTI component in LMS is rendered
|
||||
Scenario: LTI component in LMS with incorrect lti_id is rendered incorrectly
|
||||
Given the course has correct LTI credentials
|
||||
And the course has an LTI component filled with correct fields
|
||||
Then I view the LTI and it is rendered
|
||||
And the course has an LTI component with incorrect_lti_id fields, new_page is false
|
||||
Then I view the LTI but incorrect_signature warning is rendered
|
||||
|
||||
Scenario: LTI component in LMS is rendered incorrectly
|
||||
Given the course has incorrect LTI credentials
|
||||
And the course has an LTI component filled with correct fields
|
||||
And the course has an LTI component with correct fields, new_page is false
|
||||
Then I view the LTI but incorrect_signature warning is rendered
|
||||
|
||||
Scenario: LTI component in LMS is correctly rendered in new page
|
||||
Given the course has correct LTI credentials
|
||||
And the course has an LTI component with correct fields, new_page is true
|
||||
Then I view the LTI and it is rendered in new page
|
||||
|
||||
Scenario: LTI component in LMS is correctly rendered in iframe
|
||||
Given the course has correct LTI credentials
|
||||
And the course has an LTI component with correct fields, new_page is false
|
||||
Then I view the LTI and it is rendered in iframe
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pylint: disable=C0111
|
||||
|
||||
import os
|
||||
from django.contrib.auth.models import User
|
||||
from lettuce import world, step
|
||||
from lettuce.django import django_url
|
||||
@@ -8,33 +9,19 @@ from common import course_id
|
||||
from student.models import CourseEnrollment
|
||||
|
||||
|
||||
@step('I view the LTI and it is not rendered$')
|
||||
@step('I view the LTI and error is shown$')
|
||||
def lti_is_not_rendered(_step):
|
||||
# lti div has no class rendered
|
||||
assert world.is_css_not_present('div.lti.rendered')
|
||||
|
||||
# error is shown
|
||||
assert world.css_visible('.error_message')
|
||||
assert world.is_css_present('.error_message')
|
||||
|
||||
# iframe is not visible
|
||||
assert not world.css_visible('iframe')
|
||||
# iframe is not presented
|
||||
assert not world.is_css_present('iframe')
|
||||
|
||||
location = world.scenario_dict['LTI'].location.html_id()
|
||||
iframe_name = 'ltiLaunchFrame-' + location
|
||||
|
||||
#inside iframe test content is not presented
|
||||
with world.browser.get_iframe(iframe_name) as iframe:
|
||||
# iframe does not contain functions from terrain/ui_helpers.py
|
||||
world.browser.driver.implicitly_wait(1)
|
||||
try:
|
||||
assert iframe.is_element_not_present_by_css('.result', wait_time=1)
|
||||
except:
|
||||
raise
|
||||
finally:
|
||||
world.browser.driver.implicitly_wait(world.IMPLICIT_WAIT)
|
||||
# link is not presented
|
||||
assert not world.is_css_present('.link_lti_new_window')
|
||||
|
||||
|
||||
def check_lti_ifarme_content(text):
|
||||
def check_lti_iframe_content(text):
|
||||
#inside iframe test content is presented
|
||||
location = world.scenario_dict['LTI'].location.html_id()
|
||||
iframe_name = 'ltiLaunchFrame-' + location
|
||||
@@ -47,30 +34,33 @@ def check_lti_ifarme_content(text):
|
||||
))
|
||||
|
||||
|
||||
@step('I view the LTI and it is rendered$')
|
||||
def lti_is_rendered(_step):
|
||||
# lti div has class rendered
|
||||
assert world.is_css_present('div.lti.rendered')
|
||||
@step('I view the LTI and it is rendered in (.*)$')
|
||||
def lti_is_rendered(_step, rendered_in):
|
||||
if rendered_in.strip() == 'iframe':
|
||||
assert world.is_css_present('iframe')
|
||||
assert not world.is_css_present('.link_lti_new_window')
|
||||
assert not world.is_css_present('.error_message')
|
||||
|
||||
# error is hidden
|
||||
assert not world.css_visible('.error_message')
|
||||
# iframe is visible
|
||||
assert world.css_visible('iframe')
|
||||
check_lti_iframe_content("This is LTI tool. Success.")
|
||||
|
||||
# iframe is visible
|
||||
assert world.css_visible('iframe')
|
||||
check_lti_ifarme_content("This is LTI tool. Success.")
|
||||
elif rendered_in.strip() == 'new page':
|
||||
assert not world.is_css_present('iframe')
|
||||
assert world.is_css_present('.link_lti_new_window')
|
||||
assert not world.is_css_present('.error_message')
|
||||
check_lti_popup()
|
||||
else: # incorrent rendered_in parameter
|
||||
assert False
|
||||
|
||||
|
||||
@step('I view the LTI but incorrect_signature warning is rendered$')
|
||||
def incorrect_lti_is_rendered(_step):
|
||||
# lti div has class rendered
|
||||
assert world.is_css_present('div.lti.rendered')
|
||||
|
||||
# error is hidden
|
||||
assert not world.css_visible('.error_message')
|
||||
|
||||
# iframe is visible
|
||||
assert world.css_visible('iframe')
|
||||
check_lti_ifarme_content("Wrong LTI signature")
|
||||
assert world.is_css_present('iframe')
|
||||
assert not world.is_css_present('.link_lti_new_window')
|
||||
assert not world.is_css_present('.error_message')
|
||||
#inside iframe test content is presented
|
||||
check_lti_iframe_content("Wrong LTI signature")
|
||||
|
||||
|
||||
@step('the course has correct LTI credentials$')
|
||||
@@ -97,44 +87,33 @@ def set_incorrect_lti_passport(_step):
|
||||
i_am_registered_for_the_course(coursenum, metadata)
|
||||
|
||||
|
||||
@step('the course has an LTI component filled with correct fields$')
|
||||
def add_correct_lti_to_course(_step):
|
||||
@step('the course has an LTI component with (.*) fields, new_page is(.*)$')
|
||||
def add_correct_lti_to_course(_step, fields, new_page):
|
||||
category = 'lti'
|
||||
world.scenario_dict['LTI'] = world.ItemFactory.create(
|
||||
# parent_location=section_location(course),
|
||||
parent_location=world.scenario_dict['SEQUENTIAL'].location,
|
||||
category=category,
|
||||
display_name='LTI',
|
||||
metadata={
|
||||
'lti_id': 'correct_lti_id',
|
||||
'launch_url': world.lti_server.oauth_settings['lti_base'] + world.lti_server.oauth_settings['lti_endpoint']
|
||||
}
|
||||
)
|
||||
course = world.scenario_dict["COURSE"]
|
||||
chapter_name = world.scenario_dict['SECTION'].display_name.replace(
|
||||
" ", "_")
|
||||
section_name = chapter_name
|
||||
path = "/courses/{org}/{num}/{name}/courseware/{chapter}/{section}".format(
|
||||
org=course.org,
|
||||
num=course.number,
|
||||
name=course.display_name.replace(' ', '_'),
|
||||
chapter=chapter_name,
|
||||
section=section_name)
|
||||
url = django_url(path)
|
||||
lti_id = 'correct_lti_id'
|
||||
launch_url = world.lti_server.oauth_settings['lti_base'] + world.lti_server.oauth_settings['lti_endpoint']
|
||||
if fields.strip() == 'incorrect_lti_id': # incorrect fields
|
||||
lti_id = 'incorrect_lti_id'
|
||||
elif fields.strip() == 'correct': # correct fields
|
||||
pass
|
||||
elif fields.strip() == 'no_launch_url':
|
||||
launch_url = u''
|
||||
else: # incorrect parameter
|
||||
assert False
|
||||
|
||||
world.browser.visit(url)
|
||||
if new_page.strip().lower() == 'false':
|
||||
new_page = False
|
||||
else: # default is True
|
||||
new_page = True
|
||||
|
||||
|
||||
@step('the course has an LTI component with incorrect fields$')
|
||||
def add_incorrect_lti_to_course(_step):
|
||||
category = 'lti'
|
||||
world.scenario_dict['LTI'] = world.ItemFactory.create(
|
||||
parent_location=world.scenario_dict['SEQUENTIAL'].location,
|
||||
category=category,
|
||||
display_name='LTI',
|
||||
metadata={
|
||||
'lti_id': 'incorrect_lti_id',
|
||||
'lti_url': world.lti_server.oauth_settings['lti_base'] + world.lti_server.oauth_settings['lti_endpoint']
|
||||
'lti_id': lti_id,
|
||||
'launch_url': launch_url,
|
||||
'open_in_a_new_page': new_page
|
||||
}
|
||||
)
|
||||
course = world.scenario_dict["COURSE"]
|
||||
@@ -192,3 +171,28 @@ def i_am_registered_for_the_course(course, metadata):
|
||||
CourseEnrollment.enroll(usr, course_id(course))
|
||||
|
||||
world.log_in(username='robot', password='test')
|
||||
|
||||
|
||||
def check_lti_popup():
|
||||
parent_window = world.browser.current_window # Save the parent window
|
||||
world.css_find('.link_lti_new_window').first.click()
|
||||
|
||||
assert len(world.browser.windows) != 1
|
||||
|
||||
for window in world.browser.windows:
|
||||
world.browser.switch_to_window(window) # Switch to a different window (the pop-up)
|
||||
# Check if this is the one we want by comparing the url
|
||||
url = world.browser.url
|
||||
basename = os.path.basename(url)
|
||||
pathname = os.path.splitext(basename)[0]
|
||||
|
||||
if pathname == u'correct_lti_endpoint':
|
||||
break
|
||||
|
||||
result = world.css_find('.result').first.text
|
||||
assert result == u'This is LTI tool. Success.'
|
||||
|
||||
world.browser.driver.close() # Close the pop-up window
|
||||
world.browser.switch_to_window(parent_window) # Switch to the main window again
|
||||
|
||||
|
||||
|
||||
@@ -72,9 +72,11 @@ class TestLTI(BaseTestXmodule):
|
||||
generated_context = self.item_module.render('student_view').content
|
||||
expected_context = {
|
||||
'input_fields': self.correct_headers,
|
||||
'display_name': self.item_module.display_name,
|
||||
'element_class': self.item_module.location.category,
|
||||
'element_id': self.item_module.location.html_id(),
|
||||
'launch_url': 'http://www.example.com', # default value
|
||||
'open_in_a_new_page': True,
|
||||
}
|
||||
self.assertEqual(
|
||||
generated_context,
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
<div id="${element_id}" class="${element_class}">
|
||||
<%! import json %>
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
|
||||
## This form will be hidden. Once available on the client, the LTI
|
||||
## module JavaScript will trigget a "submit" on the form, and the
|
||||
<div
|
||||
id="${element_id}"
|
||||
class="${element_class}"
|
||||
data-open_in_a_new_page="${json.dumps(open_in_a_new_page)}"
|
||||
>
|
||||
|
||||
## This form will be hidden.
|
||||
## If open_in_a_new_page is false then, once available on the client, the
|
||||
## LTI module JavaScript will trigger a "submit" on the form, and the
|
||||
## result will be rendered to the below iFrame.
|
||||
## If open_in_a_new_page is true, then link will be shown, and by clicking
|
||||
## on it, LTI will pop up in new window.
|
||||
<form
|
||||
action="${launch_url}"
|
||||
name="ltiLaunchForm-${element_id}"
|
||||
class="ltiLaunchForm"
|
||||
method="post"
|
||||
target="ltiLaunchFrame-${element_id}"
|
||||
target=${"_blank" if open_in_a_new_page else "ltiLaunchFrame-{0}".format(element_id)}
|
||||
encType="application/x-www-form-urlencoded"
|
||||
>
|
||||
|
||||
@@ -19,16 +29,29 @@
|
||||
<input type="submit" value="Press to Launch" />
|
||||
</form>
|
||||
|
||||
|
||||
% if launch_url and launch_url != 'http://www.example.com':
|
||||
% if open_in_a_new_page:
|
||||
<div class="wrapper-lti-link">
|
||||
<h3 class="title">
|
||||
${display_name} (${_('External resource')})
|
||||
</h3>
|
||||
<p class="lti-link external"><a href="#" class='link_lti_new_window'>
|
||||
${_('View resource in a new window')}
|
||||
<i class="icon-external-link"></i>
|
||||
</a></p>
|
||||
</div>
|
||||
% else:
|
||||
## The result of the form submit will be rendered here.
|
||||
<iframe
|
||||
name="ltiLaunchFrame-${element_id}"
|
||||
class="ltiLaunchFrame"
|
||||
src=""
|
||||
></iframe>
|
||||
% endif
|
||||
% else:
|
||||
<h3 class="error_message">
|
||||
Please provide launch_url. Click "Edit", and fill in the
|
||||
required fields.
|
||||
${_('Please provide launch_url. Click "Edit", and fill in the required fields.')}
|
||||
</h3>
|
||||
|
||||
## The result of the form submit will be rendered here.
|
||||
<iframe
|
||||
name="ltiLaunchFrame-${element_id}"
|
||||
class="ltiLaunchFrame"
|
||||
src=""
|
||||
></iframe>
|
||||
|
||||
%endif
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user