From d763dba204e008a5d8b7e1a3f82412f53838e797 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 11 Jul 2023 19:29:30 +0530 Subject: [PATCH 1/5] discussions in class --- lms/public/css/style.css | 13 ++++++------ lms/public/js/website.bundle.js | 1 + lms/www/batch/learn.html | 6 ++++-- lms/www/classes/class.html | 36 +++++++++++++++++++++++++++------ lms/www/classes/class.py | 3 +++ 5 files changed, 45 insertions(+), 14 deletions(-) diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 7037a6f6..ed90ef3f 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -43,7 +43,7 @@ body { margin-top: 2rem; } -.frappe-control .ql-editor:not(.read-mode) { +.field-group .frappe-control .ql-editor:not(.read-mode) { background-color: #FFFFFF; } @@ -321,11 +321,12 @@ input[type=checkbox] { } .common-card-style { - display: flex; - background: #FFFFFF; - border-radius: var(--border-radius-md); - position: relative; - border: 1px solid var(--gray-300) + display: flex; + background: #FFFFFF; + border-radius: var(--border-radius-md); + position: relative; + border: 1px solid var(--gray-300); + box-shadow: var(--card-shadow); } .course-card { diff --git a/lms/public/js/website.bundle.js b/lms/public/js/website.bundle.js index 7d1dfd83..d5a2c398 100644 --- a/lms/public/js/website.bundle.js +++ b/lms/public/js/website.bundle.js @@ -1,3 +1,4 @@ import "./profile.js"; import "./common_functions.js"; import "../../../../frappe/frappe/public/js/frappe/ui/chart.js"; +import "../../../../frappe/frappe/public/js/frappe/ui/keyboard.js"; diff --git a/lms/www/batch/learn.html b/lms/www/batch/learn.html index 23003702..7c92be60 100644 --- a/lms/www/batch/learn.html +++ b/lms/www/batch/learn.html @@ -118,7 +118,9 @@ {% endif %} -
{{ frappe.utils.format_date(lesson.creation, "medium") }}
+
+ {{ frappe.utils.format_date(lesson.creation, "medium") }} +
@@ -240,9 +242,9 @@ {% endmacro %} - {%- block script %} {{ super() }} + {{ include_script('controls.bundle.js') }} + {% else %} + {% endif %} {{ include_script('controls.bundle.js') }} diff --git a/lms/www/classes/class.js b/lms/www/classes/class.js index a8a45a9e..a926036f 100644 --- a/lms/www/classes/class.js +++ b/lms/www/classes/class.js @@ -42,6 +42,14 @@ frappe.ready(() => { $(".btn-close").click((e) => { window.location.reload(); }); + + $(".btn-schedule-eval").click((e) => { + open_evaluation_form(e); + }); + + $(document).on("click", ".slot", (e) => { + mark_active_slot(e); + }); }); const create_live_class = (e) => { @@ -544,3 +552,121 @@ const remove_assessment = (e) => { }); }); }; + +const open_evaluation_form = (e) => { + this.eval_form = new frappe.ui.Dialog({ + title: __("Schedule Evaluation"), + fields: [ + { + fieldtype: "Link", + fieldname: "course", + label: __("Course"), + options: "LMS Course", + reqd: 1, + filters: { + name: ["in", courses], + }, + filter_description: " ", + }, + { + fieldtype: "Date", + fieldname: "date", + label: __("Date"), + reqd: 1, + min_date: new Date( + frappe.datetime.add_days(frappe.datetime.get_today(), 1) + ), + change: () => { + get_slots(); + }, + }, + { + fieldtype: "HTML", + fieldname: "slots", + label: __("Slots"), + }, + ], + primary_action: (values) => { + submit_evaluation_form(values); + }, + }); + this.eval_form.show(); + setTimeout(() => { + $(".modal-body").css("min-height", "300px"); + }, 1000); +}; + +const get_slots = () => { + frappe.call({ + method: "lms.lms.doctype.course_evaluator.course_evaluator.get_schedule", + args: { + course: this.eval_form.get_value("course"), + date: this.eval_form.get_value("date"), + class_name: $(".class-details").data("class"), + }, + callback: (r) => { + if (r.message) { + display_slots(r.message); + } + }, + }); +}; + +const display_slots = (slots) => { + let slot_html = ""; + let day = moment(this.eval_form.get_value("date")).format("dddd"); + + slots.forEach((slot) => { + if (slot.day == day) { + slot_html += `
+ ${moment(slot.start_time, "hh:mm").format("hh:mm a")} - + ${moment(slot.end_time, "hh:mm").format("hh:mm a")} +
`; + } + }); + + if (!slot_html) { + slot_html = ``; + } + + $("[data-fieldname='slots']").html(slot_html); +}; + +const mark_active_slot = (e) => { + $(".slot").removeClass("btn-outline-primary"); + $(e.currentTarget).addClass("btn-outline-primary"); + this.current_slot = $(e.currentTarget); +}; + +const submit_evaluation_form = (values) => { + if (!this.current_slot) { + frappe.throw(__("Please select a slot")); + } + + this.eval_form.hide(); + frappe.call({ + method: "lms.lms.doctype.lms_certificate_request.lms_certificate_request.create_certificate_request", + args: { + course: values.course, + date: values.date, + start_time: this.current_slot.data("start"), + end_time: this.current_slot.data("end"), + day: this.current_slot.data("day"), + class_name: $(".class-details").data("class"), + }, + callback: (r) => { + frappe.show_alert({ + message: __("Evaluation scheduled successfully"), + indicator: "green", + }); + setTimeout(() => { + window.location.reload(); + }, 1000); + }, + }); +}; diff --git a/lms/www/classes/class.py b/lms/www/classes/class.py index dd911d35..dfab4810 100644 --- a/lms/www/classes/class.py +++ b/lms/www/classes/class.py @@ -1,13 +1,14 @@ from frappe import _ import frappe -from frappe.utils import getdate +from frappe.utils import getdate, cint from lms.www.utils import get_assessments from lms.lms.utils import ( has_course_moderator_role, has_course_evaluator_role, - get_course_progress, + get_upcoming_evals, has_submitted_assessment, has_graded_assessment, + get_membership, ) @@ -39,14 +40,10 @@ def get_context(context): context.reference_doctype = "LMS Class" context.reference_name = class_name - context.published_courses = frappe.get_all( - "LMS Course", {"published": 1}, ["name", "title"] - ) - class_courses = frappe.get_all( "Class Course", {"parent": class_name}, - ["name", "course"], + ["name", "course", "title"], order_by="creation desc", ) @@ -65,6 +62,7 @@ def get_context(context): ) context.class_courses = get_class_course_details(class_courses) + context.course_list = [course.course for course in context.class_courses] context.all_courses = frappe.get_list( "LMS Course", fields=["name", "title"], limit_page_length=0 ) @@ -74,6 +72,10 @@ def get_context(context): class_students, class_courses, context.assessments ) context.is_student = is_student(class_students) + + context.current_student = ( + get_current_student_details(class_courses, class_name) if context.is_student else None + ) context.all_assignments = get_all_assignments(class_name) context.all_quizzes = get_all_quizzes(class_name) @@ -139,36 +141,47 @@ def get_class_student_details(class_students, class_courses, assessments): ) ) student.update(frappe.db.get_value("User", student.student, "last_active", as_dict=1)) - - courses_completed = 0 - for course in class_courses: - if get_course_progress(course.course, student.student) == 100: - courses_completed += 1 - student["courses_completed"] = courses_completed - - assessments_completed = 0 - assessments_graded = 0 - for assessment in assessments: - submission = has_submitted_assessment( - assessment.assessment_name, assessment.assessment_type, student.student - ) - if submission: - assessments_completed += 1 - - if ( - assessment.assessment_type == "LMS Assignment" - and has_graded_assessment(submission) - ): - assessments_graded += 1 - elif assessment.assessment_type == "LMS Quiz": - assessments_graded += 1 - - student["assessments_completed"] = assessments_completed - student["assessments_graded"] = assessments_graded + get_progress_info(student, class_courses) + get_assessment_info(student, assessments) return sort_students(class_students) +def get_progress_info(student, class_courses): + courses_completed = 0 + student["courses"] = frappe._dict() + for course in class_courses: + membership = get_membership(course.course, student.student) + if membership and membership.progress == 100: + courses_completed += 1 + + student["courses_completed"] = courses_completed + return student + + +def get_assessment_info(student, assessments): + assessments_completed = 0 + assessments_graded = 0 + for assessment in assessments: + submission = has_submitted_assessment( + assessment.assessment_name, assessment.assessment_type, student.student + ) + if submission: + assessments_completed += 1 + + if ( + assessment.assessment_type == "LMS Assignment" and has_graded_assessment(submission) + ): + assessments_graded += 1 + elif assessment.assessment_type == "LMS Quiz": + assessments_graded += 1 + + student["assessments_completed"] = assessments_completed + student["assessments_graded"] = assessments_graded + + return student + + def sort_students(class_students): session_user = [] remaining_students = [] @@ -188,3 +201,25 @@ def sort_students(class_students): def is_student(class_students): students = [student.student for student in class_students] return frappe.session.user in students + + +def get_current_student_details(class_courses, class_name): + student_details = frappe._dict() + student_details.courses = frappe._dict() + course_list = [course.course for course in class_courses] + + get_course_progress(class_courses, student_details) + student_details.name = frappe.session.user + student_details.assessments = get_assessments(class_name, frappe.session.user) + student_details.upcoming_evals = get_upcoming_evals(frappe.session.user, course_list) + + return student_details + + +def get_course_progress(class_courses, student_details): + for course in class_courses: + membership = get_membership(course.course, frappe.session.user) + if membership: + student_details.courses[course.course] = membership.progress + else: + student_details.courses[course.course] = 0 diff --git a/lms/www/classes/progress.html b/lms/www/classes/progress.html index 2639eda7..1c191be0 100644 --- a/lms/www/classes/progress.html +++ b/lms/www/classes/progress.html @@ -64,112 +64,13 @@ {% macro UpcomingEvals(upcoming_evals) %}
-
- {{ _("Upcoming Evaluations") }} -
- {% if upcoming_evals | length %} -
- {% for eval in upcoming_evals %} -
-
-
- {{ eval.course_title }} -
- {% if eval.google_meet_link %} - - {{ _("Join") }} - - {% endif %} -
- -
- - - - - {{ frappe.utils.format_date(eval.date, "medium") }} -  - - - {{ frappe.utils.format_time(eval.start_time, "hh:mm a") }} - -
-
- - {{ _("Evaluator") }}: - - - {{ eval.evaluator_name }} - -
-
- {% endfor %} -
- {% else %} -

