Commit e498872a by Braden MacDonald Committed by E. Kolpakov

Move update link to the validation area

parent 80ea764c
# -*- coding: utf-8 -*-
"""
LibraryContent: The XBlock used to include blocks from a library in a course.
"""
......@@ -280,9 +281,21 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule):
)
)
return validation
for library_key, version in self.source_libraries: # pylint: disable=unused-variable
for library_key, version in self.source_libraries:
library = _get_library(self.runtime.descriptor_runtime.modulestore, library_key)
if library is None:
if library is not None:
latest_version = library.location.library_key.version_guid
if version is None or version != latest_version:
validation.set_summary(
StudioValidationMessage(
StudioValidationMessage.WARNING,
_(u'This component is out of date. The library has new content.'),
action_class='library-update-btn', # TODO: change this to action_runtime_event='...' once the unit page supports that feature.
action_label=_(u"↻ Update now")
)
)
break
else:
validation.set_summary(
StudioValidationMessage(
StudioValidationMessage.ERROR,
......@@ -298,7 +311,7 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule):
def author_view(self, context):
"""
Renders the Studio views.
Normal studio view: displays library status and has an "Update" button.
Normal studio view: If block is properly configured, displays library status summary
Studio container view: displays a preview of all possible children.
"""
fragment = Fragment()
......@@ -311,45 +324,25 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule):
fragment.add_content(self.system.render_template("library-block-author-preview-header.html", {
'max_count': self.max_count,
'display_name': self.display_name or self.url_name,
'mode': self.mode,
}))
self.render_children(context, fragment, can_reorder=False, can_add=False)
else:
fragment.add_content(u'<p>{}</p>'.format(
_('No matching content found in library, no library configured, or not yet loaded from library.')
))
else:
UpdateStatus = enum( # pylint: disable=invalid-name
CANNOT=0, # Cannot update - library is not set, invalid, deleted, etc.
NEEDED=1, # An update is needed - prompt the user to update
UP_TO_DATE=2, # No update necessary - library is up to date
)
# When shown on a unit page, don't show any sort of preview - just the status of this block.
library_ok = bool(self.source_libraries) # True if at least one source library is defined
library_names = []
update_status = UpdateStatus.UP_TO_DATE
for library_key, version in self.source_libraries:
for library_key, version in self.source_libraries: # pylint: disable=unused-variable
library = _get_library(self.runtime.descriptor_runtime.modulestore, library_key)
if library is None:
update_status = UpdateStatus.CANNOT
library_ok = False
break
library_names.append(library.display_name)
latest_version = library.location.library_key.version_guid
if version is None or version != latest_version:
update_status = UpdateStatus.NEEDED
fragment.add_content(self.system.render_template('library-block-author-view.html', {
'library_names': library_names,
'library_ok': library_ok,
'UpdateStatus': UpdateStatus,
'update_status': update_status,
'max_count': self.max_count,
'mode': self.mode,
'num_children': len(self.children), # pylint: disable=no-member
}))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/library_content_edit.js'))
fragment.initialize_js('LibraryContentAuthorView')
if library is not None:
library_names.append(library.display_name)
if library_names:
fragment.add_content(self.system.render_template('library-block-author-view.html', {
'library_names': library_names,
'max_count': self.max_count,
'num_children': len(self.children), # pylint: disable=no-member
}))
# The following JS is used to make the "Update now" button work on the unit page and the container view:
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/library_content_edit.js'))
fragment.initialize_js('LibraryContentAuthorView')
return fragment
def get_child_descriptors(self):
......
/* JavaScript for editing operations that can be done on LibraryContentXBlock */
/* JavaScript for special editing operations that can be done on LibraryContentXBlock */
window.LibraryContentAuthorView = function (runtime, element) {
$(element).find('.library-update-btn').on('click', function(e) {
"use strict";
var usage_id = $(element).data('usage-id');
// The "Update Now" button is not a child of 'element', as it is in the validation message area
// But it is still inside this xblock's wrapper element, which we can easily find:
var $wrapper = $(element).parents('*[data-locator="'+usage_id+'"]');
// We can't bind to the button itself because in the bok choy test environment,
// it may not yet exist at this point in time... not sure why.
$wrapper.on('click', '.library-update-btn', function(e) {
e.preventDefault();
// Update the XBlock with the latest matching content from the library:
runtime.notify('save', {
......
......@@ -337,6 +337,45 @@ class XBlockWrapper(PageObject):
return [descendant for descendant in descendants if descendant.locator not in grand_locators]
@property
def has_validation_message(self):
""" Is a validation warning/error/message shown? """
return self.q(css=self._bounded_selector('.xblock-message.validation')).present
def _validation_paragraph(self, css_class):
""" Helper method to return the <p> element of a validation warning """
return self.q(css=self._bounded_selector('.xblock-message.validation p.{}'.format(css_class)))
@property
def has_validation_warning(self):
""" Is a validation warning shown? """
return self._validation_paragraph('warning').present
@property
def has_validation_error(self):
""" Is a validation error shown? """
return self._validation_paragraph('error').present
@property
def has_validation_not_configured_warning(self):
""" Is a validation "not configured" message shown? """
return self._validation_paragraph('not-configured').present
@property
def validation_warning_text(self):
""" Get the text of the validation warning. """
return self._validation_paragraph('warning').text[0]
@property
def validation_error_text(self):
""" Get the text of the validation error. """
return self._validation_paragraph('error').text[0]
@property
def validation_not_configured_warning_text(self):
""" Get the text of the validation "not configured" message. """
return self._validation_paragraph('not-configured').text[0]
@property
def preview_selector(self):
return self._bounded_selector('.xblock-student_view,.xblock-author_view')
......
......@@ -246,13 +246,6 @@ class StudioLibraryContainerXBlockWrapper(XBlockWrapper):
"""
return cls(xblock_wrapper.browser, xblock_wrapper.locator)
@property
def header_text(self):
"""
Gets library content text
"""
return self.get_body_paragraphs().first.text[0]
def get_body_paragraphs(self):
"""
Gets library content body paragraphs
......@@ -263,5 +256,7 @@ class StudioLibraryContainerXBlockWrapper(XBlockWrapper):
"""
Click "Update now..." button
"""
refresh_button = self.q(css=self._bounded_selector(".library-update-btn"))
btn_selector = self._bounded_selector(".library-update-btn")
refresh_button = self.q(css=btn_selector)
refresh_button.click()
self.wait_for_element_absence(btn_selector, 'Wait for the XBlock to reload')
......@@ -94,40 +94,61 @@ class StudioLibraryContainerTest(ContainerBase, StudioLibraryTest):
And I edit set library key to none
Then I can see that library content block is misconfigured
"""
expected_text = 'No library or filters configured. Press "Edit" to configure.'
expected_text = 'A library has not yet been selected.'
expected_action = 'Select a Library'
library_container = self._get_library_xblock_wrapper(self.unit_page.xblocks[0])
# precondition check - assert library is configured before we remove it
self.assertNotIn(expected_text, library_container.header_text)
# precondition check - the library block should be configured before we remove the library setting
self.assertFalse(library_container.has_validation_not_configured_warning)
edit_modal = StudioLibraryContentXBlockEditModal(library_container.edit())
edit_modal.library_key = None
library_container.save_settings()
self.assertIn(expected_text, library_container.header_text)
self.assertTrue(library_container.has_validation_not_configured_warning)
self.assertIn(expected_text, library_container.validation_not_configured_warning_text)
self.assertIn(expected_action, library_container.validation_not_configured_warning_text)
@ddt.data(
'library-v1:111+111',
'library-v1:edX+L104',
)
def test_set_missing_library_shows_correct_label(self, library_key):
def test_set_missing_library_shows_correct_label(self):
"""
Scenario: Given I have a library, a course and library content xblock in a course
When I go to studio unit page for library content block
And I edit set library key to non-existent library
Then I can see that library content block is misconfigured
"""
nonexistent_lib_key = 'library-v1:111+111'
expected_text = "Library is invalid, corrupt, or has been deleted."
library_container = self._get_library_xblock_wrapper(self.unit_page.xblocks[0])
# precondition check - assert library is configured before we remove it
self.assertNotIn(expected_text, library_container.header_text)
self.assertFalse(library_container.has_validation_error)
edit_modal = StudioLibraryContentXBlockEditModal(library_container.edit())
edit_modal.library_key = library_key
edit_modal.library_key = nonexistent_lib_key
library_container.save_settings()
self.assertIn(expected_text, library_container.header_text)
self.assertTrue(library_container.has_validation_error)
self.assertIn(expected_text, library_container.validation_error_text)
def test_out_of_date_message(self):
"""
Scenario: Given I have a library, a course and library content xblock in a course
When I go to studio unit page for library content block
Then I can see that library content block needs to be updated
When I click on the update link
Then I can see that the content no longer needs to be updated
"""
expected_text = "This component is out of date. The library has new content."
library_container = self._get_library_xblock_wrapper(self.unit_page.xblocks[0])
self.assertTrue(library_container.has_validation_warning)
self.assertIn(expected_text, library_container.validation_warning_text)
library_container.refresh_children()
self.unit_page.wait_for_page() # Wait for the page to reload
library_container = self._get_library_xblock_wrapper(self.unit_page.xblocks[0])
self.assertFalse(library_container.has_validation_message)
......@@ -3,7 +3,7 @@
<div class="xblock-message information">
<p>
<span class="message-text">
${_('Showing all matching content eligible to be added into {display_name}. Each student will be assigned {mode} {max_count} components from this list.').format(max_count=max_count, display_name=display_name, mode=mode)}
${_('Showing all matching content eligible to be added into {display_name}. Each student will be assigned {max_count} component[s] drawn randomly from this list.').format(max_count=max_count, display_name=display_name)}
</span>
</p>
</div>
......
......@@ -2,12 +2,5 @@
from django.utils.translation import ugettext as _
%>
<div class="xblock-header-secondary">
% if library_ok:
<p>${_('This component will be replaced by {mode} {max_count} components from the {num_children} matching components from {lib_names}.').format(mode=mode, max_count=max_count, num_children=num_children, lib_names=', '.join(library_names))}</p>
% if update_status == UpdateStatus.NEEDED:
<p><strong>${_('This component is out of date.')}</strong> <a href="#" class="library-update-btn">↻ ${_('Update now with latest components from the library')}</a></p>
% elif update_status == UpdateStatus.UP_TO_DATE:
<p>${_(u'✓ Up to date.')}</p>
% endif
% endif
<p>${_('This component will be replaced by {max_count} component[s] randomly chosen from the {num_children} matching components in {lib_names}.').format(mode=mode, max_count=max_count, num_children=num_children, lib_names=', '.join(library_names))}</p>
</div>
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