Complete Order History tab for students on account settings page.
ECOM-2361
This commit is contained in:
64
common/djangoapps/terrain/stubs/ecommerce.py
Normal file
64
common/djangoapps/terrain/stubs/ecommerce.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Stub implementation of ecommerce service for acceptance tests
|
||||
"""
|
||||
|
||||
import re
|
||||
import urlparse
|
||||
from .http import StubHttpRequestHandler, StubHttpService
|
||||
|
||||
|
||||
class StubEcommerceServiceHandler(StubHttpRequestHandler): # pylint: disable=missing-docstring
|
||||
|
||||
def do_GET(self): # pylint: disable=invalid-name, missing-docstring
|
||||
pattern_handlers = {
|
||||
'/api/v2/orders/$': self.get_orders_list,
|
||||
}
|
||||
if self.match_pattern(pattern_handlers):
|
||||
return
|
||||
self.send_response(404, content='404 Not Found')
|
||||
|
||||
def match_pattern(self, pattern_handlers):
|
||||
"""
|
||||
Find the correct handler method given the path info from the HTTP request.
|
||||
"""
|
||||
path = urlparse.urlparse(self.path).path
|
||||
for pattern in pattern_handlers:
|
||||
match = re.match(pattern, path)
|
||||
if match:
|
||||
pattern_handlers[pattern](**match.groupdict())
|
||||
return True
|
||||
return None
|
||||
|
||||
def get_orders_list(self):
|
||||
"""
|
||||
Stubs the orders list endpoint.
|
||||
"""
|
||||
orders = {
|
||||
'results': [
|
||||
{
|
||||
'status': 'Complete',
|
||||
'number': 'Edx-123',
|
||||
'total_excl_tax': '100.0',
|
||||
'date_placed': '2016-04-21T23:14:23Z',
|
||||
'lines': [
|
||||
{
|
||||
'title': 'Test Course',
|
||||
'product': {
|
||||
'attribute_values': [
|
||||
{
|
||||
'name': 'certificate_type',
|
||||
'value': 'verified'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
orders = self.server.config.get('orders', orders)
|
||||
self.send_json_response(orders)
|
||||
|
||||
|
||||
class StubEcommerceService(StubHttpService): # pylint: disable=missing-docstring
|
||||
HANDLER_CLASS = StubEcommerceServiceHandler
|
||||
@@ -4,7 +4,9 @@ Command-line utility to start a stub service.
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
|
||||
from .comments import StubCommentsService
|
||||
from .ecommerce import StubEcommerceService
|
||||
from .xqueue import StubXQueueService
|
||||
from .youtube import StubYouTubeService
|
||||
from .lti import StubLtiService
|
||||
@@ -23,6 +25,7 @@ SERVICES = {
|
||||
'video': VideoSourceHttpService,
|
||||
'edxnotes': StubEdxNotesService,
|
||||
'programs': StubProgramsService,
|
||||
'ecommerce': StubEcommerceService,
|
||||
}
|
||||
|
||||
# Log to stdout, including debug messages
|
||||
|
||||
@@ -63,3 +63,19 @@ class AccountSettingsPage(FieldsMixin, PageObject):
|
||||
Switch between the different account settings tabs.
|
||||
"""
|
||||
self.q(css='#{}'.format(tab_id)).click()
|
||||
|
||||
@property
|
||||
def is_order_history_tab_visible(self):
|
||||
""" Check if tab with the name "Order History" is visible."""
|
||||
return self.q(css='.u-field-orderHistory').visible
|
||||
|
||||
def get_value_of_order_history_row_item(self, field_id, field_name):
|
||||
""" Return the text value of the provided order field name."""
|
||||
query = self.q(css='.u-field-{} .u-field-order-{}'.format(field_id, field_name))
|
||||
return query.text[0] if query.present else None
|
||||
|
||||
def order_button_is_visible(self, field_id):
|
||||
""" Check that if hovering over the order history row shows the
|
||||
order detail link or not.
|
||||
"""
|
||||
return self.q(css='.u-field-{} .u-field-{}'.format(field_id, 'link')).visible
|
||||
|
||||
@@ -444,6 +444,28 @@ class AccountSettingsPageTest(AccountSettingsTestMixin, WebAppTest):
|
||||
self.assertEqual(self.account_settings_page.title_for_field(field_id), title)
|
||||
self.assertEqual(self.account_settings_page.link_title_for_link_field(field_id), link_title)
|
||||
|
||||
def test_order_history(self):
|
||||
"""
|
||||
Test that we can see orders on Order History tab.
|
||||
"""
|
||||
# switch to "Order History" tab
|
||||
self.account_settings_page.switch_account_settings_tabs('orders-tab')
|
||||
# verify that we are on correct tab
|
||||
self.assertTrue(self.account_settings_page.is_order_history_tab_visible)
|
||||
|
||||
expected_order_data = {
|
||||
'title': 'Test Course',
|
||||
'date': 'Date Placed:\nApr 21, 2016',
|
||||
'price': 'Cost:\n$100.0',
|
||||
'number': 'Order Number:\nEdx-123'
|
||||
}
|
||||
for field_name, value in expected_order_data.iteritems():
|
||||
self.assertEqual(
|
||||
self.account_settings_page.get_value_of_order_history_row_item('order-Edx-123', field_name), value
|
||||
)
|
||||
|
||||
self.assertTrue(self.account_settings_page.order_button_is_visible('order-Edx-123'))
|
||||
|
||||
|
||||
@attr('a11y')
|
||||
class AccountSettingsA11yTest(AccountSettingsTestMixin, WebAppTest):
|
||||
|
||||
10
common/test/db_fixtures/commerce_config.json
Normal file
10
common/test/db_fixtures/commerce_config.json
Normal file
@@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "commerce.commerceconfiguration",
|
||||
"fields": {
|
||||
"enabled": 1,
|
||||
"change_date": "2016-04-21 10:19:32.034856"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -189,6 +189,10 @@ INSTALLED_APPS += ('coursewarehistoryextended',)
|
||||
|
||||
BADGING_BACKEND = 'lms.djangoapps.badges.backends.tests.dummy_backend.DummyBackend'
|
||||
|
||||
# Configure the LMS to use our stub eCommerce implementation
|
||||
ECOMMERCE_API_URL = 'http://localhost:8043/api/v2/'
|
||||
ECOMMERCE_API_SIGNING_KEY = 'ecommerce-key'
|
||||
|
||||
#####################################################################
|
||||
# Lastly, see if the developer has any local overrides.
|
||||
try:
|
||||
|
||||
@@ -52,7 +52,7 @@ define(['backbone', 'jquery', 'underscore', 'common/js/spec_helpers/ajax_helpers
|
||||
|
||||
var createAccountSettingsPage = function() {
|
||||
var context = AccountSettingsPage(
|
||||
FIELDS_DATA, AUTH_DATA, Helpers.USER_ACCOUNTS_API_URL, Helpers.USER_PREFERENCES_API_URL, 'edX'
|
||||
FIELDS_DATA, [], AUTH_DATA, Helpers.USER_ACCOUNTS_API_URL, Helpers.USER_PREFERENCES_API_URL, 'edX'
|
||||
);
|
||||
return context.accountSettingsView;
|
||||
};
|
||||
|
||||
@@ -10,9 +10,18 @@
|
||||
], function (gettext, $, _, Backbone, Logger, UserAccountModel, UserPreferencesModel,
|
||||
AccountSettingsFieldViews, AccountSettingsView, StringUtils) {
|
||||
|
||||
return function (fieldsData, authData, userAccountsApiUrl, userPreferencesApiUrl, accountUserId, platformName) {
|
||||
return function (
|
||||
fieldsData,
|
||||
ordersHistoryData,
|
||||
authData,
|
||||
userAccountsApiUrl,
|
||||
userPreferencesApiUrl,
|
||||
accountUserId,
|
||||
platformName
|
||||
) {
|
||||
var accountSettingsElement, userAccountModel, userPreferencesModel, aboutSectionsData,
|
||||
accountsSectionData, accountSettingsView, showAccountSettingsPage, showLoadingError;
|
||||
accountsSectionData, ordersSectionData, accountSettingsView, showAccountSettingsPage,
|
||||
showLoadingError, orderNumber;
|
||||
|
||||
accountSettingsElement = $('.wrapper-account-settings');
|
||||
|
||||
@@ -170,13 +179,49 @@
|
||||
}
|
||||
];
|
||||
|
||||
ordersHistoryData.unshift(
|
||||
{
|
||||
'title': gettext('ORDER NAME'),
|
||||
'order_date': gettext('ORDER PLACED'),
|
||||
'price': gettext('TOTAL'),
|
||||
'number': gettext('ORDER NUMBER')
|
||||
}
|
||||
);
|
||||
|
||||
ordersSectionData = [
|
||||
{
|
||||
title: gettext('My Orders'),
|
||||
subtitle: StringUtils.interpolate(
|
||||
gettext('This page contains information about orders that you have placed with {platform_name}.'), /* jshint ignore:line */
|
||||
{platform_name: platformName}
|
||||
),
|
||||
fields: _.map(ordersHistoryData, function(order) {
|
||||
orderNumber = order.number;
|
||||
if (orderNumber === 'ORDER NUMBER') {
|
||||
orderNumber = 'orderId';
|
||||
}
|
||||
return {
|
||||
'view': new AccountSettingsFieldViews.OrderHistoryFieldView({
|
||||
title: order.title,
|
||||
totalPrice: order.price,
|
||||
orderId: order.number,
|
||||
orderDate: order.order_date,
|
||||
receiptUrl: order.receipt_url,
|
||||
valueAttribute: 'order-' + orderNumber
|
||||
})
|
||||
};
|
||||
})
|
||||
}
|
||||
];
|
||||
|
||||
accountSettingsView = new AccountSettingsView({
|
||||
model: userAccountModel,
|
||||
accountUserId: accountUserId,
|
||||
el: accountSettingsElement,
|
||||
tabSections: {
|
||||
aboutTabSections: aboutSectionsData,
|
||||
accountsTabSections: accountsSectionData
|
||||
accountsTabSections: accountsSectionData,
|
||||
ordersTabSections: ordersSectionData
|
||||
},
|
||||
userPreferencesModel: userPreferencesModel
|
||||
});
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
'text!templates/fields/field_link_account.underscore',
|
||||
'text!templates/fields/field_dropdown_account.underscore',
|
||||
'text!templates/fields/field_social_link_account.underscore',
|
||||
'edx-ui-toolkit/js/utils/string-utils'
|
||||
'text!templates/fields/field_order_history.underscore',
|
||||
'edx-ui-toolkit/js/utils/string-utils',
|
||||
'edx-ui-toolkit/js/utils/html-utils'
|
||||
], function (
|
||||
gettext, $, _, Backbone,
|
||||
FieldViews,
|
||||
@@ -20,7 +22,9 @@
|
||||
field_link_account_template,
|
||||
field_dropdown_account_template,
|
||||
field_social_link_template,
|
||||
StringUtils
|
||||
field_order_history_template,
|
||||
StringUtils,
|
||||
HtmlUtils
|
||||
)
|
||||
{
|
||||
|
||||
@@ -224,6 +228,30 @@
|
||||
return this.indicators.success + gettext('Successfully unlinked.');
|
||||
}
|
||||
}),
|
||||
|
||||
OrderHistoryFieldView: FieldViews.ReadonlyFieldView.extend({
|
||||
fieldType: 'orderHistory',
|
||||
fieldTemplate: field_order_history_template,
|
||||
|
||||
initialize: function (options) {
|
||||
this.options = options;
|
||||
this._super(options);
|
||||
this.template = HtmlUtils.template(this.fieldTemplate);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
HtmlUtils.setHtml(this.$el, this.template({
|
||||
title: this.options.title,
|
||||
totalPrice: this.options.totalPrice,
|
||||
orderId: this.options.orderId,
|
||||
orderDate: this.options.orderDate,
|
||||
receiptUrl: this.options.receiptUrl,
|
||||
valueAttribute: this.options.valueAttribute
|
||||
}));
|
||||
this.delegateEvents();
|
||||
return this;
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
return AccountSettingsFieldViews;
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
activeTab: 'aboutTabSections',
|
||||
accountSettingsTabs: [
|
||||
{name: 'aboutTabSections', id: 'about-tab', label: gettext('Account Information'), class: 'active'},
|
||||
{name: 'accountsTabSections', id: 'accounts-tab', label: gettext('Linked Accounts')}
|
||||
{name: 'accountsTabSections', id: 'accounts-tab', label: gettext('Linked Accounts')},
|
||||
{name: 'ordersTabSections', id: 'orders-tab', label: gettext('Order History')}
|
||||
],
|
||||
events: {
|
||||
'click .account-nav-link': 'changeTab'
|
||||
|
||||
@@ -213,6 +213,7 @@ $dark-gray1: rgb(74,74,74);
|
||||
$light-gray1: rgb(242,242,242);
|
||||
$light-gray2: rgb(171,171,171);
|
||||
$light-gray3: rgb(249,249,249);
|
||||
$light-gray4: rgb(252,252,252);
|
||||
$dark-gray2: rgb(151,151,151);
|
||||
$blue1: rgb(74,144,226);
|
||||
$blue2: rgb(0,161,229);
|
||||
|
||||
@@ -193,6 +193,54 @@
|
||||
}
|
||||
}
|
||||
|
||||
.u-field-order {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: em(16);
|
||||
color: $gray;
|
||||
width: 100%;
|
||||
padding-top: $baseline;
|
||||
padding-bottom: $baseline;
|
||||
line-height: normal;
|
||||
|
||||
span {
|
||||
padding: $baseline;
|
||||
}
|
||||
|
||||
.u-field-order-title {
|
||||
@include float(left);
|
||||
width: 26%;
|
||||
font-size: em(20);
|
||||
padding-left: ($baseline*2);
|
||||
}
|
||||
|
||||
.u-field-order-value {
|
||||
@include float(left);
|
||||
width: 12%;
|
||||
}
|
||||
|
||||
.u-field-order-date {
|
||||
@include float(left);
|
||||
width: 16%;
|
||||
}
|
||||
|
||||
.u-field-order-link {
|
||||
width: 15%;
|
||||
padding: 0;
|
||||
|
||||
.u-field-link {
|
||||
@extend %ui-clear-button;
|
||||
@extend %btn-pl-default-base;
|
||||
@include font-size(14);
|
||||
border: 1px solid $blue;
|
||||
color: $blue;
|
||||
line-height: normal;
|
||||
padding: 10px;
|
||||
width: 110px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.social-field-linked {
|
||||
background: $m-gray-l4;
|
||||
box-shadow: 0 1px 2px 1px $shadow-l2;
|
||||
@@ -267,6 +315,42 @@
|
||||
}
|
||||
}
|
||||
|
||||
.u-field-orderHistory {
|
||||
border-bottom: none;
|
||||
border: 1px solid $m-gray-l4;
|
||||
margin-bottom: $baseline;
|
||||
padding: 0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 1px solid $m-gray-l4;
|
||||
}
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: $light-gray4;
|
||||
}
|
||||
}
|
||||
|
||||
.u-field-order-orderId {
|
||||
border: none;
|
||||
margin-top: $baseline;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.u-field-order {
|
||||
font-weight: $font-semibold;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
|
||||
.u-field-order-title {
|
||||
font-size: em(16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.u-field-social {
|
||||
border-bottom: none;
|
||||
margin-right: 20px;
|
||||
|
||||
11
lms/templates/fields/field_order_history.underscore
Normal file
11
lms/templates/fields/field_order_history.underscore
Normal file
@@ -0,0 +1,11 @@
|
||||
<div class="field u-field-order" <% if (receiptUrl) { %> role="group" aria-labelledby="order-title-<%- orderId %>" <% } else { %> aria-hidden="true" <% } %>>
|
||||
<span class="u-field-order-title" <% if (receiptUrl) { %> id="order-title-<%- orderId %>" <% } %>><%- title %></span>
|
||||
<span class="u-field-order-date"><span class="sr">Date Placed: </span><%- orderDate %></span>
|
||||
<span class="u-field-order-value u-field-order-price"><span class="sr">Cost: </span><% if (!isNaN(parseFloat(totalPrice))) { %>$<% } %><%- totalPrice %></span>
|
||||
<span class="u-field-order-value u-field-order-number"><span class="sr">Order Number: </span><%- orderId %></span>
|
||||
<span class="u-field-order-link">
|
||||
<% if (receiptUrl) { %>
|
||||
<a class="u-field-link" target="_blank" href="<%- receiptUrl %>"><%- gettext('Order Details') %><span class="sr"> for <%- orderId %></span></a>
|
||||
<% } %>
|
||||
</span>
|
||||
</div>
|
||||
@@ -32,12 +32,14 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str
|
||||
|
||||
<%block name="js_extra">
|
||||
<%static:require_module module_name="js/student_account/views/account_settings_factory" class_name="AccountSettingsFactory">
|
||||
var fieldsData = ${ fields | n, dump_js_escaped_json };
|
||||
var authData = ${ auth | n, dump_js_escaped_json };
|
||||
var platformName = '${ static.get_platform_name() | n, js_escaped_string }';
|
||||
var fieldsData = ${ fields | n, dump_js_escaped_json },
|
||||
ordersHistoryData = ${ order_history | n, dump_js_escaped_json },
|
||||
authData = ${ auth | n, dump_js_escaped_json },
|
||||
platformName = '${ static.get_platform_name() | n, js_escaped_string }';
|
||||
|
||||
AccountSettingsFactory(
|
||||
fieldsData,
|
||||
ordersHistoryData,
|
||||
authData,
|
||||
'${ user_accounts_api_url | n, js_escaped_string }',
|
||||
'${ user_preferences_api_url | n, js_escaped_string }',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<% _.each(sections, function(section) { %>
|
||||
<div class="section">
|
||||
<% if (section.subtitle) { %>
|
||||
<p id="header-subtitle-<%- activeTabName %>" class="account-settings-header-subtitle"><%- gettext(section.subtitle) %></p>
|
||||
<p id="header-subtitle-<%- activeTabName %>" class="account-settings-header-subtitle"><%- section.subtitle %></p>
|
||||
<% } %>
|
||||
<h3 class="section-header"><%- gettext(section.title) %></h3>
|
||||
<div class="account-settings-section-body">
|
||||
|
||||
@@ -106,6 +106,11 @@ class Env(object):
|
||||
'programs': {
|
||||
'port': 8090,
|
||||
'log': BOK_CHOY_LOG_DIR / "bok_choy_programs.log",
|
||||
},
|
||||
|
||||
'ecommerce': {
|
||||
'port': 8043,
|
||||
'log': BOK_CHOY_LOG_DIR / "bok_choy_ecommerce.log",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user