Merge pull request #406 from pateljannat/stats
This commit is contained in:
@@ -21,7 +21,7 @@ app_license = "AGPL"
|
|||||||
# include js, css files in header of web template
|
# include js, css files in header of web template
|
||||||
web_include_css = "lms.bundle.css"
|
web_include_css = "lms.bundle.css"
|
||||||
# web_include_css = "/assets/lms/css/lms.css"
|
# web_include_css = "/assets/lms/css/lms.css"
|
||||||
web_include_js = ["website.bundle.js", "controls.bundle.js"]
|
web_include_js = ["website.bundle.js"]
|
||||||
|
|
||||||
# include custom scss in every website theme (without file extension ".scss")
|
# include custom scss in every website theme (without file extension ".scss")
|
||||||
# website_theme_scss = "lms/public/scss/website"
|
# website_theme_scss = "lms/public/scss/website"
|
||||||
|
|||||||
32
lms/lms/dashboard_chart/new_signups/new_signups.json
Normal file
32
lms/lms/dashboard_chart/new_signups/new_signups.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"based_on": "creation",
|
||||||
|
"chart_name": "New Signups",
|
||||||
|
"chart_type": "Count",
|
||||||
|
"color": "#4463F0",
|
||||||
|
"creation": "2021-09-28 18:57:50.047656",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Dashboard Chart",
|
||||||
|
"document_type": "User",
|
||||||
|
"dynamic_filters_json": "[]",
|
||||||
|
"filters_json": "[]",
|
||||||
|
"group_by_type": "Count",
|
||||||
|
"idx": 1,
|
||||||
|
"is_public": 1,
|
||||||
|
"is_standard": 1,
|
||||||
|
"last_synced_on": "2022-10-14 15:57:47.583435",
|
||||||
|
"modified": "2022-10-14 17:20:21.880532",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "LMS",
|
||||||
|
"name": "New Signups",
|
||||||
|
"number_of_groups": 0,
|
||||||
|
"owner": "basawaraj@erpnext.com",
|
||||||
|
"roles": [],
|
||||||
|
"source": "",
|
||||||
|
"time_interval": "Daily",
|
||||||
|
"timeseries": 1,
|
||||||
|
"timespan": "Last Quarter",
|
||||||
|
"type": "Line",
|
||||||
|
"use_report_chart": 0,
|
||||||
|
"value_based_on": "",
|
||||||
|
"y_axis": []
|
||||||
|
}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
import re
|
import re
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import flt, cint, cstr, getdate, add_months, fmt_money
|
from frappe.utils import flt, cint, cstr, getdate, add_months, fmt_money, get_datetime, format_date
|
||||||
from lms.lms.md import markdown_to_html, find_macros
|
from lms.lms.md import markdown_to_html, find_macros
|
||||||
import string
|
import string
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.desk.doctype.notification_log.notification_log import make_notification_logs
|
from frappe.desk.doctype.notification_log.notification_log import make_notification_logs
|
||||||
|
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_result
|
||||||
|
from frappe.utils.dateutils import get_period
|
||||||
|
|
||||||
|
|
||||||
RE_SLUG_NOTALLOWED = re.compile("[^a-z0-9]+")
|
RE_SLUG_NOTALLOWED = re.compile("[^a-z0-9]+")
|
||||||
|
|
||||||
@@ -546,3 +549,38 @@ def get_filtered_membership(course, memberships):
|
|||||||
def show_start_learing_cta(course, membership):
|
def show_start_learing_cta(course, membership):
|
||||||
return not course.disable_self_learning and not membership and not course.upcoming \
|
return not course.disable_self_learning and not membership and not course.upcoming \
|
||||||
and not check_profile_restriction() and not is_instructor(course.name) and course.status == "Approved"
|
and not check_profile_restriction() and not is_instructor(course.name) and course.status == "Approved"
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def get_chart_data(chart_name, timespan, timegrain, from_date, to_date):
|
||||||
|
chart = frappe.get_doc("Dashboard Chart", chart_name)
|
||||||
|
filters = [([chart.document_type, "docstatus", "<", 2, False])]
|
||||||
|
doctype = chart.document_type
|
||||||
|
datefield = chart.based_on
|
||||||
|
value_field = chart.value_based_on or "1"
|
||||||
|
from_date = get_datetime(from_date).strftime("%Y-%m-%d")
|
||||||
|
to_date = get_datetime(to_date)
|
||||||
|
|
||||||
|
filters.append([doctype, datefield, ">=", from_date, False])
|
||||||
|
filters.append([doctype, datefield, "<=", to_date, False])
|
||||||
|
|
||||||
|
data = frappe.db.get_all(
|
||||||
|
doctype,
|
||||||
|
fields=[f"{datefield} as _unit", f"SUM({value_field})", "COUNT(*)"],
|
||||||
|
filters=filters,
|
||||||
|
group_by="_unit",
|
||||||
|
order_by="_unit asc",
|
||||||
|
as_list=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = get_result(data, timegrain, from_date, to_date, chart.chart_type)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"labels": [
|
||||||
|
format_date(get_period(r[0], timegrain), parse_day_first=True)
|
||||||
|
if timegrain in ("Daily", "Weekly")
|
||||||
|
else get_period(r[0], timegrain)
|
||||||
|
for r in result
|
||||||
|
],
|
||||||
|
"datasets": [{"name": chart.name, "values": [r[1] for r in result]}],
|
||||||
|
}
|
||||||
|
|||||||
@@ -1718,20 +1718,15 @@ li {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.stats-label {
|
.stats-label {
|
||||||
color: var(--text-muted);
|
color: var(--gray-900);
|
||||||
font-weight: bold;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats-value {
|
.stats-value {
|
||||||
color: var(--gray-900);
|
color: var(--gray-900);
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
font-size: 2rem;
|
font-size: 1.5rem;
|
||||||
}
|
margin-top: 2rem;
|
||||||
|
|
||||||
.stats-card {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.indicator-pill.green::before {
|
.indicator-pill.green::before {
|
||||||
@@ -1774,3 +1769,9 @@ li {
|
|||||||
.modal-header .modal-title {
|
.modal-header .modal-title {
|
||||||
color: var(--gray-900);
|
color: var(--gray-900);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.frappe-chart .title {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--gray-900);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
frappe.ready(() => {
|
frappe.ready(() => {
|
||||||
|
|
||||||
setup_vue_and_file_size();
|
setup_file_size();
|
||||||
|
|
||||||
$(".join-batch").click((e) => {
|
$(".join-batch").click((e) => {
|
||||||
join_course(e);
|
join_course(e);
|
||||||
@@ -21,12 +21,7 @@ frappe.ready(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const setup_vue_and_file_size = () => {
|
const setup_file_size = () => {
|
||||||
frappe.require("/assets/frappe/node_modules/vue/dist/vue.js", () => {
|
|
||||||
Vue.prototype.__ = window.__;
|
|
||||||
Vue.prototype.frappe = window.frappe;
|
|
||||||
});
|
|
||||||
|
|
||||||
frappe.provide("frappe.form.formatters");
|
frappe.provide("frappe.form.formatters");
|
||||||
frappe.form.formatters.FileSize = file_size;
|
frappe.form.formatters.FileSize = file_size;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
import "./profile.js"
|
import "./profile.js"
|
||||||
import "./common_functions.js"
|
import "./common_functions.js"
|
||||||
|
import "../../../../frappe/frappe/public/js/frappe/ui/chart.js"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% set courses = get_authored_courses(user or None, only_published or False) %}
|
{% set courses = courses_created if courses_created else get_authored_courses(user or None, only_published or False) %}
|
||||||
|
|
||||||
{% if courses | length %}
|
{% if courses | length %}
|
||||||
<div class="cards-parent">
|
<div class="cards-parent">
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<img class="icon icon-xl" src="/assets/lms/icons/comment.svg">
|
<img class="icon icon-xl" src="/assets/lms/icons/comment.svg">
|
||||||
<div class="empty-state-text">
|
<div class="empty-state-text">
|
||||||
<div class="empty-state-heading">{{ _("No courses created") }}</div>
|
<div class="empty-state-heading">{{ _("No courses created") }}</div>
|
||||||
<div class="course-meta">{{ _("Help others learn something new.") }}</div>
|
<div class="course-meta">{{ _("Help others learn something new by creating a course.") }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<div class="stats-parent">
|
<div class="stats-parent">
|
||||||
|
|
||||||
{% if published_courses %}
|
{% if published_courses %}
|
||||||
<div class="stats-card">
|
<div class="common-card-style p-4 flex-column">
|
||||||
<div class="stats-label">
|
<div class="stats-label">
|
||||||
{{ _("Published Courses") }}
|
{{ _("Published Courses") }}
|
||||||
</div>
|
</div>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if total_signups %}
|
{% if total_signups %}
|
||||||
<div class="stats-card">
|
<div class="common-card-style p-4 flex-column">
|
||||||
<div class="stats-label">
|
<div class="stats-label">
|
||||||
{{ _("Total Signups") }}
|
{{ _("Total Signups") }}
|
||||||
</div>
|
</div>
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if enrollment_count %}
|
{% if enrollment_count %}
|
||||||
<div class="stats-card">
|
<div class="common-card-style p-4 flex-column">
|
||||||
<div class="stats-label">
|
<div class="stats-label">
|
||||||
{{ _("Enrollment Count") }}
|
{{ _("Enrollment Count") }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
16
lms/templates/stats.html
Normal file
16
lms/templates/stats.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{% set published_courses = True %}
|
||||||
|
{% set total_signups = True %}
|
||||||
|
{% set enrollment_count = True %}
|
||||||
|
|
||||||
|
<div class="mt-10">
|
||||||
|
{% include "lms/templates/statistics.html" %}
|
||||||
|
|
||||||
|
<div class="row mt-10">
|
||||||
|
<div class="col">
|
||||||
|
<div class="common-card-style p-4" id="new-signups"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="common-card-style p-4" id="course-enrollments"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -255,8 +255,7 @@
|
|||||||
"reference_doctype": "Course Lesson",
|
"reference_doctype": "Course Lesson",
|
||||||
"reference_docname": lesson.name
|
"reference_docname": lesson.name
|
||||||
}) %}
|
}) %}
|
||||||
{% set is_instructor = frappe.session.user == course.instructor %}
|
{% set condition = is_instructor(course.name) or membership %}
|
||||||
{% set condition = is_instructor if is_instructor else membership %}
|
|
||||||
{% set doctype, docname = _("Course Lesson"), lesson.name %}
|
{% set doctype, docname = _("Course Lesson"), lesson.name %}
|
||||||
{% set title = "Questions" if topics_count else "" %}
|
{% set title = "Questions" if topics_count else "" %}
|
||||||
{% set cta_title = "Ask a Question" %}
|
{% set cta_title = "Ask a Question" %}
|
||||||
|
|||||||
@@ -293,12 +293,12 @@
|
|||||||
membership.current_lesson else "1.1" if first_lesson_exists(course.name) else None %}
|
membership.current_lesson else "1.1" if first_lesson_exists(course.name) else None %}
|
||||||
|
|
||||||
{% if show_start_learing_cta(course, membership) %}
|
{% if show_start_learing_cta(course, membership) %}
|
||||||
<div class="btn btn-primary wide-button join-batch mb-2" data-course="{{ course.name | urlencode }}">
|
<div class="btn btn-primary wide-button join-batch" data-course="{{ course.name | urlencode }}">
|
||||||
{{ _("Start Learning") }}
|
{{ _("Start Learning") }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% elif is_instructor(course.name) and not course.published and course.status != "Under Review" %}
|
{% elif is_instructor(course.name) and not course.published and course.status != "Under Review" %}
|
||||||
<div class="btn btn-primary wide-button mb-2" id="submit-for-review" data-course="{{ course.name | urlencode }}">
|
<div class="btn btn-primary wide-button" id="submit-for-review" data-course="{{ course.name | urlencode }}">
|
||||||
{{ _("Submit for Review") }}
|
{{ _("Submit for Review") }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -423,3 +423,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
|
{%- block script %}
|
||||||
|
{{ super() }}
|
||||||
|
{{ include_script('controls.bundle.js') }}
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
@@ -76,6 +76,12 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" data-toggle="tab" href="#stats">
|
||||||
|
{{ _("Statistics") }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="border-bottom mb-4"></div>
|
<div class="border-bottom mb-4"></div>
|
||||||
@@ -113,6 +119,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="tab-pane fade" id="stats" role="tabpanel" aria-labelledby="stats">
|
||||||
|
{% include "lms/templates/stats.html" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
39
lms/www/courses/index.js
Normal file
39
lms/www/courses/index.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
frappe.ready(() => {
|
||||||
|
generate_graph("New Signups");
|
||||||
|
generate_graph("Course Enrollments");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
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.get_today()
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user