From c8dbc18179c1b278fa7fa3c684f230365dd6de11 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Thu, 10 Nov 2022 20:21:43 +0530 Subject: [PATCH 1/3] feat: statistics page --- .../lesson_completion/lesson_completion.json | 33 +++++++ .../lms_batch_membership.json | 6 +- lms/lms/utils.py | 21 +++++ lms/lms/workspace/lms/lms.json | 4 +- lms/patches.txt | 3 +- lms/patches/v0_0/convert_progress_to_float.py | 8 ++ lms/patches/v0_0/set_member_in_progress.py | 1 + lms/public/css/style.css | 2 +- lms/public/js/common_functions.js | 89 ++++++++++++++----- lms/templates/statistics.html | 2 +- lms/templates/stats.html | 19 ---- lms/www/courses/index.html | 9 -- lms/www/courses/index.js | 40 +-------- lms/www/statistics/__init__.py | 0 lms/www/statistics/index.html | 46 ++++++++++ 15 files changed, 186 insertions(+), 97 deletions(-) create mode 100644 lms/lms/dashboard_chart/lesson_completion/lesson_completion.json create mode 100644 lms/patches/v0_0/convert_progress_to_float.py delete mode 100644 lms/templates/stats.html create mode 100644 lms/www/statistics/__init__.py create mode 100644 lms/www/statistics/index.html diff --git a/lms/lms/dashboard_chart/lesson_completion/lesson_completion.json b/lms/lms/dashboard_chart/lesson_completion/lesson_completion.json new file mode 100644 index 00000000..f38ccc2a --- /dev/null +++ b/lms/lms/dashboard_chart/lesson_completion/lesson_completion.json @@ -0,0 +1,33 @@ +{ + "based_on": "creation", + "chart_name": "Lesson Completion", + "chart_type": "Count", + "color": "#4463F0", + "creation": "2022-11-09 16:52:19.021695", + "custom_options": "{\"type\": \"line\", \"axisOptions\": {\"xIsSeries\": 1}, \"lineOptions\": {\"regionFill\": 1}}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "document_type": "LMS Course Progress", + "dynamic_filters_json": "[]", + "filters_json": "[]", + "group_by_type": "Count", + "idx": 0, + "is_public": 1, + "is_standard": 1, + "modified": "2022-11-09 16:52:52.241756", + "modified_by": "Administrator", + "module": "LMS", + "name": "Lesson Completion", + "number_of_groups": 0, + "owner": "Administrator", + "parent_document_type": "", + "roles": [], + "source": "", + "time_interval": "Daily", + "timeseries": 1, + "timespan": "Last Month", + "type": "Line", + "use_report_chart": 0, + "value_based_on": "", + "y_axis": [] +} \ No newline at end of file diff --git a/lms/lms/doctype/lms_batch_membership/lms_batch_membership.json b/lms/lms/doctype/lms_batch_membership/lms_batch_membership.json index 1efcf744..903f66ce 100644 --- a/lms/lms/doctype/lms_batch_membership/lms_batch_membership.json +++ b/lms/lms/doctype/lms_batch_membership/lms_batch_membership.json @@ -89,7 +89,7 @@ }, { "fieldname": "progress", - "fieldtype": "Data", + "fieldtype": "Float", "label": "Progress", "read_only": 1 }, @@ -116,7 +116,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2022-10-10 12:38:17.839525", + "modified": "2022-10-10 12:38:17.839526", "modified_by": "Administrator", "module": "LMS", "name": "LMS Batch Membership", @@ -141,4 +141,4 @@ "sort_order": "DESC", "states": [], "title_field": "member_name" -} \ No newline at end of file +} diff --git a/lms/lms/utils.py b/lms/lms/utils.py index f42d2b26..cada6ea3 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -654,3 +654,24 @@ def get_chart_data(chart_name, timespan, timegrain, from_date, to_date): ], "datasets": [{"name": chart.name, "values": [r[1] for r in result]}], } + + +@frappe.whitelist(allow_guest=True) +def get_course_completion_data(): + all_membership = frappe.db.count("LMS Batch Membership") + completed = frappe.db.count("LMS Batch Membership", { + "progress": ["like", "%100%"] + }) + + return { + "labels": ["Completed", "In Progress"], + "datasets": [ + { + "name": "Course Completion", + "values": [ + completed, + all_membership - completed + ], + } + ], + } diff --git a/lms/lms/workspace/lms/lms.json b/lms/lms/workspace/lms/lms.json index 3964ac72..02924879 100644 --- a/lms/lms/workspace/lms/lms.json +++ b/lms/lms/workspace/lms/lms.json @@ -143,7 +143,7 @@ "type": "Link" } ], - "modified": "2022-11-07 18:49:38.954136", + "modified": "2022-11-09 17:16:03.973258", "modified_by": "Administrator", "module": "LMS", "name": "LMS", @@ -169,7 +169,7 @@ "format": "{} Completed", "label": "Course Completed", "link_to": "LMS Batch Membership", - "stats_filter": "{\"progress\":[\"=\",\"100\"]}", + "stats_filter": "{\"progress\":[\"like\",\"%100%\"]}", "type": "DocType" }, { diff --git a/lms/patches.txt b/lms/patches.txt index 9438129d..9a0d5fec 100644 --- a/lms/patches.txt +++ b/lms/patches.txt @@ -34,4 +34,5 @@ lms.patches.v0_0.create_course_instructor_role #29-08-2022 lms.patches.v0_0.create_course_moderator_role lms.patches.v0_0.set_dashboard #11-10-2022 lms.patches.v0_0.set_courses_page_as_home -lms.patches.v0_0.set_member_in_progress +lms.patches.v0_0.set_member_in_progress #09-11-2022 +lms.patches.v0_0.convert_progress_to_float diff --git a/lms/patches/v0_0/convert_progress_to_float.py b/lms/patches/v0_0/convert_progress_to_float.py new file mode 100644 index 00000000..62489f45 --- /dev/null +++ b/lms/patches/v0_0/convert_progress_to_float.py @@ -0,0 +1,8 @@ +import frappe +from frappe.utils import flt + +def execute(): + frappe.reload_doc("lms", "doctype", "lms_course_progress") + progress_records = frappe.get_all("LMS Batch Membership", fields=["name", "progress"]) + for progress in progress_records: + frappe.db.set_value("LMS Batch Membership", progress.name, "progress", flt(progress.progress)) diff --git a/lms/patches/v0_0/set_member_in_progress.py b/lms/patches/v0_0/set_member_in_progress.py index e67f688a..7c9cff75 100644 --- a/lms/patches/v0_0/set_member_in_progress.py +++ b/lms/patches/v0_0/set_member_in_progress.py @@ -2,6 +2,7 @@ import frappe def execute(): + frappe.reload_doc("lms", "doctype", "lms_course_progress") progress_records = frappe.get_all("LMS Course Progress", fields=["name", "owner"]) for progress in progress_records: diff --git a/lms/public/css/style.css b/lms/public/css/style.css index a1be2240..65ef4106 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -1715,7 +1715,7 @@ li { grid-gap: 2rem; } -.tab-pane .stats-parent { +.statistics .stats-parent { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-gap: 1rem; } diff --git a/lms/public/js/common_functions.js b/lms/public/js/common_functions.js index a5e4bfe5..f8f9c2ef 100644 --- a/lms/public/js/common_functions.js +++ b/lms/public/js/common_functions.js @@ -16,6 +16,14 @@ frappe.ready(() => { $(document).on("click", ".btn-save-chapter", (e) => { save_chapter(e); }); + + if (window.location.pathname == "/statistics") { + generate_graph("New Signups", "#new-signups"); + generate_graph("Course Enrollments", "#course-enrollments"); + generate_graph("Lesson Completion", "#lesson-completion"); + generate_course_completion_graph(); + } + }); const setup_file_size = () => { @@ -51,13 +59,10 @@ const join_course = (e) => { callback: (data) => { if (data.message == "OK") { $(".no-preview-modal").modal("hide"); - frappe.show_alert( - { - message: __("Enrolled successfully"), - indicator: "green", - }, - 3 - ); + frappe.show_alert({ + message: __("Enrolled successfully"), + indicator: "green", + }, 3); setTimeout(function () { window.location.href = `/courses/${course}/learn/1.1`; }, 1000); @@ -81,15 +86,12 @@ const notify_user = (e) => { }, callback: (data) => { $(".no-preview-modal").modal("hide"); - frappe.show_alert( - { - message: __( - "You have opted to be notified for this course. You will receive an email when the course becomes available." - ), - indicator: "green", - }, - 3 - ); + frappe.show_alert({ + message: __( + "You have opted to be notified for this course. You will receive an email when the course becomes available." + ), + indicator: "green", + }, 3); setTimeout(() => { window.location.reload(); }, 3000); @@ -121,16 +123,15 @@ const add_chapter = (e) => { scroll_to_chapter_container(); }; + const scroll_to_chapter_container = () => { - $([document.documentElement, document.body]).animate( - { - scrollTop: $(".new-chapter").offset().top, - }, - 1000 - ); + $([document.documentElement, document.body]).animate({ + scrollTop: $(".new-chapter").offset().top, + }, 1000); $(".new-chapter").find(".chapter-title-main").focus(); }; + const save_chapter = (e) => { let target = $(e.currentTarget); let parent = target.closest(".chapter-parent"); @@ -155,3 +156,47 @@ const save_chapter = (e) => { }, }); }; + +const generate_graph = (chart_name, element, type="line") => { + let date = frappe.datetime; + + frappe.call({ + method: "lms.lms.utils.get_chart_data", + args: { + chart_name: chart_name, + timespan: "Select Date Range", + timegrain: "Daily", + from_date: date.add_days(date.get_today(), -30), + to_date: date.add_days(date.get_today(), +1) + }, + callback: (data) => { + render_chart(data.message, chart_name, element, type); + }, + }); +}; + +const render_chart = (data, chart_name, element, type) => { + const chart = new frappe.Chart(element, { + title: chart_name, + data: data, + type: type, + height: 250, + colors: ["#4563f1"], + axisOptions: { + xIsSeries: 1, + }, + lineOptions: { + regionFill: 1, + }, + }); +}; + + +const generate_course_completion_graph = () => { + frappe.call({ + method: "lms.lms.utils.get_course_completion_data", + callback: (data) => { + render_chart(data.message, "Course Completion", "#course-completion", "pie") + } + }) +} diff --git a/lms/templates/statistics.html b/lms/templates/statistics.html index 94ea9c1c..347107c4 100644 --- a/lms/templates/statistics.html +++ b/lms/templates/statistics.html @@ -48,7 +48,7 @@ {% if course_completion %} {% set course_completion_count = frappe.db.count("LMS Batch Membership", { - "progress":["=","100"] + "progress":["like","%100%"] }) %}
diff --git a/lms/templates/stats.html b/lms/templates/stats.html deleted file mode 100644 index e0f25fd1..00000000 --- a/lms/templates/stats.html +++ /dev/null @@ -1,19 +0,0 @@ -{% set published_courses = True %} -{% set total_signups = True %} -{% set enrollment_count = True %} -{% set course_completion = True %} -{% set lesson_completion = True %} -{% set quiz_completion = True %} - -
- {% include "lms/templates/statistics.html" %} - -
-
-
-
-
-
-
-
-
diff --git a/lms/www/courses/index.html b/lms/www/courses/index.html index a69c62ff..4940c100 100644 --- a/lms/www/courses/index.html +++ b/lms/www/courses/index.html @@ -84,11 +84,6 @@ {% endif %} -
@@ -126,10 +121,6 @@
{% endif %} -
- {% include "lms/templates/stats.html" %} -
-
{% endif %} diff --git a/lms/www/courses/index.js b/lms/www/courses/index.js index 3c4e9c8a..7d75a3d0 100644 --- a/lms/www/courses/index.js +++ b/lms/www/courses/index.js @@ -1,6 +1,4 @@ frappe.ready(() => { - generate_graph("New Signups"); - generate_graph("Course Enrollments"); $(".nav-link").click((e) => { change_hash(e); @@ -9,45 +7,9 @@ frappe.ready(() => { if (window.location.hash) { open_tab(); } + }); -const generate_graph = (chart_name) => { - let date = frappe.datetime; - - frappe.call({ - method: "lms.lms.utils.get_chart_data", - args: { - chart_name: chart_name, - timespan: "Select Date Range", - timegrain: "Daily", - from_date: date.add_days(date.get_today(), -30), - to_date: date.add_days(date.get_today(), +1), - }, - callback: (data) => { - render_chart(data.message, chart_name); - }, - }); -}; - -const render_chart = (data, chart_name) => { - let dom_element = - chart_name == "Course Enrollments" - ? "#course-enrollments" - : "#new-signups"; - const chart = new frappe.Chart(dom_element, { - title: chart_name, - data: data, - type: "line", - height: 250, - colors: ["#4563f1"], - axisOptions: { - xIsSeries: 1, - }, - lineOptions: { - regionFill: 1, - }, - }); -}; const change_hash = (e) => { window.location.hash = $(e.currentTarget).attr("href"); diff --git a/lms/www/statistics/__init__.py b/lms/www/statistics/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/www/statistics/index.html b/lms/www/statistics/index.html new file mode 100644 index 00000000..27881531 --- /dev/null +++ b/lms/www/statistics/index.html @@ -0,0 +1,46 @@ +{% extends "templates/base.html" %} + + +{% block title %} +{{ _("Statistics") }} +{% endblock %} + + +{% block content %} +
+
+
+ {{ _("Statistics") }} +
+ {% set published_courses = True %} + {% set total_signups = True %} + {% set enrollment_count = True %} + {% set course_completion = True %} + {% set lesson_completion = True %} + {% set quiz_completion = True %} + +
+ {% include "lms/templates/statistics.html" %} + +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+{% endblock %} From 157f6b45e9ff67085e5e06c9c1767faa20fcfa59 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Fri, 11 Nov 2022 10:40:29 +0530 Subject: [PATCH 2/3] feat: add pages to nav after install --- lms/hooks.py | 1 + lms/install.py | 42 ++++++++++++++ lms/lms/utils.py | 23 +++----- lms/patches.txt | 1 + lms/patches/v0_0/add_pages_to_nav.py | 5 ++ lms/patches/v0_0/convert_progress_to_float.py | 5 +- lms/public/js/common_functions.js | 56 +++++++++++-------- lms/www/classes/__init__.py | 0 lms/www/courses/index.js | 3 - 9 files changed, 95 insertions(+), 41 deletions(-) create mode 100644 lms/patches/v0_0/add_pages_to_nav.py create mode 100644 lms/www/classes/__init__.py diff --git a/lms/hooks.py b/lms/hooks.py index 52e30375..8fb9cb6f 100644 --- a/lms/hooks.py +++ b/lms/hooks.py @@ -58,6 +58,7 @@ web_include_js = ["website.bundle.js"] # ------------ # before_install = "lms.install.before_install" +after_install = "lms.install.add_pages_to_nav" after_sync = "lms.install.after_sync" after_uninstall = "lms.install.after_uninstall" diff --git a/lms/install.py b/lms/install.py index 4adf68cc..03645ab9 100644 --- a/lms/install.py +++ b/lms/install.py @@ -2,6 +2,10 @@ import frappe from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to +def after_install(): + add_pages_to_nav() + + def after_sync(): create_lms_roles() set_default_home() @@ -91,3 +95,41 @@ def delete_custom_fields(): for field in fields: frappe.db.delete("Custom Field", {"fieldname": field}) frappe.db.commit() + + +def add_pages_to_nav(): + pages = [ + {"label": "Courses", "url": "/courses", "parent": "Explore", "idx": 2}, + {"label": "Statistics", "url": "/statistics", "parent": "Explore", "idx": 3}, + {"label": "Jobs", "url": "/jobs", "parent": "Explore", "idx": 4}, + {"label": "People", "url": "/community", "parent": "Explore", "idx": 5}, + ] + + if not frappe.db.exists("Top Bar Item", {"label": "Explore"}): + frappe.get_doc( + { + "doctype": "Top Bar Item", + "label": "Explore", + "parent": "Website Settings", + "parenttype": "Website Settings", + "parentfield": "top_bar_items", + "idx": 1, + } + ).save() + + for page in pages: + if not frappe.db.exists( + "Top Bar Item", {"url": ["like", "%" + page.get("url") + "%"]} + ): + frappe.get_doc( + { + "doctype": "Top Bar Item", + "label": page.get("label"), + "url": page.get("url"), + "parent_label": page.get("parent"), + "idx": page.get("idx"), + "parent": "Website Settings", + "parenttype": "Website Settings", + "parentfield": "top_bar_items", + } + ).save() diff --git a/lms/lms/utils.py b/lms/lms/utils.py index cada6ea3..edb256b0 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -659,19 +659,14 @@ def get_chart_data(chart_name, timespan, timegrain, from_date, to_date): @frappe.whitelist(allow_guest=True) def get_course_completion_data(): all_membership = frappe.db.count("LMS Batch Membership") - completed = frappe.db.count("LMS Batch Membership", { - "progress": ["like", "%100%"] - }) + completed = frappe.db.count("LMS Batch Membership", {"progress": ["like", "%100%"]}) return { - "labels": ["Completed", "In Progress"], - "datasets": [ - { - "name": "Course Completion", - "values": [ - completed, - all_membership - completed - ], - } - ], - } + "labels": ["Completed", "In Progress"], + "datasets": [ + { + "name": "Course Completion", + "values": [completed, all_membership - completed], + } + ], + } diff --git a/lms/patches.txt b/lms/patches.txt index 9a0d5fec..012a7f4c 100644 --- a/lms/patches.txt +++ b/lms/patches.txt @@ -36,3 +36,4 @@ lms.patches.v0_0.set_dashboard #11-10-2022 lms.patches.v0_0.set_courses_page_as_home lms.patches.v0_0.set_member_in_progress #09-11-2022 lms.patches.v0_0.convert_progress_to_float +lms.patches.v0_0.add_pages_to_nav #11-11-2022 diff --git a/lms/patches/v0_0/add_pages_to_nav.py b/lms/patches/v0_0/add_pages_to_nav.py new file mode 100644 index 00000000..3546e3dc --- /dev/null +++ b/lms/patches/v0_0/add_pages_to_nav.py @@ -0,0 +1,5 @@ +import frappe +from lms.install import add_pages_to_nav + +def execute(): + add_pages_to_nav() diff --git a/lms/patches/v0_0/convert_progress_to_float.py b/lms/patches/v0_0/convert_progress_to_float.py index 62489f45..28ba459a 100644 --- a/lms/patches/v0_0/convert_progress_to_float.py +++ b/lms/patches/v0_0/convert_progress_to_float.py @@ -1,8 +1,11 @@ import frappe from frappe.utils import flt + def execute(): frappe.reload_doc("lms", "doctype", "lms_course_progress") progress_records = frappe.get_all("LMS Batch Membership", fields=["name", "progress"]) for progress in progress_records: - frappe.db.set_value("LMS Batch Membership", progress.name, "progress", flt(progress.progress)) + frappe.db.set_value( + "LMS Batch Membership", progress.name, "progress", flt(progress.progress) + ) diff --git a/lms/public/js/common_functions.js b/lms/public/js/common_functions.js index f8f9c2ef..f60b4721 100644 --- a/lms/public/js/common_functions.js +++ b/lms/public/js/common_functions.js @@ -23,7 +23,6 @@ frappe.ready(() => { generate_graph("Lesson Completion", "#lesson-completion"); generate_course_completion_graph(); } - }); const setup_file_size = () => { @@ -59,10 +58,13 @@ const join_course = (e) => { callback: (data) => { if (data.message == "OK") { $(".no-preview-modal").modal("hide"); - frappe.show_alert({ - message: __("Enrolled successfully"), - indicator: "green", - }, 3); + frappe.show_alert( + { + message: __("Enrolled successfully"), + indicator: "green", + }, + 3 + ); setTimeout(function () { window.location.href = `/courses/${course}/learn/1.1`; }, 1000); @@ -86,12 +88,15 @@ const notify_user = (e) => { }, callback: (data) => { $(".no-preview-modal").modal("hide"); - frappe.show_alert({ - message: __( - "You have opted to be notified for this course. You will receive an email when the course becomes available." - ), - indicator: "green", - }, 3); + frappe.show_alert( + { + message: __( + "You have opted to be notified for this course. You will receive an email when the course becomes available." + ), + indicator: "green", + }, + 3 + ); setTimeout(() => { window.location.reload(); }, 3000); @@ -123,15 +128,16 @@ const add_chapter = (e) => { scroll_to_chapter_container(); }; - const scroll_to_chapter_container = () => { - $([document.documentElement, document.body]).animate({ - scrollTop: $(".new-chapter").offset().top, - }, 1000); + $([document.documentElement, document.body]).animate( + { + scrollTop: $(".new-chapter").offset().top, + }, + 1000 + ); $(".new-chapter").find(".chapter-title-main").focus(); }; - const save_chapter = (e) => { let target = $(e.currentTarget); let parent = target.closest(".chapter-parent"); @@ -157,7 +163,7 @@ const save_chapter = (e) => { }); }; -const generate_graph = (chart_name, element, type="line") => { +const generate_graph = (chart_name, element, type = "line") => { let date = frappe.datetime; frappe.call({ @@ -167,7 +173,7 @@ const generate_graph = (chart_name, element, type="line") => { timespan: "Select Date Range", timegrain: "Daily", from_date: date.add_days(date.get_today(), -30), - to_date: date.add_days(date.get_today(), +1) + to_date: date.add_days(date.get_today(), +1), }, callback: (data) => { render_chart(data.message, chart_name, element, type); @@ -191,12 +197,16 @@ const render_chart = (data, chart_name, element, type) => { }); }; - const generate_course_completion_graph = () => { frappe.call({ method: "lms.lms.utils.get_course_completion_data", callback: (data) => { - render_chart(data.message, "Course Completion", "#course-completion", "pie") - } - }) -} + render_chart( + data.message, + "Course Completion", + "#course-completion", + "pie" + ); + }, + }); +}; diff --git a/lms/www/classes/__init__.py b/lms/www/classes/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/www/courses/index.js b/lms/www/courses/index.js index 7d75a3d0..f3bc44ec 100644 --- a/lms/www/courses/index.js +++ b/lms/www/courses/index.js @@ -1,5 +1,4 @@ frappe.ready(() => { - $(".nav-link").click((e) => { change_hash(e); }); @@ -7,10 +6,8 @@ frappe.ready(() => { if (window.location.hash) { open_tab(); } - }); - const change_hash = (e) => { window.location.hash = $(e.currentTarget).attr("href"); }; From 3785e7066e8d2a6f1acc4abd6dcaabdb120049bb Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Fri, 11 Nov 2022 10:46:35 +0530 Subject: [PATCH 3/3] fix: formatting --- lms/patches/v0_0/add_pages_to_nav.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lms/patches/v0_0/add_pages_to_nav.py b/lms/patches/v0_0/add_pages_to_nav.py index 3546e3dc..d883d755 100644 --- a/lms/patches/v0_0/add_pages_to_nav.py +++ b/lms/patches/v0_0/add_pages_to_nav.py @@ -1,5 +1,6 @@ import frappe from lms.install import add_pages_to_nav + def execute(): add_pages_to_nav()