Commit 537966ab by Tyler Hallada Committed by GitHub

Merge branch 'master' into EDUCATOR-926

parents 1b2fec21 679bd2c6
...@@ -1194,6 +1194,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F ...@@ -1194,6 +1194,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
elif xblock.category == 'sequential': elif xblock.category == 'sequential':
xblock_info.update({ xblock_info.update({
'is_proctored_exam': xblock.is_proctored_exam, 'is_proctored_exam': xblock.is_proctored_exam,
'online_proctoring_rules': settings.PROCTORING_SETTINGS.get('LINK_URLS', {}).get('online_proctoring_rules', {}),
'is_practice_exam': xblock.is_practice_exam, 'is_practice_exam': xblock.is_practice_exam,
'is_time_limited': xblock.is_time_limited, 'is_time_limited': xblock.is_time_limited,
'exam_review_rules': xblock.exam_review_rules, 'exam_review_rules': xblock.exam_review_rules,
......
...@@ -36,6 +36,7 @@ from lms.envs.test import ( ...@@ -36,6 +36,7 @@ from lms.envs.test import (
MEDIA_URL, MEDIA_URL,
COMPREHENSIVE_THEME_DIRS, COMPREHENSIVE_THEME_DIRS,
JWT_AUTH, JWT_AUTH,
REGISTRATION_EXTRA_FIELDS,
) )
# mongo connection settings # mongo connection settings
......
...@@ -42,7 +42,18 @@ ...@@ -42,7 +42,18 @@
<textarea cols="50" maxlength="255" aria-describedby="review-rules-description" <textarea cols="50" maxlength="255" aria-describedby="review-rules-description"
class="review-rules input input-text" autocomplete="off" /> class="review-rules input input-text" autocomplete="off" />
</label> </label>
<p class='field-message' id='review-rules-description'><%- gettext('Specify any additional rules or rule exceptions that the proctoring review team should enforce when reviewing the videos. For example, you could specify that calculators are allowed.') %></p> <% var online_proctoring_rules = xblockInfo.get('online_proctoring_rules'); %>
<p class='field-message' id='review-rules-description'>
<%= edx.HtmlUtils.interpolateHtml(
gettext('Specify any rules or rule exceptions that the proctoring review team should enforce when reviewing the videos. For example, you could specify that calculators are allowed. These specified rules are visible to learners before the learners start the exam, along with the {linkStart}general proctored exam rules{linkEnd}.'),
{
linkStart: edx.HtmlUtils.interpolateHtml(
edx.HtmlUtils.HTML('<a href="{onlineProctoringUrl}" title="{onlineProctoringTitle}">'),
{ onlineProctoringUrl: online_proctoring_rules, onlineProctoringTitle: gettext('General Proctored Exam Rules')}),
linkEnd: edx.HtmlUtils.HTML('</a>')
})
%>
</p>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -7,6 +7,7 @@ import requests ...@@ -7,6 +7,7 @@ import requests
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.http import Http404 from django.http import Http404
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django_countries import countries
from social_core.backends.saml import OID_EDU_PERSON_ENTITLEMENT, SAMLAuth, SAMLIdentityProvider from social_core.backends.saml import OID_EDU_PERSON_ENTITLEMENT, SAMLAuth, SAMLIdentityProvider
from social_core.exceptions import AuthForbidden from social_core.exceptions import AuthForbidden
...@@ -134,6 +135,77 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider): ...@@ -134,6 +135,77 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
'odata_client_id', 'odata_client_id',
) )
# Define the relationships between SAPSF record fields and Open edX logistration fields.
default_field_mapping = {
'username': 'username',
'firstName': 'first_name',
'lastName': 'last_name',
'defaultFullName': 'fullname',
'email': 'email',
'country': 'country',
'city': 'city',
}
# Define a simple mapping to relate SAPSF values to Open edX-compatible values for
# any given field. By default, this only contains the Country field, as SAPSF supplies
# a country name, which has to be translated to a country code.
default_value_mapping = {
'country': {name: code for code, name in countries}
}
# Unfortunately, not everything has a 1:1 name mapping between Open edX and SAPSF, so
# we need some overrides. TODO: Fill in necessary mappings
default_value_mapping.update({
'United States': 'US',
})
def get_registration_fields(self, response):
"""
Get a dictionary mapping registration field names to default values.
"""
field_mapping = self.field_mappings
registration_fields = {edx_name: response['d'].get(odata_name, '') for odata_name, edx_name in field_mapping.items()}
value_mapping = self.value_mappings
for field, value in registration_fields.items():
if field in value_mapping and value in value_mapping[field]:
registration_fields[field] = value_mapping[field][value]
return registration_fields
@property
def field_mappings(self):
"""
Get a dictionary mapping the field names returned in an SAP SuccessFactors
user entity to the field names with which those values should be used in
the Open edX registration form.
"""
overrides = self.conf.get('sapsf_field_mappings', {})
base = self.default_field_mapping.copy()
base.update(overrides)
return base
@property
def value_mappings(self):
"""
Get a dictionary mapping of field names to override objects which each
map values received from SAP SuccessFactors to values expected in the
Open edX platform registration form.
"""
overrides = self.conf.get('sapsf_value_mappings', {})
base = self.default_value_mapping.copy()
for field, override in overrides.items():
if field in base:
base[field].update(override)
else:
base[field] = override[field]
return base
@property
def timeout(self):
"""
The number of seconds OData API requests should wait for a response before failing.
"""
return self.conf.get('odata_api_request_timeout', 10)
@property @property
def sapsf_idp_url(self): def sapsf_idp_url(self):
return self.conf['sapsf_oauth_root_url'] + 'idp' return self.conf['sapsf_oauth_root_url'] + 'idp'
...@@ -187,7 +259,7 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider): ...@@ -187,7 +259,7 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
'token_url': self.sapsf_token_url, 'token_url': self.sapsf_token_url,
'private_key': self.sapsf_private_key, 'private_key': self.sapsf_private_key,
}, },
timeout=10, timeout=self.timeout,
) )
assertion.raise_for_status() assertion.raise_for_status()
assertion = assertion.text assertion = assertion.text
...@@ -199,7 +271,7 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider): ...@@ -199,7 +271,7 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
'grant_type': 'urn:ietf:params:oauth:grant-type:saml2-bearer', 'grant_type': 'urn:ietf:params:oauth:grant-type:saml2-bearer',
'assertion': assertion, 'assertion': assertion,
}, },
timeout=10, timeout=self.timeout,
) )
token.raise_for_status() token.raise_for_status()
token = token.json()['access_token'] token = token.json()['access_token']
...@@ -220,12 +292,14 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider): ...@@ -220,12 +292,14 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
username = details['username'] username = details['username']
try: try:
client = self.get_odata_api_client(user_id=username) client = self.get_odata_api_client(user_id=username)
fields = ','.join(self.field_mappings)
response = client.get( response = client.get(
'{root_url}User(userId=\'{user_id}\')?$select=username,firstName,lastName,defaultFullName,email'.format( '{root_url}User(userId=\'{user_id}\')?$select={fields}'.format(
root_url=self.odata_api_root_url, root_url=self.odata_api_root_url,
user_id=username user_id=username,
fields=fields,
), ),
timeout=10, timeout=self.timeout,
) )
response.raise_for_status() response.raise_for_status()
response = response.json() response = response.json()
...@@ -237,13 +311,7 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider): ...@@ -237,13 +311,7 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
self.odata_company_id, self.odata_company_id,
) )
return details return details
return { return self.get_registration_fields(response)
'username': response['d']['username'],
'first_name': response['d']['firstName'],
'last_name': response['d']['lastName'],
'fullname': response['d']['defaultFullName'],
'email': response['d']['email'],
}
def get_saml_idp_choices(): def get_saml_idp_choices():
......
...@@ -54,7 +54,7 @@ class IntegrationTestMixin(object): ...@@ -54,7 +54,7 @@ class IntegrationTestMixin(object):
self.addCleanup(patcher.stop) self.addCleanup(patcher.stop)
# Override this method in a subclass and enable at least one provider. # Override this method in a subclass and enable at least one provider.
def test_register(self): def test_register(self, **extra_defaults):
# The user goes to the register page, and sees a button to register with the provider: # The user goes to the register page, and sees a button to register with the provider:
provider_register_url = self._check_register_page() provider_register_url = self._check_register_page()
# The user clicks on the Dummy button: # The user clicks on the Dummy button:
...@@ -76,6 +76,8 @@ class IntegrationTestMixin(object): ...@@ -76,6 +76,8 @@ class IntegrationTestMixin(object):
self.assertEqual(form_fields['email']['defaultValue'], self.USER_EMAIL) self.assertEqual(form_fields['email']['defaultValue'], self.USER_EMAIL)
self.assertEqual(form_fields['name']['defaultValue'], self.USER_NAME) self.assertEqual(form_fields['name']['defaultValue'], self.USER_NAME)
self.assertEqual(form_fields['username']['defaultValue'], self.USER_USERNAME) self.assertEqual(form_fields['username']['defaultValue'], self.USER_USERNAME)
for field_name, value in extra_defaults.items():
self.assertEqual(form_fields[field_name]['defaultValue'], value)
registration_values = { registration_values = {
'email': 'email-edited@tpa-test.none', 'email': 'email-edited@tpa-test.none',
'name': 'My Customized Name', 'name': 'My Customized Name',
......
...@@ -309,6 +309,7 @@ class SuccessFactorsIntegrationTest(SamlIntegrationTestUtilities, IntegrationTes ...@@ -309,6 +309,7 @@ class SuccessFactorsIntegrationTest(SamlIntegrationTestUtilities, IntegrationTes
'lastName': 'Smith', 'lastName': 'Smith',
'defaultFullName': 'John Smith', 'defaultFullName': 'John Smith',
'email': 'john@smith.com', 'email': 'john@smith.com',
'country': 'Australia',
} }
}) })
) )
...@@ -331,23 +332,119 @@ class SuccessFactorsIntegrationTest(SamlIntegrationTestUtilities, IntegrationTes ...@@ -331,23 +332,119 @@ class SuccessFactorsIntegrationTest(SamlIntegrationTestUtilities, IntegrationTes
self.USER_USERNAME = "myself" self.USER_USERNAME = "myself"
super(SuccessFactorsIntegrationTest, self).test_register() super(SuccessFactorsIntegrationTest, self).test_register()
@patch.dict('django.conf.settings.REGISTRATION_EXTRA_FIELDS', country='optional')
def test_register_sapsf_metadata_present(self): def test_register_sapsf_metadata_present(self):
""" """
Configure the provider such that it can talk to a mocked-out version of the SAP SuccessFactors Configure the provider such that it can talk to a mocked-out version of the SAP SuccessFactors
API, and ensure that the data it gets that way gets passed to the registration form. API, and ensure that the data it gets that way gets passed to the registration form.
Check that value mappings overrides work in cases where we override a value other than
what we're looking for, and when an empty override is provided (expected behavior is that
existing value maps will be left alone).
""" """
expected_country = 'AU'
provider_settings = {
'sapsf_oauth_root_url': 'http://successfactors.com/oauth/',
'sapsf_private_key': 'fake_private_key_here',
'odata_api_root_url': 'http://api.successfactors.com/odata/v2/',
'odata_company_id': 'NCC1701D',
'odata_client_id': 'TatVotSEiCMteSNWtSOnLanCtBGwNhGB',
}
self._configure_testshib_provider( self._configure_testshib_provider(
identity_provider_type='sap_success_factors', identity_provider_type='sap_success_factors',
metadata_source=TESTSHIB_METADATA_URL, metadata_source=TESTSHIB_METADATA_URL,
other_settings=json.dumps({ other_settings=json.dumps(provider_settings)
)
super(SuccessFactorsIntegrationTest, self).test_register(country=expected_country)
@patch.dict('django.conf.settings.REGISTRATION_EXTRA_FIELDS', country='optional')
def test_register_sapsf_metadata_present_override_relevant_value(self):
"""
Configure the provider such that it can talk to a mocked-out version of the SAP SuccessFactors
API, and ensure that the data it gets that way gets passed to the registration form.
Check that value mappings overrides work in cases where we override a value other than
what we're looking for, and when an empty override is provided (expected behavior is that
existing value maps will be left alone).
"""
value_map = {'country': {'Australia': 'NZ'}}
expected_country = 'NZ'
provider_settings = {
'sapsf_oauth_root_url': 'http://successfactors.com/oauth/', 'sapsf_oauth_root_url': 'http://successfactors.com/oauth/',
'sapsf_private_key': 'fake_private_key_here', 'sapsf_private_key': 'fake_private_key_here',
'odata_api_root_url': 'http://api.successfactors.com/odata/v2/', 'odata_api_root_url': 'http://api.successfactors.com/odata/v2/',
'odata_company_id': 'NCC1701D', 'odata_company_id': 'NCC1701D',
'odata_client_id': 'TatVotSEiCMteSNWtSOnLanCtBGwNhGB', 'odata_client_id': 'TatVotSEiCMteSNWtSOnLanCtBGwNhGB',
}) }
if value_map:
provider_settings['sapsf_value_mappings'] = value_map
self._configure_testshib_provider(
identity_provider_type='sap_success_factors',
metadata_source=TESTSHIB_METADATA_URL,
other_settings=json.dumps(provider_settings)
) )
super(SuccessFactorsIntegrationTest, self).test_register() super(SuccessFactorsIntegrationTest, self).test_register(country=expected_country)
@patch.dict('django.conf.settings.REGISTRATION_EXTRA_FIELDS', country='optional')
def test_register_sapsf_metadata_present_override_other_value(self):
"""
Configure the provider such that it can talk to a mocked-out version of the SAP SuccessFactors
API, and ensure that the data it gets that way gets passed to the registration form.
Check that value mappings overrides work in cases where we override a value other than
what we're looking for, and when an empty override is provided (expected behavior is that
existing value maps will be left alone).
"""
value_map = {'country': {'United States': 'blahfake'}}
expected_country = 'AU'
provider_settings = {
'sapsf_oauth_root_url': 'http://successfactors.com/oauth/',
'sapsf_private_key': 'fake_private_key_here',
'odata_api_root_url': 'http://api.successfactors.com/odata/v2/',
'odata_company_id': 'NCC1701D',
'odata_client_id': 'TatVotSEiCMteSNWtSOnLanCtBGwNhGB',
}
if value_map:
provider_settings['sapsf_value_mappings'] = value_map
self._configure_testshib_provider(
identity_provider_type='sap_success_factors',
metadata_source=TESTSHIB_METADATA_URL,
other_settings=json.dumps(provider_settings)
)
super(SuccessFactorsIntegrationTest, self).test_register(country=expected_country)
@patch.dict('django.conf.settings.REGISTRATION_EXTRA_FIELDS', country='optional')
def test_register_sapsf_metadata_present_empty_value_override(self):
"""
Configure the provider such that it can talk to a mocked-out version of the SAP SuccessFactors
API, and ensure that the data it gets that way gets passed to the registration form.
Check that value mappings overrides work in cases where we override a value other than
what we're looking for, and when an empty override is provided (expected behavior is that
existing value maps will be left alone).
"""
value_map = {'country': {}}
expected_country = 'AU'
provider_settings = {
'sapsf_oauth_root_url': 'http://successfactors.com/oauth/',
'sapsf_private_key': 'fake_private_key_here',
'odata_api_root_url': 'http://api.successfactors.com/odata/v2/',
'odata_company_id': 'NCC1701D',
'odata_client_id': 'TatVotSEiCMteSNWtSOnLanCtBGwNhGB',
}
if value_map:
provider_settings['sapsf_value_mappings'] = value_map
self._configure_testshib_provider(
identity_provider_type='sap_success_factors',
metadata_source=TESTSHIB_METADATA_URL,
other_settings=json.dumps(provider_settings)
)
super(SuccessFactorsIntegrationTest, self).test_register(country=expected_country)
def test_register_http_failure(self): def test_register_http_failure(self):
""" """
......
...@@ -13,10 +13,6 @@ from common.test.acceptance.tests.helpers import select_option_by_value ...@@ -13,10 +13,6 @@ from common.test.acceptance.tests.helpers import select_option_by_value
PROFILE_VISIBILITY_SELECTOR = '#u-field-select-account_privacy option[value="{}"]' PROFILE_VISIBILITY_SELECTOR = '#u-field-select-account_privacy option[value="{}"]'
PROFILE_VISIBILITY_INPUT = '#u-field-select-account_privacy' PROFILE_VISIBILITY_INPUT = '#u-field-select-account_privacy'
FIELD_ICONS = {
'country': 'fa-map-marker',
'language_proficiencies': 'fa-comment',
}
class Badge(PageObject): class Badge(PageObject):
...@@ -214,18 +210,6 @@ class LearnerProfilePage(FieldsMixin, PageObject): ...@@ -214,18 +210,6 @@ class LearnerProfilePage(FieldsMixin, PageObject):
self.wait_for_ajax() self.wait_for_ajax()
return self.q(css='#u-field-select-account_privacy').visible return self.q(css='#u-field-select-account_privacy').visible
def field_icon_present(self, field_id):
"""
Check if an icon is present for a field. Only dropdown fields have icons.
Arguments:
field_id (str): field id
Returns:
True/False
"""
return self.icon_for_field(field_id, FIELD_ICONS[field_id])
def wait_for_public_fields(self): def wait_for_public_fields(self):
""" """
Wait for `country`, `language` and `bio` fields to be visible. Wait for `country`, `language` and `bio` fields to be visible.
......
...@@ -367,8 +367,6 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, AcceptanceTest): ...@@ -367,8 +367,6 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, AcceptanceTest):
profile_page.make_field_editable('country') profile_page.make_field_editable('country')
self.assertEqual(profile_page.mode_for_field('country'), 'edit') self.assertEqual(profile_page.mode_for_field('country'), 'edit')
self.assertTrue(profile_page.field_icon_present('country'))
def test_language_field(self): def test_language_field(self):
""" """
Test behaviour of `Language` field. Test behaviour of `Language` field.
...@@ -396,8 +394,6 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, AcceptanceTest): ...@@ -396,8 +394,6 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, AcceptanceTest):
profile_page.make_field_editable('language_proficiencies') profile_page.make_field_editable('language_proficiencies')
self.assertTrue(profile_page.mode_for_field('language_proficiencies'), 'edit') self.assertTrue(profile_page.mode_for_field('language_proficiencies'), 'edit')
self.assertTrue(profile_page.field_icon_present('language_proficiencies'))
def test_about_me_field(self): def test_about_me_field(self):
""" """
Test behaviour of `About Me` field. Test behaviour of `About Me` field.
......
...@@ -515,15 +515,16 @@ ...@@ -515,15 +515,16 @@
'click .wrapper-u-field': 'startEditing', 'click .wrapper-u-field': 'startEditing',
'click .u-field-placeholder': 'startEditing', 'click .u-field-placeholder': 'startEditing',
'focusout textarea': 'finishEditing', 'focusout textarea': 'finishEditing',
'change textarea': 'adjustTextareaHeight', 'change textarea': 'manageTextareaContentChange',
'keyup textarea': 'adjustTextareaHeight', 'keyup textarea': 'manageTextareaContentChange',
'keydown textarea': 'onKeyDown', 'keydown textarea': 'onKeyDown',
'paste textarea': 'adjustTextareaHeight', 'paste textarea': 'manageTextareaContentChange',
'cut textarea': 'adjustTextareaHeight' 'cut textarea': 'manageTextareaContentChange'
}, },
initialize: function(options) { initialize: function(options) {
_.bindAll(this, 'render', 'onKeyDown', 'adjustTextareaHeight', 'fieldValue', 'saveValue', 'updateView'); _.bindAll(this, 'render', 'onKeyDown', 'adjustTextareaHeight', 'manageTextareaContentChange',
'fieldValue', 'saveValue', 'updateView');
this._super(options); this._super(options);
this.listenTo(this.model, 'change:' + this.options.valueAttribute, this.updateView); this.listenTo(this.model, 'change:' + this.options.valueAttribute, this.updateView);
}, },
...@@ -541,7 +542,8 @@ ...@@ -541,7 +542,8 @@
value: value, value: value,
message: this.helpMessage, message: this.helpMessage,
messagePosition: this.options.messagePosition || 'footer', messagePosition: this.options.messagePosition || 'footer',
placeholderValue: this.options.placeholderValue placeholderValue: this.options.placeholderValue,
maxCharacters: this.options.maxCharacters || ''
})); }));
this.delegateEvents(); this.delegateEvents();
this.title((this.modelValue() || this.mode === 'edit') ? this.title((this.modelValue() || this.mode === 'edit') ?
...@@ -562,12 +564,26 @@ ...@@ -562,12 +564,26 @@
} }
}, },
updateCharCount: function() {
var curCharCount;
// Update character count for textarea
if (this.options.maxCharacters) {
curCharCount = $('#u-field-textarea-' + this.options.valueAttribute).val().length;
$('.u-field-footer .current-char-count').text(curCharCount);
}
},
adjustTextareaHeight: function() { adjustTextareaHeight: function() {
if (this.persistChanges === false) { return; } if (this.persistChanges === false) { return; }
var textarea = this.$('textarea'); var textarea = this.$('textarea');
textarea.css('height', 'auto').css('height', textarea.prop('scrollHeight') + 10); textarea.css('height', 'auto').css('height', textarea.prop('scrollHeight') + 10);
}, },
manageTextareaContentChange: function() {
this.updateCharCount();
this.adjustTextareaHeight();
},
modelValue: function() { modelValue: function() {
var value = this._super(); var value = this._super();
return value ? $.trim(value) : ''; return value ? $.trim(value) : '';
......
...@@ -55,7 +55,6 @@ ...@@ -55,7 +55,6 @@
// base - specific views // base - specific views
@import "views/account-settings"; @import "views/account-settings";
@import "views/learner-profile";
@import 'views/login-register'; @import 'views/login-register';
@import 'views/verification'; @import 'views/verification';
@import 'views/decoupled-verification'; @import 'views/decoupled-verification';
...@@ -69,6 +68,7 @@ ...@@ -69,6 +68,7 @@
// features // features
@import 'features/bookmarks-v1'; @import 'features/bookmarks-v1';
@import 'features/learner-profile';
// search // search
@import 'search/search'; @import 'search/search';
......
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
margin: 0 ($baseline*0.75); margin: 0 ($baseline*0.75);
padding: ($baseline/4); padding: ($baseline/4);
text-align: center; text-align: center;
color: $gray; color: $gray-d1;
} }
.current-page { .current-page {
......
...@@ -25,8 +25,6 @@ ...@@ -25,8 +25,6 @@
} }
.profile-image-field { .profile-image-field {
@include float(left);
button { button {
background: transparent !important; background: transparent !important;
border: none !important; border: none !important;
...@@ -41,13 +39,18 @@ ...@@ -41,13 +39,18 @@
.image-wrapper { .image-wrapper {
width: $profile-image-dimension; width: $profile-image-dimension;
position: relative; position: relative;
margin: auto;
.image-frame { .image-frame {
display: block; display: block;
position: relative; position: relative;
width: $profile-image-dimension; width: $profile-image-dimension;
height: $profile-image-dimension; height: $profile-image-dimension;
border-radius: ($baseline/4); border-radius: ($profile-image-dimension/2);
overflow: hidden;
border: 3px solid $gray-lightest;
margin-top: $baseline*-0.75;
background: $white;
} }
.u-field-upload-button { .u-field-upload-button {
...@@ -55,13 +58,12 @@ ...@@ -55,13 +58,12 @@
top: 0; top: 0;
width: $profile-image-dimension; width: $profile-image-dimension;
height: $profile-image-dimension; height: $profile-image-dimension;
border-radius: ($baseline/4); border-radius: ($profile-image-dimension/2);
border: 2px dashed transparent; border: 2px dashed transparent;
background: rgba(229,241,247, .8); background: rgba(229,241,247, .8);
color: $link-color; color: $link-color;
text-shadow: none; text-shadow: none;
@include transition(all $tmg-f1 ease-in-out 0s); @include transition(all $tmg-f1 ease-in-out 0s);
opacity: 0;
z-index: 6; z-index: 6;
i { i {
...@@ -87,17 +89,20 @@ ...@@ -87,17 +89,20 @@
line-height: 1.3em; line-height: 1.3em;
text-align: center; text-align: center;
z-index: 7; z-index: 7;
color: $base-font-color;
} }
.upload-button-input { .upload-button-input {
position: absolute; position: absolute;
top: -($profile-image-dimension * 2); top: 0;
@include left(0); @include left(0);
width: $profile-image-dimension; width: $profile-image-dimension;
border-radius: ($profile-image-dimension/2);
height: 100%; height: 100%;
cursor: pointer; cursor: pointer;
z-index: 5; z-index: 5;
outline: 0; outline: 0;
opacity: 0;
} }
.u-field-remove-button { .u-field-remove-button {
...@@ -113,6 +118,7 @@ ...@@ -113,6 +118,7 @@
.wrapper-profile { .wrapper-profile {
min-height: 200px; min-height: 200px;
background-color: $gray-lightest;
.ui-loading-indicator { .ui-loading-indicator {
margin-top: 100px; margin-top: 100px;
...@@ -133,7 +139,7 @@ ...@@ -133,7 +139,7 @@
@extend .container; @extend .container;
border: none; border: none;
box-shadow: none; box-shadow: none;
padding: 0 ($baseline*1.5); padding: 0 ($baseline*3);
} }
.u-field-title { .u-field-title {
...@@ -164,53 +170,93 @@ ...@@ -164,53 +170,93 @@
.wrapper-profile-sections { .wrapper-profile-sections {
@extend .container; @extend .container;
padding: 0 ($baseline*1.5); @include padding($baseline*1.5, $baseline*1.5, $baseline*1.5, 0);
min-width: 0;
@media (max-width: $learner-profile-container-flex) { // Switch to map-get($grid-breakpoints,md) for bootstrap
@include margin-left(0);
@include padding($baseline*1.5, 0, $baseline*1.5, 0);
}
}
.profile-header {
@include padding(0, $baseline*2, $baseline, $baseline*3);
.header {
@extend %t-title4;
@extend %t-ultrastrong;
display: inline-block;
}
.subheader {
@extend %t-title6;
}
} }
.wrapper-profile-section-one { .wrapper-profile-section-one {
@include float(left);
@include margin-left($baseline*3);
width: 300px;
background-color: $white;
border-top: 5px solid $blue;
padding-bottom: $baseline;
@media (max-width: $learner-profile-container-flex) { // Switch to map-get($grid-breakpoints,md) for bootstrap
@include margin-left(0);
width: 100%; width: 100%;
display: inline-block; }
margin-top: ($baseline*1.5);
@include margin-left($baseline/2);
} }
.profile-section-one-fields { .profile-section-one-fields {
@include float(left); margin: 0 $baseline/2;
width: flex-grid(4, 12);
@include margin-left($baseline);
.u-field { .u-field {
margin-bottom: ($baseline/4); @extend %t-weight4;
padding-top: 3px; @include padding(0, 0, 0, 3px);
padding-bottom: 3px; color: $base-font-color;
@include padding-left(3px);
}
.u-field-username { .u-field-value {
@extend %t-weight4;
width: calc(100% - 40px);
input[type="text"] { .u-field-value-readonly {
font-weight: 600; @extend %t-weight3;
}
} }
.u-field-value { .u-field-title {
width: 350px; color: $base-font-color;
@extend %t-title4; font-size: $body-font-size;
display: block;
} }
&.u-field-dropdown {
margin-top: $baseline/5;
&:not(.editable-never) {
cursor: pointer;
} }
.u-field-icon { &:not(:last-child) {
display: inline-block; padding-bottom: $baseline/4;
vertical-align: baseline; border-bottom: 1px solid $gray-lighter;
}
}
} }
.u-field-title { &>.u-field {
width: 0; &:not(:first-child) {
font-size: $body-font-size;
color: $base-font-color;
font-weight: $font-light;
margin-bottom: 0;
} }
.u-field-value { &:first-child {
width: 200px; @extend %t-title4;
display: inline-block; @extend %t-weight4;
vertical-align: baseline; font-size: em(24);
}
} }
select { select {
...@@ -230,16 +276,29 @@ ...@@ -230,16 +276,29 @@
} }
} }
.wrapper-profile-section-two { .wrapper-profile-section-container-two {
padding-top: 1em; @include float(left);
width: flex-grid(8, 12); width: calc(100% - 380px);
} max-width: $learner-profile-container-flex; // Switch to map-get($grid-breakpoints,md) for bootstrap
.profile-section-two-fields { @media (max-width: $learner-profile-container-flex) { // Switch to map-get($grid-breakpoints,md) for bootstrap
width: 100%;
margin-top: $baseline;
}
.u-field-textarea { .u-field-textarea {
margin-bottom: ($baseline/2); margin-bottom: ($baseline/2);
padding: ($baseline/2) ($baseline*.75) ($baseline*.75) ($baseline*.75); padding: 0 ($baseline*.75) ($baseline*.75) ($baseline*.75);
.u-field-header {
position: relative;
.u-field-message {
@include right(0);
top: $baseline/4;
position: absolute;
}
}
&.editable-toggle { &.editable-toggle {
cursor: pointer; cursor: pointer;
...@@ -247,22 +306,30 @@ ...@@ -247,22 +306,30 @@
} }
.u-field-title { .u-field-title {
@extend %t-title5; @extend %t-title6;
@extend %t-weight4; @extend %t-weight5;
display: inline-block; display: inline-block;
margin-top: 0; margin-top: 0;
margin-bottom: ($baseline/4); margin-bottom: ($baseline/4);
color: inherit; color: $gray-dark;
width: 100%;
} }
.u-field-value { .u-field-value {
@extend %t-copy-base; @extend %t-copy-base;
width: 100%; width: 100%;
overflow: scroll;
textarea { textarea {
width: 100%; width: 100%;
background-color: transparent; background-color: transparent;
border-radius: 5px;
border-color: $gray-d1;
resize: none;
white-space: pre-line; white-space: pre-line;
outline: 0;
box-shadow: none;
-webkit-appearance: none;
} }
a { a {
...@@ -273,16 +340,22 @@ ...@@ -273,16 +340,22 @@
.u-field-message { .u-field-message {
@include float(right); @include float(right);
width: auto; width: auto;
.message-can-edit {
position: absolute;
}
} }
.u-field.mode-placeholder { .u-field.mode-placeholder {
padding: $baseline; padding: $baseline;
margin: $baseline * 0.75;
border: 2px dashed $gray-l3; border: 2px dashed $gray-l3;
i { i {
font-size: 12px; font-size: 12px;
@include padding-right(5px); @include padding-right(5px);
vertical-align: middle; vertical-align: middle;
color: $gray; color: $base-font-color;
} }
.u-field-title { .u-field-title {
width: 100%; width: 100%;
...@@ -293,7 +366,7 @@ ...@@ -293,7 +366,7 @@
text-align: center; text-align: center;
line-height: 1.5em; line-height: 1.5em;
@extend %t-copy-sub1; @extend %t-copy-sub1;
color: $gray; color: $base-font-color;
} }
} }
...@@ -304,6 +377,28 @@ ...@@ -304,6 +377,28 @@
color: $link-color; color: $link-color;
} }
} }
.wrapper-u-field {
font-size: $body-font-size;
color: $base-font-color;
.u-field-header .u-field-title{
color: $base-font-color;
}
.u-field-footer {
.field-textarea-character-count {
@extend %t-weight1;
@include float(right);
margin-top: $baseline/4;
}
}
}
.profile-private-message {
@include padding-left($baseline*0.75);
line-height: 3.0em;
}
} }
.badge-paging-header { .badge-paging-header {
......
...@@ -223,6 +223,9 @@ $success-color-hover: rgb(0, 129, 0) !default; ...@@ -223,6 +223,9 @@ $success-color-hover: rgb(0, 129, 0) !default;
// ---------------------------- // ----------------------------
// #COLORS- Bootstrap-style // #COLORS- Bootstrap-style
// ---------------------------- // ----------------------------
$gray-dark: #4e5455 !default;
$gray-lighter: #eceeef !default;
$gray-lightest: #f7f7f9 !default;
$state-success-text: $black !default; $state-success-text: $black !default;
$state-success-bg: #dff0d8 !default; $state-success-bg: #dff0d8 !default;
...@@ -545,6 +548,9 @@ $palette-success-border: #b9edb9; ...@@ -545,6 +548,9 @@ $palette-success-border: #b9edb9;
$palette-success-back: #ecfaec; $palette-success-back: #ecfaec;
$palette-success-text: #008100; $palette-success-text: #008100;
// learner profile elements
$learner-profile-container-flex: 768px;
// course elements // course elements
$content-wrapper-bg: $white !default; $content-wrapper-bg: $white !default;
$course-bg-color: $uxpl-grayscale-x-back !default; $course-bg-color: $uxpl-grayscale-x-back !default;
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
border-radius: 3px; border-radius: 3px;
span { span {
color: $gray; color: $gray-d1;
} }
&:hover { &:hover {
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
.u-field-title { .u-field-title {
width: flex-grid(3, 12); width: flex-grid(3, 12);
display: inline-block; display: inline-block;
color: $gray; color: $gray-d1;
vertical-align: top; vertical-align: top;
margin-bottom: 0; margin-bottom: 0;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
.header-global { .header-global {
@extend %ui-depth1; @extend %ui-depth1;
border-bottom: 2px solid $header-border-color; border-bottom: 1px solid $header-border-color;
box-shadow: 0 1px 5px 0 $shadow-l1; box-shadow: 0 1px 5px 0 $shadow-l1;
background: $header-bg; background: $header-bg;
position: relative; position: relative;
......
<% if (editable !== 'never') { %> <% if (title && titleVisible) { %>
<% if (title && titleVisible) { %>
<label class="u-field-title" for="u-field-select-<%- id %>"> <label class="u-field-title" for="u-field-select-<%- id %>">
<%- title %> <%- title %>
</label> </label>
<% } else { %> <% } else { %>
<label class="sr" for="u-field-select-<%- id %>"> <label class="sr" for="u-field-select-<%- id %>">
<%- screenReaderTitle %> <%- screenReaderTitle %>
</label> </label>
<% } %>
<% } %> <% } %>
<% if (iconName) { %> <% if (editable !== 'never') { %>
<% if (iconName) { %>
<span class="u-field-icon icon fa <%- iconName %> fa-fw" aria-hidden="true"></span> <span class="u-field-icon icon fa <%- iconName %> fa-fw" aria-hidden="true"></span>
<% } %>
<% } %> <% } %>
<span class="u-field-value"> <span class="u-field-value">
......
<div class="u-field-value field"> <div class="u-field-value field">
<% if (editable !== 'never') { %>
<% if (title && titleVisible) { %> <% if (title && titleVisible) { %>
<label class="u-field-title field-label" for="u-field-select-<%- id %>"> <label class="u-field-title field-label" for="u-field-select-<%- id %>">
<%- title %> <%- title %>
...@@ -9,7 +8,6 @@ ...@@ -9,7 +8,6 @@
<%- screenReaderTitle %> <%- screenReaderTitle %>
</label> </label>
<% } %> <% } %>
<% } %>
<% if (iconName) { %> <% if (iconName) { %>
<span class="u-field-icon icon fa <%- iconName %> fa-fw" aria-hidden="true"></span> <span class="u-field-icon icon fa <%- iconName %> fa-fw" aria-hidden="true"></span>
......
<% if (title) { %> <% if (title) { %>
<span class="u-field-title" aria-hidden="true"><%- title %></span> <span class="u-field-title" aria-hidden="true"><%- title %></span>
<% } %> <% } %>
<span class="sr" for="u-field-value-<%- id %>"><%- screenReaderTitle %></span> <span class="sr" for="u-field-value-<%- id %>"><%- screenReaderTitle %></span>
<span class="u-field-value" id="u-field-value-<%- id %>" aria-describedby="u-field-message-<%- id %>"><%- value %></span> <span class="u-field-value" id="u-field-value-<%- id %>" aria-describedby="u-field-message-<%- id %>"><%- value %></span>
<span class="u-field-message" id="u-field-message-<%- id %>"> <span class="u-field-message" id="u-field-message-<%- id %>">
......
<div class="wrapper-u-field"> <div class="wrapper-u-field" role="group">
<div class="u-field-header"> <div class="u-field-header">
<% if (mode === 'edit') { %> <% if (mode === 'edit') { %>
<label class="u-field-title" for="u-field-textarea-<%- id %>" id="u-field-title-<%- id %>"></label> <label class="u-field-title" for="u-field-textarea-<%- id %>" id="u-field-title-<%- id %>"></label>
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<div class="u-field-value" id="u-field-value-<%- id %>" <div class="u-field-value" id="u-field-value-<%- id %>"
<% if (mode === 'edit') { %> <% if (mode === 'edit') { %>
aria-labelledby="u-field-title-<%- id %>"><textarea id="u-field-textarea-<%- id %>" rows="4" aria-labelledby="u-field-title-<%- id %>"><textarea maxlength="<%- maxCharacters%>" id="u-field-textarea-<%- id %>" rows="4"
<% if (message) { %> <% if (message) { %>
aria-describedby="u-field-message-help-<%- id %>" aria-describedby="u-field-message-help-<%- id %>"
<% } %> <% } %>
...@@ -43,5 +43,20 @@ ...@@ -43,5 +43,20 @@
<span class="u-field-message-help" id="u-field-message-help-<%- id %>"> <%- message %></span> <span class="u-field-message-help" id="u-field-message-help-<%- id %>"> <%- message %></span>
</span> </span>
<% } %> <% } %>
<% if (mode === 'edit' && maxCharacters) { %>
<div class="field-textarea-character-count">
<%=
HtmlUtils.interpolateHtml(
gettext('{currentCountOpeningTag}{currentCharacterCount}{currentCountClosingTag} of {maxCharacters}'),
{
currentCountOpeningTag: HtmlUtils.HTML('<span class="current-char-count">'),
currentCountClosingTag: HtmlUtils.HTML('</span>'),
currentCharacterCount: value.length,
maxCharacters: maxCharacters
}
)
%>
</div>
<% } %>
</div> </div>
</div> </div>
...@@ -20,8 +20,8 @@ ...@@ -20,8 +20,8 @@
</form> </form>
</div> </div>
<div class="form-actions"> <div class="form-actions">
<a class="btn btn-brand action-resume-course" href="${resume_course_url}"> <a class="btn btn-brand action-resume-course" href="/courses/course-v1:edX+DemoX+Demo_Course/courseware/19a30717eff543078a5d94ae9d6c18a5/">
Start Course <span data-action-type="start">Start Course</span>
</a> </a>
</div> </div>
</div> </div>
......
...@@ -2,6 +2,19 @@ ...@@ -2,6 +2,19 @@
export class CourseHome { // eslint-disable-line import/prefer-default-export export class CourseHome { // eslint-disable-line import/prefer-default-export
constructor(options) { constructor(options) {
// Logging for 'Resume Course' or 'Start Course' button click
const $resumeCourseLink = $(options.resumeCourseLink);
$resumeCourseLink.on('click', (event) => {
const eventType = $resumeCourseLink.find('span').data('action-type');
Logger.log(
'edx.course.home.resume_course.clicked',
{
event_type: eventType,
url: event.currentTarget.href,
},
);
});
// Logging for course tool click events // Logging for course tool click events
const $courseToolLink = $(options.courseToolLink); const $courseToolLink = $(options.courseToolLink);
$courseToolLink.on('click', (event) => { $courseToolLink.on('click', (event) => {
......
...@@ -9,11 +9,24 @@ describe('Course Home factory', () => { ...@@ -9,11 +9,24 @@ describe('Course Home factory', () => {
beforeEach(() => { beforeEach(() => {
loadFixtures('course_experience/fixtures/course-home-fragment.html'); loadFixtures('course_experience/fixtures/course-home-fragment.html');
home = new CourseHome({ home = new CourseHome({
resumeCourseLink: '.action-resume-course',
courseToolLink: '.course-tool-link', courseToolLink: '.course-tool-link',
}); });
spyOn(Logger, 'log'); spyOn(Logger, 'log');
}); });
it('sends an event when resume or start course is clicked', () => {
$('.action-resume-course').click();
expect(Logger.log).toHaveBeenCalledWith(
'edx.course.home.resume_course.clicked',
{
event_type: 'start',
url: `http://${window.location.host}/courses/course-v1:edX+DemoX+Demo_Course/courseware` +
'/19a30717eff543078a5d94ae9d6c18a5/',
},
);
});
it('sends an event when an course tool is clicked', () => { it('sends an event when an course tool is clicked', () => {
const courseToolNames = document.querySelectorAll('.course-tool-link'); const courseToolNames = document.querySelectorAll('.course-tool-link');
for (let i = 0; i < courseToolNames.length; i += 1) { for (let i = 0; i < courseToolNames.length; i += 1) {
......
...@@ -45,9 +45,9 @@ from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, SHOW_REV ...@@ -45,9 +45,9 @@ from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, SHOW_REV
% if resume_course_url: % if resume_course_url:
<a class="btn btn-brand action-resume-course" href="${resume_course_url}"> <a class="btn btn-brand action-resume-course" href="${resume_course_url}">
% if has_visited_course: % if has_visited_course:
${_("Resume Course")} <span data-action-type="resume">${_("Resume Course")}</span>
% else: % else:
${_("Start Course")} <span data-action-type="start">${_("Start Course")}</span>
% endif % endif
</a> </a>
% endif % endif
...@@ -109,6 +109,7 @@ from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, SHOW_REV ...@@ -109,6 +109,7 @@ from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, SHOW_REV
<%static:webpack entry="CourseHome"> <%static:webpack entry="CourseHome">
new CourseHome({ new CourseHome({
resumeCourseLink: ".action-resume-course",
courseToolLink: ".course-tool-link", courseToolLink: ".course-tool-link",
}); });
</%static:webpack> </%static:webpack>
......
...@@ -114,11 +114,8 @@ class CourseHomeMessageFragmentView(EdxFragmentView): ...@@ -114,11 +114,8 @@ class CourseHomeMessageFragmentView(EdxFragmentView):
CourseHomeMessages.register_info_message( CourseHomeMessages.register_info_message(
request, request,
Text(_( Text(_(
"{add_reminder_open_tag}Don't forget to add a calendar reminder!{add_reminder_close_tag}." "Don't forget to add a calendar reminder!"
)).format( )),
add_reminder_open_tag='',
add_reminder_close_tag=''
),
title=Text("Course starts in {days_until_start_string} on {course_start_date}.").format( title=Text("Course starts in {days_until_start_string} on {course_start_date}.").format(
days_until_start_string=course_start_data['days_until_start_string'], days_until_start_string=course_start_data['days_until_start_string'],
course_start_date=course_start_data['course_start_date'] course_start_date=course_start_data['course_start_date']
......
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
var accountPrivacyFieldView, var accountPrivacyFieldView,
profileImageFieldView, profileImageFieldView,
usernameFieldView, usernameFieldView,
nameFieldView,
sectionOneFieldViews, sectionOneFieldViews,
sectionTwoFieldViews, sectionTwoFieldViews,
BadgeCollection, BadgeCollection,
...@@ -65,10 +66,7 @@ ...@@ -65,10 +66,7 @@
required: true, required: true,
editable: 'always', editable: 'always',
showMessages: false, showMessages: false,
title: StringUtils.interpolate( title: gettext('Profile Visibility:'),
gettext('{platform_name} learners can see my:'),
{platform_name: options.platform_name}
),
valueAttribute: 'account_privacy', valueAttribute: 'account_privacy',
options: [ options: [
['private', gettext('Limited Profile')], ['private', gettext('Limited Profile')],
...@@ -97,29 +95,37 @@ ...@@ -97,29 +95,37 @@
helpMessage: '' helpMessage: ''
}); });
nameFieldView = new FieldsView.ReadonlyFieldView({
model: accountSettingsModel,
screenReaderTitle: gettext('Full Name'),
valueAttribute: 'name',
helpMessage: ''
});
sectionOneFieldViews = [ sectionOneFieldViews = [
new FieldsView.DropdownFieldView({ new FieldsView.DropdownFieldView({
title: gettext('Location'),
titleVisible: true,
model: accountSettingsModel, model: accountSettingsModel,
screenReaderTitle: gettext('Country'), screenReaderTitle: gettext('Country'),
titleVisible: false,
required: true, required: true,
editable: editable, editable: editable,
showMessages: false, showMessages: false,
iconName: 'fa-map-marker',
placeholderValue: gettext('Add Country'), placeholderValue: gettext('Add Country'),
valueAttribute: 'country', valueAttribute: 'country',
options: options.country_options, options: options.country_options,
helpMessage: '', helpMessage: '',
persistChanges: true persistChanges: true
}), }),
new AccountSettingsFieldViews.LanguageProficienciesFieldView({ new AccountSettingsFieldViews.LanguageProficienciesFieldView({
title: gettext('Language'),
titleVisible: true,
model: accountSettingsModel, model: accountSettingsModel,
screenReaderTitle: gettext('Preferred Language'), screenReaderTitle: gettext('Preferred Language'),
titleVisible: false,
required: false, required: false,
editable: editable, editable: editable,
showMessages: false, showMessages: false,
iconName: 'fa-comment',
placeholderValue: gettext('Add language'), placeholderValue: gettext('Add language'),
valueAttribute: 'language_proficiencies', valueAttribute: 'language_proficiencies',
options: options.language_options, options: options.language_options,
...@@ -139,7 +145,8 @@ ...@@ -139,7 +145,8 @@
valueAttribute: 'bio', valueAttribute: 'bio',
helpMessage: '', helpMessage: '',
persistChanges: true, persistChanges: true,
messagePosition: 'header' messagePosition: 'header',
maxCharacters: 300
}) })
]; ];
...@@ -172,9 +179,11 @@ ...@@ -172,9 +179,11 @@
accountPrivacyFieldView: accountPrivacyFieldView, accountPrivacyFieldView: accountPrivacyFieldView,
profileImageFieldView: profileImageFieldView, profileImageFieldView: profileImageFieldView,
usernameFieldView: usernameFieldView, usernameFieldView: usernameFieldView,
nameFieldView: nameFieldView,
sectionOneFieldViews: sectionOneFieldViews, sectionOneFieldViews: sectionOneFieldViews,
sectionTwoFieldViews: sectionTwoFieldViews, sectionTwoFieldViews: sectionTwoFieldViews,
badgeListContainer: badgeListContainer badgeListContainer: badgeListContainer,
platformName: options.platform_name
}); });
getProfileVisibility = function() { getProfileVisibility = function() {
......
/* eslint-disable vars-on-top */ /* eslint-disable vars-on-top */
define( define(
[ [
'gettext',
'backbone', 'backbone',
'jquery', 'jquery',
'underscore', 'underscore',
...@@ -18,7 +19,7 @@ define( ...@@ -18,7 +19,7 @@ define(
'js/student_account/views/account_settings_fields', 'js/student_account/views/account_settings_fields',
'js/views/message_banner' 'js/views/message_banner'
], ],
function(Backbone, $, _, PagingCollection, AjaxHelpers, TemplateHelpers, Helpers, LearnerProfileHelpers, function(gettext, Backbone, $, _, PagingCollection, AjaxHelpers, TemplateHelpers, Helpers, LearnerProfileHelpers,
FieldViews, UserAccountModel, AccountPreferencesModel, LearnerProfileFields, LearnerProfileView, FieldViews, UserAccountModel, AccountPreferencesModel, LearnerProfileFields, LearnerProfileView,
BadgeListContainer, AccountSettingsFieldViews, MessageBannerView) { BadgeListContainer, AccountSettingsFieldViews, MessageBannerView) {
'use strict'; 'use strict';
...@@ -73,13 +74,19 @@ define( ...@@ -73,13 +74,19 @@ define(
helpMessage: '' helpMessage: ''
}); });
var nameFieldView = new FieldViews.ReadonlyFieldView({
model: accountSettingsModel,
valueAttribute: 'name',
helpMessage: ''
});
var sectionOneFieldViews = [ var sectionOneFieldViews = [
new FieldViews.DropdownFieldView({ new FieldViews.DropdownFieldView({
title: gettext('Location'),
model: accountSettingsModel, model: accountSettingsModel,
required: false, required: false,
editable: editable, editable: editable,
showMessages: false, showMessages: false,
iconName: 'fa-map-marker',
placeholderValue: '', placeholderValue: '',
valueAttribute: 'country', valueAttribute: 'country',
options: Helpers.FIELD_OPTIONS, options: Helpers.FIELD_OPTIONS,
...@@ -87,11 +94,11 @@ define( ...@@ -87,11 +94,11 @@ define(
}), }),
new AccountSettingsFieldViews.LanguageProficienciesFieldView({ new AccountSettingsFieldViews.LanguageProficienciesFieldView({
title: gettext('Language'),
model: accountSettingsModel, model: accountSettingsModel,
required: false, required: false,
editable: editable, editable: editable,
showMessages: false, showMessages: false,
iconName: 'fa-comment',
placeholderValue: 'Add language', placeholderValue: 'Add language',
valueAttribute: 'language_proficiencies', valueAttribute: 'language_proficiencies',
options: Helpers.FIELD_OPTIONS, options: Helpers.FIELD_OPTIONS,
...@@ -131,6 +138,7 @@ define( ...@@ -131,6 +138,7 @@ define(
preferencesModel: accountPreferencesModel, preferencesModel: accountPreferencesModel,
accountPrivacyFieldView: accountPrivacyFieldView, accountPrivacyFieldView: accountPrivacyFieldView,
usernameFieldView: usernameFieldView, usernameFieldView: usernameFieldView,
nameFieldView: nameFieldView,
profileImageFieldView: profileImageFieldView, profileImageFieldView: profileImageFieldView,
sectionOneFieldViews: sectionOneFieldViews, sectionOneFieldViews: sectionOneFieldViews,
sectionTwoFieldViews: sectionTwoFieldViews, sectionTwoFieldViews: sectionTwoFieldViews,
......
...@@ -65,7 +65,7 @@ define( ...@@ -65,7 +65,7 @@ define(
view.render(); view.render();
var bio = view.$el.find('.u-field-bio'); var bio = view.$el.find('.u-field-bio');
expect(bio.length).toBe(0); expect(bio.length).toBe(0);
var msg = view.$el.find('span.profile-private--message'); var msg = view.$el.find('span.profile-private-message');
expect(msg.length).toBe(1); expect(msg.length).toBe(1);
expect(_.count(msg.html(), messageString)).toBeTruthy(); expect(_.count(msg.html(), messageString)).toBeTruthy();
}; };
......
...@@ -41,11 +41,12 @@ define(['underscore', 'URI', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers' ...@@ -41,11 +41,12 @@ define(['underscore', 'URI', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'
var expectSectionOneTobeRendered = function(learnerProfileView) { var expectSectionOneTobeRendered = function(learnerProfileView) {
var sectionOneFieldElements = $(learnerProfileView.$('.wrapper-profile-section-one')).find('.u-field'); var sectionOneFieldElements = $(learnerProfileView.$('.wrapper-profile-section-one')).find('.u-field');
expect(sectionOneFieldElements.length).toBe(4); expect(sectionOneFieldElements.length).toBe(5);
expectProfileElementContainsField(sectionOneFieldElements[0], learnerProfileView.options.profileImageFieldView); expectProfileElementContainsField(sectionOneFieldElements[0], learnerProfileView.options.profileImageFieldView);
expectProfileElementContainsField(sectionOneFieldElements[1], learnerProfileView.options.usernameFieldView); expectProfileElementContainsField(sectionOneFieldElements[1], learnerProfileView.options.usernameFieldView);
expectProfileElementContainsField(sectionOneFieldElements[2], learnerProfileView.options.nameFieldView);
_.each(_.rest(sectionOneFieldElements, 2), function(sectionFieldElement, fieldIndex) { _.each(_.rest(sectionOneFieldElements, 3), function(sectionFieldElement, fieldIndex) {
expectProfileElementContainsField( expectProfileElementContainsField(
sectionFieldElement, sectionFieldElement,
learnerProfileView.options.sectionOneFieldViews[fieldIndex] learnerProfileView.options.sectionOneFieldViews[fieldIndex]
...@@ -89,10 +90,10 @@ define(['underscore', 'URI', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers' ...@@ -89,10 +90,10 @@ define(['underscore', 'URI', 'edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers'
); );
if (othersProfile) { if (othersProfile) {
expect($('.profile-private--message').text()) expect($('.profile-private-message').text())
.toBe('This learner is currently sharing a limited profile.'); .toBe('This learner is currently sharing a limited profile.');
} else { } else {
expect($('.profile-private--message').text()).toBe('You are currently sharing a limited profile.'); expect($('.profile-private-message').text()).toBe('You are currently sharing a limited profile.');
} }
}; };
......
...@@ -6,9 +6,10 @@ ...@@ -6,9 +6,10 @@
'gettext', 'jquery', 'underscore', 'backbone', 'edx-ui-toolkit/js/utils/html-utils', 'gettext', 'jquery', 'underscore', 'backbone', 'edx-ui-toolkit/js/utils/html-utils',
'common/js/components/views/tabbed_view', 'common/js/components/views/tabbed_view',
'learner_profile/js/views/section_two_tab', 'learner_profile/js/views/section_two_tab',
'text!learner_profile/templates/learner_profile.underscore' 'text!learner_profile/templates/learner_profile.underscore',
'edx-ui-toolkit/js/utils/string-utils'
], ],
function(gettext, $, _, Backbone, HtmlUtils, TabbedView, SectionTwoTab, learnerProfileTemplate) { function(gettext, $, _, Backbone, HtmlUtils, TabbedView, SectionTwoTab, learnerProfileTemplate, StringUtils) {
var LearnerProfileView = Backbone.View.extend({ var LearnerProfileView = Backbone.View.extend({
initialize: function(options) { initialize: function(options) {
...@@ -53,10 +54,19 @@ ...@@ -53,10 +54,19 @@
ownProfile: this.options.ownProfile ownProfile: this.options.ownProfile
}); });
HtmlUtils.setHtml(this.$el, HtmlUtils.template(learnerProfileTemplate)({ HtmlUtils.setHtml(this.$el, HtmlUtils.template(learnerProfileTemplate)({
username: self.options.accountSettingsModel.get('username'), username: self.options.accountSettingsModel.get('username'),
name: self.options.accountSettingsModel.get('name'),
ownProfile: self.options.ownProfile, ownProfile: self.options.ownProfile,
showFullProfile: self.showFullProfile() showFullProfile: self.showFullProfile(),
profile_header: gettext('My Profile'),
profile_subheader:
StringUtils.interpolate(
gettext('Build out your profile to personalize your identity on {platform_name}.'), {
platform_name: self.options.platformName
}
)
})); }));
this.renderFields(); this.renderFields();
...@@ -98,7 +108,7 @@ ...@@ -98,7 +108,7 @@
Backbone.history.start(); Backbone.history.start();
} }
} else { } else {
this.$el.find('.account-settings-container').append(this.sectionTwoView.render().el); this.$el.find('.wrapper-profile-section-container-two').append(this.sectionTwoView.render().el);
} }
return this; return this;
}, },
...@@ -120,6 +130,10 @@ ...@@ -120,6 +130,10 @@
fieldView.delegateEvents(); fieldView.delegateEvents();
} }
// Do not show name when in limited mode or no name has been set
if (this.showFullProfile() && this.options.accountSettingsModel.get('name')) {
this.$('.profile-section-one-fields').append(this.options.nameFieldView.render().el);
}
this.$('.profile-section-one-fields').append(this.options.usernameFieldView.render().el); this.$('.profile-section-one-fields').append(this.options.usernameFieldView.render().el);
imageView = this.options.profileImageFieldView; imageView = this.options.profileImageFieldView;
......
<div class="profile <%- ownProfile ? 'profile-self' : 'profile-other' %>"> <div class="profile <%- ownProfile ? 'profile-self' : 'profile-other' %>">
<div class="wrapper-profile-field-account-privacy"></div> <div class="wrapper-profile-field-account-privacy"></div>
<div class="wrapper-profile-sections account-settings-container"> <div class="wrapper-profile-sections account-settings-container">
<% if (ownProfile) { %>
<div class="profile-header">
<div class="header"> <%- profile_header %></div>
<div class="subheader"> <%- profile_subheader %></div>
</div>
<% } %>
<div class="wrapper-profile-section-container-one">
<div class="wrapper-profile-section-one"> <div class="wrapper-profile-section-one">
<div class="profile-image-field"> <div class="profile-image-field">
</div> </div>
...@@ -12,4 +19,7 @@ ...@@ -12,4 +19,7 @@
<span class="copy"><%- gettext("An error occurred. Try loading the page again.") %></span> <span class="copy"><%- gettext("An error occurred. Try loading the page again.") %></span>
</div> </div>
</div> </div>
<div class="wrapper-profile-section-container-two">
</div>
</div>
</div> </div>
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
<div class="field-container"></div> <div class="field-container"></div>
<% if (!showFullProfile) { %> <% if (!showFullProfile) { %>
<% if(ownProfile) { %> <% if(ownProfile) { %>
<span class="profile-private--message" tabindex="0"><%- gettext("You are currently sharing a limited profile.") %></span> <span class="profile-private-message"><%- gettext("You are currently sharing a limited profile.") %></span>
<% } else { %> <% } else { %>
<span class="profile-private--message" tabindex="0"><%- gettext("This learner is currently sharing a limited profile.") %></span> <span class="profile-private-message"><%- gettext("This learner is currently sharing a limited profile.") %></span>
<% } %> <% } %>
<% } %> <% } %>
</div> </div>
\ No newline at end of file
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
# For Harvard courses: # For Harvard courses:
-e git+https://github.com/gsehub/xblock-mentoring.git@4d1cce78dc232d5da6ffd73817b5c490e87a6eee#egg=xblock-mentoring -e git+https://github.com/gsehub/xblock-mentoring.git@4d1cce78dc232d5da6ffd73817b5c490e87a6eee#egg=xblock-mentoring
git+https://github.com/open-craft/problem-builder.git@v2.6.10#egg=xblock-problem-builder==2.6.10 git+https://github.com/open-craft/problem-builder.git@v2.6.5#egg=xblock-problem-builder==2.6.5
# Oppia XBlock # Oppia XBlock
-e git+https://github.com/oppia/xblock.git@9f6b95b7eb7dbabb96b77198a3202604f96adf65#egg=oppia-xblock -e git+https://github.com/oppia/xblock.git@9f6b95b7eb7dbabb96b77198a3202604f96adf65#egg=oppia-xblock
......
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