Move update link to the validation area
This commit is contained in:
committed by
E. Kolpakov
parent
80ea764c9d
commit
e498872ab1
@@ -1,3 +1,4 @@
|
||||
# -*- 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
|
||||
if library is not None:
|
||||
library_names.append(library.display_name)
|
||||
|
||||
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_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):
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
/* 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', {
|
||||
|
||||
@@ -336,6 +336,45 @@ class XBlockWrapper(PageObject):
|
||||
grand_locators = [grandkid.locator for grandkid in grandkids]
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user