{{ _("No Upcoming Evaluations") }}

- {% endif %} + {% include "lms/templates/upcoming_evals.html" %}
{% endmacro %} {% macro Assessments(class_info, student) %}
-
- {{ _("Assessments") }} -
- {% if assessments | length %} -
-
-
-
-
- {{ _("Assessment") }} -
-
- {{ _("Type") }} -
-
- {{ _("Status/Score") }} -
-
-
-
- {% for assessment in assessments %} - {% set has_access = is_moderator and assessment.submission or frappe.session.user == student.name %} -
-
- - {{ assessment.title }} - -
- {{ (assessment.assessment_type).split("LMS ")[1] }} -
- -
- {% if assessment.submission %} - {% if assessment.assessment_type == "LMS Assignment" %} - {% set status = assessment.submission.status %} - {% set color = "green" if status == "Pass" else "red" if status == "Fail" else "orange" %} -
- {{ status }} -
- {% else %} -
- {{ assessment.submission.score }} -
- {% endif %} - {% else %} -
- {{ _("Not Attempted") }} -
- {% endif %} -
- -
-
- {% endfor %} -
- {% else %} -

{{ _("No Assessments") }}

- {% endif %} + {% include "lms/templates/assessments.html" %}
{% endmacro %} diff --git a/lms/www/classes/progress.js b/lms/www/classes/progress.js index 44c4268b..6c136c9a 100644 --- a/lms/www/classes/progress.js +++ b/lms/www/classes/progress.js @@ -2,130 +2,4 @@ frappe.ready(() => { $(".clickable-row").click((e) => { window.location.href = $(e.currentTarget).data("href"); }); - - $(".btn-schedule-eval").click((e) => { - open_evaluation_form(e); - }); - - $(document).on("click", ".slot", (e) => { - mark_active_slot(e); - }); }); - -const open_evaluation_form = (e) => { - this.eval_form = new frappe.ui.Dialog({ - title: __("Schedule Evaluation"), - fields: [ - { - fieldtype: "Link", - fieldname: "course", - label: __("Course"), - options: "LMS Course", - reqd: 1, - filters: { - name: ["in", courses], - }, - filter_description: " ", - }, - { - fieldtype: "Date", - fieldname: "date", - label: __("Date"), - reqd: 1, - min_date: new Date( - frappe.datetime.add_days(frappe.datetime.get_today(), 1) - ), - change: () => { - get_slots(); - }, - }, - { - fieldtype: "HTML", - fieldname: "slots", - label: __("Slots"), - }, - ], - primary_action: (values) => { - submit_evaluation_form(values); - }, - }); - this.eval_form.show(); - setTimeout(() => { - $(".modal-body").css("min-height", "300px"); - }, 1000); -}; - -const get_slots = () => { - frappe.call({ - method: "lms.lms.doctype.course_evaluator.course_evaluator.get_schedule", - args: { - course: this.eval_form.get_value("course"), - date: this.eval_form.get_value("date"), - class_name: class_name, - }, - callback: (r) => { - if (r.message) { - display_slots(r.message); - } - }, - }); -}; - -const display_slots = (slots) => { - let slot_html = ""; - let day = moment(this.eval_form.get_value("date")).format("dddd"); - - slots.forEach((slot) => { - if (slot.day == day) { - slot_html += `
- ${moment(slot.start_time, "hh:mm").format("hh:mm a")} - - ${moment(slot.end_time, "hh:mm").format("hh:mm a")} -
`; - } - }); - - if (!slot_html) { - slot_html = ``; - } - - $("[data-fieldname='slots']").html(slot_html); -}; - -const mark_active_slot = (e) => { - $(".slot").removeClass("btn-outline-primary"); - $(e.currentTarget).addClass("btn-outline-primary"); - this.current_slot = $(e.currentTarget); -}; - -const submit_evaluation_form = (values) => { - if (!this.current_slot) { - frappe.throw(__("Please select a slot")); - } - - this.eval_form.hide(); - frappe.call({ - method: "lms.lms.doctype.lms_certificate_request.lms_certificate_request.create_certificate_request", - args: { - course: values.course, - date: values.date, - start_time: this.current_slot.data("start"), - end_time: this.current_slot.data("end"), - day: this.current_slot.data("day"), - class_name: class_name, - }, - callback: (r) => { - frappe.show_alert({ - message: __("Evaluation scheduled successfully"), - indicator: "green", - }); - setTimeout(() => { - window.location.reload(); - }, 1000); - }, - }); -}; diff --git a/lms/www/classes/progress.py b/lms/www/classes/progress.py index d273162c..90b41282 100644 --- a/lms/www/classes/progress.py +++ b/lms/www/classes/progress.py @@ -1,5 +1,9 @@ import frappe -from lms.lms.utils import has_course_moderator_role, has_course_evaluator_role +from lms.lms.utils import ( + has_course_moderator_role, + has_course_evaluator_role, + get_upcoming_evals, +) from frappe import _ from lms.www.utils import get_assessments @@ -34,20 +38,4 @@ def get_context(context): ) context.assessments = get_assessments(class_name, context.student.name) - - upcoming_evals = frappe.get_all( - "LMS Certificate Request", - { - "member": context.student.name, - "course": ["in", context.courses], - "date": [">=", frappe.utils.nowdate()], - }, - ["date", "start_time", "course", "evaluator", "google_meet_link"], - order_by="date", - ) - - for evals in upcoming_evals: - evals.course_title = frappe.db.get_value("LMS Course", evals.course, "title") - evals.evaluator_name = frappe.db.get_value("User", evals.evaluator, "full_name") - - context.upcoming_evals = upcoming_evals + context.upcoming_evals = get_upcoming_evals(context.student.name, context.courses) From 8479e90aebb039f2f6724401c3662359ebe56555 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 9 Aug 2023 10:16:48 +0530 Subject: [PATCH 3/5] fix: allow evaluators to access the discussions section --- lms/www/classes/class.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index c57e9444..1f20a6e4 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -173,7 +173,7 @@ {% endif %} - {% if class_students | length and (is_moderator or is_student) %} + {% if class_students | length and (is_moderator or is_student or is_evaluator) %}
{{ Discussions(class_info) }}
@@ -230,7 +230,7 @@ {% macro Discussions(class_info) %}
- {% set condition = is_moderator or is_student %} + {% set condition = is_moderator or is_student or is_evaluator %} {% set doctype, docname = _("LMS Class"), class_info.name %} {% set single_thread = True %} {% set title = "Discussions" %} From 6e47b4a94149324366b4572a083f63fe2e07c6c2 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 9 Aug 2023 10:53:52 +0530 Subject: [PATCH 4/5] test: fix discussions test --- cypress/e2e/course_creation.cy.js | 58 ++++++++++++++++++++++--------- lms/www/batch/learn.html | 4 ++- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/cypress/e2e/course_creation.cy.js b/cypress/e2e/course_creation.cy.js index 3aaf6155..48f3c5dd 100644 --- a/cypress/e2e/course_creation.cy.js +++ b/cypress/e2e/course_creation.cy.js @@ -86,26 +86,50 @@ describe("Course Creation", () => { // Add Discussion cy.get(".reply").click(); cy.wait(500); - cy.get(".topic-title").type("Question Title"); - cy.get(".comment-field").type( - "Question Content. This is a very long question. It contains more than once sentence. Its meant to be this long as this is a UI test." - ); - cy.get(".submit-discussion").click(); + cy.get(".discussion-modal").should("be.visible"); - // View Discussion - cy.wait(1000); - cy.get(".discussion-topic-title:first").contains("Question Title"); - cy.get(".sidebar-parent:first").click(); - cy.get(".reply-text").contains( - "Question Content. This is a very long question. It contains more than once sentence. Its meant to be this long as this is a UI test." + // Enter title + cy.get(".modal .topic-title") + .type("Discussion from tests") + .should("have.value", "Discussion from tests"); + + // Enter comment + cy.get(".modal .discussions-comment").type( + "This is a discussion from the cypress ui tests." ); - cy.get(".comment-field:visible").type( - "This is a reply to the previous comment. Its not that long." + + // Submit + cy.get(".modal .submit-discussion").click(); + cy.wait(2000); + + // Check if discussion is added to page and content is visible + cy.get(".sidebar-parent:first .discussion-topic-title").should( + "have.text", + "Discussion from tests" ); - cy.get(".submit-discussion:visible").click(); - cy.wait(1000); - cy.get(".reply-text:last p").contains( - "This is a reply to the previous comment. Its not that long." + cy.get(".sidebar-parent:first .discussion-topic-title").click(); + cy.get(".discussion-on-page:visible").should("have.class", "show"); + cy.get( + ".discussion-on-page:visible .reply-card .reply-text .ql-editor p" + ).should( + "have.text", + "This is a discussion from the cypress ui tests." ); + + cy.get(".discussion-form:visible .discussions-comment").type( + "This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page." + ); + + cy.get(".discussion-form:visible .submit-discussion").click(); + cy.wait(3000); + cy.get(".discussion-on-page:visible").should("have.class", "show"); + cy.get(".discussion-on-page:visible") + .children(".reply-card") + .eq(1) + .find(".reply-text") + .should( + "have.text", + "This is a discussion from the cypress ui tests. This comment was entered through the commentbox on the page.\n" + ); }); }); diff --git a/lms/www/batch/learn.html b/lms/www/batch/learn.html index 7c92be60..9c582d68 100644 --- a/lms/www/batch/learn.html +++ b/lms/www/batch/learn.html @@ -238,7 +238,9 @@ {% set redirect_to = "/courses/" + course.name %} {% set empty_state_title = _("Have a doubt?") %} {% set empty_state_subtitle = _("Post it here, our mentors will help you out.") %} - {% include "frappe/templates/discussions/discussions_section.html" %} +
+ {% include "frappe/templates/discussions/discussions_section.html" %} +
{% endmacro %} From 71dc32098ecc47b5e155cda7a79f47566b61ea69 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 9 Aug 2023 11:09:17 +0530 Subject: [PATCH 5/5] fix: removed unnecessary code --- lms/lms/utils.py | 2 -- lms/www/classes/class.html | 21 --------------------- 2 files changed, 23 deletions(-) diff --git a/lms/lms/utils.py b/lms/lms/utils.py index ecd65f35..3a2b8b1f 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -795,7 +795,6 @@ def get_evaluator(course, class_name=None): def get_upcoming_evals(student, courses): - print(student, courses) upcoming_evals = frappe.get_all( "LMS Certificate Request", { @@ -810,5 +809,4 @@ def get_upcoming_evals(student, courses): for evals in upcoming_evals: evals.course_title = frappe.db.get_value("LMS Course", evals.course, "title") evals.evaluator_name = frappe.db.get_value("User", evals.evaluator, "full_name") - print(upcoming_evals) return upcoming_evals diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index 1f20a6e4..336c7b91 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -205,27 +205,6 @@
{% include "lms/templates/assessments.html" %}
- {% endmacro %} {% macro Discussions(class_info) %}