Skip to content
Open
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
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"root": true,
"globals": {
"frappe": true,
"tinymce": true,
"Vue": true,
"SetVueGlobals": true,
"__": true,
Expand Down
99 changes: 99 additions & 0 deletions .github/workflows/linters.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: Linters

on:
pull_request:
branches: [staging, test-production, version-15]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: linters-one_lms-${{ github.event_name }}-${{ github.event.number || github.run_id }}
cancel-in-progress: true

jobs:
commit-lint:
name: 'Semantic Commits'
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 200
- uses: actions/setup-node@v3
with:
node-version: 18
check-latest: true

- name: Verify commitlint config exists
run: |
if [ ! -f "commitlint.config.js" ]; then
echo "Error: commitlint.config.js not found"
exit 1
fi

- name: Check commit titles
run: |
npm install @commitlint/cli @commitlint/config-conventional
npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}

linter:
name: 'Semgrep Rules'
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: '3.11'
cache: pip

- name: Download Semgrep rules
run: git clone --depth 1 --branch develop https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules

- name: Run Semgrep rules
run: |
pip install semgrep
semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness

deps-vulnerable-check:
name: 'Vulnerable Dependency Check'
runs-on: ubuntu-latest

steps:
- uses: actions/setup-python@v4
with:
python-version: '3.11'

- uses: actions/checkout@v4

- name: Cache pip
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-

- name: Install and run pip-audit
run: |
pip install pip-audit
cd ${GITHUB_WORKSPACE}
pip-audit --desc on .

precommit:
name: 'Pre-commit Hooks'
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v4
with:
python-version: '3.11'

- name: Run pre-commit
uses: pre-commit/action@v3.0.1
3 changes: 3 additions & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
extends: ["@commitlint/config-conventional"],
};
3 changes: 2 additions & 1 deletion one_lms/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from one_lms.overrides.plugins import assignment_renderer, quiz_renderer
from lms import plugins
from lms.lms import utils as lms_utils

from one_lms import utils
from one_lms.overrides.plugins import assignment_renderer, quiz_renderer

__version__ = "0.0.1"

Expand Down
219 changes: 109 additions & 110 deletions one_lms/after_migrate/execute.py
Original file line number Diff line number Diff line change
@@ -1,127 +1,126 @@
import frappe
import os
import shlex
import shutil
import subprocess
import shlex

import frappe


def run_command(command_list, cwd=None):
"""
Executes a command from a list of arguments safely.
"""
# For display purposes, join the command list back into a readable string
command_str = shlex.join(command_list)
try:
# Execute the command list, shell=False is the default and is safer
result = subprocess.run(
command_list,
cwd=cwd,
check=True,
text=True,
capture_output=True
)
# Print standard output and error
print(result.stdout)
if result.stderr:
print(result.stderr)
print(f"✅ Command '{command_str}' executed successfully.")
except subprocess.CalledProcessError as e:
# Handle errors in the command execution
print(f"❌ An error occurred while running the command: {command_str}")
print(f"Output: {e.stdout}")
print(f"Error: {e.stderr}")


"""
Executes a command from a list of arguments safely.
"""
# For display purposes, join the command list back into a readable string
command_str = shlex.join(command_list)
try:
# Execute the command list, shell=False is the default and is safer
result = subprocess.run(command_list, cwd=cwd, check=True, text=True, capture_output=True)
# Print standard output and error
print(result.stdout)
if result.stderr:
print(result.stderr)
print(f"✅ Command '{command_str}' executed successfully.")
except subprocess.CalledProcessError as e:
# Handle errors in the command execution
print(f"❌ An error occurred while running the command: {command_str}")
print(f"Output: {e.stdout}")
print(f"Error: {e.stderr}")


def update_lesson():
"""
Replaces the standard LMS Lesson.vue file with the custom version from one_lms
and triggers a build to apply the changes.
"""
print("🚀 Overriding LMS Lesson.vue file...")

# Get the base path of the bench directory
bench_path = frappe.utils.get_bench_path()

# Define the source and destination file paths
source_file = os.path.join(
bench_path, "apps", "lms", "frontend", "src", "pages", "Lesson.vue"
)
replacement_file = os.path.join(
bench_path, "apps", "one_lms","one_lms","public","overrides", "pages", "Lesson.vue"
)

# Ensure the custom replacement file actually exists before proceeding
if not os.path.exists(replacement_file):
print(f"❌ Error: Replacement file not found at: {replacement_file}")
return False

try:
# Copy the content from your custom file to the source file, overwriting it
print(f"📄 Copying from {replacement_file} to {source_file}")
shutil.copy2(replacement_file, source_file)
print("✅ Successfully replaced Lesson.vue.")

return True

except FileNotFoundError:
print(f"⚠️ Warning: Original file not found at: {source_file}. Skipping replacement.")
except Exception as e:
print(f"🔥 An unexpected error occurred: {e}")
frappe.log_error("LMS Lesson.vue Override Failed", frappe.get_traceback())
"""
Replaces the standard LMS Lesson.vue file with the custom version from one_lms
and triggers a build to apply the changes.
"""
print("🚀 Overriding LMS Lesson.vue file...")

# Get the base path of the bench directory
bench_path = frappe.utils.get_bench_path()

