Skip to content

Commit

Permalink
Merge pull request #923 from edx/mohtamba/add-edit-button-to-allowances
Browse files Browse the repository at this point in the history
Added edit button in grouped allowance
  • Loading branch information
mohtamba authored Aug 2, 2021
2 parents 550ae1a + b01160d commit 6c0da53
Show file tree
Hide file tree
Showing 12 changed files with 264 additions and 14 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ Unreleased
~~~~~~~~~~
* Add simple history to proctored exam attempt, writing both old and new model for now. Includes admin view.

[3.22.1] - 2021-08-02
* Add edit button to grouped allowances, which allows instructors to edit the value of a single allowance.

[3.22.0] - 2021-07-26
~~~~~~~~~~~~~~~~~~~~~
* If verified name functionality is enabled through the "name_affirmation" runtime service,
Expand Down
2 changes: 1 addition & 1 deletion edx_proctoring/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"""

# Be sure to update the version number in edx_proctoring/package.json
__version__ = '3.22.0'
__version__ = '3.22.1'

default_app_config = 'edx_proctoring.apps.EdxProctoringConfig' # pylint: disable=invalid-name
1 change: 1 addition & 0 deletions edx_proctoring/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def plugin_settings(settings):
'proctoring/js/views/proctored_exam_add_bulk_allowance_view.js',
'proctoring/js/views/proctored_exam_allowance_view.js',
'proctoring/js/views/proctored_exam_attempt_view.js',
'proctoring/js/views/proctored_exam_edit_allowance_view.js',
'proctoring/js/views/proctored_exam_onboarding_view.js',
'proctoring/js/views/proctored_exam_view.js',
'proctoring/js/views/proctored_exam_info.js',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,9 @@ edx = edx || {};
},
updateAllowanceLabels: function(selectedAllowanceType) {
if (selectedAllowanceType === 'additional_time_granted') {
$('#allowance_value_label').text(gettext('Input Additional Minutes as a Positive Number'));
$('#allowance_value_label').text(gettext('Add Time(Minutes)'));
} else if (selectedAllowanceType === 'time_multiplier') {
$('#allowance_value_label').text(gettext('Input Multiplier as a Number Greater Than 1'));
$('#allowance_value_label').text(gettext('Add Multiplier as a Number Greater Than 1'));
} else {
$('#allowance_value_label').text(gettext('Add Policy Exception'));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ edx = edx || {};
events: {
'click #add-allowance': 'showAddModal',
'click .remove_allowance': 'removeAllowance',
'click .accordion-trigger': 'toggleAllowanceAccordion'
'click .accordion-trigger': 'toggleAllowanceAccordion',
'click .edit_allowance': 'editAllowance'
},
getCSRFToken: function() {
var cookieValue = null;
Expand Down Expand Up @@ -185,6 +186,29 @@ edx = edx || {};
event.stopPropagation();
event.preventDefault();
},
editAllowance: function(event) {
var $element = $(event.currentTarget);
var userName = $element.data('user-name');
var examID = $element.data('exam-id');
var examName = $element.data('exam-name');
var key = $element.data('key-name');
var keyName = $element.data('key-value');
var self = this;
self.proctoredExamCollection.fetch({
success: function() {
// eslint-disable-next-line no-new
new edx.instructor_dashboard.proctoring.EditAllowanceView({
course_id: self.course_id,
selected_exam_ID: examID,
selected_exam_name: examName,
proctored_exam_allowance_view: self,
selected_user: userName,
allowance_type: key,
allowance_type_name: keyName
});
}
});
},
toggleAllowanceAccordion: function(event) {
// based on code from openedx/features/course_experience/static/course_experience/js/CourseOutline.js
// but modified to better fit this feature's needs
Expand All @@ -193,16 +217,16 @@ edx = edx || {};
if (accordionRow.classList.contains('accordion-trigger')) {
isExpanded = accordionRow.getAttribute('aria-expanded') === 'true';
if (!isExpanded) {
$toggleChevron = $(accordionRow).find('.fa-chevron-right');
$toggleChevron = $(accordionRow).find('.fa-chevron-down');
$contentPanel = $('#' + accordionRow.innerText.trim());
$contentPanel.show();
$toggleChevron.addClass('fa-rotate-90');
$toggleChevron.addClass('fa-rotate-180');
accordionRow.setAttribute('aria-expanded', 'true');
} else {
$toggleChevron = $(accordionRow).find('.fa-chevron-right');
$toggleChevron = $(accordionRow).find('.fa-chevron-down');
$contentPanel = $('#' + accordionRow.innerText.trim());
$contentPanel.hide();
$toggleChevron.removeClass('fa-rotate-90');
$toggleChevron.removeClass('fa-rotate-180');
accordionRow.setAttribute('aria-expanded', 'false');
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
edx = edx || {};

(function(Backbone, $, _, gettext) {
'use strict';

edx.instructor_dashboard = edx.instructor_dashboard || {};
edx.instructor_dashboard.proctoring = edx.instructor_dashboard.proctoring || {};

edx.instructor_dashboard.proctoring.EditAllowanceView = Backbone.ModalView.extend({
name: 'EditAllowanceView',
template: null,
template_url: '/static/proctoring/templates/edit-allowance.underscore',
initialize: function(options) {
this.selected_exam_ID = options.selected_exam_ID;
this.selected_exam_name = options.selected_exam_name;
this.proctored_exam_allowance_view = options.proctored_exam_allowance_view;
this.course_id = options.course_id;
this.selected_user = options.selected_user;
this.allowance_type = options.allowance_type;
this.allowance_type_name = options.allowance_type_name;
this.model = new edx.instructor_dashboard.proctoring.ProctoredExamBulkAllowanceModel();
_.bindAll(this, 'render');
this.loadTemplateData();
// Backbone.Validation.bind( this, {valid:this.hideError, invalid:this.showError});
},
events: {
'submit form': 'editAllowance'
},
loadTemplateData: function() {
var self = this;
$.ajax({url: self.template_url, dataType: 'html'})
.done(function(templateData) {
self.template = _.template(templateData);
self.render();
self.showModal();
self.updateCss();
});
},
updateCss: function() {
var $el = $(this.el);
$el.find('.modal-header').css({
color: '#1580b0',
'font-size': '20px',
'font-weight': '600',
'line-height': 'normal',
padding: '10px 15px',
'border-bottom': '1px solid #ccc'
});
$el.find('form').css({
padding: '15px'
});
$el.find('form table.compact td').css({
'vertical-align': 'middle',
padding: '4px 8px'
});
$el.find('form label').css({
display: 'block',
'font-size': '14px',
margin: 0,
cursor: 'default'
});
$el.find('form input[type="text"]').css({
height: '26px',
padding: '1px 8px 2px',
'font-size': '14px',
width: '100%'
});
$el.find('form input[type="submit"]').css({
'margin-top': '10px',
float: 'right'
});
$el.find('.error-message').css({
color: '#ff0000',
'line-height': 'normal',
'font-size': '14px'
});
$el.find('.error-response').css({
color: '#ff0000',
'line-height': 'normal',
'font-size': '14px',
padding: '0px 10px 5px 7px'
});
},
getAllowanceValue: function() {
return $('#allowance_value').val();
},
hideError: function(view, attr) {
var $element = view.$form[attr];

$element.removeClass('error');
$element.parent().find('.error-message').empty();
},
showError: function(view, attr, errorMessage) {
var $element = view.$form[attr];
var $errorMessage;

$element.addClass('error');
$errorMessage = $element.parent().find('.error-message');
if ($errorMessage.length === 0) {
$errorMessage = $("<div class='error-message'></div>");
$element.parent().append($errorMessage);
}

$errorMessage.empty().append(errorMessage);
this.updateCss();
},
editAllowance: function(event) {
var $errorResponse, formHasErrors, allowanceValue;
var self = this;
event.preventDefault();
$errorResponse = $('.error-response');
$errorResponse.html();
allowanceValue = this.getAllowanceValue();
formHasErrors = false;

if (allowanceValue === '') {
formHasErrors = true;
self.showError(self, 'allowance_value', gettext('Required field'));
} else {
self.hideError(self, 'allowance_value');
}

if (!formHasErrors) {
self.model.fetch({
headers: {
'X-CSRFToken': self.proctored_exam_allowance_view.getCSRFToken()
},
type: 'PUT',
data: {
exam_ids: this.selected_exam_ID,
user_ids: this.selected_user,
allowance_type: this.allowance_type,
value: allowanceValue
},
success: function() {
// fetch the allowances again.
$errorResponse.html();
self.proctored_exam_allowance_view.collection.url =
self.proctored_exam_allowance_view.initial_url + self.course_id + '/allowance';
self.proctored_exam_allowance_view.hydrate();
self.hideModal();
},
error: function(unused, response) {
var data = $.parseJSON(response.responseText);
$errorResponse.html(gettext(data.detail));
}
});
}
},
render: function() {
$(this.el).html(this.template({
selected_user: this.selected_user,
selected_exam_name: this.selected_exam_name,
allowance_type: this.allowance_type,
allowance_type_name: this.allowance_type_name
}));

this.$form = {
allowance_value: this.$('#allowance_value')
};
return this;
}
});
}).call(this, Backbone, $, _, gettext);
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ describe('ProctoredExamAAllowanceView', function() {
// from http://www.howtocreate.co.uk/tutorials/jsexamples/syntax/prepareInline.html

// eslint-disable-next-line max-len
html = '<div class=\'modal-header\'><%- gettext("Add a New Allowance") %></div>\n<form>\n <h3 class=\'error-response\'><h3>\n <table class=\'compact\'>\n <tr>\n <td>\n <label><%- gettext("Add Usernames or Emails seperated by commas") %></label>\n </td>\n </tr>\n <tr>\n <td>\n <input type="text" id="user_info" />\n </td>\n </tr>\n <tr>\n <td>\n <label><%- gettext("Select Exam Type") %></label>\n </td>\n </tr>\n <tr>\n <td>\n <select id="exam_type">\n <option value="proctored_exam">\n <%- gettext("Proctored Exam") %>\n </option>\n <option value="timed_exam">\n <%- gettext("Timed Exam") %>\n </option>\n </select>\n </td>\n </tr>\n <tr>\n <td>\n <label><%- gettext("Select Exams") %></label>\n </td>\n </tr>\n <tr>\n <td>\n <select multiple id=\'proctored_exam\' class="exam_dropdown">\n <option hidden selected value=default> <%- gettext("Choose Exams Below") %> </option>\n <% _.each(proctored_exams, function(proctored_exam){ %>\n <option value="<%= proctored_exam.id %>">\n <%- interpolate(gettext(\' %(exam_display_name)s \'), { exam_display_name: proctored_exam.exam_name }, true) %>\n </option>\n <% }); %>\n </select>\n <select multiple id=\'timed_exam\' class="exam_dropdown" style="display:none;">\n <option hidden selected value=default> <%- gettext("Choose Exams Below") %> </option>\n <% _.each(timed_exams, function(timed_exam){ %>\n <option value="<%= timed_exam.id %>">\n <%- interpolate(gettext(\' %(exam_display_name)s \'), { exam_display_name: timed_exam.exam_name }, true) %>\n </option>\n <% }); %>\n </select>\n </td>\n </tr>\n <tr>\n <td colspan="3">\n <div id="selected_exams" ></div>\n </td>\n </tr>\n <tr>\n <td>\n <label><%- gettext("Allowance Type") %></label>\n </td>\n </tr>\n <tr>\n <td>\n <select id="allowance_type">\n <% _.each(allowance_types, function(allowance_type){ %>\n <option value="<%= allowance_type[0] %>">\n <%= allowance_type[1] %>\n </option>\n <% }); %>\n </select>\n </td>\n </tr>\n <tr>\n <td>\n <label id=\'allowance_value_label\'><%- gettext("Input Additional Minutes as a Positive Number") %></label>\n </td>\n </tr>\n <tr>\n <td>\n <input type="text" id="allowance_value" />\n </td>\n </tr>\n <tr>\n <td>\n <input id=\'addNewAllowance\' type=\'submit\' value=\'Create Allowance\' />\n </td>\n </tr>\n </table>\n</form>';
html = '<div class=\'modal-header\'><%- gettext("Add a New Allowance") %></div>\n<form>\n <h3 class=\'error-response\'><h3>\n <table class=\'compact\'>\n <tr>\n <td>\n <label><%- gettext("Add Usernames or Emails seperated by commas") %></label>\n </td>\n </tr>\n <tr>\n <td>\n <input type="text" id="user_info" />\n </td>\n </tr>\n <tr>\n <td>\n <label><%- gettext("Select Exam Type") %></label>\n </td>\n </tr>\n <tr>\n <td>\n <select id="exam_type">\n <option value="proctored_exam">\n <%- gettext("Proctored Exam") %>\n </option>\n <option value="timed_exam">\n <%- gettext("Timed Exam") %>\n </option>\n </select>\n </td>\n </tr>\n <tr>\n <td>\n <label><%- gettext("Select Exams") %></label>\n </td>\n </tr>\n <tr>\n <td>\n <select multiple id=\'proctored_exam\' class="exam_dropdown">\n <option hidden selected value=default> <%- gettext("Choose Exams Below") %> </option>\n <% _.each(proctored_exams, function(proctored_exam){ %>\n <option value="<%= proctored_exam.id %>">\n <%- interpolate(gettext(\' %(exam_display_name)s \'), { exam_display_name: proctored_exam.exam_name }, true) %>\n </option>\n <% }); %>\n </select>\n <select multiple id=\'timed_exam\' class="exam_dropdown" style="display:none;">\n <option hidden selected value=default> <%- gettext("Choose Exams Below") %> </option>\n <% _.each(timed_exams, function(timed_exam){ %>\n <option value="<%= timed_exam.id %>">\n <%- interpolate(gettext(\' %(exam_display_name)s \'), { exam_display_name: timed_exam.exam_name }, true) %>\n </option>\n <% }); %>\n </select>\n </td>\n </tr>\n <tr>\n <td colspan="3">\n <div id="selected_exams" ></div>\n </td>\n </tr>\n <tr>\n <td>\n <label><%- gettext("Allowance Type") %></label>\n </td>\n </tr>\n <tr>\n <td>\n <select id="allowance_type">\n <% _.each(allowance_types, function(allowance_type){ %>\n <option value="<%= allowance_type[0] %>">\n <%= allowance_type[1] %>\n </option>\n <% }); %>\n </select>\n </td>\n </tr>\n <tr>\n <td>\n <label id=\'allowance_value_label\'><%- gettext("Add Time(Minutes)") %></label>\n </td>\n </tr>\n <tr>\n <td>\n <input type="text" id="allowance_value" />\n </td>\n </tr>\n <tr>\n <td>\n <input id=\'addNewAllowance\' type=\'submit\' value=\'Create Allowance\' />\n </td>\n </tr>\n </table>\n</form>\n';

allowancesHtml = '<span class="tip">' +
'<%- gettext("Allowances") %>' +
Expand Down
Loading

0 comments on commit 6c0da53

Please sign in to comment.