Commit 6d82cf09 by cahrens

Correct publish titles, code review feedback.

parent ef581e11
......@@ -1209,7 +1209,7 @@ class ContentStoreTest(ContentStoreTestCase):
resp = self._show_course_overview(course.id)
self.assertContains(
resp,
'<article class="outline" data-locator="{locator}" data-course-key="{course_key}">'.format(
'<article class="outline outline-course" data-locator="{locator}" data-course-key="{course_key}">'.format(
locator='i4x://MITx/999/course/Robot_Super_Course',
course_key='MITx/999/Robot_Super_Course',
),
......
......@@ -149,7 +149,7 @@ def course_image_url(course):
path = loc.to_deprecated_string()
return path
# pylint: disable=invalid-name
def is_currently_visible_to_students(xblock):
"""
Returns true if there is a published version of the xblock that is currently visible to students.
......
......@@ -178,10 +178,6 @@ def container_handler(request, usage_key_string):
# about the block's ancestors and siblings for use by the Unit Outline.
xblock_info = create_xblock_info(xblock, include_ancestor_info=is_unit_page)
# On the unit page only, add 'has_changes' to indicate when there are changes that can be discarded.
if is_unit_page:
xblock_info['has_changes'] = modulestore().has_changes(xblock.location)
# Create the link for preview.
preview_lms_base = settings.FEATURES.get('PREVIEW_LMS_BASE')
# need to figure out where this item is in the list of children as the
......
......@@ -632,6 +632,9 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
published = modulestore().has_item(xblock.location, revision=ModuleStoreEnum.RevisionOption.published_only)
currently_visible_to_students = is_currently_visible_to_students(xblock)
is_xblock_unit = is_unit(xblock)
is_unit_with_changes = is_xblock_unit and modulestore().has_changes(xblock.location)
xblock_info = {
"id": unicode(xblock.location),
"display_name": xblock.display_name_with_default,
......@@ -646,7 +649,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
"release_date": release_date,
"release_date_from": _get_release_date_from(xblock) if release_date else None,
"currently_visible_to_students": currently_visible_to_students,
"visibility_state": _compute_visibility_state(xblock, child_info) if not xblock.category == 'course' else None
"visibility_state": _compute_visibility_state(xblock, child_info, is_unit_with_changes) if not xblock.category == 'course' else None
}
if data is not None:
xblock_info["data"] = data
......@@ -656,6 +659,11 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
xblock_info['ancestor_info'] = _create_xblock_ancestor_info(xblock)
if child_info:
xblock_info['child_info'] = child_info
# On the unit page only, add 'has_changes' to indicate when there are changes that can be discarded.
# We don't add it in general because it is an expensive operation.
if is_xblock_unit:
xblock_info['has_changes'] = is_unit_with_changes
return xblock_info
......@@ -680,18 +688,20 @@ class VisibilityState(object):
"""
live = 'live'
ready = 'ready'
unscheduled = 'unscheduled' # unscheduled
unscheduled = 'unscheduled'
needs_attention = 'needs_attention'
staff_only = 'staff_only'
def _compute_visibility_state(xblock, child_info):
def _compute_visibility_state(xblock, child_info, is_unit_with_changes):
"""
Returns the current publish state for the specified xblock and its children
"""
if xblock.visible_to_staff_only:
return VisibilityState.staff_only
elif is_unit(xblock) and modulestore().has_changes(xblock.location):
elif is_unit_with_changes:
# Note that a unit that has never been published will fall into this category,
# as well as previously published units with draft content.
return VisibilityState.needs_attention
is_unscheduled = xblock.start == DEFAULT_START_DATE
is_live = datetime.now(UTC) > xblock.start
......
......@@ -231,6 +231,7 @@ class TestCourseOutline(CourseTestCase):
self.assertEqual(json_response['category'], 'course')
self.assertEqual(json_response['id'], 'i4x://MITx/999/course/Robot_Super_Course')
self.assertEqual(json_response['display_name'], 'Robot Super Course')
self.assertTrue(json_response['published'])
self.assertIsNone(json_response['visibility_state'])
# Now verify the first child
......@@ -240,6 +241,7 @@ class TestCourseOutline(CourseTestCase):
self.assertEqual(first_child_response['category'], 'chapter')
self.assertEqual(first_child_response['id'], 'i4x://MITx/999/chapter/Week_1')
self.assertEqual(first_child_response['display_name'], 'Week 1')
self.assertTrue(json_response['published'])
self.assertEqual(first_child_response['visibility_state'], VisibilityState.unscheduled)
self.assertTrue(len(first_child_response['child_info']['children']) > 0)
......@@ -253,6 +255,7 @@ class TestCourseOutline(CourseTestCase):
self.assertIsNotNone(json_response['display_name'])
self.assertIsNotNone(json_response['id'])
self.assertIsNotNone(json_response['category'])
self.assertTrue(json_response['published'])
if json_response.get('child_info', None):
for child_response in json_response['child_info']['children']:
self.assert_correct_json_response(child_response)
......
......@@ -531,8 +531,25 @@ class TestEditItem(ItemTest):
self.assertEqual(unit2_usage_key, children[1])
def _is_location_published(self, location):
"""
Returns whether or not the item with given location has a published version.
"""
return modulestore().has_item(location, revision=ModuleStoreEnum.RevisionOption.published_only)
def _verify_published_with_no_draft(self, location):
"""
Verifies the item with given location has a published version and no draft (unpublished changes).
"""
self.assertTrue(self._is_location_published(location))
self.assertFalse(modulestore().has_changes(location))
def _verify_published_with_draft(self, location):
"""
Verifies the item with given location has a published version and also a draft version (unpublished changes).
"""
self.assertTrue(self._is_location_published(location))
self.assertTrue(modulestore().has_changes(location))
def test_make_public(self):
""" Test making a private problem public (publishing it). """
# When the problem is first created, it is only in draft (because of its category).
......@@ -541,7 +558,7 @@ class TestEditItem(ItemTest):
self.problem_update_url,
data={'publish': 'make_public'}
)
self.assertTrue(self._is_location_published(self.problem_usage_key))
self._verify_published_with_no_draft(self.problem_usage_key)
def test_make_draft(self):
""" Test creating a draft version of a public problem. """
......@@ -554,17 +571,13 @@ class TestEditItem(ItemTest):
self.problem_update_url,
data={'publish': 'discard_changes'}
)
self.assertTrue(self._is_location_published(self.problem_usage_key))
self._verify_published_with_no_draft(self.problem_usage_key)
published = modulestore().get_item(self.problem_usage_key, revision=ModuleStoreEnum.RevisionOption.published_only)
self.assertIsNone(published.due)
def test_republish(self):
""" Test republishing an item. """
new_display_name = 'New Display Name'
republish_data = {
'publish': 'republish',
'display_name': new_display_name
}
# When the problem is first created, it is only in draft (because of its category).
self.assertFalse(self._is_location_published(self.problem_usage_key))
......@@ -600,7 +613,7 @@ class TestEditItem(ItemTest):
}
}
)
self.assertTrue(self._is_location_published(self.problem_usage_key))
self._verify_published_with_no_draft(self.problem_usage_key)
published = modulestore().get_item(
self.problem_usage_key,
revision=ModuleStoreEnum.RevisionOption.published_only
......@@ -616,7 +629,7 @@ class TestEditItem(ItemTest):
self.problem_update_url,
data={'publish': 'make_public'}
)
self.assertTrue(self._is_location_published(self.problem_usage_key))
self._verify_published_with_no_draft(self.problem_usage_key)
published = modulestore().get_item(self.problem_usage_key, revision=ModuleStoreEnum.RevisionOption.published_only)
# Update the draft version and check that published is different.
......@@ -651,7 +664,7 @@ class TestEditItem(ItemTest):
self.problem_update_url,
data={'publish': 'make_public'}
)
self.assertTrue(self._is_location_published(self.problem_usage_key))
self._verify_published_with_no_draft(self.problem_usage_key)
published = modulestore().get_item(self.problem_usage_key, revision=ModuleStoreEnum.RevisionOption.published_only)
# Now make a draft
......@@ -706,8 +719,8 @@ class TestEditItem(ItemTest):
data={'publish': 'make_public'}
)
self.assertEqual(resp.status_code, 200)
self.assertTrue(self._is_location_published(unit_usage_key))
self.assertTrue(self._is_location_published(html_usage_key))
self._verify_published_with_no_draft(unit_usage_key)
self._verify_published_with_no_draft(html_usage_key)
# Make a draft for the unit and verify that the problem also has a draft
resp = self.client.ajax_post(
......@@ -718,10 +731,8 @@ class TestEditItem(ItemTest):
}
)
self.assertEqual(resp.status_code, 200)
self.assertTrue(self._is_location_published(unit_usage_key))
self.assertTrue(self._is_location_published(html_usage_key))
self.assertTrue(modulestore().get_item(unit_usage_key).has_changes())
self.assertTrue(modulestore().get_item(html_usage_key).has_changes())
self._verify_published_with_draft(unit_usage_key)
self._verify_published_with_draft(html_usage_key)
class TestEditSplitModule(ItemTest):
......@@ -1143,6 +1154,7 @@ class TestXBlockInfo(ItemTest):
self.assertEqual(xblock_info['category'], 'course')
self.assertEqual(xblock_info['id'], 'i4x://MITx/999/course/Robot_Super_Course')
self.assertEqual(xblock_info['display_name'], 'Robot Super Course')
self.assertTrue(xblock_info['published'])
# Finally, validate the entire response for consistency
self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info)
......@@ -1154,6 +1166,7 @@ class TestXBlockInfo(ItemTest):
self.assertEqual(xblock_info['category'], 'chapter')
self.assertEqual(xblock_info['id'], 'i4x://MITx/999/chapter/Week_1')
self.assertEqual(xblock_info['display_name'], 'Week 1')
self.assertTrue(xblock_info['published'])
self.assertEqual(xblock_info['edited_by'], 'testuser')
# Finally, validate the entire response for consistency
......@@ -1166,6 +1179,7 @@ class TestXBlockInfo(ItemTest):
self.assertEqual(xblock_info['category'], 'sequential')
self.assertEqual(xblock_info['id'], 'i4x://MITx/999/sequential/Lesson_1')
self.assertEqual(xblock_info['display_name'], 'Lesson 1')
self.assertTrue(xblock_info['published'])
self.assertEqual(xblock_info['edited_by'], 'testuser')
# Finally, validate the entire response for consistency
......@@ -1178,6 +1192,7 @@ class TestXBlockInfo(ItemTest):
self.assertEqual(xblock_info['category'], 'vertical')
self.assertEqual(xblock_info['id'], 'i4x://MITx/999/vertical/Unit_1')
self.assertEqual(xblock_info['display_name'], 'Unit 1')
self.assertTrue(xblock_info['published'])
self.assertEqual(xblock_info['edited_by'], 'testuser')
# Validate that the correct ancestor info has been included
......@@ -1199,6 +1214,7 @@ class TestXBlockInfo(ItemTest):
self.assertEqual(xblock_info['category'], 'video')
self.assertEqual(xblock_info['id'], 'i4x://MITx/999/video/My_Video')
self.assertEqual(xblock_info['display_name'], 'My Video')
self.assertTrue(xblock_info['published'])
self.assertEqual(xblock_info['edited_by'], 'testuser')
# Finally, validate the entire response for consistency
......@@ -1211,6 +1227,7 @@ class TestXBlockInfo(ItemTest):
self.assertIsNotNone(xblock_info['display_name'])
self.assertIsNotNone(xblock_info['id'])
self.assertIsNotNone(xblock_info['category'])
self.assertTrue(xblock_info['published'])
self.assertEqual(xblock_info['edited_by'], 'testuser')
if has_ancestor_info:
self.assertIsNotNone(xblock_info.get('ancestor_info', None))
......@@ -1243,14 +1260,17 @@ class TestXBlockPublishingInfo(ItemTest):
SECOND_UNIT_PATH = [0, 1]
def _create_child(self, parent, category, display_name, publish_item=False, staff_only=False):
"""
Creates a child xblock for the given parent.
"""
return ItemFactory.create(
parent_location=parent.location, category=category, display_name=display_name,
user_id=self.user.id, publish_item=publish_item, visible_to_staff_only=staff_only
)
def _get_child(self, xblock_info, index):
def _get_child_xblock_info(self, xblock_info, index):
"""
Returns the child at the specified index.
Returns the child xblock info at the specified index.
"""
children = xblock_info['child_info']['children']
self.assertTrue(len(children) > index)
......@@ -1293,12 +1313,12 @@ class TestXBlockPublishingInfo(ItemTest):
def _verify_visibility_state(self, xblock_info, expected_state, path=None):
"""
Verify the publish state of an item in the xblock_info. If no path is provided
then the root item is verified.
then the root item will be verified.
"""
if path:
direct_child = self._get_child(xblock_info, path[0])
direct_child_xblock_info = self._get_child_xblock_info(xblock_info, path[0])
remaining_path = path[1:] if len(path) > 1 else None
self._verify_visibility_state(direct_child, expected_state, remaining_path)
self._verify_visibility_state(direct_child_xblock_info, expected_state, remaining_path)
else:
self.assertEqual(xblock_info['visibility_state'], expected_state)
......@@ -1311,10 +1331,13 @@ class TestXBlockPublishingInfo(ItemTest):
chapter = self._create_child(self.course, 'chapter', "Test Chapter")
self._create_child(chapter, 'sequential', "Empty Sequential")
xblock_info = self._get_xblock_info(chapter.location)
self.assertEqual(xblock_info['visibility_state'], VisibilityState.unscheduled)
self.assertEqual(self._get_child(xblock_info, 0)['visibility_state'], VisibilityState.unscheduled)
self._verify_visibility_state(xblock_info, VisibilityState.unscheduled)
self._verify_visibility_state(xblock_info, VisibilityState.unscheduled, path=self.FIRST_SUBSECTION_PATH)
def test_published_unit(self):
"""
Tests the visibility state of a published unit with release date in the future.
"""
chapter = self._create_child(self.course, 'chapter', "Test Chapter")
sequential = self._create_child(chapter, 'sequential', "Test Sequential")
self._create_child(sequential, 'vertical', "Published Unit", publish_item=True)
......@@ -1327,6 +1350,9 @@ class TestXBlockPublishingInfo(ItemTest):
self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH)
def test_released_unit(self):
"""
Tests the visibility state of a published unit with release date in the past.
"""
chapter = self._create_child(self.course, 'chapter', "Test Chapter")
sequential = self._create_child(chapter, 'sequential', "Test Sequential")
self._create_child(sequential, 'vertical', "Published Unit", publish_item=True)
......@@ -1339,10 +1365,14 @@ class TestXBlockPublishingInfo(ItemTest):
self._verify_visibility_state(xblock_info, VisibilityState.staff_only, path=self.SECOND_UNIT_PATH)
def test_unpublished_changes(self):
"""
Tests the visibility state of a published unit with draft (unpublished) changes.
"""
chapter = self._create_child(self.course, 'chapter', "Test Chapter")
sequential = self._create_child(chapter, 'sequential', "Test Sequential")
unit = self._create_child(sequential, 'vertical', "Published Unit", publish_item=True)
self._create_child(sequential, 'vertical', "Staff Only Unit", staff_only=True)
# Setting the display name creates a draft version of unit.
self._set_display_name(unit.location, 'Updated Unit')
xblock_info = self._get_xblock_info(chapter.location)
self._verify_visibility_state(xblock_info, VisibilityState.needs_attention)
......
......@@ -46,7 +46,8 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu
"published_by": null,
/**
* True if the xblock has changes.
* Note: this is not always provided as a performance optimization.
* Note: this is not always provided as a performance optimization. It is only provided for
* verticals functioning as units.
*/
"has_changes": null,
/**
......
......@@ -27,7 +27,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
category: 'vertical',
published: false,
has_changes: false,
visibility_state: 'unscheduled',
visibility_state: VisibilityState.unscheduled,
edited_on: "Jul 02, 2014 at 14:20 UTC", edited_by: "joe",
published_on: "Jul 01, 2014 at 12:45 UTC", published_by: "amako",
currently_visible_to_students: false
......@@ -116,13 +116,15 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
liveClass = "is-live",
readyClass = "is-ready",
staffOnlyClass = "is-staff-only",
scheduledClass = "is-scheduled",
unscheduledClass = "",
hasWarningsClass = 'has-warnings',
publishButtonCss = ".action-publish",
discardChangesButtonCss = ".action-discard",
lastDraftCss = ".wrapper-last-draft",
releaseDateTitleCss = ".wrapper-release .title",
releaseDateContentCss = ".wrapper-release .copy",
promptSpies, sendDiscardChangesToServer;
promptSpies, sendDiscardChangesToServer, verifyPublishingBitUnscheduled;
sendDiscardChangesToServer = function() {
// Helper function to do the discard operation, up until the server response.
......@@ -143,45 +145,75 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
);
};
verifyPublishingBitUnscheduled = function() {
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(liveClass);
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(hasWarningsClass);
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(staffOnlyClass);
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(scheduledClass);
expect(containerPage.$(bitPublishingCss)).toHaveClass(unscheduledClass);
};
beforeEach(function() {
promptSpies = spyOnConstructor(Prompt, "Warning", ["show", "hide"]);
promptSpies.show.andReturn(this.promptSpies);
});
it('renders correctly with unscheduled content', function () {
it('renders correctly with private content', function () {
var verifyPrivateState = function() {
expect(containerPage.$(headerCss).text()).toContain('Draft (Never published)');
expect(containerPage.$(publishButtonCss)).not.toHaveClass(disabledCss);
expect(containerPage.$(discardChangesButtonCss)).toHaveClass(disabledCss);
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(scheduledClass);
expect(containerPage.$(bitPublishingCss)).toHaveClass(hasWarningsClass);
};
renderContainerPage(this, mockContainerXBlockHtml);
fetch({published: false, has_changes: false});
fetch({published: false, has_changes: false, visibility_state: VisibilityState.needsAttention});
verifyPrivateState();
fetch({published: false, has_changes: true});
fetch({published: false, has_changes: true, visibility_state: VisibilityState.needsAttention});
verifyPrivateState();
});
it('renders correctly with published content', function () {
renderContainerPage(this, mockContainerXBlockHtml);
fetch({published: true, has_changes: false, visibility_state: VisibilityState.ready});
fetch({
published: true, has_changes: false, visibility_state: VisibilityState.ready,
release_date: "Jul 02, 2030 at 14:20 UTC"
});
expect(containerPage.$(headerCss).text()).toContain('Published');
expect(containerPage.$(publishButtonCss)).toHaveClass(disabledCss);
expect(containerPage.$(discardChangesButtonCss)).toHaveClass(disabledCss);
expect(containerPage.$(bitPublishingCss)).toHaveClass(readyClass);
expect(containerPage.$(bitPublishingCss)).toHaveClass(scheduledClass);
fetch({published: true, has_changes: true, visibility_state: VisibilityState.needsAttention});
fetch({
published: true, has_changes: true, visibility_state: VisibilityState.needsAttention,
release_date: "Jul 02, 2030 at 14:20 UTC"
});
expect(containerPage.$(headerCss).text()).toContain('Draft (Unpublished changes)');
expect(containerPage.$(publishButtonCss)).not.toHaveClass(disabledCss);
expect(containerPage.$(discardChangesButtonCss)).not.toHaveClass(disabledCss);
expect(containerPage.$(bitPublishingCss)).toHaveClass(hasWarningsClass);
expect(containerPage.$(bitPublishingCss)).toHaveClass(scheduledClass);
fetch({published: true, has_changes: false, visibility_state: VisibilityState.live});
fetch({published: true, has_changes: false, visibility_state: VisibilityState.live,
release_date: "Jul 02, 1990 at 14:20 UTC"
});
expect(containerPage.$(headerCss).text()).toContain('Published and Live');
expect(containerPage.$(publishButtonCss)).toHaveClass(disabledCss);
expect(containerPage.$(discardChangesButtonCss)).toHaveClass(disabledCss);
expect(containerPage.$(bitPublishingCss)).toHaveClass(liveClass);
expect(containerPage.$(bitPublishingCss)).toHaveClass(scheduledClass);
fetch({published: true, has_changes: false, visibility_state: VisibilityState.unscheduled,
release_date: null
});
expect(containerPage.$(headerCss).text()).toContain('Published');
expect(containerPage.$(publishButtonCss)).toHaveClass(disabledCss);
expect(containerPage.$(discardChangesButtonCss)).toHaveClass(disabledCss);
verifyPublishingBitUnscheduled();
});
it('can publish private content', function () {
......@@ -217,7 +249,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it('does not refresh if publish fails', function () {
renderContainerPage(this, mockContainerXBlockHtml);
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
verifyPublishingBitUnscheduled();
// Click publish
containerPage.$(publishButtonCss).click();
......@@ -228,8 +260,8 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
expect(requests.length).toEqual(numRequests);
// Verify still in draft state.
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
// Verify still in draft (unscheduled) state.
verifyPublishingBitUnscheduled();
// Verify that the "published" value has been cleared out of the model.
expect(containerPage.model.get("publish")).toBeNull();
});
......@@ -273,7 +305,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it('does not discard changes on cancel', function () {
renderContainerPage(this, mockContainerXBlockHtml);
fetch({published: true, has_changes: true});
fetch({published: true, has_changes: true, visibility_state: VisibilityState.needsAttention});
var numRequests = requests.length;
// Click discard changes
......@@ -373,7 +405,8 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
create_sinon.expectJsonRequest(requests, 'GET', '/xblock/locator-container');
create_sinon.respondWithJson(requests, createXBlockInfo({
published: containerPage.model.get('published'),
visibility_state: isStaffOnly ? VisibilityState.staffOnly : VisibilityState.live
visibility_state: isStaffOnly ? VisibilityState.staffOnly : VisibilityState.live,
release_date: "Jul 02, 2000 at 14:20 UTC"
}));
};
......@@ -382,6 +415,7 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
expect(containerPage.$('.action-staff-lock i')).toHaveClass('icon-check');
expect(containerPage.$('.wrapper-visibility .copy').text()).toBe('Staff Only');
expect(containerPage.$(bitPublishingCss)).toHaveClass(staffOnlyClass);
expect(containerPage.$(bitPublishingCss)).toHaveClass(scheduledClass);
} else {
expect(containerPage.$('.action-staff-lock i')).toHaveClass('icon-check-empty');
expect(containerPage.$('.wrapper-visibility .copy').text()).toBe('Staff and Students');
......@@ -403,18 +437,19 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
it("can remove staff only setting", function() {
promptSpy = edit_helpers.createPromptSpy();
renderContainerPage(this, mockContainerXBlockHtml, {
visibility_state: VisibilityState.staffOnly
visibility_state: VisibilityState.staffOnly,
release_date: "Jul 02, 2000 at 14:20 UTC"
});
requestStaffOnly(false);
verifyStaffOnly(false);
expect(containerPage.$(bitPublishingCss)).not.toHaveClass(readyClass);
});
it("does not refresh if removing staff only is canceled", function() {
var requestCount;
promptSpy = edit_helpers.createPromptSpy();
renderContainerPage(this, mockContainerXBlockHtml, {
visibility_state: VisibilityState.staffOnly
visibility_state: VisibilityState.staffOnly,
release_date: "Jul 02, 2000 at 14:20 UTC"
});
requestCount = requests.length;
containerPage.$('.action-staff-lock').click();
......
......@@ -118,6 +118,8 @@ define(["jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers"
category: 'vertical',
studio_url: '/container/mock-unit',
is_container: true,
has_changes: false,
published: true,
visibility_state: 'unscheduled',
edited_on: 'Jul 02, 2014 at 20:56 UTC',
edited_by: 'MockUser'
......
......@@ -116,7 +116,6 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
published: this.model.get('published'),
publishedOn: this.model.get('published_on'),
publishedBy: this.model.get('published_by'),
releasedToStudents: this.model.get('released_to_students'),
releaseDate: this.model.get('release_date'),
releaseDateFrom: this.model.get('release_date_from')
}));
......
......@@ -4,7 +4,7 @@
define(["jquery", "underscore", "gettext", "js/views/utils/view_utils", "js/utils/module"],
function($, _, gettext, ViewUtils, ModuleUtils) {
var addXBlock, deleteXBlock, createUpdateRequestData, updateXBlockField, VisibilityState,
getXBlockVisibilityClass;
getXBlockVisibilityClass, getXBlockListTypeClass;
/**
* Represents the possible visibility states for an xblock:
......@@ -131,11 +131,24 @@ define(["jquery", "underscore", "gettext", "js/views/utils/view_utils", "js/util
return '';
};
getXBlockListTypeClass = function (xblockType) {
var listType = 'list-unknown';
if (xblockType === 'course') {
listType = 'list-sections';
} else if (xblockType === 'section') {
listType = 'list-subsections';
} else if (xblockType === 'subsection') {
listType = 'list-units';
}
return listType;
};
return {
'VisibilityState': VisibilityState,
'addXBlock': addXBlock,
'deleteXBlock': deleteXBlock,
'updateXBlockField': updateXBlockField,
'getXBlockVisibilityClass': getXBlockVisibilityClass
'getXBlockVisibilityClass': getXBlockVisibilityClass,
'getXBlockListTypeClass': getXBlockListTypeClass
};
});
......@@ -69,6 +69,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
html = this.template({
xblockInfo: xblockInfo,
visibilityClass: XBlockViewUtils.getXBlockVisibilityClass(xblockInfo.get('visibility_state')),
typeListClass: XBlockViewUtils.getXBlockListTypeClass(xblockType),
parentInfo: this.parentInfo,
xblockType: xblockType,
parentType: parentType,
......
......@@ -47,7 +47,7 @@ from contentstore.utils import reverse_usage_url
<h3 class="sr">${_("Page Actions")}</h3>
<ul>
<li class="nav-item">
<a href="#" class="button button-new" data-category="chapter" data-parent="${context_course.location}" data-default-name="Section">
<a href="#" class="button button-new" data-category="chapter" data-parent="${context_course.location}" data-default-name="${_('Section')}">
<i class="icon-plus"></i>${_('New Section')}
</a>
</li>
......
......@@ -2,28 +2,20 @@
var category = xblockInfo.get('category');
var releasedToStudents = xblockInfo.get('released_to_students');
var visibilityState = xblockInfo.get('visibility_state');
var listType = 'list-unknown';
if (xblockType === 'course') {
listType = 'list-sections';
} else if (xblockType === 'section') {
listType = 'list-subsections';
} else if (xblockType === 'subsection') {
listType = 'list-units';
}
var published = xblockInfo.get('published');
var statusMessage = null;
var statusType = null;
if (visibilityState === 'staff_only') {
statusType = 'staff-only';
statusMessage = 'Contains staff only content';
statusMessage = gettext('Contains staff only content');
} else if (visibilityState === 'needs_attention') {
if (category === 'vertical') {
statusType = 'warning';
if (releasedToStudents) {
statusMessage = 'Unpublished changes to live content';
if (published && releasedToStudents) {
statusMessage = gettext('Unpublished changes to live content');
} else {
statusMessage = 'Unpublished units will not be released';
statusMessage = gettext('Unpublished units will not be released');
}
}
}
......@@ -75,7 +67,7 @@ if (statusType === 'warning') {
<% if (category !== 'vertical') { %>
<div class="status-release">
<p>
<span class="sr status-release-label">Release Status:</span>
<span class="sr status-release-label"><%= gettext('Release Status:') %></span>
<span class="status-release-value">
<% if (xblockInfo.get('released_to_students')) { %>
<i class="icon icon-check-sign"></i>
......@@ -115,7 +107,7 @@ if (statusType === 'warning') {
</div>
<% } else { %>
<div class="outline-content <%= xblockType %>-content">
<ol class="<%= listType %> is-sortable">
<ol class="<%= typeListClass %> is-sortable">
</ol>
<% if (childType) { %>
<div class="add-<%= childType %> add-item">
......
......@@ -4,9 +4,9 @@ if (visibilityState === 'staff_only') {
title = gettext("Unpublished (Staff only)");
} else if (visibilityState === 'live') {
title = gettext("Published and Live");
} else if (visibilityState === 'ready') {
} else if (published && !hasChanges) {
title = gettext("Published");
} else if (visibilityState === 'needs_attention') {
} else if (published && hasChanges) {
title = gettext("Draft (Unpublished changes)");
}
......@@ -19,7 +19,7 @@ if (visibilityState === 'live') {
var visibleToStaffOnly = visibilityState === 'staff_only';
%>
<div class="bit-publishing <%= visibilityClass %>">
<div class="bit-publishing <%= visibilityClass %> <% if (releaseDate) { %>is-scheduled<% } %>">
<h3 class="bar-mod-title pub-status"><span class="sr"><%= gettext("Publishing Status") %></span>
<%= title %>
</h3>
......
<%
var listType = 'list-for-' + xblockType;
if (xblockType === 'course') {
listType = 'list-sections';
} else if (xblockType === 'section') {
listType = 'list-subsections';
} else if (xblockType === 'subsection') {
listType = 'list-units';
}
%>
<% if (parentInfo) { %>
<li class="outline-item outline-<%= xblockType %> <%= visibilityClass %>"
data-parent="<%= parentInfo.get('id') %>" data-locator="<%= xblockInfo.get('id') %>">
......@@ -21,7 +11,7 @@ if (xblockType === 'course') {
<% } %>
<div class="<%= xblockType %>-content outline-content">
<ol class="<%= listType %>">
<ol class="<%= typeListClass %>">
</ol>
<% if (childType) { %>
<div class="add-<%= childType %> add-item">
......
......@@ -18,4 +18,3 @@ class HtmlComponentEditorView(ComponentEditorView):
ActionChains(self.browser).click(editor).\
send_keys([Keys.CONTROL, 'a']).key_up(Keys.CONTROL).send_keys(content).perform()
click_css(self, 'a.action-save')
......@@ -380,7 +380,7 @@ class UnitPublishingTest(ContainerBase):
__test__ = True
PUBLISHED_STATUS = "Publishing Status\nPublished"
PUBLISHED_LIVE_STATUS ="Publishing Status\nPublished and Live"
PUBLISHED_LIVE_STATUS = "Publishing Status\nPublished and Live"
DRAFT_STATUS = "Publishing Status\nDraft (Unpublished changes)"
LOCKED_STATUS = "Publishing Status\nUnpublished (Staff only)"
RELEASE_TITLE_RELEASED = "RELEASED:"
......@@ -398,6 +398,7 @@ class UnitPublishingTest(ContainerBase):
self.courseware = CoursewarePage(self.browser, self.course_id)
past_start_date = datetime.datetime(1974, 6, 22)
self.past_start_date_text = "Jun 22, 1974 at 00:00 UTC"
future_start_date = datetime.datetime(2100, 9, 13)
course_fixture.add_children(
XBlockFixtureDesc('chapter', 'Test Section').add_children(
......@@ -407,20 +408,29 @@ class UnitPublishingTest(ContainerBase):
)
)
),
XBlockFixtureDesc('chapter', 'Unlocked Section', metadata={'start': past_start_date.isoformat()}).add_children(
XBlockFixtureDesc('sequential', 'Unlocked Subsection').add_children(
XBlockFixtureDesc('vertical', 'Unlocked Unit').add_children(
XBlockFixtureDesc('problem', '<problem></problem>', data=self.html_content)
)
)
XBlockFixtureDesc('chapter', 'Unlocked Section',
metadata={'start': past_start_date.isoformat()}).add_children(
XBlockFixtureDesc('sequential', 'Unlocked Subsection').add_children(
XBlockFixtureDesc('vertical', 'Unlocked Unit').add_children(
XBlockFixtureDesc('problem', '<problem></problem>', data=self.html_content)
)
)
),
XBlockFixtureDesc('chapter', 'Section With Locked Unit').add_children(
XBlockFixtureDesc('sequential', 'Subsection With Locked Unit', metadata={'start': past_start_date.isoformat()}).add_children(
XBlockFixtureDesc('vertical', 'Locked Unit', metadata={'visible_to_staff_only': True}).add_children(
XBlockFixtureDesc('discussion', '', data=self.html_content)
)
)
)
XBlockFixtureDesc('sequential', 'Subsection With Locked Unit',
metadata={'start': past_start_date.isoformat()}).add_children(
XBlockFixtureDesc('vertical', 'Locked Unit',
metadata={'visible_to_staff_only': True}).add_children(
XBlockFixtureDesc('discussion', '', data=self.html_content)
)
)
),
XBlockFixtureDesc('chapter', 'Unreleased Section',
metadata={'start': future_start_date.isoformat()}).add_children(
XBlockFixtureDesc('sequential', 'Unreleased Subsection').add_children(
XBlockFixtureDesc('vertical', 'Unreleased Unit')
)
)
)
def test_publishing(self):
......@@ -658,6 +668,25 @@ class UnitPublishingTest(ContainerBase):
self._view_published_version(unit)
self.assertEqual(0, self.courseware.num_xblock_components)
def test_published_not_live(self):
"""
Scenario: The publish title displays correctly for units that are not live
Given I have a published unit with no unpublished changes that releases in the future
When I go to the unit page in Studio
Then the title in the Publish information box is "Published"
And when I add a component to the unit
Then the title in the Publish information box is "Draft (Unpublished changes)"
And when I click the Publish button
Then the title in the Publish information box is "Published"
"""
unit = self.go_to_unit_page('Unreleased Section', 'Unreleased Subsection', 'Unreleased Unit')
self._verify_publish_title(unit, self.PUBLISHED_STATUS)
add_discussion(unit)
self._verify_publish_title(unit, self.DRAFT_STATUS)
unit.publish_action.click()
unit.wait_for_ajax()
self._verify_publish_title(unit, self.PUBLISHED_STATUS)
def _view_published_version(self, unit):
"""
Goes to the published version, then waits for the browser to load the page.
......
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