appsembler / figures

Reporting and data retrieval app for Open edX
MIT License
44 stars 37 forks source link

Koa compatibility #323

Open mrtndwrd opened 3 years ago

mrtndwrd commented 3 years ago

I'm taking a look at whether the current 0.4.dev8 version could already be (or be made) compatible with Koa.

I'm pushing my work here and I'm using this fork of the tutor-figures plugin to install it on edX.

What works:

What doesn't work:

This is probably because when I run python manage.py lms populate_figures_metrics --no-delay this error occurs a lot of times:

2021-02-17 16:24:04,984 ERROR 77 [figures.tasks] [user None] [ip None] tasks.py:110 - FIGURES:PIPELINE:DAILY:SITE:COURSE:FAIL:populate_daily_metrics_for_site. site_id:4, date_for:2021-02-17. course_id:course-v1:Totem+TP_SM_FA+001 exception:["'None' value must be a decimal number."]
Traceback (most recent call last):
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/query.py", line 554, in update_or_create
    obj = self.select_for_update().get(**kwargs)
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/query.py", line 406, in get
    raise self.model.DoesNotExist(
figures.models.CourseDailyMetrics.DoesNotExist: CourseDailyMetrics matching query does not exist.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/fields/__init__.py", line 1560, in to_python
    return decimal.Decimal(value)
decimal.InvalidOperation: [<class 'decimal.ConversionSyntax'>]

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/openedx/venv/src/figures/figures/tasks.py", line 103, in populate_daily_metrics_for_site
    populate_single_cdm(course_id=course_id,
  File "/openedx/venv/lib/python3.8/site-packages/celery/local.py", line 191, in __call__
    return self._get_current_object()(*a, **kw)
  File "/openedx/venv/lib/python3.8/site-packages/celery/app/task.py", line 393, in __call__
    return self.run(*args, **kwargs)
  File "/openedx/venv/src/figures/figures/tasks.py", line 65, in populate_single_cdm
    cdm_obj, _created = CourseDailyMetricsLoader(
  File "/openedx/venv/src/figures/figures/pipeline/course_daily_metrics.py", line 352, in load
    return self.save_metrics(date_for=date_for, data=data)
  File "/opt/pyenv/versions/3.8.6/lib/python3.8/contextlib.py", line 75, in inner
    return func(*args, **kwds)
  File "/openedx/venv/src/figures/figures/pipeline/course_daily_metrics.py", line 310, in save_metrics
    cdm, created = CourseDailyMetrics.objects.update_or_create(
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/query.py", line 559, in update_or_create
    obj, created = self._create_object_from_params(kwargs, params, lock=True)
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/query.py", line 575, in _create_object_from_params
    obj = self.create(**params)
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/query.py", line 422, in create
    obj.save(force_insert=True, using=self.db)
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/base.py", line 743, in save
    self.save_base(using=using, force_insert=force_insert,
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/base.py", line 780, in save_base
    updated = self._save_table(
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/base.py", line 873, in _save_table
    result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/base.py", line 910, in _do_insert
    return manager._insert([self], fields=fields, return_id=update_pk,
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/query.py", line 1186, in _insert
    return query.get_compiler(using=using).execute_sql(return_id)
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1376, in execute_sql
    for sql, params in self.as_sql():
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1318, in as_sql
    value_rows = [
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1319, in <listcomp>
    [self.prepare_value(field, self.pre_save_val(field, obj)) for field in fields]
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1319, in <listcomp>
    [self.prepare_value(field, self.pre_save_val(field, obj)) for field in fields]
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1260, in prepare_value
    value = field.get_db_prep_save(value, connection=self.connection)
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/fields/__init__.py", line 1569, in get_db_prep_save
    return connection.ops.adapt_decimalfield_value(self.to_python(value), self.max_digits, self.decimal_places)
  File "/openedx/venv/lib/python3.8/site-packages/django/db/models/fields/__init__.py", line 1562, in to_python
    raise exceptions.ValidationError(
django.core.exceptions.ValidationError: ["'None' value must be a decimal number."]

I assume this error happens for all the courses. I hope to be able to figure out why tomorrow. In the mean time, any advice would definitely help!

johnbaldwin commented 3 years ago

Thanks for posting this, @mrtndwrd ! I look forward to seeing what you find

mrtndwrd commented 3 years ago

OK, this was stupid... I forgot to add the --settings tutor.production argument to the manage.py command. So with this command, it works way better:

python manage.py lms --settings tutor.production populate_figures_metrics --no-delay

Now I only get a whole lot of these:

2021-02-19 11:10:44,692 ERROR 655 [figures.tasks] [user None] [ip None] tasks.py:138 - figures.tasks.update_enrollment_data. Error:CourseNotFound for course "course-v1:AFFCameroun+AFFC_SM_EN+09_2020".  CourseEnrollment ID=3959

The data already includes almost everything I need. The only thing I notice is that on the "Overview" page the "Course enrollments" and "Course completions" metric stays at 0, probably due to the above error.

mrtndwrd commented 3 years ago

The "Learners progress overview" page is also (still?) completely empty. Do I need to run another command to fill that?

mrtndwrd commented 3 years ago

More detailed findings:

I know that the following works:

What I know doesn't work:

johnbaldwin commented 3 years ago

Hey @mrtndwrd , I'm reading through your comments now. We've got Figures upgraded to Juniper and sandboxed in Juniper (but not yet in Juniper production) so there might still be a bug there. I'm a bit under schedule pressure right now, so can't put much time into helping, but will do the best I can.

My apologies, but I may have to ask some naive questions of you because I've not yet worked with Tutor myself. This means that I'm now putting "test drive tutor figures" in my short list after I dig myself out of my current backlog.

Starting points:

  1. Are you working with data you can share? This helps figure out avenues for troubleshooting
  2. what do you get when you run the following from the Django LMS shell? Do you get records returned?
from student.models import CourseEnrollment
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
CourseEnrollment.objects.count() # to get a total enrollment count for your site/instance
for rec in CourseOverview.objects.all():
    ce = CourseEnrollment.objects.filter(course_id=rec.id)
    print('Course "{}" enrollments = {}'.format(str(rec.id), ce.count()))

Hopefully I don't have a typo in the above

On specific metrics: Any one of the following you want to explore first?

A. "Course Completions" mean "Certificates generated" at this point in Figures life. We're planning on fixing this so that "course completions" means completed a threshold of sections in a course (which yeah, is a bit more vague than "Does learner have a cert? Y/N"). So it might be that

B. Course details page - "Incorrect totals and percentages" - Any specific examples would be appreciated. Let's start with one.

C. Learner Progress overview page is empty

This page is going to be empty until terms are added to either the "Search by users name, username, email" or "Filter by courses" filters

Have you tried either of these?

Note: This may not work (so may be broken) as you mentioned that there are no course enrollments showing on the overview page

ashraf09091 commented 3 years ago

i am using tutor 11.2.3 (koa) and getting the following error in the figure home page.

webpack-stats.json. Are you sure webpack has generated the file and the path is correct

mrtndwrd commented 3 years ago

@ashraf09091 how did you install Figures on your tutor setup? My original post mentions the tutor-figures plugin version I use. After installing and activating the plugin, you need to rebuild your openedx image:

tutor images build openedx \
  --build-arg FIGURES_REPOSITORY=git+https://github.com/greenhost/figures.git \
  --build-arg FIGURES_VERSION=0.4.dev8-gh

With this command you also use my fork of the figures repository, with the CELERY_IMPORTS statement commented out.

Please note that I can't give you any warranty that following these instructions it will work. I'm still figuring out the bugs myself and I'm also unsure if our data even gets updated at the moment.

mrtndwrd commented 3 years ago

@johnbaldwin thanks for your pointers! I'm sorry for being so slow in responding. I'll try to respond faster next time :)

Are you working with data you can share? This helps figure out avenues for troubleshooting

I'm afraid not (at least not the learner data, courses I can share if it helps), we treat our learner's data with as much care as we can.

what do you get when you run the following from the Django LMS shell? Do you get records returned?

Yes:

>>> CourseEnrollment.objects.count() # to get a total enrollment count for your site/instance
4302
>>> for rec in CourseOverview.objects.all():
...     ce = CourseEnrollment.objects.filter(course_id=rec.id)
...     print('Course "{}" enrollments = {}'.format(str(rec.id), ce.count()))
... 
Course "course-v1:Colnodo+Cursos_Seguridad_Digital+Totem" enrollments = 3
Course "course-v1:FreePressUnlimited+FPU_DS_EN+001" enrollments = 4
Course "course-v1:FreePressUnlimited+FPU_GM_001+2020" enrollments = 29
Course "course-v1:FreePressUnlimited+FPU_ISJ_EN+001" enrollments = 48
Course "course-v1:IWMF+IWMF_KP_AR+001" enrollments = 25
Course "course-v1:IWMF+IWMF_KP_EN+001" enrollments = 137
Course "course-v1:IWMF+IWMF_KP_ES+001" enrollments = 60
Course "course-v1:IWMF+IWMF_KP_FR+001" enrollments = 27
Course "course-v1:IWMF+IWMF_OH_AR+001" enrollments = 43
Course "course-v1:IWMF+IWMF_OH_EN+001" enrollments = 408
Course "course-v1:IWMF+IWMF_OH_ES+001" enrollments = 248
Course "course-v1:IWMF+IWMF_OH_FR+001" enrollments = 82
Course "course-v1:Totem+BOT101+course" enrollments = 12
Course "course-v1:Totem+BOT102+001" enrollments = 5
Course "course-v1:Totem+TP_AA_001+course" enrollments = 18
Course "course-v1:Totem+TP_CT_AR+001" enrollments = 27
Course "course-v1:Totem+TP_CT_EN+001" enrollments = 150
Course "course-v1:Totem+TP_CT_ES+001" enrollments = 12
Course "course-v1:Totem+TP_CT_PE+001" enrollments = 57
Course "course-v1:Totem+TP_DR_EN+001" enrollments = 120
Course "course-v1:Totem+TP_DR_FA+001" enrollments = 9
Course "course-v1:Totem+TP_ES_001+course" enrollments = 9
Course "course-v1:Totem+TP_FDR_EN+001" enrollments = 110
Course "course-v1:Totem+TP_FDR_FA+001" enrollments = 13
Course "course-v1:Totem+TP_GL_001+2018_01" enrollments = 7
Course "course-v1:Totem+TP_IP_001+2018" enrollments = 491
Course "course-v1:Totem+TP_IP_AR+001" enrollments = 35
Course "course-v1:Totem+TP_IP_ES+001" enrollments = 25
Course "course-v1:Totem+TP_IP_FA+001" enrollments = 52
Course "course-v1:Totem+TP_IP_FR+cours" enrollments = 125
Course "course-v1:Totem+TP_PM_001+course" enrollments = 218
Course "course-v1:Totem+TP_PM_AR+001" enrollments = 29
Course "course-v1:Totem+TP_PM_ES+001" enrollments = 10
Course "course-v1:Totem+TP_PM_FA+001" enrollments = 17
Course "course-v1:Totem+TP_PM_FR001+cours" enrollments = 137
Course "course-v1:Totem+TP_SD_001+course" enrollments = 12
Course "course-v1:Totem+TP_SMR_EN+001" enrollments = 179
Course "course-v1:Totem+TP_SMR_FA+001" enrollments = 19
Course "course-v1:Totem+TP_SM_001+course" enrollments = 231
Course "course-v1:Totem+TP_SM_AR+001" enrollments = 30
Course "course-v1:Totem+TP_SM_ES+001" enrollments = 14
Course "course-v1:Totem+TP_SM_FA+001" enrollments = 21
Course "course-v1:Totem+TP_SM_FR+cours" enrollments = 88
Course "course-v1:Totem+TP_SM_RU+001" enrollments = 3
Course "course-v1:Totem+TP_SP_001+course" enrollments = 303
Course "course-v1:Totem+TP_SP_AR+001" enrollments = 26
Course "course-v1:Totem+TP_SP_ES+001" enrollments = 14
Course "course-v1:Totem+TP_SP_FA+001" enrollments = 23
Course "course-v1:Totem+TP_SP_FR_001+cours" enrollments = 198
Course "course-v1:TotemProject+TP_OA_001+2018" enrollments = 7

A. "Course Completions" mean "Certificates generated" at this point in Figures life. We're planning on fixing this so that "course completions" means completed a threshold of sections in a course (which yeah, is a bit more vague than "Does learner have a cert? Y/N"). So it might be that

Hmm, I know not a lot of our users actually generate the certificate, but there are more than 0 that choose to do so. Do you do any assumptions about how the certificate is generated? For example, all our courses are self-paced, so we have early certificates

B. Course details page - "Incorrect totals and percentages" - Any specific examples would be appreciated. Let's start with one.

For example, this is our overview page:

image

Which shows 10 enrolled learners under "Access and Anonymity". When I click the course, though:

image

It only shows 0 enrolled users.

The second course I click shows an average course progress of 0%, but when I scroll down it shows 0% progress for some learners and 100% for others (sorry I can't paste a screenshot because I don't want to share our learners' data)

C. Learner Progress overview page is empty

I found out that when the search succeeds, an error happens on the server:

lms_1              | [pid: 24|app: 0|req: 19944/58196] 172.18.0.14 () {42 vars in 1655 bytes} [Mon Mar 29 13:40:41 2021] GET /figures/api/learner-metrics/?search=testt&ordering=profile__name&limit=20&offset=0 => generated 52 bytes in 69 msecs (HTTP/1.0 200) 7 headers in 353 bytes (1 switches on core 0)
lms_1              | 2021-03-29 13:40:46,695 ERROR 8 [root] [user None] [ip None] signals.py:22 - Uncaught exception from None
lms_1              | Traceback (most recent call last):
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/django/core/handlers/exception.py", line 34, in inner
lms_1              |     response = get_response(request)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 115, in _get_response
lms_1              |     response = self.process_exception_by_middleware(e, request)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 113, in _get_response
lms_1              |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
lms_1              |   File "/opt/pyenv/versions/3.8.6/lib/python3.8/contextlib.py", line 75, in inner
lms_1              |     return func(*args, **kwds)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
lms_1              |     return view_func(*args, **kwargs)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/viewsets.py", line 116, in view
lms_1              |     return self.dispatch(request, *args, **kwargs)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/views.py", line 495, in dispatch
lms_1              |     response = self.handle_exception(exc)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/views.py", line 455, in handle_exception
lms_1              |     self.raise_uncaught_exception(exc)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/views.py", line 492, in dispatch
lms_1              |     response = handler(request, *args, **kwargs)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/mixins.py", line 45, in list
lms_1              |     return self.get_paginated_response(serializer.data)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 768, in data
lms_1              |     ret = super(ListSerializer, self).data
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 262, in data
lms_1              |     self._data = self.to_representation(self.instance)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 685, in to_representation
lms_1              |     return [
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 686, in <listcomp>
lms_1              |     self.child.to_representation(item) for item in iterable
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 530, in to_representation
lms_1              |     ret[field.field_name] = field.to_representation(attribute)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 685, in to_representation
lms_1              |     return [
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 686, in <listcomp>
lms_1              |     self.child.to_representation(item) for item in iterable
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 530, in to_representation
lms_1              |     ret[field.field_name] = field.to_representation(attribute)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/fields.py", line 1232, in to_representation
lms_1              |     value = self.enforce_timezone(value)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/fields.py", line 1180, in enforce_timezone
lms_1              |     if timezone.is_aware(value):
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/django/utils/timezone.py", line 248, in is_aware
lms_1              |     return value.utcoffset() is not None
lms_1              | AttributeError: 'datetime.date' object has no attribute 'utcoffset'
lms_1              | 2021-03-29 13:40:46,801 ERROR 8 [django.request] [user 49] [ip 195.190.28.22] log.py:222 - Internal Server Error: /figures/api/learner-metrics/
lms_1              | Traceback (most recent call last):
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/django/core/handlers/exception.py", line 34, in inner
lms_1              |     response = get_response(request)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 115, in _get_response
lms_1              |     response = self.process_exception_by_middleware(e, request)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 113, in _get_response
lms_1              |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
lms_1              |   File "/opt/pyenv/versions/3.8.6/lib/python3.8/contextlib.py", line 75, in inner
lms_1              |     return func(*args, **kwds)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
lms_1              |     return view_func(*args, **kwargs)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/viewsets.py", line 116, in view
lms_1              |     return self.dispatch(request, *args, **kwargs)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/views.py", line 495, in dispatch
lms_1              |     response = self.handle_exception(exc)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/views.py", line 455, in handle_exception
lms_1              |     self.raise_uncaught_exception(exc)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/views.py", line 492, in dispatch
lms_1              |     response = handler(request, *args, **kwargs)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/mixins.py", line 45, in list
lms_1              |     return self.get_paginated_response(serializer.data)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 768, in data
lms_1              |     ret = super(ListSerializer, self).data
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 262, in data
lms_1              |     self._data = self.to_representation(self.instance)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 685, in to_representation
lms_1              |     return [
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 686, in <listcomp>
lms_1              |     self.child.to_representation(item) for item in iterable
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 530, in to_representation
lms_1              |     ret[field.field_name] = field.to_representation(attribute)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 685, in to_representation
lms_1              |     return [
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 686, in <listcomp>
lms_1              |     self.child.to_representation(item) for item in iterable
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 530, in to_representation
lms_1              |     ret[field.field_name] = field.to_representation(attribute)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/fields.py", line 1232, in to_representation
lms_1              |     value = self.enforce_timezone(value)
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/rest_framework/fields.py", line 1180, in enforce_timezone
lms_1              |     if timezone.is_aware(value):
lms_1              |   File "/openedx/venv/lib/python3.8/site-packages/django/utils/timezone.py", line 248, in is_aware
lms_1              |     return value.utcoffset() is not None
lms_1              | AttributeError: 'datetime.date' object has no attribute 'utcoffset'
lms_1              | [pid: 8|app: 0|req: 19950/58197] 172.18.0.14 () {42 vars in 1653 bytes} [Mon Mar 29 13:40:46 2021] GET /figures/api/learner-metrics/?search=test&ordering=profile__name&limit=20&offset=0 => generated 9558 bytes in 468 msecs (HTTP/1.0 500) 7 headers in 532 bytes (1 switches on core 0)

This happens regardless of if I search for an (existing) user, or if I filter on a course.

johnbaldwin commented 3 years ago

@mrtndwrd Just letting you know that I skimmed over your reply, and thanks for digging int the issue!

I'll have to carve off some focus time. I did discover today that the learner progress overview page is not working right while running on the Figures devsite development server, so I have something else to look at to compare with what you posted.

I also have to go back over completions. Aside from fixing what you observe, this is an area that really needs improvement, as you say "I know not a lot of our users actually generate the certificate".

So, what does figures do now in brief? It grabs data that can have points earned via calling g the CourseGradeFactory().read(...) method and walking through the data there to collect subsections worked out of subsections possible and points earned out of points possible. If all subsections have been worked, the learner has completed the course. This doesn't mean the learner passes the course, just that the learner has earned at least a point for each gradable subsection.

First it might help for me to document the current approach so it is clearer to everyone how figures does it now and maybe get feedback on ways to improve it, like going over the code here to see how it does it: https://github.com/edx/completion

Grading and completion policy for figures is actually one of the top things that makes me want to enable plugins for Figures server side code. So Figures can provide a reasonable default on what "progress" means, what "completion" means and what "passing" means. And then anyone else can write a plugin/middleware to set their own rules

mrtndwrd commented 3 years ago

FYI, when I try to upgrade to Lilac with Tutor 12, the build step seems to fail because Figures uses deprecated imports:

Step 82/88 : RUN openedx-assets themes     && openedx-assets collect --settings=tutor.assets     && rdfind -makesymlinks true -followsymlinks true /openedx/staticfiles/
 ---> Running in dc34a6510f7d
Compiling lms sass assets from theme /openedx/themes/indigo-totem...
rtlcss /openedx/themes/indigo-totem/lms/static/css/bootstrap/lms-main.css /openedx/themes/indigo-totem/lms/static/css/bootstrap/lms-main-rtl.css
rtlcss: Warning! No config present, using defaults.
Saving: /openedx/themes/indigo-totem/lms/static/css/bootstrap/lms-main-rtl.css
rtlcss /openedx/themes/indigo-totem/lms/static/css/discussion/lms-discussion-bootstrap.css /openedx/themes/indigo-totem/lms/static/css/discussion/lms-discussion-bootstrap-rtl.css
rtlcss: Warning! No config present, using defaults.
Saving: /openedx/themes/indigo-totem/lms/static/css/discussion/lms-discussion-bootstrap-rtl.css
 Sass dir '/openedx/themes/indigo-totem/lms/static/certificates/sass' does not exists, skipping sass compilation for '/openedx/themes/indigo-totem' 
Compiling cms sass assets from theme /openedx/themes/indigo-totem...
Compiling lms sass assets from theme /openedx/themes/indigo...
rtlcss /openedx/themes/indigo/lms/static/css/bootstrap/lms-main.css /openedx/themes/indigo/lms/static/css/bootstrap/lms-main-rtl.css
rtlcss: Warning! No config present, using defaults.
Saving: /openedx/themes/indigo/lms/static/css/bootstrap/lms-main-rtl.css
rtlcss /openedx/themes/indigo/lms/static/css/discussion/lms-discussion-bootstrap.css /openedx/themes/indigo/lms/static/css/discussion/lms-discussion-bootstrap-rtl.css
rtlcss: Warning! No config present, using defaults.
Saving: /openedx/themes/indigo/lms/static/css/discussion/lms-discussion-bootstrap-rtl.css
 Sass dir '/openedx/themes/indigo/lms/static/certificates/sass' does not exists, skipping sass compilation for '/openedx/themes/indigo' 
Compiling cms sass assets from theme /openedx/themes/indigo...
python manage.py lms --settings=tutor.assets collectstatic --ignore "fixtures" --ignore "karma_*.js" --ignore "spec" --ignore "spec_helpers" --ignore "spec-helpers" --ignore "xmodule_js" --ignore "geoip" --ignore "sass" --noinput > /dev/null
Traceback (most recent call last):
  File "manage.py", line 120, in <module>
    startup.run()
  File "/openedx/edx-platform/lms/startup.py", line 20, in run
    django.setup()
  File "/openedx/venv/lib/python3.8/site-packages/django/__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/openedx/venv/lib/python3.8/site-packages/django/apps/registry.py", line 114, in populate
    app_config.import_models()
  File "/openedx/venv/lib/python3.8/site-packages/django/apps/config.py", line 211, in import_models
    self.models_module = import_module(models_module_name)
  File "/opt/pyenv/versions/3.8.6/lib/python3.8/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 783, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/openedx/venv/src/figures/figures/models.py", line 19, in <module>
    from figures.compat import CourseEnrollment
  File "/openedx/venv/src/figures/figures/compat.py", line 79, in <module>
    from student.models import CourseAccessRole, CourseEnrollment  # noqa pylint: disable=unused-import,import-error
  File "/openedx/edx-platform/import_shims/lms/student/__init__.py", line 6, in <module>
    warn_deprecated_import('student', 'common.djangoapps.student')
  File "/openedx/edx-platform/import_shims/warn.py", line 37, in warn_deprecated_import
    raise DeprecatedEdxPlatformImportError(old_import, new_import)
import_shims.warn.DeprecatedEdxPlatformImportError: Importing student instead of common.djangoapps.student is deprecated
Traceback (most recent call last):
  File "/openedx/bin/openedx-assets", line 218, in <module>
    main()
  File "/openedx/bin/openedx-assets", line 89, in main
    args.func(args)
  File "/openedx/bin/openedx-assets", line 157, in run_collect
    assets.collect_assets(args.systems, args.settings)
  File "/openedx/edx-platform/pavelib/assets.py", line 717, in collect_assets
    sh(django_cmd(sys, settings, "collectstatic {ignore_args} --noinput {logfile_str}".format(
  File "/openedx/venv/lib/python3.8/site-packages/paver/shell.py", line 60, in sh
    return dry(command, runpipe)
  File "/openedx/venv/lib/python3.8/site-packages/paver/easy.py", line 15, in dry
    return func(*args, **kw)
  File "/openedx/venv/lib/python3.8/site-packages/paver/shell.py", line 55, in runpipe
    raise BuildFailure("Subprocess return code: %d" % p.returncode)
paver.tasks.BuildFailure: Subprocess return code: 1
The command '/bin/sh -c openedx-assets themes     && openedx-assets collect --settings=tutor.assets     && rdfind -makesymlinks true -followsymlinks true /openedx/staticfiles/' returned a non-zero code: 1
Error: Command failed with status 1: docker build -t docker.io/overhangio/openedx:12.0.0 --no-cache --build-arg EDX_PLATFORM_REPOSITORY=https://github.com/greenhost/edx-platform --build-arg EDX_PLATFORM_VERSION=open-release/lilac.1-hotfixed --build-arg FIGURES_REPOSITORY=git+https://github.com/greenhost/figures.git --build-arg FIGURES_VERSION=0.4.dev8-gh /datadisk/maarten/.local/share/tutor/env/build/openedx

I think we'll have to stop trying to use Figures, because I don't have the time to fix this unfortunately.

I'm curious, if you're at liberty to tell of course, does Appsembler not run Koa and Lilac?

everton137 commented 3 years ago

I second the questions of @mrtndwrd, if it's okay for you to answer, if there are plans to adapt Figure to Koa and Lilac.

So, I've begun to debug (and understand the code of) Figures version build-0.4.dev14 with a local Koa instance using Derex. The first number not displaying correctly was the enrollments number, so I checked the Figure APpi end-point sending this data to the 'Overview' and 'Courses' pages.

The end-point /figures/api/site-monthly-metrics/ gets the course_enrollments field. The method course_enrollments() on views.SiteMonthlyMetricsViewSet uses the method get_total_enrollments_for_time_period(site, start_date, end_date, course_ids=None), which defines the following query (for our specific test site and time range of the last 6 months):

SELECT `figures_sitedailymetrics`.`id`, `figures_sitedailymetrics`.`created`, `figures_sitedailymetrics`.`modified`, `figures_sitedailymetrics`.`site_id`, `figures_sitedailymetrics`.`date_for`, `figures_sitedailymetrics`.`cumulative_active_user_count`, `figures_sitedailymetrics`.`todays_active_user_count`, `figures_sitedailymetrics`.`total_user_count`, `figures_sitedailymetrics`.`course_count`, `figures_sitedailymetrics`.`total_enrollment_count`, `figures_sitedailymetrics`.`mau` FROM `figures_sitedailymetrics` INNER JOIN `django_site` ON (`figures_sitedailymetrics`.`site_id` = `django_site`.`id`) WHERE (`figures_sitedailymetrics`.`date_for` > 2021-06-30 AND `figures_sitedailymetrics`.`date_for` < 2021-08-01 AND `figures_sitedailymetrics`.`site_id` = 1) ORDER BY `figures_sitedailymetrics`.`date_for` DESC, `django_site`.`domain` ASC;

which return an empty set. But if I remove the data range condition

WHERE (`figures_sitedailymetrics`.`date_for`> 2021-06-30 AND`figures_sitedailymetrics`.`date_for`< 2021-08-01 AND`figures_sitedailymetrics`.`site_id`= 1)

I can get the table with the expected enrollments number (5 on my test koa instance):

+----+----------------------------+----------------------------+---------+------------+------------------------------+--------------------------+------------------+--------------+------------------------+------+
| id | created                    | modified                   | site_id | date_for   | cumulative_active_user_count | todays_active_user_count | total_user_count | course_count | total_enrollment_count | mau  |
+----+----------------------------+----------------------------+---------+------------+------------------------------+--------------------------+------------------+--------------+------------------------+------+
|  7 | 2021-07-14 07:20:26.983229 | 2021-07-14 07:20:26.983229 |       1 | 2021-07-13 |                            0 |                        0 |               12 |            2 |                      5 |    0 |
|  6 | 2021-07-13 13:12:01.848197 | 2021-07-13 13:12:01.848197 |       1 | 2021-07-12 |                            0 |                        0 |               11 |            1 |                      3 |    0 |
|  5 | 2021-07-09 14:12:52.544050 | 2021-07-09 14:12:52.544050 |       1 | 2021-07-08 |                            0 |                        0 |               10 |            1 |                      2 |    0 |
|  4 | 2021-07-07 08:09:11.879721 | 2021-07-07 08:09:11.884793 |       1 | 2021-07-06 |                            0 |                        0 |               10 |            1 |                      2 |    0 |
|  3 | 2021-07-05 09:19:03.532025 | 2021-07-05 09:19:03.548824 |       1 | 2021-07-04 |                            0 |                        0 |               10 |            1 |                      2 |    0 |
|  2 | 2021-07-04 20:22:32.974242 | 2021-07-04 20:58:53.538713 |       1 | 2021-07-03 |                            0 |                        0 |               10 |            1 |                      2 |    0 |
|  1 | 2021-07-02 15:05:01.759535 | 2021-07-02 15:05:01.759535 |       1 | 2021-07-01 |                            0 |                        0 |                8 |            0 |                      0 |    0 |
+----+----------------------------+----------------------------+---------+------------+------------------------------+--------------------------+------------------+--------------+------------------------+------+

It seems to me there is some problem in the date range specified on views.SiteMonthlyMetricsViewSet. After some days, the data is correctly fetched by this end-point. Also, by running the Figures' command

python manage.py lms backfill_figures_daily_metrics 

The table figures_enrollmentdata is correctly updated with all enrolled users.

I will continue debugging some other fields that seem to be wrong, like the completion data and some details' data from each course page.