From 5800ac67c4eeff93614f5afa24e6a7aa2abf5640 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Fri, 18 Apr 2025 18:05:57 +0530 Subject: [PATCH 1/4] chore: identify user persona --- frontend/src/App.vue | 2 +- frontend/src/components/AppSidebar.vue | 2 +- frontend/src/pages/Courses.vue | 35 ++++- frontend/src/pages/PersonaForm.vue | 176 +++++++++++++++++++++++++ frontend/src/router.js | 5 + frontend/vite.config.js | 2 +- lms/lms/api.py | 19 +++ 7 files changed, 231 insertions(+), 10 deletions(-) create mode 100644 frontend/src/pages/PersonaForm.vue diff --git a/frontend/src/App.vue b/frontend/src/App.vue index d890f55a..c79397af 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -24,7 +24,7 @@ const router = useRouter() const noSidebar = ref(false) router.beforeEach((to, from, next) => { - if (to.query.fromLesson) { + if (to.query.fromLesson || to.path === '/persona') { noSidebar.value = true } else { noSidebar.value = false diff --git a/frontend/src/components/AppSidebar.vue b/frontend/src/components/AppSidebar.vue index 50c4f318..0dcc59f0 100644 --- a/frontend/src/components/AppSidebar.vue +++ b/frontend/src/components/AppSidebar.vue @@ -76,7 +76,7 @@ />
{ + identifyUserPersona() setFiltersFromQuery() updateCourses() categories.value = [ @@ -145,16 +148,34 @@ const courses = createListResource({ pageLength: pageLength.value, start: start.value, onSuccess(data) { - let allCategories = data.map((course) => course.category) - allCategories = allCategories.filter( - (category, index) => allCategories.indexOf(category) === index && category - ) - if (categories.value.length <= allCategories.length) { - updateCategories(data) - } + setCategories(data) }, }) +const setCategories = (data) => { + let allCategories = data.map((course) => course.category) + allCategories = allCategories.filter( + (category, index) => allCategories.indexOf(category) === index && category + ) + if (categories.value.length <= allCategories.length) { + updateCategories(data) + } +} + +const identifyUserPersona = () => { + if (user.data?.is_system_manager) { + call('frappe.client.get_count', { + doctype: 'LMS Course', + }).then((data) => { + if (!data) { + router.push({ + name: 'PersonaForm', + }) + } + }) + } +} + const updateCourses = () => { updateFilters() courses.update({ diff --git a/frontend/src/pages/PersonaForm.vue b/frontend/src/pages/PersonaForm.vue new file mode 100644 index 00000000..c2300d4d --- /dev/null +++ b/frontend/src/pages/PersonaForm.vue @@ -0,0 +1,176 @@ + + diff --git a/frontend/src/router.js b/frontend/src/router.js index 7eba6f7a..fb04cc7d 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -210,6 +210,11 @@ const routes = [ name: 'AssignmentSubmissionList', component: () => import('@/pages/AssignmentSubmissionList.vue'), }, + { + path: '/persona', + name: 'PersonaForm', + component: () => import('@/pages/PersonaForm.vue'), + }, ] let router = createRouter({ diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 4be6085e..16881bc4 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -25,7 +25,7 @@ export default defineConfig({ }), ], server: { - allowedHosts: ['fs', 'bs'], + allowedHosts: ['fs', 'persona'], }, resolve: { alias: { diff --git a/lms/lms/api.py b/lms/lms/api.py index cc8f7df0..229e9281 100644 --- a/lms/lms/api.py +++ b/lms/lms/api.py @@ -2,6 +2,7 @@ """ import json +import requests import frappe import zipfile import os @@ -1389,3 +1390,21 @@ def add_an_evaluator(email): evaluator.insert() return evaluator + + +@frappe.whitelist() +def capture_user_persona(site, role, number_of_students, use_case, frappe_products): + requests.post( + "https://school.frappe.io/api/method/capture_persona", + json={ + "site": site, + "role": role, + "number_of_students": number_of_students, + "use_case": use_case, + "frappe_products": frappe_products, + }, + headers={ + "Authorization": f"token {frappe.local.conf.frappe_token}", + "Content-Type": "application/json", + }, + ) From f1b383f0b727883cad6b0c5f14174aacebc4f90a Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 22 Apr 2025 15:02:21 +0530 Subject: [PATCH 2/4] fix: check persona_captured after details get saved --- frappe-ui | 2 +- frontend/src/pages/Courses.vue | 17 +++++++-- frontend/src/pages/PersonaForm.vue | 27 ++++++++++++-- lms/lms/api.py | 36 +++++++++---------- .../doctype/lms_settings/lms_settings.json | 29 +++++---------- 5 files changed, 66 insertions(+), 45 deletions(-) diff --git a/frappe-ui b/frappe-ui index 29307e4f..8cd9b06a 160000 --- a/frappe-ui +++ b/frappe-ui @@ -1 +1 @@ -Subproject commit 29307e4fffaacdbb3d9c5d95c5270b2f245a5607 +Subproject commit 8cd9b06a5ed50dc181d9672b2fe84c1594b52b48 diff --git a/frontend/src/pages/Courses.vue b/frontend/src/pages/Courses.vue index d46d96ab..61f514f5 100644 --- a/frontend/src/pages/Courses.vue +++ b/frontend/src/pages/Courses.vue @@ -162,8 +162,21 @@ const setCategories = (data) => { } } -const identifyUserPersona = () => { - if (user.data?.is_system_manager) { +const isPersonaCaptured = async () => { + let persona = await call('frappe.client.get_single_value', { + doctype: 'LMS Settings', + field: 'persona_captured', + }) + return persona +} + +const identifyUserPersona = async () => { + let personaCaptured = await isPersonaCaptured() + if ( + user.data?.is_system_manager && + !user.data?.developer_mode && + !personaCaptured + ) { call('frappe.client.get_count', { doctype: 'LMS Course', }).then((data) => { diff --git a/frontend/src/pages/PersonaForm.vue b/frontend/src/pages/PersonaForm.vue index c2300d4d..0ba7cf4d 100644 --- a/frontend/src/pages/PersonaForm.vue +++ b/frontend/src/pages/PersonaForm.vue @@ -1,6 +1,6 @@ @@ -81,6 +87,7 @@ import { sessionStore } from '@/stores/session' const user = inject('$user') const router = useRouter() const { brand } = sessionStore() +console.log(user.data?.sitename) const persona = reactive({ role: null, @@ -90,12 +97,28 @@ const persona = reactive({ }) const submitPersona = () => { - call('lms.lms.api.capture_user_persona', { + let responses = { site: user.data?.sitename, role: persona.role, no_of_students: persona.noOfStudents, use_case: persona.useCase, frappe_products: persona.frappeProducts, + } + call('lms.lms.api.capture_user_persona', { + responses: JSON.stringify(responses), + }).then(() => { + router.push({ + name: 'Courses', + }) + }) +} + +const skipPersonaForm = () => { + call('frappe.client.set_value', { + doctype: 'LMS Settings', + name: null, + fieldname: 'persona_captured', + value: 1, }).then(() => { router.push({ name: 'Courses', diff --git a/lms/lms/api.py b/lms/lms/api.py index 229e9281..1a133532 100644 --- a/lms/lms/api.py +++ b/lms/lms/api.py @@ -2,7 +2,6 @@ """ import json -import requests import frappe import zipfile import os @@ -185,9 +184,10 @@ def get_user_info(): ) user.is_fc_site = is_fc_site() user.is_system_manager = "System Manager" in user.roles + user.sitename = frappe.local.site + user.developer_mode = frappe.conf.developer_mode if user.is_fc_site and user.is_system_manager: user.site_info = current_site_info() - user.sitename = frappe.local.site return user @@ -679,13 +679,13 @@ def get_categories(doctype, filters): @frappe.whitelist() def get_members(start=0, search=""): """Get members for the given search term and start index. - Args: start (int): Start index for the query. + Args: start (int): Start index for the query. <<<<<<< HEAD - search (str): Search term to filter the results. + search (str): Search term to filter the results. ======= - search (str): Search term to filter the results. + search (str): Search term to filter the results. >>>>>>> 4869bba7bbb2fb38477d6fc29fb3b5838e075577 - Returns: List of members. + Returns: List of members. """ filters = {"enabled": 1, "name": ["not in", ["Administrator", "Guest"]]} @@ -1393,18 +1393,14 @@ def add_an_evaluator(email): @frappe.whitelist() -def capture_user_persona(site, role, number_of_students, use_case, frappe_products): - requests.post( - "https://school.frappe.io/api/method/capture_persona", - json={ - "site": site, - "role": role, - "number_of_students": number_of_students, - "use_case": use_case, - "frappe_products": frappe_products, - }, - headers={ - "Authorization": f"token {frappe.local.conf.frappe_token}", - "Content-Type": "application/json", - }, +def capture_user_persona(responses): + frappe.only_for("System Manager") + data = frappe.parse_json(responses) + data = json.dumps(data) + response = frappe.integrations.utils.make_post_request( + "https://school.frappe.io/api/method/capture-persona", + data={"response": data}, ) + if response.get("message").get("name"): + frappe.db.set_single_value("LMS Settings", "persona_captured", True) + return response diff --git a/lms/lms/doctype/lms_settings/lms_settings.json b/lms/lms/doctype/lms_settings/lms_settings.json index 772a6fbe..597fee53 100644 --- a/lms/lms/doctype/lms_settings/lms_settings.json +++ b/lms/lms/doctype/lms_settings/lms_settings.json @@ -8,6 +8,7 @@ "general_tab", "default_home", "send_calendar_invite_for_evaluations", + "persona_captured", "column_break_zdel", "allow_guest_access", "enable_learning_paths", @@ -60,10 +61,7 @@ "column_break_uwsp", "payment_reminder_template", "seo_tab", - "meta_description", - "meta_image", - "column_break_xijv", - "meta_keywords" + "meta_description" ], "fields": [ { @@ -375,28 +373,19 @@ "label": "Meta Description" }, { - "description": "This image will be shown on lists and pages that don't have an image by default", - "fieldname": "meta_image", - "fieldtype": "Attach Image", - "label": "Meta Image" - }, - { - "description": "Common keywords that will be used for all pages", - "fieldname": "meta_keywords", - "fieldtype": "Small Text", - "label": "Meta Keywords" - }, - { - "fieldname": "column_break_xijv", - "fieldtype": "Column Break" + "default": "0", + "fieldname": "persona_captured", + "fieldtype": "Check", + "label": "Persona Captured", + "read_only": 1 } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2025-04-19 12:19:24.037931", - "modified_by": "sayali@frappe.io", + "modified": "2025-04-22 14:34:19.656896", + "modified_by": "Administrator", "module": "LMS", "name": "LMS Settings", "owner": "Administrator", From 3aeb9cf0b1cc3c7e2fc16e1562ca58478f2ea005 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 22 Apr 2025 15:32:01 +0530 Subject: [PATCH 3/4] ci: skip persona form for ui tests --- cypress/e2e/course_creation.cy.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/cypress/e2e/course_creation.cy.js b/cypress/e2e/course_creation.cy.js index 69c0f1ca..061fabb7 100644 --- a/cypress/e2e/course_creation.cy.js +++ b/cypress/e2e/course_creation.cy.js @@ -2,7 +2,10 @@ describe("Course Creation", () => { it("creates a new course", () => { cy.login(); cy.wait(1000); - cy.visit("/lms/courses"); + cy.visit("/lms"); + cy.wait(1000); + cy.get("div").contains("Skip").click(); + cy.wait(1000); // Create a course cy.get("button").contains("New").click(); @@ -19,13 +22,6 @@ describe("Course Creation", () => { ); cy.fixture("profile.png", "base64").then((fileContent) => { - /* cy.get('input[type="file"]').should("be.hidden").attachFile({ - fileContent, - fileName: "profile.png", - mimeType: "image/png", - encoding: "base64", - }); */ - cy.get("div") .contains("Course Image") .siblings("div") From 470123c77a14fff37eddc352a82a2adbbe62c5ac Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 22 Apr 2025 16:17:29 +0530 Subject: [PATCH 4/4] chore: fixed settings --- .github/workflows/ui-tests.yml | 1 + cypress.config.js | 2 +- cypress/e2e/course_creation.cy.js | 5 +--- frontend/src/components/Modals/Settings.vue | 4 +-- frontend/src/pages/Courses.vue | 5 ++++ .../doctype/lms_settings/lms_settings.json | 27 ++++++++++++++++--- lms/lms/utils.py | 4 +++ 7 files changed, 37 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 5338837c..f4b87366 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -100,6 +100,7 @@ jobs: bench --site lms.test execute frappe.utils.install.complete_setup_wizard bench --site lms.test execute frappe.tests.ui_test_helpers.create_test_user bench --site lms.test set-password frappe@example.com admin + bench --site lms.test execute lms.lms.utils.persona_captured - name: cypress pre-requisites run: | diff --git a/cypress.config.js b/cypress.config.js index 5e1c68ea..c7c1354e 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -13,6 +13,6 @@ module.exports = defineConfig({ openMode: 0, }, e2e: { - baseUrl: "http://testui:8000", + baseUrl: "http://pertest:8000", }, }); diff --git a/cypress/e2e/course_creation.cy.js b/cypress/e2e/course_creation.cy.js index 061fabb7..28b97041 100644 --- a/cypress/e2e/course_creation.cy.js +++ b/cypress/e2e/course_creation.cy.js @@ -2,10 +2,7 @@ describe("Course Creation", () => { it("creates a new course", () => { cy.login(); cy.wait(1000); - cy.visit("/lms"); - cy.wait(1000); - cy.get("div").contains("Skip").click(); - cy.wait(1000); + cy.visit("/lms/courses"); // Create a course cy.get("button").contains("New").click(); diff --git a/frontend/src/components/Modals/Settings.vue b/frontend/src/components/Modals/Settings.vue index ab89d535..315d066f 100644 --- a/frontend/src/components/Modals/Settings.vue +++ b/frontend/src/components/Modals/Settings.vue @@ -322,11 +322,11 @@ const tabsStructure = computed(() => { icon: 'LogIn', fields: [ { - label: 'Identify User Persona', + label: 'Identify User Category', name: 'user_category', type: 'checkbox', description: - 'Enable this option to identify the user persona during signup.', + 'Enable this option to identify the user category during signup.', }, { label: 'Disable signup', diff --git a/frontend/src/pages/Courses.vue b/frontend/src/pages/Courses.vue index 61f514f5..58b4717a 100644 --- a/frontend/src/pages/Courses.vue +++ b/frontend/src/pages/Courses.vue @@ -172,6 +172,11 @@ const isPersonaCaptured = async () => { const identifyUserPersona = async () => { let personaCaptured = await isPersonaCaptured() + debugger + console.log('personaCaptured', personaCaptured) + console.log('user.data?.is_system_manager', user.data?.is_system_manager) + console.log('user.data?.developer_mode', user.data?.developer_mode) + if ( user.data?.is_system_manager && !user.data?.developer_mode && diff --git a/lms/lms/doctype/lms_settings/lms_settings.json b/lms/lms/doctype/lms_settings/lms_settings.json index 597fee53..8bf72de4 100644 --- a/lms/lms/doctype/lms_settings/lms_settings.json +++ b/lms/lms/doctype/lms_settings/lms_settings.json @@ -61,7 +61,10 @@ "column_break_uwsp", "payment_reminder_template", "seo_tab", - "meta_description" + "meta_description", + "meta_image", + "column_break_xijv", + "meta_keywords" ], "fields": [ { @@ -106,7 +109,7 @@ "default": "0", "fieldname": "user_category", "fieldtype": "Check", - "label": "Identify User Persona" + "label": "Identify User Category" }, { "default": "0", @@ -372,6 +375,22 @@ "fieldtype": "Small Text", "label": "Meta Description" }, + { + "description": "This image will be shown on lists and pages that don't have an image by default", + "fieldname": "meta_image", + "fieldtype": "Attach Image", + "label": "Meta Image" + }, + { + "fieldname": "column_break_xijv", + "fieldtype": "Column Break" + }, + { + "description": "Common keywords that will be used for all pages", + "fieldname": "meta_keywords", + "fieldtype": "Small Text", + "label": "Meta Keywords" + }, { "default": "0", "fieldname": "persona_captured", @@ -384,8 +403,8 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2025-04-22 14:34:19.656896", - "modified_by": "Administrator", + "modified": "2025-04-22 16:05:27.914422", + "modified_by": "sayali@frappe.io", "module": "LMS", "name": "LMS Settings", "owner": "Administrator", diff --git a/lms/lms/utils.py b/lms/lms/utils.py index 9a39b4a9..83e219ec 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -2167,3 +2167,7 @@ def get_palette(full_name): hash_name = hashlib.md5(encoded_name).hexdigest() idx = cint((int(hash_name[4:6], 16) + 1) / 5.33) return palette[idx % 8] + + +def persona_captured(): + frappe.db.set_single_value("LMS Settings", "persona_captured", 1)