Skip to content

Add feature to send an e-mail to staff when a student enrolls - #16669

Merged
iloveagent57 merged 1 commit into
openedx:masterfrom
open-craft:clemente/enrollment-email
Aug 2, 2018
Merged

Add feature to send an e-mail to staff when a student enrolls#16669
iloveagent57 merged 1 commit into
openedx:masterfrom
open-craft:clemente/enrollment-email

Conversation

@clemente

@clemente clemente commented Nov 24, 2017

Copy link
Copy Markdown
Contributor

This sends an e-mail to a specified e-mail address each time a student enrolls to a course.

  • a new lms.env.json variable, ENROLLMENT_NOTIFICATION_EMAIL, contains the destination e-mail. Leave it to blank to disable the feature
  • a SiteConfiguration variable of the same name and function can be used to enable/disable the feature from admin. If present it will have precedence over the lms.env.json one
  • this feature is disabled by default

JIRA tickets: None

Discussions: None

Dependencies: None
Documentation PR not required because this doesn't need a feature flag (FEATURES in lms.env.json), but a normal lms.env.json variable.

Screenshots:

Sandbox URL: TBD - sandbox is being provisioned.

Partner information: None

Deployment targets: N/A

Merge deadline: None

Testing instructions:

  1. In lms.env.json, set ENROLLMENT_NOTIFICATION_EMAIL to your e-mail.
  2. Enroll to a course
  3. The specified address should receive an e-mail informing of which student enrolled into which course
  4. Try to unenroll, no e-mail expected
  5. In admin, add a SiteConfiguration object with { "ENROLLMENT_NOTIFICATION_EMAIL":"setfromsiteconf@example.com" }
  6. Restart LMS or try the next steps many times, because SiteConfiguration changes sometimes take a short time to be updated
  7. Try again the enrollment. You should see the e-mail sent to the new address
  8. Try to use '' in SiteConfiguration to disable the feature, while it's enabled in lms.env.json. Retry, it should disable the feature
  9. Remove both SiteConfiguration and the lms.env.json setting and retry the enrollment, check that by default it doesn't send e-mail.

Author notes and concerns:

The SiteConfiguration variable allows to do this change from admin without restarting, whereas the lms.env.json one is there because many other e-mail addresses are defined there.

Reviewers

Settings

@openedx-webhooks

Copy link
Copy Markdown

Thanks for the pull request, @clemente! It looks like you're a member of a company that does contract work for edX. If you're doing this work as part of a paid contract with edX, you should talk to edX about who will review this pull request. If this work is not part of a paid contract with edX, then you should ensure that there is an OSPR issue to track this work in JIRA, so that we don't lose track of your pull request.

Create an OSPR issue for this pull request.

@clemente clemente changed the title WIP - Add feature to send an e-mail to staff when a student enrolls Add feature to send an e-mail to staff when a student enrolls Nov 25, 2017

@tomaszgy tomaszgy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left few small comments - please have a look if they make sense to you :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When get_full_name() returns empty string it gives a bit strange effect, e.g. tomasz (), so maybe brackets could be conditional?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe: by setting the e-mail of the staff in?

Comment thread lms/envs/common.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe: Please specify the destination e-mail address.?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this section be ordered alphabetically?

@tomaszgy

Copy link
Copy Markdown
Contributor

