Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: setup instructor dashboard filter #2

Merged
merged 14 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# issue is really that Django is missing to avoid masking other
# exceptions on Python 2.
try:
import django # pylint: disable=unused-import, wrong-import-position
import django # pylint: disable=unused-import
except ImportError as import_error:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
Expand Down
5 changes: 5 additions & 0 deletions platform_plugin_superset/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,9 @@
An Open edX plugin to integrate Superset with Open edX.
"""

import os
from pathlib import Path

__version__ = "0.1.0"

ROOT_DIRECTORY = Path(os.path.dirname(os.path.abspath(__file__)))
21 changes: 21 additions & 0 deletions platform_plugin_superset/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,24 @@ class PlatformPluginSupersetConfig(AppConfig):
"""

name = "platform_plugin_superset"

plugin_app = {
"settings_config": {
"lms.djangoapp": {
"common": {"relative_path": "settings.common"},
"test": {"relative_path": "settings.test"},
"production": {"relative_path": "settings.production"},
},
"cms.djangoapp": {
"common": {"relative_path": "settings.common"},
"test": {"relative_path": "settings.test"},
"production": {"relative_path": "settings.production"},
},
},
}

def ready(self):
"""Load modules of platform_plugin_superset."""
from platform_plugin_superset.extensions import ( # pylint: disable=unused-import, import-outside-toplevel
filters,
)
10 changes: 10 additions & 0 deletions platform_plugin_superset/extensions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""
An Open edX plugin to integrate Superset with Open edX.
"""

import os
from pathlib import Path

__version__ = "0.1.0"

ROOT_DIRECTORY = Path(os.path.dirname(os.path.abspath(__file__)))
145 changes: 145 additions & 0 deletions platform_plugin_superset/extensions/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
"""
Open edX Filters needed for Superset integration.
"""
import os

import pkg_resources
from crum import get_current_user
from django.conf import settings
from django.template import Context, Template
from openedx_filters import PipelineStep
from supersetapiclient.client import SupersetClient
from web_fragments.fragment import Fragment

os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
Ian2012 marked this conversation as resolved.
Show resolved Hide resolved

TEMPLATE_ABSOLUTE_PATH = "/instructor_dashboard/"
BLOCK_CATEGORY = "superset"


def generate_guest_token(user, course):
"""
Generate a Superset guest token for the user.

Args:
user: User object.
course: Course object.

Returns:
tuple: Superset guest token and dashboard id.
or None, exception if Superset is missconfigured or cannot generate guest token.
"""
superset_config = getattr(settings, "SUPERSET_CONFIG", {})
superset_internal_host = superset_config.get("service_url", "http://superset:8088/")
superset_username = superset_config.get("username")
superset_password = superset_config.get("password")

instructor_dashboard_config = getattr(settings, "SUPERSET_INSTRUCTOR_DASHBOARD", {})
dashboard_id = instructor_dashboard_config.get("dashboard_uuid")

try:
client = SupersetClient(
host=superset_internal_host,
username=superset_username,
password=superset_password,
)
except Exception as exc: # pylint: disable=broad-except
return None, exc

course_run = course.children[0].course_key.run

extra_filters_format = getattr(settings, "SUPERSET_EXTRA_FILTERS_FORMAT", [])

default_filters = [
"org = '{course.org}'",
"course_name = '{course.display_name}'",
"course_run = '{course_run}'",
]

filters = default_filters + extra_filters_format

formatted_filters = [
filter.format(course=course, course_run=course_run, user=user) for filter in filters
]

data = {
"user": {
"username": user.username,
"first_name": "John",
"last_name": "Doe",
Ian2012 marked this conversation as resolved.
Show resolved Hide resolved
},
"resources": [{"type": "dashboard", "id": dashboard_id}],
"rls": [
{"clause": filter} for filter in formatted_filters
],
}

try:
response = client.session.post(
url=f"{superset_internal_host}api/v1/security/guest_token/",
json=data,
headers={"Content-Type": "application/json"},
)
response.raise_for_status()
token = response.json()["token"]

return token, dashboard_id
except Exception as exc: # pylint: disable=broad-except
return None, exc


class AddSupersetTab(PipelineStep):
"""Add superset tab to instructor dashboard."""

def run_filter(
self, context, template_name
): # pylint: disable=arguments-differ, unused-argument
"""Execute filter that modifies the instructor dashboard context.
Args:
context (dict): the context for the instructor dashboard.
_ (str): instructor dashboard template name.
"""
course = context["course"]

user = get_current_user()
superset_token, dashboard_id = generate_guest_token(user, course)
Ian2012 marked this conversation as resolved.
Show resolved Hide resolved

if superset_token:
context.update(
{
"superset_token": superset_token,
"dashboard_id": dashboard_id,
"superset_url": settings.SUPERSET_CONFIG.get("host"),
}
)
else:
context.update(
{
"error_message": "Superset is not configured properly. Please contact your system administrator.",
"exception": dashboard_id,
}
)
template = Template(self.resource_string("static/html/superset.html"))

html = template.render(Context(context))
frag = Fragment(html)

frag.add_css(self.resource_string("static/css/superset.css"))
frag.add_javascript(self.resource_string("static/js/superset.js"))

section_data = {
"fragment": frag,
"section_key": BLOCK_CATEGORY,
"section_display_name": "Superset",
"course_id": str(course.id),
"template_path_prefix": TEMPLATE_ABSOLUTE_PATH,
}
context["sections"].append(section_data)
return {
"context": context,
}

def resource_string(self, path):
"""Handy helper for getting resources from our kit."""
data = pkg_resources.resource_string("platform_plugin_superset", path)
return data.decode("utf8")
Empty file.
49 changes: 49 additions & 0 deletions platform_plugin_superset/settings/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""
Common Django settings for eox_hooks project.
For more information on this file, see
https://docs.djangoproject.com/en/2.22/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.22/ref/settings/
"""
from platform_plugin_superset import ROOT_DIRECTORY

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.22/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "secret-key"