# Define the source and destination file paths
source_file = os.path.join(bench_path, "apps", "lms", "frontend", "src", "pages", "Lesson.vue")
replacement_file = os.path.join(
bench_path, "apps", "one_lms", "one_lms", "public", "overrides", "pages", "Lesson.vue"
)

# Ensure the custom replacement file actually exists before proceeding
if not os.path.exists(replacement_file):
print(f"❌ Error: Replacement file not found at: {replacement_file}")
return False

try:
# Copy the content from your custom file to the source file, overwriting it
print(f"📄 Copying from {replacement_file} to {source_file}")
shutil.copy2(replacement_file, source_file)
print("✅ Successfully replaced Lesson.vue.")

return True

except FileNotFoundError:
print(f"⚠️ Warning: Original file not found at: {source_file}. Skipping replacement.")
except Exception as e:
print(f"🔥 An unexpected error occurred: {e}")
frappe.log_error("LMS Lesson.vue Override Failed", frappe.get_traceback())


def update_course_card_overlay():
"""
Replaces the standard LMS CourseCardOverlay.vue file with the custom version from one_lms
and triggers a build to apply the changes.
"""
print("🚀 Overriding LMS CourseCardOverlay.vue file...")

# Get the base path of the bench directory
bench_path = frappe.utils.get_bench_path()

# Define the source and destination file paths
source_file = os.path.join(
bench_path, "apps", "lms", "frontend", "src", "components", "CourseCardOverlay.vue",
)
replacement_file = os.path.join(
bench_path, "apps", "one_lms","one_lms","public","overrides", "components", "CourseCardOverlay.vue"
)

# Ensure the custom replacement file actually exists before proceeding
if not os.path.exists(replacement_file):
print(f"❌ Error: Replacement file not found at: {replacement_file}")
return False

try:
# Copy the content from your custom file to the source file, overwriting it
print(f"📄 Copying from {replacement_file} to {source_file}")
shutil.copy2(replacement_file, source_file)
print("✅ Successfully replaced CourseCardOverlay.vue.")

return True

except FileNotFoundError:
print(f"⚠️ Warning: Original file not found at: {source_file}. Skipping replacement.")
except Exception as e:
print(f"🔥 An unexpected error occurred: {e}")
frappe.log_error("LMS CourseCardOverlay.vue Override Failed", frappe.get_traceback())
"""
Replaces the standard LMS CourseCardOverlay.vue file with the custom version from one_lms
and triggers a build to apply the changes.
"""
print("🚀 Overriding LMS CourseCardOverlay.vue file...")

# Get the base path of the bench directory
bench_path = frappe.utils.get_bench_path()

# Define the source and destination file paths
source_file = os.path.join(
bench_path,
"apps",
"lms",
"frontend",
"src",
"components",
"CourseCardOverlay.vue",
)
replacement_file = os.path.join(
bench_path, "apps", "one_lms", "one_lms", "public", "overrides", "components", "CourseCardOverlay.vue"
)

# Ensure the custom replacement file actually exists before proceeding
if not os.path.exists(replacement_file):
print(f"❌ Error: Replacement file not found at: {replacement_file}")
return False

try:
# Copy the content from your custom file to the source file, overwriting it
print(f"📄 Copying from {replacement_file} to {source_file}")
shutil.copy2(replacement_file, source_file)
print("✅ Successfully replaced CourseCardOverlay.vue.")

return True

except FileNotFoundError:
print(f"⚠️ Warning: Original file not found at: {source_file}. Skipping replacement.")
except Exception as e:
print(f"🔥 An unexpected error occurred: {e}")
frappe.log_error("LMS CourseCardOverlay.vue Override Failed", frappe.get_traceback())


def after_migrate():
bench_path = frappe.utils.get_bench_path()
any_change = False
bench_path = frappe.utils.get_bench_path()
any_change = False

if update_lesson():
any_change = True
if update_lesson():
any_change = True

if update_course_card_overlay():
any_change = True

if any_change:
if update_course_card_overlay():
any_change = True

# Trigger the build process to make the frontend changes live
print("🏗️ Running 'bench build' to apply frontend changes...")
if any_change:
# Trigger the build process to make the frontend changes live
print("🏗️ Running 'bench build' to apply frontend changes...")

run_command(['bench', 'build', '--app', 'lms'], cwd=bench_path)
run_command(["bench", "build", "--app", "lms"], cwd=bench_path)

print("🎉 Override process completed successfully!")
print("🎉 Override process completed successfully!")
36 changes: 18 additions & 18 deletions one_lms/custom/custom_field/course_lesson.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
def get_course_lesson_custom_fields():
return {
"Course Lesson": [
{
"fieldname": "lms_assignment",
"fieldtype": "Link",
"label": "LMS Assignment",
"options": "LMS Assignment",
"insert_after": "section_break_16"
},
{
"fieldname": "quiz",
"fieldtype": "Link",
"label": "Quiz",
"options": "LMS Quiz",
"insert_after": "column_break_9",
}
]
}
return {
"Course Lesson": [
{
"fieldname": "lms_assignment",
"fieldtype": "Link",
"label": "LMS Assignment",
"options": "LMS Assignment",
"insert_after": "section_break_16",
},
{
"fieldname": "quiz",
"fieldtype": "Link",
"label": "Quiz",
"options": "LMS Quiz",
"insert_after": "column_break_9",
},
]
}
Loading
Loading