Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
ec3c8458
Commit
ec3c8458
authored
May 06, 2013
by
Greg Price
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1911 from edx/feature/gprice/feedback-button
parents
66185423
203a958e
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
501 additions
and
51 deletions
+501
-51
common/djangoapps/student/tests/factories.py
+11
-1
common/djangoapps/util/tests.py
+0
-0
common/djangoapps/util/views.py
+143
-30
common/lib/xmodule/xmodule/modulestore/tests/factories.py
+11
-5
github-requirements.txt
+1
-0
lms/djangoapps/courseware/tabs.py
+24
-9
lms/djangoapps/courseware/tests/test_tabs.py
+63
-0
lms/djangoapps/instructor/tests/test_gradebook.py
+1
-3
lms/envs/aws.py
+5
-0
lms/envs/common.py
+13
-1
lms/static/sass/base/_base.scss
+57
-0
lms/static/sass/shared/_modal.scss
+1
-1
lms/templates/help_modal.html
+167
-0
lms/templates/navigation.html
+2
-0
lms/urls.py
+2
-1
No files found.
common/djangoapps/student/tests/factories.py
View file @
ec3c8458
...
...
@@ -2,7 +2,7 @@ from student.models import (User, UserProfile, Registration,
CourseEnrollmentAllowed
,
CourseEnrollment
)
from
django.contrib.auth.models
import
Group
from
datetime
import
datetime
from
factory
import
DjangoModelFactory
,
Factory
,
SubFactory
,
PostGenerationMethodCall
from
factory
import
DjangoModelFactory
,
Factory
,
SubFactory
,
PostGenerationMethodCall
,
post_generation
from
uuid
import
uuid4
...
...
@@ -45,6 +45,16 @@ class UserFactory(DjangoModelFactory):
last_login
=
datetime
(
2012
,
1
,
1
)
date_joined
=
datetime
(
2011
,
1
,
1
)
@post_generation
def
profile
(
obj
,
create
,
extracted
,
**
kwargs
):
if
create
:
obj
.
save
()
return
UserProfileFactory
.
create
(
user
=
obj
,
**
kwargs
)
elif
kwargs
:
raise
Exception
(
"Cannot build a user profile without saving the user"
)
else
:
return
None
class
AdminFactory
(
UserFactory
):
is_staff
=
True
...
...
common/djangoapps/util/tests.py
View file @
ec3c8458
This diff is collapsed.
Click to expand it.
common/djangoapps/util/views.py
View file @
ec3c8458
import
datetime
import
json
import
logging
import
pprint
import
sys
...
...
@@ -7,15 +8,21 @@ from django.conf import settings
from
django.contrib.auth.models
import
User
from
django.core.context_processors
import
csrf
from
django.core.mail
import
send_mail
from
django.
http
import
Http404
from
django.http
import
Http
Response
from
django.
core.validators
import
ValidationError
,
validate_email
from
django.http
import
Http
404
,
HttpResponse
,
HttpResponseBadRequest
,
HttpResponseNotAllowed
,
HttpResponseServerError
from
django.shortcuts
import
redirect
from
django_future.csrf
import
ensure_csrf_cookie
from
mitxmako.shortcuts
import
render_to_response
,
render_to_string
from
urllib
import
urlencode
import
zendesk
import
capa.calc
import
track.views
log
=
logging
.
getLogger
(
__name__
)
def
calculate
(
request
):
''' Calculator in footer of every page. '''
equation
=
request
.
GET
[
'equation'
]
...
...
@@ -29,36 +36,142 @@ def calculate(request):
return
HttpResponse
(
json
.
dumps
({
'result'
:
str
(
result
)}))
def
send_feedback
(
request
):
''' Feeback mechanism in footer of every page. '''
try
:
username
=
request
.
user
.
username
email
=
request
.
user
.
email
except
:
username
=
"anonymous"
email
=
"anonymous"
class
_ZendeskApi
(
object
):
def
__init__
(
self
):
"""
Instantiate the Zendesk API.
All of `ZENDESK_URL`, `ZENDESK_USER`, and `ZENDESK_API_KEY` must be set
in `django.conf.settings`.
"""
self
.
_zendesk_instance
=
zendesk
.
Zendesk
(
settings
.
ZENDESK_URL
,
settings
.
ZENDESK_USER
,
settings
.
ZENDESK_API_KEY
,
use_api_token
=
True
,
api_version
=
2
)
def
create_ticket
(
self
,
ticket
):
"""
Create the given `ticket` in Zendesk.
The ticket should have the format specified by the zendesk package.
"""
ticket_url
=
self
.
_zendesk_instance
.
create_ticket
(
data
=
ticket
)
return
zendesk
.
get_id_from_url
(
ticket_url
)
def
update_ticket
(
self
,
ticket_id
,
update
):
"""
Update the Zendesk ticket with id `ticket_id` using the given `update`.
The update should have the format specified by the zendesk package.
"""
self
.
_zendesk_instance
.
update_ticket
(
ticket_id
=
ticket_id
,
data
=
update
)
def
submit_feedback_via_zendesk
(
request
):
"""
Create a new user-requested Zendesk ticket.
If Zendesk submission is not enabled, any request will raise `Http404`.
If any configuration parameter (`ZENDESK_URL`, `ZENDESK_USER`, or
`ZENDESK_API_KEY`) is missing, any request will raise an `Exception`.
The request must be a POST request specifying `subject` and `details`.
If the user is not authenticated, the request must also specify `name` and
`email`. If the user is authenticated, the `name` and `email` will be
populated from the user's information. If any required parameter is
missing, a 400 error will be returned indicating which field is missing and
providing an error message. If Zendesk returns any error on ticket
creation, a 500 error will be returned with no body. Once created, the
ticket will be updated with a private comment containing additional
information from the browser and server, such as HTTP headers and user
state. Whether or not the update succeeds, if the user's ticket is
successfully created, an empty successful response (200) will be returned.
"""
if
not
settings
.
MITX_FEATURES
.
get
(
'ENABLE_FEEDBACK_SUBMISSION'
,
False
):
raise
Http404
()
if
request
.
method
!=
"POST"
:
return
HttpResponseNotAllowed
([
"POST"
])
if
(
not
settings
.
ZENDESK_URL
or
not
settings
.
ZENDESK_USER
or
not
settings
.
ZENDESK_API_KEY
):
raise
Exception
(
"Zendesk enabled but not configured"
)
def
build_error_response
(
status_code
,
field
,
err_msg
):
return
HttpResponse
(
json
.
dumps
({
"field"
:
field
,
"error"
:
err_msg
}),
status
=
status_code
)
additional_info
=
{}
required_fields
=
[
"subject"
,
"details"
]
if
not
request
.
user
.
is_authenticated
():
required_fields
+=
[
"name"
,
"email"
]
required_field_errs
=
{
"subject"
:
"Please provide a subject."
,
"details"
:
"Please provide details."
,
"name"
:
"Please provide your name."
,
"email"
:
"Please provide a valid e-mail."
,
}
for
field
in
required_fields
:
if
field
not
in
request
.
POST
or
not
request
.
POST
[
field
]:
return
build_error_response
(
400
,
field
,
required_field_errs
[
field
])
subject
=
request
.
POST
[
"subject"
]
details
=
request
.
POST
[
"details"
]
tags
=
[]
if
"tag"
in
request
.
POST
:
tags
=
[
request
.
POST
[
"tag"
]]
if
request
.
user
.
is_authenticated
():
realname
=
request
.
user
.
profile
.
name
email
=
request
.
user
.
email
additional_info
[
"username"
]
=
request
.
user
.
username
else
:
realname
=
request
.
POST
[
"name"
]
email
=
request
.
POST
[
"email"
]
try
:
validate_email
(
email
)
except
ValidationError
:
return
build_error_response
(
400
,
"email"
,
required_field_errs
[
"email"
])
for
header
in
[
"HTTP_REFERER"
,
"HTTP_USER_AGENT"
]:
additional_info
[
header
]
=
request
.
META
.
get
(
header
)
zendesk_api
=
_ZendeskApi
()
additional_info_string
=
(
"Additional information:
\n\n
"
+
"
\n
"
.
join
(
"
%
s:
%
s"
%
(
key
,
value
)
for
(
key
,
value
)
in
additional_info
.
items
()
if
value
is
not
None
)
)
new_ticket
=
{
"ticket"
:
{
"requester"
:
{
"name"
:
realname
,
"email"
:
email
},
"subject"
:
subject
,
"comment"
:
{
"body"
:
details
},
"tags"
:
tags
}
}
try
:
browser
=
request
.
META
[
'HTTP_USER_AGENT'
]
except
:
browser
=
"Unknown"
feedback
=
render_to_string
(
"feedback_email.txt"
,
{
"subject"
:
request
.
POST
[
'subject'
],
"url"
:
request
.
POST
[
'url'
],
"time"
:
datetime
.
datetime
.
now
()
.
isoformat
(),
"feedback"
:
request
.
POST
[
'message'
],
"email"
:
email
,
"browser"
:
browser
,
"user"
:
username
})
send_mail
(
"MITx Feedback / "
+
request
.
POST
[
'subject'
],
feedback
,
settings
.
DEFAULT_FROM_EMAIL
,
[
settings
.
DEFAULT_FEEDBACK_EMAIL
],
fail_silently
=
False
)
return
HttpResponse
(
json
.
dumps
({
'success'
:
True
}))
ticket_id
=
zendesk_api
.
create_ticket
(
new_ticket
)
except
zendesk
.
ZendeskError
as
err
:
log
.
error
(
"
%
s"
,
str
(
err
))
return
HttpResponse
(
status
=
500
)
# Additional information is provided as a private update so the information
# is not visible to the user.
ticket_update
=
{
"ticket"
:
{
"comment"
:
{
"public"
:
False
,
"body"
:
additional_info_string
}}}
try
:
zendesk_api
.
update_ticket
(
ticket_id
,
ticket_update
)
except
zendesk
.
ZendeskError
as
err
:
log
.
error
(
"
%
s"
,
str
(
err
))
# The update is not strictly necessary, so do not indicate failure to the user
pass
return
HttpResponse
()
def
info
(
request
):
...
...
common/lib/xmodule/xmodule/modulestore/tests/factories.py
View file @
ec3c8458
...
...
@@ -37,11 +37,17 @@ class XModuleCourseFactory(Factory):
new_course
.
display_name
=
display_name
new_course
.
lms
.
start
=
gmtime
()
new_course
.
tabs
=
[{
"type"
:
"courseware"
},
{
"type"
:
"course_info"
,
"name"
:
"Course Info"
},
{
"type"
:
"discussion"
,
"name"
:
"Discussion"
},
{
"type"
:
"wiki"
,
"name"
:
"Wiki"
},
{
"type"
:
"progress"
,
"name"
:
"Progress"
}]
new_course
.
tabs
=
kwargs
.
get
(
'tabs'
,
[
{
"type"
:
"courseware"
},
{
"type"
:
"course_info"
,
"name"
:
"Course Info"
},
{
"type"
:
"discussion"
,
"name"
:
"Discussion"
},
{
"type"
:
"wiki"
,
"name"
:
"Wiki"
},
{
"type"
:
"progress"
,
"name"
:
"Progress"
}
]
)
new_course
.
discussion_link
=
kwargs
.
get
(
'discussion_link'
)
# Update the data in the mongo datastore
store
.
update_metadata
(
new_course
.
location
.
url
(),
own_metadata
(
new_course
))
...
...
github-requirements.txt
View file @
ec3c8458
...
...
@@ -5,6 +5,7 @@
-e git://github.com/edx/django-pipeline.git#egg=django-pipeline
-e git://github.com/edx/django-wiki.git@e2e84558#egg=django-wiki
-e git://github.com/dementrock/pystache_custom.git@776973740bdaad83a3b029f96e415a7d1e8bec2f#egg=pystache_custom-dev
-e git://github.com/eventbrite/zendesk.git@d53fe0e81b623f084e91776bcf6369f8b7b63879#egg=zendesk
# Our libraries:
-e git+https://github.com/edx/XBlock.git@5ce6f70a#egg=XBlock
lms/djangoapps/courseware/tabs.py
View file @
ec3c8458
...
...
@@ -294,6 +294,27 @@ def get_course_tabs(user, course, active_page):
return
tabs
def
get_discussion_link
(
course
):
"""
Return the URL for the discussion tab for the given `course`.
If they have a discussion link specified, use that even if we disable
discussions. Disabling discsussions is mostly a server safety feature at
this point, and we don't need to worry about external sites. Otherwise,
if the course has a discussion tab or uses the default tabs, return the
discussion view URL. Otherwise, return None to indicate the lack of a
discussion tab.
"""
if
course
.
discussion_link
:
return
course
.
discussion_link
elif
not
settings
.
MITX_FEATURES
.
get
(
'ENABLE_DISCUSSION_SERVICE'
):
return
None
elif
hasattr
(
course
,
'tabs'
)
and
course
.
tabs
and
not
any
([
tab
[
'type'
]
==
'discussion'
for
tab
in
course
.
tabs
]):
return
None
else
:
return
reverse
(
'django_comment_client.forum.views.forum_form_discussion'
,
args
=
[
course
.
id
])
def
get_default_tabs
(
user
,
course
,
active_page
):
# When calling the various _tab methods, can omit the 'type':'blah' from the
...
...
@@ -308,15 +329,9 @@ def get_default_tabs(user, course, active_page):
tabs
.
extend
(
_textbooks
({},
user
,
course
,
active_page
))
## If they have a discussion link specified, use that even if we feature
## flag discussions off. Disabling that is mostly a server safety feature
## at this point, and we don't need to worry about external sites.
if
course
.
discussion_link
:
tabs
.
append
(
CourseTab
(
'Discussion'
,
course
.
discussion_link
,
active_page
==
'discussion'
))
elif
settings
.
MITX_FEATURES
.
get
(
'ENABLE_DISCUSSION_SERVICE'
):
link
=
reverse
(
'django_comment_client.forum.views.forum_form_discussion'
,
args
=
[
course
.
id
])
tabs
.
append
(
CourseTab
(
'Discussion'
,
link
,
active_page
==
'discussion'
))
discussion_link
=
get_discussion_link
(
course
)
if
discussion_link
:
tabs
.
append
(
CourseTab
(
'Discussion'
,
discussion_link
,
active_page
==
'discussion'
))
tabs
.
extend
(
_wiki
({
'name'
:
'Wiki'
,
'type'
:
'wiki'
},
user
,
course
,
active_page
))
...
...
lms/djangoapps/courseware/tests/test_tabs.py
View file @
ec3c8458
from
django.test
import
TestCase
from
mock
import
MagicMock
from
mock
import
patch
import
courseware.tabs
as
tabs
from
django.test.utils
import
override_settings
from
django.core.urlresolvers
import
reverse
from
courseware.tests.tests
import
TEST_DATA_MONGO_MODULESTORE
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
class
ProgressTestCase
(
TestCase
):
...
...
@@ -257,3 +261,62 @@ class ValidateTabsTestCase(TestCase):
self
.
assertRaises
(
tabs
.
InvalidTabsException
,
tabs
.
validate_tabs
,
self
.
courses
[
2
])
self
.
assertIsNone
(
tabs
.
validate_tabs
(
self
.
courses
[
3
]))
self
.
assertRaises
(
tabs
.
InvalidTabsException
,
tabs
.
validate_tabs
,
self
.
courses
[
4
])
@override_settings
(
MODULESTORE
=
TEST_DATA_MONGO_MODULESTORE
)
class
DiscussionLinkTestCase
(
ModuleStoreTestCase
):
def
setUp
(
self
):
self
.
tabs_with_discussion
=
[
{
'type'
:
'courseware'
},
{
'type'
:
'course_info'
},
{
'type'
:
'discussion'
},
{
'type'
:
'textbooks'
},
]
self
.
tabs_without_discussion
=
[
{
'type'
:
'courseware'
},
{
'type'
:
'course_info'
},
{
'type'
:
'textbooks'
},
]
@staticmethod
def
_patch_reverse
(
course
):
def
patched_reverse
(
viewname
,
args
):
if
viewname
==
"django_comment_client.forum.views.forum_form_discussion"
and
args
==
[
course
.
id
]:
return
"default_discussion_link"
else
:
return
None
return
patch
(
"courseware.tabs.reverse"
,
patched_reverse
)
@patch.dict
(
"django.conf.settings.MITX_FEATURES"
,
{
"ENABLE_DISCUSSION_SERVICE"
:
False
})
def
test_explicit_discussion_link
(
self
):
"""Test that setting discussion_link overrides everything else"""
course
=
CourseFactory
.
create
(
discussion_link
=
"other_discussion_link"
,
tabs
=
self
.
tabs_with_discussion
)
self
.
assertEqual
(
tabs
.
get_discussion_link
(
course
),
"other_discussion_link"
)
@patch.dict
(
"django.conf.settings.MITX_FEATURES"
,
{
"ENABLE_DISCUSSION_SERVICE"
:
False
})
def
test_discussions_disabled
(
self
):
"""Test that other cases return None with discussions disabled"""
for
i
,
t
in
enumerate
([
None
,
self
.
tabs_with_discussion
,
self
.
tabs_without_discussion
]):
course
=
CourseFactory
.
create
(
tabs
=
t
,
number
=
str
(
i
))
self
.
assertEqual
(
tabs
.
get_discussion_link
(
course
),
None
)
@patch.dict
(
"django.conf.settings.MITX_FEATURES"
,
{
"ENABLE_DISCUSSION_SERVICE"
:
True
})
def
test_no_tabs
(
self
):
"""Test a course without tabs configured"""
course
=
CourseFactory
.
create
(
tabs
=
None
)
with
self
.
_patch_reverse
(
course
):
self
.
assertEqual
(
tabs
.
get_discussion_link
(
course
),
"default_discussion_link"
)
@patch.dict
(
"django.conf.settings.MITX_FEATURES"
,
{
"ENABLE_DISCUSSION_SERVICE"
:
True
})
def
test_tabs_with_discussion
(
self
):
"""Test a course with a discussion tab configured"""
course
=
CourseFactory
.
create
(
tabs
=
self
.
tabs_with_discussion
)
with
self
.
_patch_reverse
(
course
):
self
.
assertEqual
(
tabs
.
get_discussion_link
(
course
),
"default_discussion_link"
)
@patch.dict
(
"django.conf.settings.MITX_FEATURES"
,
{
"ENABLE_DISCUSSION_SERVICE"
:
True
})
def
test_tabs_without_discussion
(
self
):
"""Test a course with tabs configured but without a discussion tab"""
course
=
CourseFactory
.
create
(
tabs
=
self
.
tabs_without_discussion
)
self
.
assertEqual
(
tabs
.
get_discussion_link
(
course
),
None
)
lms/djangoapps/instructor/tests/test_gradebook.py
View file @
ec3c8458
...
...
@@ -49,7 +49,6 @@ class TestGradebook(ModuleStoreTestCase):
]
for
user
in
self
.
users
:
UserProfileFactory
.
create
(
user
=
user
)
CourseEnrollmentFactory
.
create
(
user
=
user
,
course_id
=
self
.
course
.
id
)
for
i
in
xrange
(
USER_COUNT
-
1
):
...
...
@@ -151,4 +150,4 @@ class TestLetterCutoffPolicy(TestGradebook):
# User 0 has 0 on Homeworks [1]
# User 0 has 0 on the class [1]
# One use at the top of the page [1]
self
.
assertEquals
(
3
,
self
.
response
.
content
.
count
(
'grade_None'
))
\ No newline at end of file
self
.
assertEquals
(
3
,
self
.
response
.
content
.
count
(
'grade_None'
))
lms/envs/aws.py
View file @
ec3c8458
...
...
@@ -88,6 +88,8 @@ META_UNIVERSITIES = ENV_TOKENS.get('META_UNIVERSITIES', {})
COMMENTS_SERVICE_URL
=
ENV_TOKENS
.
get
(
"COMMENTS_SERVICE_URL"
,
''
)
COMMENTS_SERVICE_KEY
=
ENV_TOKENS
.
get
(
"COMMENTS_SERVICE_KEY"
,
''
)
CERT_QUEUE
=
ENV_TOKENS
.
get
(
"CERT_QUEUE"
,
'test-pull'
)
ZENDESK_URL
=
ENV_TOKENS
.
get
(
"ZENDESK_URL"
)
FEEDBACK_SUBMISSION_EMAIL
=
ENV_TOKENS
.
get
(
"FEEDBACK_SUBMISSION_EMAIL"
)
############################## SECURE AUTH ITEMS ###############
# Secret things: passwords, access keys, etc.
...
...
@@ -123,3 +125,6 @@ DATADOG_API = AUTH_TOKENS.get("DATADOG_API")
# Analytics dashboard server
ANALYTICS_SERVER_URL
=
ENV_TOKENS
.
get
(
"ANALYTICS_SERVER_URL"
)
ANALYTICS_API_KEY
=
AUTH_TOKENS
.
get
(
"ANALYTICS_API_KEY"
,
""
)
ZENDESK_USER
=
AUTH_TOKENS
.
get
(
"ZENDESK_USER"
)
ZENDESK_API_KEY
=
AUTH_TOKENS
.
get
(
"ZENDESK_API_KEY"
)
lms/envs/common.py
View file @
ec3c8458
...
...
@@ -90,7 +90,10 @@ MITX_FEATURES = {
# Give a UI to show a student's submission history in a problem by the
# Staff Debug tool.
'ENABLE_STUDENT_HISTORY_VIEW'
:
True
'ENABLE_STUDENT_HISTORY_VIEW'
:
True
,
# Provide a UI to allow users to submit feedback from the LMS
'ENABLE_FEEDBACK_SUBMISSION'
:
False
,
}
# Used for A/B testing
...
...
@@ -323,6 +326,14 @@ WIKI_LINK_DEFAULT_LEVEL = 2
PEARSONVUE_SIGNINPAGE_URL
=
"https://www1.pearsonvue.com/testtaker/signin/SignInPage/EDX"
# TESTCENTER_ACCOMMODATION_REQUEST_EMAIL = "exam-help@edx.org"
##### Feedback submission mechanism #####
FEEDBACK_SUBMISSION_EMAIL
=
None
##### Zendesk #####
ZENDESK_URL
=
None
ZENDESK_USER
=
None
ZENDESK_API_KEY
=
None
################################# open ended grading config #####################
#By setting up the default settings with an incorrect user name and password,
...
...
@@ -582,3 +593,4 @@ INSTALLED_APPS = (
# Discussion forums
'django_comment_client'
,
)
lms/static/sass/base/_base.scss
View file @
ec3c8458
...
...
@@ -202,5 +202,62 @@ mark {
}
}
.help-tab
{
@include
transform
(
rotate
(
-90deg
));
@include
transform-origin
(
0
0
);
top
:
50%
;
left
:
0
;
position
:
fixed
;
z-index
:
99
;
a
:link
,
a
:visited
{
cursor
:
pointer
;
border
:
1px
solid
#ccc
;
border-top-style
:
none
;
@include
border-radius
(
0px
0px
10px
10px
);
background
:
transparentize
(
#fff
,
0
.25
);
color
:
transparentize
(
#333
,
0
.25
);
font-weight
:
bold
;
text-decoration
:
none
;
padding
:
6px
22px
11px
;
display
:
inline-block
;
&
:hover
{
color
:
#fff
;
background
:
#1D9DD9
;
}
}
}
.help-buttons
{
padding
:
10px
50px
;
a
:link
,
a
:visited
{
padding
:
15px
0px
;
text-align
:
center
;
cursor
:
pointer
;
background
:
#fff
;
text-decoration
:
none
;
display
:
block
;
border
:
1px
solid
#ccc
;
&
#feedback_link_problem
{
border-bottom-style
:
none
;
@include
border-radius
(
10px
10px
0px
0px
);
}
&
#feedback_link_question
{
border-top-style
:
none
;
@include
border-radius
(
0px
0px
10px
10px
);
}
&
:hover
{
color
:
#fff
;
background
:
#1D9DD9
;
}
}
}
#feedback_form
textarea
[
name
=
"details"
]
{
height
:
150px
;
}
lms/static/sass/shared/_modal.scss
View file @
ec3c8458
...
...
@@ -155,7 +155,7 @@
display
:
block
;
color
:
#8F0E0E
;
+
input
{
+
input
,
+
textarea
{
border
:
1px
solid
#CA1111
;
color
:
#8F0E0E
;
}
...
...
lms/templates/help_modal.html
0 → 100644
View file @
ec3c8458
<
%
namespace
name=
'static'
file=
'static_content.html'
/>
<
%!
from
django
.
conf
import
settings
%
>
<
%!
from
courseware
.
tabs
import
get_discussion_link
%
>
% if settings.MITX_FEATURES.get('ENABLE_FEEDBACK_SUBMISSION', False):
<div
class=
"help-tab"
>
<a
href=
"#help-modal"
rel=
"leanModal"
>
Help
</a>
</div>
<section
id=
"help-modal"
class=
"modal"
>
<div
class=
"inner-wrapper"
id=
"help_wrapper"
>
<header>
<h2><span
class=
"edx"
>
edX
</span>
Help
</h2>
<hr>
</header>
<
%
discussion_link =
get_discussion_link(course)
if
course
else
None
%
>
% if discussion_link:
<p>
Have a course-specific question?
<a
href=
"${discussion_link}"
target=
"_blank"
/>
Post it on the course forums.
</a>
</p>
<hr>
% endif
<p>
Have a general question about edX?
<a
href=
"/help"
target=
"_blank"
>
Check the FAQ
</a>
.
</p>
<hr>
<div
class=
"help-buttons"
>
<a
href=
"#"
id=
"feedback_link_problem"
>
Report a problem
</a>
<a
href=
"#"
id=
"feedback_link_suggestion"
>
Make a suggestion
</a>
<a
href=
"#"
id=
"feedback_link_question"
>
Ask a question
</a>
</div>
## TODO: find a way to refactor this
<div
class=
"close-modal"
>
<div
class=
"inner"
>
<p>
✕
</p>
</div>
</div>
</div>
<div
class=
"inner-wrapper"
id=
"feedback_form_wrapper"
>
<header></header>
<form
id=
"feedback_form"
class=
"feedback_form"
method=
"post"
data-remote=
"true"
action=
"/submit_feedback"
>
<div
id=
"feedback_error"
class=
"modal-form-error"
></div>
% if not user.is_authenticated():
<label
data-field=
"name"
>
Name*
</label>
<input
name=
"name"
type=
"text"
>
<label
data-field=
"email"
>
E-mail*
</label>
<input
name=
"email"
type=
"text"
>
% endif
<label
data-field=
"subject"
>
Subject*
</label>
<input
name=
"subject"
type=
"text"
>
<label
data-field=
"details"
>
Details*
</label>
<textarea
name=
"details"
></textarea>
<input
name=
"tag"
type=
"hidden"
>
<div
class=
"submit"
>
<input
name=
"submit"
type=
"submit"
value=
"Submit"
>
</div>
</form>
<div
class=
"close-modal"
>
<div
class=
"inner"
>
<p>
✕
</p>
</div>
</div>
</div>
<div
class=
"inner-wrapper"
id=
"feedback_success_wrapper"
>
<header>
<h2>
Thank You!
</h2>
<hr>
</header>
<p>
Thanks for your feedback. We will read your message, and our
support team may contact you to respond or ask for further clarification.
</p>
<div
class=
"close-modal"
>
<div
class=
"inner"
>
<p>
✕
</p>
</div>
</div>
</div>
</section>
<script
type=
"text/javascript"
>
(
function
()
{
$
(
".help-tab"
).
click
(
function
()
{
$
(
".field-error"
).
removeClass
(
"field-error"
);
$
(
"#feedback_form"
)[
0
].
reset
();
$
(
"#feedback_form input[type='submit']"
).
removeAttr
(
"disabled"
);
$
(
"#feedback_form_wrapper"
).
css
(
"display"
,
"none"
);
$
(
"#feedback_error"
).
css
(
"display"
,
"none"
);
$
(
"#feedback_success_wrapper"
).
css
(
"display"
,
"none"
);
$
(
"#help_wrapper"
).
css
(
"display"
,
"block"
);
});
showFeedback
=
function
(
e
,
tag
,
title
)
{
$
(
"#help_wrapper"
).
css
(
"display"
,
"none"
);
$
(
"#feedback_form input[name='tag']"
).
val
(
tag
);
$
(
"#feedback_form_wrapper"
).
css
(
"display"
,
"block"
);
$
(
"#feedback_form_wrapper header"
).
html
(
"<h2>"
+
title
+
"</h2><hr>"
);
e
.
preventDefault
();
};
$
(
"#feedback_link_problem"
).
click
(
function
(
e
)
{
showFeedback
(
e
,
"problem"
,
"Report a Problem"
);
});
$
(
"#feedback_link_suggestion"
).
click
(
function
(
e
)
{
showFeedback
(
e
,
"suggestion"
,
"Make a Suggestion"
);
});
$
(
"#feedback_link_question"
).
click
(
function
(
e
)
{
showFeedback
(
e
,
"question"
,
"Ask a Question"
);
});
$
(
"#feedback_form"
).
submit
(
function
()
{
$
(
"input[type='submit']"
,
this
).
attr
(
"disabled"
,
"disabled"
);
});
$
(
"#feedback_form"
).
on
(
"ajax:complete"
,
function
()
{
$
(
"input[type='submit']"
,
this
).
removeAttr
(
"disabled"
);
});
$
(
"#feedback_form"
).
on
(
"ajax:success"
,
function
(
event
,
data
,
status
,
xhr
)
{
$
(
"#feedback_form_wrapper"
).
css
(
"display"
,
"none"
);
$
(
"#feedback_success_wrapper"
).
css
(
"display"
,
"block"
);
});
$
(
"#feedback_form"
).
on
(
"ajax:error"
,
function
(
event
,
xhr
,
status
,
error
)
{
$
(
".field-error"
).
removeClass
(
"field-error"
);
var
responseData
;
try
{
responseData
=
jQuery
.
parseJSON
(
xhr
.
responseText
);
}
catch
(
err
)
{
}
if
(
responseData
)
{
$
(
"[data-field='"
+
responseData
.
field
+
"']"
).
addClass
(
"field-error"
);
$
(
"#feedback_error"
).
html
(
responseData
.
error
).
stop
().
css
(
"display"
,
"block"
);
}
else
{
// If no data (or malformed data) is returned, a server error occurred
htmlStr
=
"An error has occurred."
;
%
if
settings
.
FEEDBACK_SUBMISSION_EMAIL
:
htmlStr
+=
" Please <a href='#' id='feedback_email'>send us e-mail</a>."
;
%
else
:
// If no email is configured, we can't do much other than
// ask the user to try again later
htmlStr
+=
" Please try again later."
;
%
endif
$
(
"#feedback_error"
).
html
(
htmlStr
).
stop
().
css
(
"display"
,
"block"
);
%
if
settings
.
FEEDBACK_SUBMISSION_EMAIL
:
$
(
"#feedback_email"
).
click
(
function
(
e
)
{
mailto
=
"mailto:"
+
"${settings.FEEDBACK_SUBMISSION_EMAIL}"
+
"?subject="
+
$
(
"#feedback_form input[name='subject']"
).
val
()
+
"&body="
+
$
(
"#feedback_form textarea[name='details']"
).
val
();
window
.
open
(
mailto
);
e
.
preventDefault
();
});
%
endif
}
});
})(
this
)
</script>
%endif
lms/templates/navigation.html
View file @
ec3c8458
...
...
@@ -96,3 +96,5 @@ site_status_msg = get_site_status_msg(course_id)
<
%
include
file=
"signup_modal.html"
/>
<
%
include
file=
"forgot_password_modal.html"
/>
%endif
<
%
include
file=
"help_modal.html"
/>
lms/urls.py
View file @
ec3c8458
...
...
@@ -114,8 +114,9 @@ urlpatterns = ('', # nopep8
# Favicon
(
r'^favicon\.ico$'
,
'django.views.generic.simple.redirect_to'
,
{
'url'
:
'/static/images/favicon.ico'
}),
url
(
r'^submit_feedback$'
,
'util.views.submit_feedback_via_zendesk'
),
# TODO: These urls no longer work. They need to be updated before they are re-enabled
# url(r'^send_feedback$', 'util.views.send_feedback'),
# url(r'^reactivate/(?P<key>[^/]*)$', 'student.views.reactivation_email'),
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment