Commit 3d09290c by Diana Huang Committed by Andy Armstrong

Add a dismiss button to welcome message.

Store user's preference indefinitely.
parent 72fb9722
define ["js/models/uploads", "js/views/uploads", "js/models/chapter", define ["sinon", "js/models/uploads", "js/views/uploads", "js/models/chapter",
"edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "js/spec_helpers/modal_helpers"], "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", "js/spec_helpers/modal_helpers"],
(FileUpload, UploadDialog, Chapter, AjaxHelpers, modal_helpers) -> (sinon, FileUpload, UploadDialog, Chapter, AjaxHelpers, modal_helpers) ->
describe "UploadDialog", -> describe "UploadDialog", ->
tpl = readFixtures("upload-dialog.underscore") tpl = readFixtures("upload-dialog.underscore")
......
define(['js/utils/drag_and_drop', 'common/js/components/views/feedback_notification', define(['sinon', 'js/utils/drag_and_drop', 'common/js/components/views/feedback_notification',
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'jquery', 'underscore'], 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'jquery', 'underscore'],
function(ContentDragger, Notification, AjaxHelpers, $, _) { function(sinon, ContentDragger, Notification, AjaxHelpers, $, _) {
'use strict';
describe('Overview drag and drop functionality', function() { describe('Overview drag and drop functionality', function() {
beforeEach(function() { beforeEach(function() {
setFixtures(readFixtures('mock/mock-outline.underscore')); setFixtures(readFixtures('mock/mock-outline.underscore'));
......
/* global define, sinon */ /* global define */
define([ define([
'jquery', 'jquery',
'sinon',
'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers',
'js/certificates/models/certificate_exception', 'js/certificates/models/certificate_exception',
'js/certificates/views/certificate_whitelist', 'js/certificates/views/certificate_whitelist',
'js/certificates/views/certificate_whitelist_editor', 'js/certificates/views/certificate_whitelist_editor',
'js/certificates/collections/certificate_whitelist' 'js/certificates/collections/certificate_whitelist'
], ],
function($, AjaxHelpers, CertificateExceptionModel, CertificateWhiteListView, CertificateWhiteListEditorView, function($, sinon, AjaxHelpers, CertificateExceptionModel, CertificateWhiteListView, CertificateWhiteListEditorView,
CertificateWhiteListCollection) { CertificateWhiteListCollection) {
'use strict'; 'use strict';
describe('edx.certificates.models.certificates_exception.CertificateExceptionModel', function() { describe('edx.certificates.models.certificates_exception.CertificateExceptionModel', function() {
var certificate_exception = null; var certificateException = null;
var assertValid = function(fields, isValid, expectedErrors) { var assertValid = function(fields, isValid, expectedErrors) {
certificate_exception.set(fields); var errors;
var errors = certificate_exception.validate(certificate_exception.attributes); certificateException.set(fields);
errors = certificateException.validate(certificateException.attributes);
if (isValid) { if (isValid) {
expect(errors).toBe(undefined); expect(errors).toBe(undefined);
...@@ -29,8 +32,8 @@ define([ ...@@ -29,8 +32,8 @@ define([
}; };
beforeEach(function() { beforeEach(function() {
certificate_exception = new CertificateExceptionModel({user_name: 'test_user'}, {url: 'test/url/'}); certificateException = new CertificateExceptionModel({user_name: 'test_user'}, {url: 'test/url/'});
certificate_exception.set({ certificateException.set({
notes: 'Test notes' notes: 'Test notes'
}); });
}); });
...@@ -50,9 +53,9 @@ define([ ...@@ -50,9 +53,9 @@ define([
}); });
describe('edx.certificates.collections.certificate_whitelist.CertificateWhiteList', function() { describe('edx.certificates.collections.certificate_whitelist.CertificateWhiteList', function() {
var certificate_white_list = null, var certificateWhiteList = null,
certificate_exception_url = 'test/url/'; certificateExceptionUrl = 'test/url/';
var certificates_exceptions_json = [ var certificatesExceptionsJson = [
{ {
id: 1, id: 1,
user_id: 1, user_id: 1,
...@@ -74,38 +77,48 @@ define([ ...@@ -74,38 +77,48 @@ define([
]; ];
beforeEach(function() { beforeEach(function() {
certificate_white_list = new CertificateWhiteListCollection(certificates_exceptions_json, { certificateWhiteList = new CertificateWhiteListCollection(certificatesExceptionsJson, {
parse: true, parse: true,
canBeEmpty: true, canBeEmpty: true,
url: certificate_exception_url, url: certificateExceptionUrl,
generate_certificates_url: certificate_exception_url generate_certificates_url: certificateExceptionUrl
}); });
}); });
it('has 2 models in the collection after initialization', function() { it('has 2 models in the collection after initialization', function() {
expect(certificate_white_list.models.length).toEqual(2); expect(certificateWhiteList.models.length).toEqual(2);
}); });
it("returns correct model on getModel call and 'undefined' if queried model is not present", function() { it("returns correct model on getModel call and 'undefined' if queried model is not present", function() {
expect(certificate_white_list.getModel({user_name: 'test1'})).not.toBe(undefined); expect(certificateWhiteList.getModel({user_name: 'test1'})).not.toBe(undefined);
expect(certificate_white_list.getModel({user_name: 'test_invalid_user'})).toBe(undefined); expect(certificateWhiteList.getModel({user_name: 'test_invalid_user'})).toBe(undefined);
expect(certificate_white_list.getModel({user_email: 'test1@test.com'})).not.toBe(undefined); expect(certificateWhiteList.getModel({user_email: 'test1@test.com'})).not.toBe(undefined);
expect(certificate_white_list.getModel({user_email: 'test_invalid_user@test.com'})).toBe(undefined); expect(certificateWhiteList.getModel({user_email: 'test_invalid_user@test.com'})).toBe(undefined);
expect(certificate_white_list.getModel({user_name: 'test1'}).attributes).toEqual( expect(certificateWhiteList.getModel({user_name: 'test1'}).attributes).toEqual(
{ {
id: 1, user_id: 1, user_name: 'test1', user_email: 'test1@test.com', id: 1,
course_id: 'edX/test/course', created: 'Thursday, October 29, 2015', user_id: 1,
notes: 'test notes for test certificate exception', certificate_generated: '' user_name: 'test1',
user_email: 'test1@test.com',
course_id: 'edX/test/course',
created: 'Thursday, October 29, 2015',
notes: 'test notes for test certificate exception',
certificate_generated: ''
} }
); );
expect(certificate_white_list.getModel({user_email: 'test2@test.com'}).attributes).toEqual( expect(certificateWhiteList.getModel({user_email: 'test2@test.com'}).attributes).toEqual(
{ {
id: 2, user_id: 2, user_name: 'test2', user_email: 'test2@test.com', id: 2,
course_id: 'edX/test/course', created: 'Thursday, October 29, 2015', user_id: 2,
notes: 'test notes for test certificate exception', certificate_generated: '' user_name: 'test2',
user_email: 'test2@test.com',
course_id: 'edX/test/course',
created: 'Thursday, October 29, 2015',
notes: 'test notes for test certificate exception',
certificate_generated: ''
} }
); );
}); });
...@@ -114,13 +127,13 @@ define([ ...@@ -114,13 +127,13 @@ define([
var successCallback = sinon.spy(), var successCallback = sinon.spy(),
errorCallback = sinon.spy(), errorCallback = sinon.spy(),
requests = AjaxHelpers.requests(this), requests = AjaxHelpers.requests(this),
add_students = 'all'; addStudents = 'all';
var expected = { var expected = {
url: certificate_exception_url + add_students, url: certificateExceptionUrl + addStudents,
postData: [] postData: []
}; };
certificate_white_list.sync({success: successCallback, error: errorCallback}, add_students); certificateWhiteList.sync({success: successCallback, error: errorCallback}, addStudents);
AjaxHelpers.expectJsonRequest(requests, 'POST', expected.url, expected.postData); AjaxHelpers.expectJsonRequest(requests, 'POST', expected.url, expected.postData);
}); });
...@@ -128,13 +141,14 @@ define([ ...@@ -128,13 +141,14 @@ define([
var successCallback = sinon.spy(), var successCallback = sinon.spy(),
errorCallback = sinon.spy(), errorCallback = sinon.spy(),
requests = AjaxHelpers.requests(this), requests = AjaxHelpers.requests(this),
add_students = 'new'; addStudents = 'new',
expected;
certificate_white_list.add({user_name: 'test3', notes: 'test3 notes', new: true}); certificateWhiteList.add({user_name: 'test3', notes: 'test3 notes', new: true});
certificate_white_list.sync({success: successCallback, error: errorCallback}, add_students); certificateWhiteList.sync({success: successCallback, error: errorCallback}, addStudents);
var expected = { expected = {
url: certificate_exception_url + add_students, url: certificateExceptionUrl + addStudents,
postData: [ postData: [
{user_id: '', {user_id: '',
user_name: 'test3', user_name: 'test3',
...@@ -151,9 +165,9 @@ define([ ...@@ -151,9 +165,9 @@ define([
describe('edx.certificates.views.certificate_whitelist.CertificateWhiteListView', function() { describe('edx.certificates.views.certificate_whitelist.CertificateWhiteListView', function() {
var view = null, var view = null,
certificate_exception_url = 'test/url/'; certificateExceptionUrl = 'test/url/';
var certificates_exceptions_json = [ var certificatesExceptionsJson = [
{ {
id: 1, id: 1,
user_id: 1, user_id: 1,
...@@ -175,17 +189,17 @@ define([ ...@@ -175,17 +189,17 @@ define([
]; ];
beforeEach(function() { beforeEach(function() {
var fixture;
setFixtures(); setFixtures();
var fixture = fixture = readFixtures('templates/instructor/instructor_dashboard_2/certificate-white-list.underscore');
readFixtures('templates/instructor/instructor_dashboard_2/certificate-white-list.underscore');
setFixtures("<script type='text/template' id='certificate-white-list-tpl'>" + fixture + '</script>' + setFixtures("<script type='text/template' id='certificate-white-list-tpl'>" + fixture + '</script>' +
"<div class='white-listed-students' id='white-listed-students'></div>"); "<div class='white-listed-students' id='white-listed-students'></div>");
this.certificate_white_list = new CertificateWhiteListCollection(certificates_exceptions_json, { this.certificate_white_list = new CertificateWhiteListCollection(certificatesExceptionsJson, {
parse: true, parse: true,
canBeEmpty: true, canBeEmpty: true,
url: certificate_exception_url, url: certificateExceptionUrl,
generate_certificates_url: certificate_exception_url generate_certificates_url: certificateExceptionUrl
}); });
...@@ -231,10 +245,10 @@ define([ ...@@ -231,10 +245,10 @@ define([
{user_name: user, notes: notes, user_email: email} {user_name: user, notes: notes, user_email: email}
]); ]);
expect(view.$el.find('table tbody tr td:contains("' + user + '")').parent().html()). expect(view.$el.find('table tbody tr td:contains("' + user + '")').parent().html())
toMatch(notes); .toMatch(notes);
expect(view.$el.find('table tbody tr td:contains("' + user + '")').parent().html()). expect(view.$el.find('table tbody tr td:contains("' + user + '")').parent().html())
toMatch(email); .toMatch(email);
}); });
it('verifies collection sync is called when "Generate Exception Certificates" is clicked', function() { it('verifies collection sync is called when "Generate Exception Certificates" is clicked', function() {
...@@ -248,8 +262,8 @@ define([ ...@@ -248,8 +262,8 @@ define([
view.$el.find('#generate-exception-certificates').click(); view.$el.find('#generate-exception-certificates').click();
expect(view.collection.sync.called).toBe(true); expect(view.collection.sync.called).toBe(true);
expect(view.collection.sync.calledWith({success: successCallback, error: errorCallback})). expect(view.collection.sync.calledWith({success: successCallback, error: errorCallback}))
toBe(true); .toBe(true);
}); });
it('verifies sync is called with "new/all" argument depending upon selected radio button', function() { it('verifies sync is called with "new/all" argument depending upon selected radio button', function() {
...@@ -263,24 +277,24 @@ define([ ...@@ -263,24 +277,24 @@ define([
view.$el.find('#generate-exception-certificates').click(); view.$el.find('#generate-exception-certificates').click();
// By default 'Generate a Certificate for all New additions to the Exception list ' is selected // By default 'Generate a Certificate for all New additions to the Exception list ' is selected
expect(view.collection.sync.calledWith({success: successCallback, error: errorCallback}), 'new'). expect(view.collection.sync.calledWith({success: successCallback, error: errorCallback}), 'new')
toBe(true); .toBe(true);
// Select 'Generate a Certificate for all users on the Exception list ' option // Select 'Generate a Certificate for all users on the Exception list ' option
view.$el.find('input:radio[name=generate-exception-certificates-radio][value=all]').click(); view.$el.find('input:radio[name=generate-exception-certificates-radio][value=all]').click();
view.$el.find('#generate-exception-certificates').click(); view.$el.find('#generate-exception-certificates').click();
expect(view.collection.sync.calledWith({success: successCallback, error: errorCallback}), 'all'). expect(view.collection.sync.calledWith({success: successCallback, error: errorCallback}), 'all')
toBe(true); .toBe(true);
}); });
}); });
describe('edx.certificates.views.certificate_whitelist_editor.CertificateWhiteListEditorView', function() { describe('edx.certificates.views.certificate_whitelist_editor.CertificateWhiteListEditorView', function() {
var view = null, var view = null,
list_view = null, listView = null,
certificate_exception_url = 'test/url/'; certificateExceptionUrl = 'test/url/';
var certificates_exceptions_json = [ var certificatesExceptionsJson = [
{ {
url: certificate_exception_url, url: certificateExceptionUrl,
id: 1, id: 1,
user_id: 1, user_id: 1,
user_name: 'test1', user_name: 'test1',
...@@ -291,7 +305,7 @@ define([ ...@@ -291,7 +305,7 @@ define([
new: true new: true
}, },
{ {
url: certificate_exception_url, url: certificateExceptionUrl,
id: 2, id: 2,
user_id: 2, user_id: 2,
user_name: 'test2', user_name: 'test2',
...@@ -303,41 +317,42 @@ define([ ...@@ -303,41 +317,42 @@ define([
]; ];
beforeEach(function() { beforeEach(function() {
var fixture, fixture2, certificateWhiteList;
setFixtures(); setFixtures();
var fixture = readFixtures( fixture = readFixtures(
'templates/instructor/instructor_dashboard_2/certificate-white-list-editor.underscore' 'templates/instructor/instructor_dashboard_2/certificate-white-list-editor.underscore'
); );
var fixture_2 = readFixtures( fixture2 = readFixtures(
'templates/instructor/instructor_dashboard_2/certificate-white-list.underscore' 'templates/instructor/instructor_dashboard_2/certificate-white-list.underscore'
); );
setFixtures( setFixtures(
"<script type='text/template' id='certificate-white-list-editor-tpl'>" + fixture + '</script>' + "<script type='text/template' id='certificate-white-list-editor-tpl'>" + fixture + '</script>' +
"<script type='text/template' id='certificate-white-list-tpl'>" + fixture_2 + '</script>' + "<script type='text/template' id='certificate-white-list-tpl'>" + fixture2 + '</script>' +
"<div id='certificate-white-list-editor'></div>" + "<div id='certificate-white-list-editor'></div>" +
"<div class='white-listed-students' id='white-listed-students'></div>" "<div class='white-listed-students' id='white-listed-students'></div>"
); );
var certificate_white_list = new CertificateWhiteListCollection(certificates_exceptions_json, { certificateWhiteList = new CertificateWhiteListCollection(certificatesExceptionsJson, {
parse: true, parse: true,
canBeEmpty: true, canBeEmpty: true,
url: certificate_exception_url, url: certificateExceptionUrl,
generate_certificates_url: certificate_exception_url generate_certificates_url: certificateExceptionUrl
}); });
view = new CertificateWhiteListEditorView({ view = new CertificateWhiteListEditorView({
collection: certificate_white_list, collection: certificateWhiteList,
url: certificate_exception_url url: certificateExceptionUrl
}); });
view.render(); view.render();
list_view = new CertificateWhiteListView({ listView = new CertificateWhiteListView({
collection: certificate_white_list, collection: certificateWhiteList,
certificateWhiteListEditorView: view certificateWhiteListEditorView: view
}); });
list_view.render(); listView.render();
}); });
it('verifies view is initialized and rendered successfully', function() { it('verifies view is initialized and rendered successfully', function() {
...@@ -348,16 +363,16 @@ define([ ...@@ -348,16 +363,16 @@ define([
}); });
it('verifies success and error messages', function() { it('verifies success and error messages', function() {
var message_selector = '.message', var messageSelector = '.message',
success_message = 'test_user has been successfully added to the exception list. Click Generate' + successMessage = 'test_user has been successfully added to the exception list. Click Generate' +
' Exception Certificate below to send the certificate.', ' Exception Certificate below to send the certificate.',
requests = AjaxHelpers.requests(this), requests = AjaxHelpers.requests(this),
duplicate_user = 'test_user'; duplicateUser = 'test_user';
var error_messages = { var errorMessages = {
empty_user_name_email: 'Student username/email field is required and can not be empty. ' + empty_user_name_email: 'Student username/email field is required and can not be empty. ' +
'Kindly fill in username/email and then press "Add to Exception List" button.', 'Kindly fill in username/email and then press "Add to Exception List" button.',
duplicate_user: '<p>' + (duplicate_user) + ' already in exception list.</p>' duplicate_user: '<p>' + (duplicateUser) + ' already in exception list.</p>'
}; };
// click 'Add Exception' button with empty username/email field // click 'Add Exception' button with empty username/email field
...@@ -365,10 +380,10 @@ define([ ...@@ -365,10 +380,10 @@ define([
view.$el.find('#add-exception').click(); view.$el.find('#add-exception').click();
// Verify error message for missing username/email // Verify error message for missing username/email
expect(view.$el.find(message_selector).html()).toMatch(error_messages.empty_user_name_email); expect(view.$el.find(messageSelector).html()).toMatch(errorMessages.empty_user_name_email);
// Add a new Exception to list // Add a new Exception to list
view.$el.find('#certificate-exception').val(duplicate_user); view.$el.find('#certificate-exception').val(duplicateUser);
view.$el.find('#notes').val('test user notes'); view.$el.find('#notes').val('test user notes');
view.$el.find('#add-exception').click(); view.$el.find('#add-exception').click();
...@@ -377,7 +392,7 @@ define([ ...@@ -377,7 +392,7 @@ define([
{ {
id: 3, id: 3,
user_id: 3, user_id: 3,
user_name: duplicate_user, user_name: duplicateUser,
user_email: 'test2@test.com', user_email: 'test2@test.com',
course_id: 'edX/test/course', course_id: 'edX/test/course',
created: 'Thursday, October 29, 2015', created: 'Thursday, October 29, 2015',
...@@ -386,29 +401,29 @@ define([ ...@@ -386,29 +401,29 @@ define([
); );
// Verify success message // Verify success message
expect(view.$el.find(message_selector).html()).toMatch(success_message); expect(view.$el.find(messageSelector).html()).toMatch(successMessage);
// Add a duplicate Certificate Exception // Add a duplicate Certificate Exception
view.$el.find('#certificate-exception').val(duplicate_user); view.$el.find('#certificate-exception').val(duplicateUser);
view.$el.find('#notes').val('test user notes'); view.$el.find('#notes').val('test user notes');
view.$el.find('#add-exception').click(); view.$el.find('#add-exception').click();
// Verify success message // Verify success message
expect(view.$el.find(message_selector).html()).toEqual(error_messages.duplicate_user); expect(view.$el.find(messageSelector).html()).toEqual(errorMessages.duplicate_user);
}); });
it('verifies certificate exception can be deleted by clicking "delete" ', function() { it('verifies certificate exception can be deleted by clicking "delete" ', function() {
var user_name = 'test1', var username = 'test1',
certificate_exception_selector = "div.white-listed-students table tr:contains('" + user_name + "')", certificateExceptionSelector = "div.white-listed-students table tr:contains('" + username + "')",
delete_btn_selector = deleteBtnSelector =
certificate_exception_selector + ' td .delete-exception', certificateExceptionSelector + ' td .delete-exception',
requests = AjaxHelpers.requests(this); requests = AjaxHelpers.requests(this);
$(delete_btn_selector).click(); $(deleteBtnSelector).click();
AjaxHelpers.respondWithJson(requests, {}); AjaxHelpers.respondWithJson(requests, {});
// Verify the certificate exception is removed from the list // Verify the certificate exception is removed from the list
expect($(certificate_exception_selector).length).toBe(0); expect($(certificateExceptionSelector).length).toBe(0);
}); });
}); });
} }
......
...@@ -10,6 +10,10 @@ ...@@ -10,6 +10,10 @@
font-weight: $font-bold; font-weight: $font-bold;
color: $black; color: $black;
} }
.dismiss-message {
@include float(right);
}
} }
// Course sidebar // Course sidebar
......
(function(define) { (function(define) {
'use strict'; 'use strict';
define(['gettext', 'jquery', 'underscore', 'backbone', 'js/views/message_banner'], define(['gettext', 'jquery', 'underscore', 'backbone', 'js/views/message_banner'],
function(gettext, $, _, Backbone, MessageBannerView) { function(gettext, $, _, Backbone, MessageBannerView) {
return Backbone.View.extend({ return Backbone.View.extend({
......
(function(define) { (function(define) {
'use strict'; 'use strict';
define([ define([
'gettext', 'jquery', 'underscore', 'backbone', 'logger', 'moment', 'edx-ui-toolkit/js/utils/html-utils', 'gettext', 'jquery', 'underscore', 'backbone', 'logger', 'moment', 'edx-ui-toolkit/js/utils/html-utils',
'common/js/components/views/paging_header', 'common/js/components/views/paging_footer', 'common/js/components/views/paging_header', 'common/js/components/views/paging_footer',
......
<div class="welcome-message">
<div class="dismiss-message">
<button type="button" class="btn-link">${_("Dismiss")}</button>
</div>
This is a useful welcome message!
</div>
/* globals $ */
import 'jquery.cookie';
export class WelcomeMessage { // eslint-disable-line import/prefer-default-export
constructor(dismissUrl) {
$('.dismiss-message button').click(() => {
$.ajax({
type: 'POST',
url: dismissUrl,
headers: {
'X-CSRFToken': $.cookie('csrftoken'),
},
success: () => {
$('.welcome-message').hide();
},
});
});
}
}
/* globals $, loadFixtures */
import {
expectRequest,
requests as mockRequests,
respondWithJson,
} from 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers';
import { WelcomeMessage } from '../WelcomeMessage';
describe('Welcome Message factory', () => {
describe('Ensure button click', () => {
const endpointUrl = '/course/course_id/dismiss_message/';
beforeEach(() => {
loadFixtures('course_experience/fixtures/welcome-message-fragment.html');
new WelcomeMessage(endpointUrl); // eslint-disable-line no-new
});
it('When button click is made, ajax call is made and message is hidden.', () => {
const $message = $('.welcome-message');
const requests = mockRequests(this);
document.querySelector('.dismiss-message button').dispatchEvent(new Event('click'));
expectRequest(
requests,
'POST',
endpointUrl,
);
respondWithJson(requests);
expect($message.attr('style')).toBe('display: none;');
requests.restore();
});
});
});
...@@ -4,11 +4,21 @@ ...@@ -4,11 +4,21 @@
<%namespace name='static' file='../static_content.html'/> <%namespace name='static' file='../static_content.html'/>
<%! <%!
from openedx.core.djangolib.js_utils import js_escaped_string
from django.utils.translation import ugettext as _
from openedx.core.djangolib.markup import HTML from openedx.core.djangolib.markup import HTML
%> %>
<%block name="content"> <%block name="content">
<div class="welcome-message"> <div class="welcome-message">
<div class="dismiss-message">
<button type="button" class="btn-link">${_("Dismiss")}</button>
</div>
${HTML(welcome_message_html)} ${HTML(welcome_message_html)}
</div> </div>
</%block> </%block>
<%static:webpack entry="WelcomeMessage">
new WelcomeMessage("${dismiss_url | n, js_escaped_string}");
</%static:webpack>
...@@ -89,7 +89,7 @@ class TestCourseHomePage(SharedModuleStoreTestCase): ...@@ -89,7 +89,7 @@ class TestCourseHomePage(SharedModuleStoreTestCase):
course_home_url(self.course) course_home_url(self.course)
# Fetch the view and verify the query counts # Fetch the view and verify the query counts
with self.assertNumQueries(48): with self.assertNumQueries(49):
with check_mongo_calls(5): with check_mongo_calls(5):
url = course_home_url(self.course) url = course_home_url(self.course)
self.client.get(url) self.client.get(url)
...@@ -27,6 +27,18 @@ def welcome_message_url(course): ...@@ -27,6 +27,18 @@ def welcome_message_url(course):
) )
def dismiss_message_url(course):
"""
Returns the URL for the dismiss message endpoint.
"""
return reverse(
'openedx.course_experience.dismiss_welcome_message',
kwargs={
'course_id': unicode(course.id),
}
)
class TestWelcomeMessageView(ModuleStoreTestCase): class TestWelcomeMessageView(ModuleStoreTestCase):
""" """
Tests for the course welcome message fragment view. Tests for the course welcome message fragment view.
...@@ -41,10 +53,8 @@ class TestWelcomeMessageView(ModuleStoreTestCase): ...@@ -41,10 +53,8 @@ class TestWelcomeMessageView(ModuleStoreTestCase):
chapter = ItemFactory.create(category='chapter', parent_location=self.course.location) chapter = ItemFactory.create(category='chapter', parent_location=self.course.location)
section = ItemFactory.create(category='sequential', parent_location=chapter.location) section = ItemFactory.create(category='sequential', parent_location=chapter.location)
ItemFactory.create(category='vertical', parent_location=section.location) ItemFactory.create(category='vertical', parent_location=section.location)
self.user = UserFactory(password=TEST_PASSWORD) self.user = UserFactory(password=TEST_PASSWORD)
CourseEnrollment.enroll(self.user, self.course.id) CourseEnrollment.enroll(self.user, self.course.id)
self.client.login(username=self.user.username, password=TEST_PASSWORD) self.client.login(username=self.user.username, password=TEST_PASSWORD)
def tearDown(self): def tearDown(self):
...@@ -58,6 +68,7 @@ class TestWelcomeMessageView(ModuleStoreTestCase): ...@@ -58,6 +68,7 @@ class TestWelcomeMessageView(ModuleStoreTestCase):
response = self.client.get(welcome_message_url(self.course)) response = self.client.get(welcome_message_url(self.course))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Second Update') self.assertContains(response, 'Second Update')
self.assertContains(response, 'Dismiss')
def test_replace_urls(self): def test_replace_urls(self):
img_url = 'img.png' img_url = 'img.png'
...@@ -72,3 +83,15 @@ class TestWelcomeMessageView(ModuleStoreTestCase): ...@@ -72,3 +83,15 @@ class TestWelcomeMessageView(ModuleStoreTestCase):
def test_empty_welcome_message(self): def test_empty_welcome_message(self):
response = self.client.get(welcome_message_url(self.course)) response = self.client.get(welcome_message_url(self.course))
self.assertEqual(response.status_code, 204) self.assertEqual(response.status_code, 204)
def test_dismiss_message(self):
create_course_update(self.course, self.user, 'First Update', date='January 1, 2017')
response = self.client.get(welcome_message_url(self.course))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'First Update')
self.client.post(dismiss_message_url(self.course))
response = self.client.get(welcome_message_url(self.course))
self.assertNotIn('First Update', response)
self.assertEqual(response.status_code, 204)
...@@ -7,8 +7,8 @@ from django.conf.urls import url ...@@ -7,8 +7,8 @@ from django.conf.urls import url
from views.course_home import CourseHomeFragmentView, CourseHomeView from views.course_home import CourseHomeFragmentView, CourseHomeView
from views.course_outline import CourseOutlineFragmentView from views.course_outline import CourseOutlineFragmentView
from views.course_updates import CourseUpdatesFragmentView, CourseUpdatesView from views.course_updates import CourseUpdatesFragmentView, CourseUpdatesView
from views.welcome_message import WelcomeMessageFragmentView
from views.course_sock import CourseSockFragmentView from views.course_sock import CourseSockFragmentView
from views.welcome_message import WelcomeMessageFragmentView, dismiss_welcome_message
urlpatterns = [ urlpatterns = [
url( url(
...@@ -46,4 +46,9 @@ urlpatterns = [ ...@@ -46,4 +46,9 @@ urlpatterns = [
CourseSockFragmentView.as_view(), CourseSockFragmentView.as_view(),
name='openedx.course_experience.course_sock_fragment_view', name='openedx.course_experience.course_sock_fragment_view',
), ),
url(
r'^dismiss_welcome_message$',
dismiss_welcome_message,
name='openedx.course_experience.dismiss_welcome_message',
),
] ]
...@@ -2,13 +2,19 @@ ...@@ -2,13 +2,19 @@
View logic for handling course welcome messages. View logic for handling course welcome messages.
""" """
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseBadRequest
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.views.decorators.csrf import ensure_csrf_cookie
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from web_fragments.fragment import Fragment from web_fragments.fragment import Fragment
from course_updates import CourseUpdatesFragmentView from course_updates import CourseUpdatesFragmentView
from courseware.courses import get_course_info_section_module, get_course_with_access from courseware.courses import get_course_info_section_module, get_course_with_access
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from openedx.core.djangoapps.user_api.course_tag.api import set_course_tag, get_course_tag
PREFERENCE_KEY = 'view-welcome-message'
class WelcomeMessageFragmentView(EdxFragmentView): class WelcomeMessageFragmentView(EdxFragmentView):
...@@ -27,10 +33,18 @@ class WelcomeMessageFragmentView(EdxFragmentView): ...@@ -27,10 +33,18 @@ class WelcomeMessageFragmentView(EdxFragmentView):
if not welcome_message_html: if not welcome_message_html:
return None return None
dismiss_url = reverse(
'openedx.course_experience.dismiss_welcome_message', kwargs={'course_id': unicode(course_key)}
)
context = { context = {
'dismiss_url': dismiss_url,
'welcome_message_html': welcome_message_html, 'welcome_message_html': welcome_message_html,
} }
if get_course_tag(request.user, course_key, PREFERENCE_KEY) == 'False':
return None
else:
html = render_to_string('course_experience/welcome-message-fragment.html', context) html = render_to_string('course_experience/welcome-message-fragment.html', context)
return Fragment(html) return Fragment(html)
...@@ -51,3 +65,13 @@ class WelcomeMessageFragmentView(EdxFragmentView): ...@@ -51,3 +65,13 @@ class WelcomeMessageFragmentView(EdxFragmentView):
content = info_block.system.replace_urls(ordered_updates[0]['content']) content = info_block.system.replace_urls(ordered_updates[0]['content'])
return content return content
@ensure_csrf_cookie
def dismiss_welcome_message(request, course_id):
"""
Given the course_id in the request, disable displaying the welcome message for the user.
"""
course_key = CourseKey.from_string(course_id)
set_course_tag(request.user, course_key, PREFERENCE_KEY, 'False')
return HttpResponse()
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
"eslint-config-edx": "^2.0.1", "eslint-config-edx": "^2.0.1",
"eslint-config-edx-es5": "^2.0.0", "eslint-config-edx-es5": "^2.0.0",
"eslint-import-resolver-webpack": "^0.8.1", "eslint-import-resolver-webpack": "^0.8.1",
"jasmine-core": "^2.4.1", "jasmine-core": "^2.6.4",
"jasmine-jquery": "^2.1.1", "jasmine-jquery": "^2.1.1",
"karma": "^0.13.22", "karma": "^0.13.22",
"karma-chrome-launcher": "^0.2.3", "karma-chrome-launcher": "^0.2.3",
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
"pa11y": "4.0.1", "pa11y": "4.0.1",
"pa11y-reporter-json-oldnode": "1.0.0", "pa11y-reporter-json-oldnode": "1.0.0",
"plato": "1.2.2", "plato": "1.2.2",
"sinon": "^1.17.7", "sinon": "2.3.5",
"squirejs": "^0.1.0" "squirejs": "^0.1.0"
} }
} }
...@@ -20,6 +20,7 @@ var wpconfig = { ...@@ -20,6 +20,7 @@ var wpconfig = {
entry: { entry: {
CourseOutline: './openedx/features/course_experience/static/course_experience/js/CourseOutline.js', CourseOutline: './openedx/features/course_experience/static/course_experience/js/CourseOutline.js',
CourseSock: './openedx/features/course_experience/static/course_experience/js/CourseSock.js', CourseSock: './openedx/features/course_experience/static/course_experience/js/CourseSock.js',
WelcomeMessage: './openedx/features/course_experience/static/course_experience/js/WelcomeMessage.js',
Import: './cms/static/js/features/import/factories/import.js' Import: './cms/static/js/features/import/factories/import.js'
}, },
......
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