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. LibraryContent: The XBlock used to include blocks from a library in a course.
""" """
...@@ -280,9 +281,21 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule): ...@@ -280,9 +281,21 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule):
) )
) )
return validation 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) 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( validation.set_summary(
StudioValidationMessage( StudioValidationMessage(
StudioValidationMessage.ERROR, StudioValidationMessage.ERROR,
...@@ -298,7 +311,7 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule): ...@@ -298,7 +311,7 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule):
def author_view(self, context): def author_view(self, context):
""" """
Renders the Studio views. 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. Studio container view: displays a preview of all possible children.
""" """
fragment = Fragment() fragment = Fragment()
...@@ -311,45 +324,25 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule): ...@@ -311,45 +324,25 @@ class LibraryContentModule(LibraryContentFields, XModule, StudioEditableModule):
fragment.add_content(self.system.render_template("library-block-author-preview-header.html", { fragment.add_content(self.system.render_template("library-block-author-preview-header.html", {
'max_count': self.max_count, 'max_count': self.max_count,
'display_name': self.display_name or self.url_name, 'display_name': self.display_name or self.url_name,
'mode': self.mode,
})) }))
self.render_children(context, fragment, can_reorder=False, can_add=False) 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: 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. # 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 = [] library_names = []
update_status = UpdateStatus.UP_TO_DATE 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) library = _get_library(self.runtime.descriptor_runtime.modulestore, library_key)
if library is None: if library is not None:
update_status = UpdateStatus.CANNOT library_names.append(library.display_name)
library_ok = False
break if library_names:
library_names.append(library.display_name) fragment.add_content(self.system.render_template('library-block-author-view.html', {
latest_version = library.location.library_key.version_guid 'library_names': library_names,
if version is None or version != latest_version: 'max_count': self.max_count,
update_status = UpdateStatus.NEEDED 'num_children': len(self.children), # pylint: disable=no-member
}))
fragment.add_content(self.system.render_template('library-block-author-view.html', { # The following JS is used to make the "Update now" button work on the unit page and the container view:
'library_names': library_names, fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/library_content_edit.js'))
'library_ok': library_ok, fragment.initialize_js('LibraryContentAuthorView')
'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')
return fragment return fragment
def get_child_descriptors(self): 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) { 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(); e.preventDefault();
// Update the XBlock with the latest matching content from the library: // Update the XBlock with the latest matching content from the library:
runtime.notify('save', { runtime.notify('save', {
......
...@@ -337,6 +337,45 @@ class XBlockWrapper(PageObject): ...@@ -337,6 +337,45 @@ class XBlockWrapper(PageObject):
return [descendant for descendant in descendants if descendant.locator not in grand_locators] return [descendant for descendant in descendants if descendant.locator not in grand_locators]
@property @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): def preview_selector(self):
return self._bounded_selector('.xblock-student_view,.xblock-author_view') return self._bounded_selector('.xblock-student_view,.xblock-author_view')
......
...@@ -246,13 +246,6 @@ class StudioLibraryContainerXBlockWrapper(XBlockWrapper): ...@@ -246,13 +246,6 @@ class StudioLibraryContainerXBlockWrapper(XBlockWrapper):
""" """
return cls(xblock_wrapper.browser, xblock_wrapper.locator) 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): def get_body_paragraphs(self):
""" """
Gets library content body paragraphs Gets library content body paragraphs
...@@ -263,5 +256,7 @@ class StudioLibraryContainerXBlockWrapper(XBlockWrapper): ...@@ -263,5 +256,7 @@ class StudioLibraryContainerXBlockWrapper(XBlockWrapper):
""" """
Click "Update now..." button 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() refresh_button.click()
self.wait_for_element_absence(btn_selector, 'Wait for the XBlock to reload')
...@@ -94,40 +94,61 @@ class StudioLibraryContainerTest(ContainerBase, StudioLibraryTest): ...@@ -94,40 +94,61 @@ class StudioLibraryContainerTest(ContainerBase, StudioLibraryTest):
And I edit set library key to none And I edit set library key to none
Then I can see that library content block is misconfigured 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]) library_container = self._get_library_xblock_wrapper(self.unit_page.xblocks[0])
# precondition check - assert library is configured before we remove it # precondition check - the library block should be configured before we remove the library setting
self.assertNotIn(expected_text, library_container.header_text) self.assertFalse(library_container.has_validation_not_configured_warning)
edit_modal = StudioLibraryContentXBlockEditModal(library_container.edit()) edit_modal = StudioLibraryContentXBlockEditModal(library_container.edit())
edit_modal.library_key = None edit_modal.library_key = None
library_container.save_settings() 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( def test_set_missing_library_shows_correct_label(self):
'library-v1:111+111',
'library-v1:edX+L104',
)
def test_set_missing_library_shows_correct_label(self, library_key):
""" """
Scenario: Given I have a library, a course and library content xblock in a course 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 When I go to studio unit page for library content block
And I edit set library key to non-existent library And I edit set library key to non-existent library
Then I can see that library content block is misconfigured 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." expected_text = "Library is invalid, corrupt, or has been deleted."
library_container = self._get_library_xblock_wrapper(self.unit_page.xblocks[0]) library_container = self._get_library_xblock_wrapper(self.unit_page.xblocks[0])
# precondition check - assert library is configured before we remove it # 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 = StudioLibraryContentXBlockEditModal(library_container.edit())
edit_modal.library_key = library_key edit_modal.library_key = nonexistent_lib_key
library_container.save_settings() 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 @@ ...@@ -3,7 +3,7 @@
<div class="xblock-message information"> <div class="xblock-message information">
<p> <p>
<span class="message-text"> <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> </span>
</p> </p>
</div> </div>
......
...@@ -2,12 +2,5 @@ ...@@ -2,12 +2,5 @@
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
%> %>
<div class="xblock-header-secondary"> <div class="xblock-header-secondary">
% if library_ok: <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>
<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
</div> </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