👍 after comments are addressed (those that @clemente agrees with)

  • I tested this:
    I followed the testing instructions from @clemente. All worked as expected.
  • I read through the code and left few comments.
  • I checked for accessibility issues
  • Includes documentation

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Does this really warrant a separate comment? This pattern is used throughout the codebase a lot.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: can we use django.core.mail.message.EmailMessage (example: https://github.com/edx/edx-platform/pull/16624/files#diff-5a31e91b78b2599dc5ace9c2a3b31d8cR776)? It may allow a little more flexibility later.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can just use smtplib.SMTPException.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: log -> LOGGER.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: I haven't actually looked, but is this the best way to generate these URLs? Wondering if there's any prebuilt function for this as I'd imagine this is a relatively common thing to do.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Django ways to get a URL are in https://stackoverflow.com/questions/2345708/how-can-i-get-the-full-absolute-url-with-domain-in-django, but in this case we don't have a {{request}} object and the offered solutions are also several lines long.
There might be an openedx way. But I didn't find a {{get_absolute_url}} in CourseLocator, nor a method in CourseMode. The methods that return a path return relative URLs. Grepping for course∩absolute∩url didn't show any code for this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason this isn't in setUp?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should capitalize learner ('Learner').

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should capitalize course ('Course').

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure of the UX involved here with a raw course URL being pasted in. Depending on that, this may have to have an HTML alternative where 'Course' is clickable or something.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have sent me a test e-mail in text/plain and verified that e.g. gmail makes URLs clickable. As long as we don't need design, text/plain is fine for URLs.
I also don't know the UX requirements, maybe this needs a logo, header, footer etc. I expect the client or edx to come back with changes to this template.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above about the raw URL.

@clemente

Copy link
Copy Markdown
Contributor Author

@tomaszgy @UmanShahzad Thanks for the comments and the testing. I applied all your comments except the 2 where I replied.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@UmanShahzad UmanShahzad Nov 30, 2017

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 And note that if you do this, you'll have to strip newlines. See https://github.com/edx/edx-platform/blob/master/common/djangoapps/third_party_auth/pipeline.py#L778-L782

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here: text should be translated.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I signed up using the client's themed registration form, with matching registration form config, and this offers only the "Full Name" UserProfile field. So unfortunately, the Django User.get_full_name() method never returns anything.

So I think you need to do something like this instead.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clemente I hate to be fussy about this, but I think upstream will be too, so it's better to sort it out now.

When translating a line of text, you need to include all of the pieces so they can be translated all together. Some languages may switch the order around completely, or change punctuation, or anything.

So for example, this file should be:

${_("Learner: {username} ({full_name})").format(
    username=user.username,
    full_name=user_full_name,
)}
${_("Course: {course_url}").format(course_url=course_url)}

${_("More learner data in admin: {user_admin_url}").format(user_admin_url=user_admin_url}

See e.g. https://github.com/edx/edx-platform/blob/6d2468d0dcf782a7561ecc61946146d6fae2762f/lms/templates/sysadmin_dashboard_gitlogs.html#L133
https://github.com/edx/edx-platform/blob/c0c740ad1d98b3b40fb3ce91620ec7faa7460b93/lms/templates/dashboard.html#L231

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: it could be useful to factor out this context and send it into both subject and message so that some overriding theme could potentially use some detail (like user's full name) in the subject.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this could be 1 line:

user_full_name = user.profile.name if hasattr(user, 'profile') else None

@UmanShahzad UmanShahzad Dec 3, 2017

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: now that we're using pytest, we can do assert len(mail.outbox) == 1 and get all the benefits of self.assertEqual. Same goes for the others, but up to you to change it.

@UmanShahzad UmanShahzad left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @clemente! I have just some nits + what @pomegranited said, then we should be good. Note I have not had time to manually test this, though; mostly just doing a code review.

@clemente

clemente commented Dec 3, 2017

Copy link
Copy Markdown
Contributor Author

@UmanShahzad @pomegranited I fixed your comments.

@openedx-webhooks

Copy link
Copy Markdown

Thanks for the pull request, @clemente! I've created OSPR-2029 to keep track of it in JIRA. JIRA is a place for product owners to prioritize feature reviews by the engineering development teams.

Feel free to add as much of the following information to the ticket:

  • supporting documentation
  • edx-code email threads
  • timeline information ("this must be merged by XX date", and why that is)
  • partner information ("this is a course on edx.org")
  • any other information that can help Product understand the context for the PR

All technical communication about the code itself will still be done via the GitHub pull request interface. As a reminder, our process documentation is here.

If you like, you can add yourself to the AUTHORS file for this repo, though that isn't required. Please see the CONTRIBUTING file for more information.

@openedx-webhooks openedx-webhooks added needs triage open-source-contribution PR author is not from Axim or 2U labels Dec 4, 2017

@pomegranited pomegranited left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clemente 👍

  • I tested this on my master devstack using the instructions provided, and it worked as expected.
  • I read through the code
  • I checked for accessibility issues N/A
  • Includes documentation - docstrings are sufficient.

@nedbat

nedbat commented Dec 4, 2017

Copy link
Copy Markdown
Contributor

@edx/learner-product I think this should get an OK from product first.

@openedx-webhooks openedx-webhooks added product review PR requires product review before merging and removed needs triage labels Dec 4, 2017
@marcotuts

Copy link
Copy Markdown
Contributor

I've pinged @sstack22 on this, and will update the ticket to be associated with the educator team. Thanks for flagging this Ned.

@clemente
clemente force-pushed the clemente/enrollment-email branch from c08e1a7 to 080dfa3 Compare January 29, 2018 06:29
@clemente

Copy link
Copy Markdown
Contributor Author

Squashed commits and rebased on master.

@nedbat

nedbat commented Feb 26, 2018

Copy link
Copy Markdown
Contributor

@sstack22 Any thoughts on this?

@sstack22

Copy link
Copy Markdown

Thanks for the ping @nedbat. @clemente - this isn't something we'd want enabled for edX. I would recommend considering a daily email in general though, as individual emails may be overwhelming.

@bradenmacdonald

Copy link
Copy Markdown
Contributor

@sstack22 Good point about the digest. In this case, I believe the use case we built this for would be sending the email to salesforce or some other automated CRM workflow, so a digest would be less useful in that case. Certainly a good option to add in the future though, and I can see that making this much more widely useful.

@nedbat

nedbat commented Feb 28, 2018

Copy link
Copy Markdown
Contributor

Should this PR be closed then?

@clemente
clemente force-pushed the clemente/enrollment-email branch from fdd6e7f to 9c95564 Compare August 2, 2018 12:53
@clemente

clemente commented Aug 2, 2018

Copy link
Copy Markdown
Contributor Author

@iloveagent57 Thanks, fixed. I also rebased on top of master.

@edx-status-bot

Copy link
Copy Markdown

Your PR has finished running tests. There were no failures.

@iloveagent57
iloveagent57 merged commit 370501d into openedx:master Aug 2, 2018
@openedx-webhooks

Copy link
Copy Markdown

@clemente 🎉 Your pull request was merged!

Please take a moment to answer a two question survey so we can improve your experience in the future.

@iloveagent57

Copy link
Copy Markdown
Contributor

@clemente Thank you for your patience. I've talked with @nedbat and others here about ways that we can make this type of thing better in the future, mostly by allowing you (or any other community contributor) to extend the functionality of edx-platform without having to write a PR against it (and wait months and months for a review/approval). For this type of PR, where you're adding a downstream signal receiver, I think there are two approaches that would work:

  1. You guys create a catch-all edx extensions library using the Django App Plugin functionality. It would define a set of signals to listen for, do whatever needs to be done based on those signals, and install this library into whatever installation relies on this extended functionality.
  2. You/we/someone adds another endpoint to the enrollments API like /api/enrollments/v1/course-enrollments that responds with a paginated set of users enrolled in a particular course, or optionally all courses, and optionally ordered by descending enrollment update time. This would let you run a cron via jenkins or whatever you want to send emails to whichever course staff wants an enrollment notification email.

What do you think of these approaches? I can talk to some folks here and see if there's a general preference between these alternatives.

@edx-pipeline-bot

Copy link
Copy Markdown
Contributor

EdX Release Notice: This PR has been deployed to the staging environment in preparation for a release to production on Friday, August 03, 2018.

@edx-pipeline-bot

Copy link
Copy Markdown
Contributor

EdX Release Notice: This PR may have caused e2e tests to fail on Stage. E2E tests have failed. https://gocd.tools.edx.org/go/tab/pipeline/history/STAGE_edxapp_M-D

@nasthagiri

Copy link
Copy Markdown
Contributor

@iloveagent57 Option #2 is better implemented as a web-hook - where an external handler registers itself for notifications and gets called whenever an enrollment happens.

@edx-pipeline-bot

Copy link
Copy Markdown
Contributor

EdX Release Notice: This PR has been deployed to the production environment.

1 similar comment
@edx-pipeline-bot

Copy link
Copy Markdown
Contributor

EdX Release Notice: This PR has been deployed to the production environment.

@nasthagiri

Copy link
Copy Markdown
Contributor

To provide some context, we are adopting the Open-Closed principle for edx-platform as presented (slides) at the Open edX conference. So new features that are not "core" to the platform should be added as extensions or plugins for everyone to use.

Given that, to help us follow through on this PR, let me summarize the implementation options that we have discussed thus far.

1. Django App Plugin, in a separate repo, running in the LMS process

As also recommended on another recent PR, you can use the Django App Plugin design pattern for this feature. That design pattern will allow you to put this new feature in its own repo - without needing to contribute it back to our core platform.

For a reference implementation, you can take a look at how the Completion Django app is defined in its own repo and uses the Plugin pattern to insert itself in the LMS.

If you choose this option, you can put your new repo in the friendsofopenedx github organization. This is a new github organization that the open source community plans to use to share plugins with each other.

2. Real-time events using xAPI or Caliper

There is an upcoming feature to send real-time events to interested parties using standard protocols such as xAPI and Caliper. The design proposal is currently under review.

Once that feature is in place, you can integrate events, such as for enrollments, directly with external services.

3. Real-time notification with a Web Hook

Another alternative is to introduce a web hook within the edx-platform. There would be a new REST endpoint where interested listeners would register callback URLs for enrollment events. This would be a generic integration point that we could add to the core for this and future extensions.

4. Poll for new enrollments via a new Enrollment API

As @iloveagent57 suggests above, another option would be to add a REST API in the core where interested parties can get a list of all enrollments, ordered by date. This wouldn't be a real-time notification, but would be a generically useful integration point for various types of extensions.

I apologize for not presenting these options earlier. But given that we are now working on this feature, we can now focus on implementing it for its long-term benefit for open edX users.

@schenedx

schenedx commented Aug 3, 2018

Copy link
Copy Markdown
Contributor

@clemente and @pomegranited, given the desired directions detailed above by @nasthagiri, please let us know which direction you will take, and the timeline for when the steps will be taken.
If we don't see a response from opencraft by next Wednesday, I will proceed to revert the changes in this PR from edx-platform code base.
I apologize for the timeline and prescribed action. We believe this is the most effective way to get right feature to be integrated into edx-platform.
If you have strong concerns with our approach, please let us know as soon as you can.

@pomegranited

Copy link
Copy Markdown
Contributor

@schenedx I don't understand why this PR needs to be reverted. This PR was submitted almost a full year ago, and it has been merged, and deployed to production without issue, correct?

For our future PRs, we will certainly use the django-plugin approach, and we're pleased to see it in operation.

But for this change, we are out of client budget, and cannot take more time to reimplement it. So yes, we have strong concerns about reverting this now.

CC @clemente @bradenmacdonald @nasthagiri @nedbat

@pomegranited

Copy link
Copy Markdown
Contributor

My apologies @schenedx -- I was surprised to see the word "revert" used here after such a long review process! Have had a chat with @nasthagiri , and we'll get back to you soon about what next steps our client budget can support, if any.

@schenedx

schenedx commented Aug 6, 2018

Copy link
Copy Markdown
Contributor

@pomegranited Your surprise is understandable. I will connect again on Wednesday to check if you and opencraft made any decisions.

@clemente

clemente commented Aug 7, 2018

Copy link
Copy Markdown
Contributor Author

@schenedx We weren't up to date about the latest requirements, sorry. We'll accept if this is unmerged in the near future, so you can proceed with the revert now.

For the long-term future, we're still getting to a conclusion (to be shared tomorrow).

Notifying instructors that enrollments happened can still be a useful feature. Could I suggest as another option, to implement this feature as part of [notifier|https://github.com/edx/notifier/], which currently sends digest e-mails of forum posts?

@clemente

clemente commented Aug 8, 2018

Copy link
Copy Markdown
Contributor Author

@schenedx And the decision is… we're working on it, we still need to experiment a bit more to find a long-term solution. We'd prefer not to use Django plugin because they depend on the internals.
For now, reverting this PR is ok.

Obviously we'd want to upstream features that may useful to everyone, even if it's new APIs to do these extensions.

Also see comments about implementing it as part of notifier.

@schenedx

schenedx commented Aug 8, 2018

Copy link
Copy Markdown
Contributor

@clemente Sounds good. Here is the revert PR at https://github.com/edx/edx-platform/pull/18762. Will merge that when test passes

@clemente

Copy link
Copy Markdown
Contributor Author

@nasthagiri
We started testing out how this development could be reimplemented within the new direction of using Django plugins to extend the platform. We started a new plugin that detects enrollments via a signal, same as what was done in this PR.

However, plugins have the same risk of code drift, because they use and depend on internal features that may change. Therefore we want to use only public APIs in our plugins, and work on contributing these public APIs to Open edX if they aren't there.

Our plugin now depends on two other systems (maybe more, correct me if I'm wrong):

  1. we depend on a signal, ENROLL_STATUS_CHANGE, to detect the enrollment. I consider signals as a form of API. But we're not sure that a signal can be considered a public API just because of being a signal (in that case, all signals would be public APIs). But we don't know how else we can differentiate public vs. internals APIs. A decorator? A different package name? Some comments guaranteeing that it's fine to use these from plugins and that they won't change?
  2. we depend on edx-ace to send the e-mails. I guess this is closer to being a stable API, but how can we make it explicit? Should it say that the interface is stable? Should it be included in a central whitelist of stable interfaces?

We would like to find out the best way to proceed here during the transition to the new system, and how could we help.
After the direction is clear we'll finish the plugin (adding tests, better templates and a new setting to hold the destination e-mail).
Thanks.

CC @pomegranited

@clemente

Copy link
Copy Markdown
Contributor Author

@nasthagiri This isn't urgent at all, but it would be interesting to know how our approach follows edX's guidelines (see previous message); thanks.

@clemente

Copy link
Copy Markdown
Contributor Author

@nasthagiri Another ping to see whether we can discuss the public APIs mentioned in the previous message, or know about the latest guidelines or about this particular case (Django signals for enrollment). Thanks.

@clemente

Copy link
Copy Markdown
Contributor Author

@nasthagiri About the APIs: our window to develop the APIs for this feature has now closed, since the client project is over. That means we won't be reimplementing this feature (enrollment e-mails).
We still have many other cases where we'll need to agree on public APIs to create plugins, but we'll all need to be more reactive in order to define the APIs quicker, to avoid more contributions stalling in the same way. Thanks.
CC @antoviaque @pomegranited

@antoviaque antoviaque mentioned this pull request Apr 16, 2019
2 tasks
@bradenmacdonald
bradenmacdonald deleted the clemente/enrollment-email branch January 12, 2022 01:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

engineering review open-source-contribution PR author is not from Axim or 2U

Projects

None yet

Development

Successfully merging this pull request may close these issues.