feat: Implement program discussions iframe (#28770)

* feat: Implement program discussions iframe

* Fixed conflicts

* Fixed conflicts

* Fixed conflicts

* Refactored code
This commit is contained in:
AsadAzam
2021-09-21 16:24:52 +05:00
committed by GitHub
parent 98de16f97a
commit 208e1b177c
5 changed files with 98 additions and 11 deletions

View File

@@ -5,6 +5,7 @@ Fragments for rendering programs.
import json
from django.contrib.sites.shortcuts import get_current_site
from django.http import Http404
from django.template.loader import render_to_string
from django.urls import reverse
@@ -13,9 +14,11 @@ from web_fragments.fragment import Fragment
from lms.djangoapps.commerce.utils import EcommerceService
from lms.djangoapps.learner_dashboard.utils import FAKE_COURSE_KEY, strip_course_id, program_discussions_is_enabled
from lti_consumer.lti_1p1.contrib.django import lti_embed
from openedx.core.djangoapps.catalog.constants import PathwayType
from openedx.core.djangoapps.catalog.utils import get_pathways
from openedx.core.djangoapps.credentials.utils import get_credentials_records_url
from openedx.core.djangoapps.discussions.models import ProgramDiscussionsConfiguration
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
from openedx.core.djangoapps.programs.utils import (
@@ -25,6 +28,7 @@ from openedx.core.djangoapps.programs.utils import (
get_program_marketing_url
)
from openedx.core.djangoapps.user_api.preferences.api import get_user_preferences
from openedx.core.djangolib.markup import HTML
class ProgramsFragmentView(EdxFragmentView):
@@ -70,14 +74,93 @@ class ProgramDetailsFragmentView(EdxFragmentView):
"""
Render the program details fragment.
"""
DEFAULT_ROLE = 'student'
@staticmethod
def get_program_discussion_configuration(program_uuid):
return ProgramDiscussionsConfiguration.objects.filter(
program_uuid=program_uuid
).first()
@staticmethod
def _get_resource_link_id(program_uuid, request) -> str:
site = get_current_site(request)
return f'{site.domain}-{program_uuid}'
@staticmethod
def _get_result_sourcedid(context_id, resource_link_id, user_id) -> str:
return f'{context_id}:{resource_link_id}:{user_id}'
def _get_lti_embed_code(self, program_discussions_configuration, request) -> str:
"""
Returns the LTI embed code for embedding in the program discussions tab
Args:
program_discussions_configuration (ProgramDiscussionsConfiguration): ProgramDiscussionsConfiguration object.
request (HttpRequest): Request object for view in which LTI will be embedded.
Returns:
HTML code to embed LTI in program page.
"""
program_uuid = program_discussions_configuration.program_uuid
lti_consumer = program_discussions_configuration.lti_configuration.get_lti_consumer()
user_id = str(request.user.id)
context_id = program_uuid
resource_link_id = self._get_resource_link_id(program_uuid, request)
# TODO: Add support for multiple roles
roles = self.DEFAULT_ROLE
context_title = program_uuid
result_sourcedid = self._get_result_sourcedid(context_id, resource_link_id, user_id)
return lti_embed(
html_element_id='lti-tab-launcher',
lti_consumer=lti_consumer,
resource_link_id=resource_link_id,
user_id=user_id,
roles=roles,
context_id=context_id,
context_title=context_title,
context_label=context_id,
result_sourcedid=result_sourcedid
)
def render_discussions_fragment(self, program_uuid, request) -> dict:
"""
Returns the program discussion fragment if program discussions configuration exists for a program uuid
"""
if program_discussions_is_enabled():
program_discussions_configuration = self.get_program_discussion_configuration(program_uuid)
if program_discussions_configuration:
lti_embed_html = self._get_lti_embed_code(program_discussions_configuration, request)
fragment = Fragment(
HTML(
"""
<iframe
id='lti-tab-embed'
style='width: 100%; min-height: 800px; border: none'
srcdoc='{srcdoc}'
>
</iframe>
"""
).format(
srcdoc=lti_embed_html
)
)
return {
'iframe': fragment.content,
'enabled': True
}
return {
'iframe': '',
'enabled': False
}
def render_to_fragment(self, request, program_uuid, **kwargs): # lint-amnesty, pylint: disable=arguments-differ
"""View details about a specific program."""
programs_config = kwargs.get('programs_config') or ProgramsApiConfig.current()
user = request.user
if not programs_config.enabled or not request.user.is_authenticated:
raise Http404
meter = ProgramProgressMeter(request.site, request.user, uuid=program_uuid)
meter = ProgramProgressMeter(request.site, user, uuid=program_uuid)
program_data = meter.programs[0]
if not program_data:
@@ -88,9 +171,9 @@ class ProgramDetailsFragmentView(EdxFragmentView):
except ValueError:
mobile_only = False
program_data = ProgramDataExtender(program_data, request.user, mobile_only=mobile_only).extend()
program_data = ProgramDataExtender(program_data, user, mobile_only=mobile_only).extend()
course_data = meter.progress(programs=[program_data], count_only=False)[0]
certificate_data = get_certificates(request.user, program_data)
certificate_data = get_certificates(user, program_data)
program_data.pop('courses')
skus = program_data.get('skus')
@@ -130,13 +213,14 @@ class ProgramDetailsFragmentView(EdxFragmentView):
context = {
'urls': urls,
'user_preferences': get_user_preferences(request.user),
'user_preferences': get_user_preferences(user),
'program_data': program_data,
'course_data': course_data,
'certificate_data': certificate_data,
'industry_pathways': industry_pathways,
'credit_pathways': credit_pathways,
'program_discussions_enabled': program_discussions_is_enabled()
'program_discussions_enabled': program_discussions_is_enabled(),
'discussion_fragment': self.render_discussions_fragment(program_uuid, request)
}
html = render_to_string('learner_dashboard/program_details_fragment.html', context)

View File

@@ -80,8 +80,9 @@ class ProgramDetailsView extends Backbone.View {
completedCount,
completeProgramURL: buyButtonUrl,
programDiscussionEnabled: this.options.programDiscussionEnabled,
industryPathways : this.options.industryPathways,
creditPathways : this.options.creditPathways,
industryPathways: this.options.industryPathways,
creditPathways: this.options.creditPathways,
discussionFragment: this.options.discussionFragment,
};
data = $.extend(data, this.programModel.toJSON());
HtmlUtils.setHtml(this.$el, this.tpl(data));

View File

@@ -21,6 +21,7 @@ ProgramDetailsFactory({
industryPathways: ${industry_pathways | n, dump_js_escaped_json},
creditPathways: ${credit_pathways | n, dump_js_escaped_json},
programDiscussionEnabled: ${program_discussions_enabled | n, dump_js_escaped_json},
discussionFragment: ${discussion_fragment, | n, dump_js_escaped_json}
});
</%static:webpack>
</%block>

View File

@@ -7,7 +7,7 @@
<li class="nav-item" role="presentation">
<button class="nav-link btn-link active" id="home-tab" data-toggle="tab" data-target="#journey" type="button" role="tab" aria-controls="journey" aria-selected="true">Journey</button>
</li>
<% if (programDiscussionEnabled) { %>
<% if (discussionFragment.enabled) { %>
<li class="nav-item" role="presentation">
<button class="nav-link btn-link" id="profile-tab" data-toggle="tab" data-target="#community" type="button" role="tab" aria-controls="community" aria-selected="false">Community</button>
</li>
@@ -101,9 +101,9 @@
</article>
</div>
<% if (programDiscussionEnabled) { %>
<div class="tab-pane fade" id="community" role="tabpanel" aria-labelledby="community-tab">Community tab content</div>
<% } %>
<% if (discussionFragment.enabled) { %>
<div class="tab-pane fade" id="community" role="tabpanel" aria-labelledby="community-tab"><%= HtmlUtils.HTML(discussionFragment.iframe) %></div>
<% } %>
<div class="tab-pane fade" id="live" role="tabpanel" aria-labelledby="live-tab">Live tab content</div>
<div class="tab-pane fade" id="pathways" role="tabpanel" aria-labelledby="pathaways-tab">
<% if (creditPathways.length > 0) { %>

View File

@@ -1,5 +1,6 @@
<header class="js-program-header program-header full-width-banner"></header>
<!-- TODO: consider if article is the most appropriate element here -->
<article class="program-details-content">
<div class="program-heading">
<% if (completedCount === totalCount) { %>