# Application definition

INSTALLED_APPS = [
"platform_plugin_superset",
]


# Internationalization
# https://docs.djangoproject.com/en/2.22/topics/i18n/

LANGUAGE_CODE = "en-us"

TIME_ZONE = "UTC"

USE_TZ = True


def plugin_settings(settings):
"""
Set of plugin settings used by the Open Edx platform.
More info: https://github.com/edx/edx-platform/blob/master/openedx/core/djangoapps/plugins/README.rst
"""
settings.MAKO_TEMPLATE_DIRS_BASE.append(ROOT_DIRECTORY / "templates")
settings.SUPERSET_CONFIG = {
"url": "http://superset.local.overhang.io:8088",
"username": "superset",
"password": "superset",
}
settings.SUPERSET_INSTRUCTOR_DASHBOARD = {
"dashboard_slug": "instructor-dashboard",
"dashboard_uuid": "1d6bf904-f53f-47fd-b1c9-6cd7e284d286",
}
settings.SUPERSET_EXTRA_FILTERS_FORMAT = []
19 changes: 19 additions & 0 deletions platform_plugin_superset/settings/production.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""
Production Django settings for platform_plugin_forum_email_notifier project.
"""


def plugin_settings(settings):
"""
Set of plugin settings used by the Open Edx platform.
More info: https://github.com/edx/edx-platform/blob/master/openedx/core/djangoapps/plugins/README.rst
"""
settings.SUPERSET_CONFIG = getattr(settings, "ENV_TOKENS", {}).get(
"SUPERSET_CONFIG", settings.SUPERSET_CONFIG
)
settings.SUPERSET_INSTRUCTOR_DASHBOARD = getattr(settings, "ENV_TOKENS", {}).get(
"SUPERSET_INSTRUCTOR_DASHBOARD", settings.SUPERSET_INSTRUCTOR_DASHBOARD
)
settings.SUPERSET_EXTRA_FILTERS_FORMAT = getattr(settings, "ENV_TOKENS", {}).get(
"SUPERSET_EXTRA_FILTERS_FORMAT", settings.SUPERSET_EXTRA_FILTERS_FORMAT
)
42 changes: 42 additions & 0 deletions platform_plugin_superset/settings/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""
Common Django settings for eox_hooks project.
For more information on this file, see
https://docs.djangoproject.com/en/2.22/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.22/ref/settings/
"""


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.22/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": "default.db",
"USER": "",
"PASSWORD": "",
"HOST": "",
"PORT": "",
},
"read_replica": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": "read_replica.db",
"USER": "",
"PASSWORD": "",
"HOST": "",
"PORT": "",
},
}

SECRET_KEY = "not-so-secret-key"

# Internationalization
# https://docs.djangoproject.com/en/2.22/topics/i18n/

LANGUAGE_CODE = "en-us"

TIME_ZONE = "UTC"

USE_TZ = True
5 changes: 5 additions & 0 deletions platform_plugin_superset/static/css/superset.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#superset-embedded-container > iframe {
height: 720px;
width: 100%;
display: block;
}
40 changes: 40 additions & 0 deletions platform_plugin_superset/static/html/superset.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{% load i18n %}

<script src="https://unpkg.com/@superset-ui/embedded-sdk"></script>

<div class="email-notifier-instructor-wrapper" width="parent">
<div>{{title}}</div>

{% if dashboard_id %}
<div id="superset-embedded-container">
</div>
<script type="text/javascript">
supersetEmbeddedSdk
.embedDashboard({
id: "{{dashboard_id}}", // given by the Superset embedding UI
supersetDomain: "{{superset_url}}", // your Superset instance
mountPoint: document.getElementById("superset-embedded-container"), // any html element that can contain an iframe
fetchGuestToken: () => "{{superset_token}}",
dashboardUiConfig: {
// dashboard UI config: hideTitle, hideTab, hideChartControls, filters.visible, filters.expanded (optional)
hideTitle: true,
filters: {
expanded: false,
},
hideTab: true,
hideChartControls: true,
hideFilters: true,
},
})
.then((dashboard) => {
mountPoint = document.getElementById("superset-embedded-container");
Ian2012 marked this conversation as resolved.
Show resolved Hide resolved
/*
Perform extra operations on the dashboard object or the container
when the dashboard is loaded
*/
});
</script>
{% else %}
<p>{{error_message}}</p>
{% endif %}
</div>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<%page args="section_data" expression_filter="h"/>
<%! from openedx.core.djangolib.markup import HTML %>

<%include file="/courseware/xqa_interface.html/"/>

<section class="superset">
${HTML(section_data['fragment'].body_html())}
</section>
5 changes: 4 additions & 1 deletion requirements/base.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
-c constraints.txt

Django # Web application framework

openedx-filters
web_fragments
superset-api-client
django_crum
Loading