diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8ad10f8eca20..7f736932fd8b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes, in roughly chronological order, most recent first. Add your entries at or near the top. Include a label indicating the component affected. +Studio: Add edit button to leaf xblocks on the container page. STUD-1306. + Blades: Add LTI context_id parameter. BLD-584. Blades: Update LTI resource_link_id parameter. BLD-768. diff --git a/cms/djangoapps/contentstore/features/common.py b/cms/djangoapps/contentstore/features/common.py index da1e5bf3ebcf..37f0e8eac4f2 100644 --- a/cms/djangoapps/contentstore/features/common.py +++ b/cms/djangoapps/contentstore/features/common.py @@ -350,7 +350,7 @@ def attach_file(filename, sub_path): def upload_file(filename, sub_path=''): attach_file(filename, sub_path) - button_css = '.upload-dialog .action-upload' + button_css = '.wrapper-modal-window-assetupload .action-upload' world.css_click(button_css) diff --git a/cms/djangoapps/contentstore/features/component.py b/cms/djangoapps/contentstore/features/component.py index 0e8b7f9b5ccb..e700c49df685 100644 --- a/cms/djangoapps/contentstore/features/component.py +++ b/cms/djangoapps/contentstore/features/component.py @@ -169,11 +169,11 @@ def change_display_name(step, display_name): world.edit_component_and_select_settings() index = world.get_setting_entry_index(DISPLAY_NAME) world.set_field_value(index, display_name) - world.save_component(step) + world.save_component() @step(u'I unset the display name') def unset_display_name(step): world.edit_component_and_select_settings() world.revert_setting_entry(DISPLAY_NAME) - world.save_component(step) + world.save_component() diff --git a/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py b/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py index 60632f77a7ac..85bffed9d968 100644 --- a/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py +++ b/cms/djangoapps/contentstore/features/component_settings_editor_helpers.py @@ -6,6 +6,7 @@ from terrain.steps import reload_the_page from common import type_in_codemirror from selenium.webdriver.common.keys import Keys +from cms.envs.common import FEATURES @world.absorb @@ -105,9 +106,16 @@ def click_component_from_menu(category, component_type, is_advanced): @world.absorb def edit_component_and_select_settings(): - world.wait_for(lambda _driver: world.css_visible('a.edit-button')) - world.css_click('a.edit-button') - world.css_click('#settings-mode a') + world.edit_component() + world.ensure_settings_visible() + + +@world.absorb +def ensure_settings_visible(): + # Select the 'settings' tab if there is one (it isn't displayed if it is the only option) + settings_button = world.browser.find_by_css('.settings-button') + if len(settings_button) > 0: + world.css_click('.settings-button') @world.absorb @@ -116,6 +124,17 @@ def edit_component(): world.css_click('a.edit-button') +@world.absorb +def select_editor_tab(tab_name): + editor_tabs = world.browser.find_by_css('.editor-tabs a') + expected_tab_text = tab_name.strip().upper() + matching_tabs = [tab for tab in editor_tabs if tab.text.upper() == expected_tab_text] + assert len(matching_tabs) == 1 + tab = matching_tabs[0] + tab.click() + world.wait_for_ajax_complete() + + def enter_xml_in_advanced_problem(step, text): """ Edits an advanced problem (assumes only on page), @@ -123,7 +142,7 @@ def enter_xml_in_advanced_problem(step, text): """ world.edit_component() type_in_codemirror(0, text) - world.save_component(step) + world.save_component() @world.absorb @@ -173,14 +192,14 @@ def verify_all_setting_entries(expected_entries): @world.absorb -def save_component(step): - world.css_click("a.save-button") +def save_component(): + world.css_click("a.action-save") world.wait_for_ajax_complete() @world.absorb def save_component_and_reopen(step): - save_component(step) + save_component() # We have a known issue that modifications are still shown within the edit window after cancel (though) # they are not persisted. Refresh the browser to make sure the changes WERE persisted after Save. reload_the_page(step) @@ -189,7 +208,7 @@ def save_component_and_reopen(step): @world.absorb def cancel_component(step): - world.css_click("a.cancel-button") + world.css_click("a.action-cancel") # We have a known issue that modifications are still shown within the edit window after cancel (though) # they are not persisted. Refresh the browser to make sure the changes were not persisted. reload_the_page(step) diff --git a/cms/djangoapps/contentstore/features/discussion-editor.feature b/cms/djangoapps/contentstore/features/discussion-editor.feature index 17904e88208e..2275da870689 100644 --- a/cms/djangoapps/contentstore/features/discussion-editor.feature +++ b/cms/djangoapps/contentstore/features/discussion-editor.feature @@ -4,13 +4,13 @@ Feature: CMS.Discussion Component Editor Scenario: User can view discussion component metadata Given I have created a Discussion Tag - And I edit and select Settings + And I edit the component Then I see three alphabetized settings and their expected values # Safari doesn't save the name properly @skip_safari Scenario: User can modify display name Given I have created a Discussion Tag - And I edit and select Settings + And I edit the component Then I can modify the display name And my display name change is persisted on save diff --git a/cms/djangoapps/contentstore/features/discussion-editor.py b/cms/djangoapps/contentstore/features/discussion-editor.py index 58dbc6b8dd22..d16279be4d64 100644 --- a/cms/djangoapps/contentstore/features/discussion-editor.py +++ b/cms/djangoapps/contentstore/features/discussion-editor.py @@ -21,3 +21,8 @@ def i_see_only_the_settings_and_values(step): ['Display Name', "Discussion", False], ['Subcategory', "Topic-Level Student-Visible Label", False] ]) + + +@step('I edit the component$') +def i_edit_and_select_settings(_step): + world.edit_component() diff --git a/cms/djangoapps/contentstore/features/html-editor.py b/cms/djangoapps/contentstore/features/html-editor.py index 4a39eb299f74..8b1717e61759 100644 --- a/cms/djangoapps/contentstore/features/html-editor.py +++ b/cms/djangoapps/contentstore/features/html-editor.py @@ -123,7 +123,7 @@ def perform_action_in_plugin(action): @step('I save the page$') def i_click_on_save(step): - world.save_component(step) + world.save_component() @step('the page text contains:') diff --git a/cms/djangoapps/contentstore/features/pages.py b/cms/djangoapps/contentstore/features/pages.py index e86177282d43..43f7bacfefcb 100644 --- a/cms/djangoapps/contentstore/features/pages.py +++ b/cms/djangoapps/contentstore/features/pages.py @@ -44,14 +44,13 @@ def click_edit_or_delete(step, edit_or_delete): @step(u'I change the name to "([^"]*)"$') def change_name(step, new_name): - settings_css = '#settings-mode a' + settings_css = '.settings-button' world.css_click(settings_css) input_css = 'input.setting-input' world.css_fill(input_css, new_name) if world.is_firefox(): world.trigger_event(input_css) - save_button = 'a.save-button' - world.css_click(save_button) + world.save_component() @step(u'I drag the first static page to the last$') diff --git a/cms/djangoapps/contentstore/features/problem-editor.py b/cms/djangoapps/contentstore/features/problem-editor.py index 1c480b2d47e3..9bada6f78cd3 100644 --- a/cms/djangoapps/contentstore/features/problem-editor.py +++ b/cms/djangoapps/contentstore/features/problem-editor.py @@ -286,5 +286,5 @@ def set_weight(weight): def open_high_level_source(): - world.css_click('a.edit-button') + world.edit_component() world.css_click('.launch-latex-compiler > a') diff --git a/cms/djangoapps/contentstore/features/transcripts.py b/cms/djangoapps/contentstore/features/transcripts.py index e198ee3f3253..4a864b602682 100644 --- a/cms/djangoapps/contentstore/features/transcripts.py +++ b/cms/djangoapps/contentstore/features/transcripts.py @@ -209,24 +209,21 @@ def check_text_in_the_captions(_step, text): @step('I see value "([^"]*)" in the field "([^"]*)"$') def check_transcripts_field(_step, values, field_name): - world.click_link_by_text('Advanced') + world.select_editor_tab('Advanced') field_id = '#' + world.browser.find_by_xpath('//label[text()="%s"]' % field_name.strip())[0]['for'] values_list = [i.strip() == world.css_value(field_id) for i in values.split('|')] assert any(values_list) - world.click_link_by_text('Basic') + world.select_editor_tab('Basic') @step('I save changes$') def save_changes(_step): - save_css = 'a.save-button' - world.css_click(save_css) - world.wait_for_ajax_complete() + world.save_component() @step('I open tab "([^"]*)"$') def open_tab(_step, tab_name): - world.click_link_by_text(tab_name.strip()) - world.wait_for_ajax_complete() + world.select_editor_tab(tab_name) @step('I set value "([^"]*)" to the field "([^"]*)"$') diff --git a/cms/djangoapps/contentstore/features/video-editor.py b/cms/djangoapps/contentstore/features/video-editor.py index a7974f020ffb..148c3bc6e4f9 100644 --- a/cms/djangoapps/contentstore/features/video-editor.py +++ b/cms/djangoapps/contentstore/features/video-editor.py @@ -72,7 +72,7 @@ def check_header(self, name, value): def success_upload_file(filename): upload_file(filename, sub_path="uploads/") world.css_has_text('#upload_confirm', 'Success!') - world.is_css_not_present('.wrapper-dialog-assetupload', wait_time=30) + world.is_css_not_present('.wrapper-modal-window-assetupload', wait_time=30) def get_translations_container(): @@ -112,11 +112,10 @@ def set_show_captions(step, setting): # Prevent cookies from overriding course settings world.browser.cookies.delete('hide_captions') - world.css_click('a.edit-button') - world.wait_for(lambda _driver: world.css_visible('a.save-button')) - world.click_link_by_text('Advanced') + world.edit_component() + world.select_editor_tab('Advanced') world.browser.select('Transcript Display', setting) - world.css_click('a.save-button') + world.save_component() @step('when I view the video it (.*) show the captions$') @@ -161,7 +160,7 @@ def correct_video_settings(_step): @step('my video display name change is persisted on save$') def video_name_persisted(step): - world.css_click('a.save-button') + world.save_component() reload_the_page(step) world.wait_for_xmodule() world.edit_component() diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py index e2152d987259..425b33035326 100644 --- a/cms/djangoapps/contentstore/views/component.py +++ b/cms/djangoapps/contentstore/views/component.py @@ -317,14 +317,17 @@ def container_handler(request, tag=None, package_id=None, branch=None, version_g while parent and parent.category != 'sequential': ancestor_xblocks.append(parent) parent = get_parent_xblock(parent) - ancestor_xblocks.reverse() + unit = ancestor_xblocks[0] if ancestor_xblocks else None + unit_publish_state = compute_publish_state(unit) if unit else None + return render_to_response('container.html', { 'context_course': course, 'xblock': xblock, 'xblock_locator': locator, - 'unit': None if not ancestor_xblocks else ancestor_xblocks[0], + 'unit': unit, + 'unit_publish_state': unit_publish_state, 'ancestor_xblocks': ancestor_xblocks, }) else: diff --git a/cms/djangoapps/contentstore/views/helpers.py b/cms/djangoapps/contentstore/views/helpers.py index 946fa1637d37..2141ecc3c578 100644 --- a/cms/djangoapps/contentstore/views/helpers.py +++ b/cms/djangoapps/contentstore/views/helpers.py @@ -7,6 +7,9 @@ __all__ = ['edge', 'event', 'landing'] +EDITING_TEMPLATES = [ + "basic-modal", "modal-button", "edit-xblock-modal", "editor-mode-button", "upload-dialog", "image-modal" +] # points to the temporary course landing page with log in and sign up def landing(request, org, course, coursename): diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index 344c6d5c5731..f27df9c1b5dd 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -34,6 +34,7 @@ from .access import has_course_access from .helpers import _xmodule_recurse +from contentstore.utils import compute_publish_state, PublishState from contentstore.views.preview import get_preview_fragment from edxmako.shortcuts import render_to_string from models.settings.course_grading import CourseGradingModel @@ -224,11 +225,12 @@ def xblock_view_handler(request, package_id, view_name, tag=None, branch=None, v }) elif view_name in ('student_view', 'container_preview'): is_container_view = (view_name == 'container_preview') + component_publish_state = compute_publish_state(component) + is_read_only_view = component_publish_state == PublishState.public # Only show the new style HTML for the container view, i.e. for non-verticals # Note: this special case logic can be removed once the unit page is replaced # with the new container view. - is_read_only_view = is_container_view context = { 'runtime_type': 'studio', 'container_view': is_container_view, diff --git a/cms/djangoapps/contentstore/views/preview.py b/cms/djangoapps/contentstore/views/preview.py index b3e4f0562ecc..17c1548299a7 100644 --- a/cms/djangoapps/contentstore/views/preview.py +++ b/cms/djangoapps/contentstore/views/preview.py @@ -1,7 +1,6 @@ from __future__ import absolute_import import logging -import hashlib from functools import partial from django.conf import settings @@ -170,7 +169,7 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False): """ # Only add the Studio wrapper when on the container page. The unit page will remain as is for now. if context.get('container_view', None) and view == 'student_view': - locator = loc_mapper().translate_location(xblock.course_id, xblock.location) + locator = loc_mapper().translate_location(xblock.course_id, xblock.location, published=False) template_context = { 'xblock_context': context, 'xblock': xblock, diff --git a/cms/djangoapps/contentstore/views/tests/test_container.py b/cms/djangoapps/contentstore/views/tests/test_container.py index 0752ca61410f..3c19efc896f8 100644 --- a/cms/djangoapps/contentstore/views/tests/test_container.py +++ b/cms/djangoapps/contentstore/views/tests/test_container.py @@ -3,7 +3,9 @@ """ from contentstore.tests.utils import CourseTestCase +from contentstore.utils import compute_publish_state, PublishState from contentstore.views.helpers import xblock_studio_url +from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.factories import ItemFactory @@ -26,13 +28,19 @@ def setUp(self): category="video", display_name="My Video") def test_container_html(self): + branch_name = "MITx.999.Robot_Super_Course/branch/draft/block" self._test_html_content( self.child_vertical, - expected_section_tag='
', + branch_name=branch_name, + expected_section_tag=( + '