Skip to content

Commit

Permalink
feat: adds endpoints for fetchGuestToken
Browse files Browse the repository at this point in the history
* generate_superset_context : adds a superset_guest_token_url to the context
* generate_superset_token : called when ^ hit instead of on render.
* adds a plugin url and view
* removes superset_token from the context
* adds/updates tests
  • Loading branch information
pomegranited committed Apr 10, 2024
1 parent da0bb9c commit 01737d6
Show file tree
Hide file tree
Showing 14 changed files with 304 additions and 122 deletions.
9 changes: 8 additions & 1 deletion platform_plugin_aspects/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""

from django.apps import AppConfig
from edx_django_utils.plugins import PluginSettings, PluginSignals
from edx_django_utils.plugins import PluginSettings, PluginSignals, PluginURLs


class PlatformPluginAspectsConfig(AppConfig):
Expand All @@ -14,6 +14,13 @@ class PlatformPluginAspectsConfig(AppConfig):
name = "platform_plugin_aspects"

plugin_app = {
PluginURLs.CONFIG: {
"lms.djangoapp": {
PluginURLs.NAMESPACE: name,
PluginURLs.REGEX: r"^aspects/",
PluginURLs.RELATIVE_PATH: "urls",
},
},
PluginSettings.CONFIG: {
"lms.djangoapp": {
"production": {PluginSettings.RELATIVE_PATH: "settings.production"},
Expand Down
11 changes: 0 additions & 11 deletions platform_plugin_aspects/extensions/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@
TEMPLATE_ABSOLUTE_PATH = "/instructor_dashboard/"
BLOCK_CATEGORY = "aspects"

ASPECTS_SECURITY_FILTERS_FORMAT = [
"org = '{course.org}'",
"course_name = '{course.display_name}'",
"course_run = '{course.id.run}'",
]


class AddSupersetTab(PipelineStep):
"""
Expand All @@ -36,9 +30,6 @@ def run_filter(
"""
course = context["course"]
dashboards = settings.ASPECTS_INSTRUCTOR_DASHBOARDS
extra_filters_format = settings.SUPERSET_EXTRA_FILTERS_FORMAT

filters = ASPECTS_SECURITY_FILTERS_FORMAT + extra_filters_format

user = get_current_user()

Expand All @@ -51,9 +42,7 @@ def run_filter(

context = generate_superset_context(
context,
user,
dashboards=dashboards,
filters=filters,
language=formatted_language,
)

Expand Down
4 changes: 2 additions & 2 deletions platform_plugin_aspects/static/html/superset.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ <h2>{{display_name}}</h2>
<p>{{exception}}</p>
{% elif not superset_dashboards %}
<p>Dashboard UUID is not set. Please set the dashboard UUID in the Studio.</p>
{% elif superset_url and superset_token %} {% if xblock_id %}
{% elif superset_url and superset_guest_token_url %} {% if xblock_id %}
<div class="superset-embedded-container" id="superset-embedded-container-{{xblock_id}}"></div>
{% else %}
<div class="aspects-tabs">
Expand All @@ -31,7 +31,7 @@ <h2>{{display_name}}</h2>
<script type="text/javascript">
window.superset_dashboards = {{superset_dashboards | safe }};
window.superset_url = "{{superset_url}}";
window.superset_token = "{{superset_token}}";
window.superset_guest_token_url = "{{superset_guest_token_url}}";
</script>
{% endif %}
</div>
19 changes: 16 additions & 3 deletions platform_plugin_aspects/static/js/embed_dashboard.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
function embedDashboard(dashboard_uuid, superset_url, superset_token, xblock_id) {
function embedDashboard(dashboard_uuid, superset_url, guest_token_url, xblock_id) {
xblock_id = xblock_id || "";

async fetchGuestToken() {
// Fetch the guest token from your backend
const response = await fetch(guest_token_url, {
method: 'POST',
body: JSON.stringify({
// TODO csrf_token: csrf_token,
})
});
const data = await response.json();
return data.guestToken;
}

window.supersetEmbeddedSdk
.embedDashboard({
id: dashboard_uuid, // given by the Superset embedding UI
supersetDomain: superset_url, // your Superset instance
mountPoint: document.getElementById(`superset-embedded-container-${xblock_id}`), // any html element that can contain an iframe
fetchGuestToken: () => superset_token, // function that returns a Promise with the guest token
fetchGuestToken: fetchGuestToken,
dashboardUiConfig: {
// dashboard UI config: hideTitle, hideTab, hideChartControls, filters.visible, filters.expanded (optional)
hideTitle: true,
Expand All @@ -28,6 +41,6 @@ function embedDashboard(dashboard_uuid, superset_url, superset_token, xblock_id)

if (window.superset_dashboards !== undefined) {
window.superset_dashboards.forEach(function(dashboard) {
embedDashboard(dashboard.uuid, window.superset_url, window.superset_token, dashboard.uuid);
embedDashboard(dashboard.uuid, window.superset_url, window.superset_guest_token_url);
});
}
4 changes: 2 additions & 2 deletions platform_plugin_aspects/static/js/superset.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
function SupersetXBlock(runtime, element, context) {
const dashboard_uuid = context.dashboard_uuid;
const superset_url = context.superset_url;
const superset_token = context.superset_token;
const superset_guest_token_url = context.superset_guest_token_url;
const xblock_id = context.xblock_id

function initSuperset(supersetEmbeddedSdk) {
embedDashboard(dashboard_uuid, superset_url, superset_token, xblock_id);
embedDashboard(dashboard_uuid, superset_url, superset_guest_token_url, xblock_id);
}

if (typeof require === "function") {
Expand Down
78 changes: 25 additions & 53 deletions platform_plugin_aspects/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@
Test utils.
"""

from collections import namedtuple
from unittest.mock import Mock, patch

from django.conf import settings
from django.test import TestCase

from platform_plugin_aspects.utils import (
generate_guest_token,
generate_superset_context,
get_ccx_courses,
get_model,
)

User = namedtuple("User", ["username"])
COURSE_ID = "course-v1:org+course+run"


class TestUtils(TestCase):
Expand Down Expand Up @@ -104,15 +104,11 @@ def test_get_ccx_courses_feature_disabled(self):
"password": "superset",
},
)
@patch("platform_plugin_aspects.utils._generate_guest_token")
def test_generate_superset_context(self, mock_generate_guest_token):
def test_generate_superset_context(self):
"""
Test generate_superset_context
"""
course_mock = Mock()
filter_mock = Mock()
user_mock = Mock()
context = {"course": course_mock}
context = {"course": COURSE_ID}
dashboards = settings.ASPECTS_INSTRUCTOR_DASHBOARDS

dashboards.append(
Expand All @@ -123,42 +119,41 @@ def test_generate_superset_context(self, mock_generate_guest_token):
}
)

mock_generate_guest_token.return_value = ("test-token", dashboards)

context = generate_superset_context(
context,
user_mock,
dashboards=dashboards,
filters=[filter_mock],
language="en_US",
)

self.assertEqual(context["superset_token"], "test-token")
self.assertEqual(
context["superset_guest_token_url"], f"/superset_guest_token/{COURSE_ID}"
)
self.assertEqual(context["superset_dashboards"], dashboards)
self.assertEqual(context["superset_url"], "http://superset-dummy-url/")
self.assertNotIn("superset_token", context)
self.assertNotIn("exception", context)

@patch("platform_plugin_aspects.utils.SupersetClient")
def test_generate_superset_context_with_superset_client_exception(
def test_generate_guest_token_with_superset_client_exception(
self, mock_superset_client
):
"""
Test generate_superset_context
Test generate_guest_token
"""
course_mock = Mock()
filter_mock = Mock()
user_mock = Mock()
context = {"course": course_mock}
mock_superset_client.side_effect = Exception("test-exception")

context = generate_superset_context(
context,
user_mock,
token, exception = generate_guest_token(
user=user_mock,
course=COURSE_ID,
dashboards=[{"name": "test", "uuid": "test-dashboard-uuid"}],
filters=[filter_mock],
)

self.assertIn("exception", context)
mock_superset_client.assert_called_once()
self.assertIsNone(token)
self.assertEqual(str(exception), "test-exception")

@patch.object(
settings,
Expand All @@ -171,49 +166,26 @@ def test_generate_superset_context_with_superset_client_exception(
},
)
@patch("platform_plugin_aspects.utils.SupersetClient")
def test_generate_superset_context_succesful(self, mock_superset_client):
def test_generate_guest_token_succesful(self, mock_superset_client):
"""
Test generate_superset_context
Test generate_guest_token
"""
course_mock = Mock()
filter_mock = Mock()
user_mock = Mock()
user_mock.username = "test-user"
context = {"course": course_mock}
response_mock = Mock(status_code=200)
mock_superset_client.return_value.session.post.return_value = response_mock
response_mock.json.return_value = {
"token": "test-token",
}

dashboards = [{"name": "test", "uuid": "test-dashboard-uuid"}]

context = generate_superset_context(
context,
user_mock,
dashboards=dashboards,
filters=[filter_mock],
)

self.assertEqual(context["superset_token"], "test-token")
self.assertEqual(context["superset_dashboards"], dashboards)
self.assertEqual(context["superset_url"], "http://dummy-superset-url/")

def test_generate_superset_context_with_exception(self):
"""
Test generate_superset_context
"""
course_mock = Mock()
filter_mock = Mock()
user_mock = Mock()
user_mock.username = "test-user"
context = {"course": course_mock}
dashboards = [{"name": "test", "uuid": "test-dashboard-uuid"}]

context = generate_superset_context(
context,
user_mock,
dashboards=[{"name": "test", "uuid": "test-dashboard-uuid"}],
token, _errors = generate_guest_token(
user=user_mock,
course=COURSE_ID,
dashboards=dashboards,
filters=[filter_mock],
)

self.assertIn("exception", context)
mock_superset_client.assert_called_once()
self.assertEqual(token, "test-token")
51 changes: 51 additions & 0 deletions platform_plugin_aspects/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""
Test views.
"""

from unittest.mock import patch

from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse

COURSE_ID = "course-v1:org+course+run"
User = get_user_model()


class ViewsTestCase(TestCase):
"""
Test cases for the plugin views and URLs.
"""

def setUp(self):
"""
Set up data used by multiple tests.
"""
super().setUp()
self.superset_guest_token_url = reverse(
"superset_guest_token",
kwargs={"course_id": COURSE_ID},
)
self.user = User.objects.create(
username="user",
email="[email protected]",
)
self.user.set_password("password")
self.user.save()

def test_guest_token_requires_authorization(self):
"""
Unauthenticated hits to the endpoint redirect to login.
"""
response = self.client.post(self.superset_guest_token_url)
self.assertEqual(response.status_code, 302)
self.assertIn("login", response.url)

@patch("platform_plugin_aspects.views.generate_guest_token")
def test_guest_token(self, mock_generate_guest_token):
mock_generate_guest_token.return_value = ("test-token", "test-dashboard-uuid")
self.client.login(username="user", password="password")
response = self.client.post(self.superset_guest_token_url, follow=True)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json().get("guestToken"), "test-token")
mock_generate_guest_token.assert_called_once()
Loading

0 comments on commit 01737d6

Please sign in to comment.