Automated application execution via formplayer#34487
Conversation
Local user auth allows us to authenticate without a password
This function also doesn't work since couch users don't have a 'django_user' field
| return ret | ||
|
|
||
|
|
||
| def django_user_from_couch_id(id): |
There was a problem hiding this comment.
This is unused and also broken since couch user docs don't contain a django_user field
| 'djangoUserId': user.pk, | ||
| 'superUser': user.is_superuser, | ||
| 'authToken': None, | ||
| 'authToken': session_id, |
There was a problem hiding this comment.
always return the session_id - this just makes it easier for formplayer. Since the session_id is passed as input there's no issue including it in the output.
| from corehq.util.log import send_HTML_email | ||
|
|
||
|
|
||
| @periodic_task(run_every=crontab(minute=0, hour=0)) |
There was a problem hiding this comment.
Does this mean once a day?
esoergel
left a comment
There was a problem hiding this comment.
Haven't made it through the whole thing yet
| def _get_screen_and_data(self, current_data): | ||
| if not current_data: | ||
| return ScreenType.START, None | ||
| data = current_data.get("commands") |
There was a problem hiding this comment.
Is there any way to apply a schema or encode some expectations about the structure of current_data?
There was a problem hiding this comment.
We could use dataclasses or attrs for each of the response types
| return factory.form_response(selections, self.children) | ||
|
|
||
|
|
||
| APP1 = Menu( |
| yield exploration | ||
| continue | ||
|
|
||
| exploration.execute_next_step() |
There was a problem hiding this comment.
What's with this stepwise execution? Looks like it's to go breadth-first rather than depth first, but it's evaluated fully either way, right? Just curious what that provides versus a recursive traversal.
| def execute_workflow(session: FormplayerSession, workflow): | ||
| with session: | ||
| session.sync() | ||
| execute_step(session, None) |
There was a problem hiding this comment.
What would you think of moving self.get_session_start_data() here in place of None so that execute_step can assume step is always specified?
| Endpoints with this decorator will not enforce two factor authentication. | ||
| """ | ||
| return get_multi_auth_decorator(default=BASIC, oauth_scopes=['sync'])( | ||
| return get_multi_auth_decorator(default=BASIC, allow_formplayer=True, oauth_scopes=['sync'])( |
There was a problem hiding this comment.
can you tell me more about this change? why did mobile_auth not previously allow formplayer auth? Was it that only smsforms needed it previously, and that doesn't need these endpoints?
There was a problem hiding this comment.
I removed this and pushed it to #34441 in a different way: f16faf3
But to answer your question, there are some endpoints where we only expect mobile to auth and not formplayer so it makes sense to keep those separate. For endpoints that both access we can use the mobile_auth_or_formplayer decorator.
|
|
||
|
|
||
| @define | ||
| class AttrsObjectBuilder: |
There was a problem hiding this comment.
Is it the case that an AttrsList is a list of objects, and an AttrsDict is a dict whose values are objects, whereas this represents a single object? There are some repeated patterns between the three - would be nice to combine the builders reflecting the commonalities if that's doable.
There was a problem hiding this comment.
Is it the case that an AttrsList is a list of objects, and an AttrsDict is a dict whose values are objects, whereas this represents a single object?
Yes, that's correct.
There are some repeated patterns between the three - would be nice to combine the builders reflecting the commonalities if that's doable.
I'll take a look
| def execute_step(self, step): | ||
| data = self.get_request_data(step) if step else self.get_session_start_data() | ||
| self.data = self.client.make_request(data, self.request_url(step)) | ||
| self.log_step(step) |
| user_id = models.CharField(max_length=36) | ||
| django_user = models.ForeignKey(User, on_delete=models.CASCADE) | ||
| workflow = AttrsObject(AppWorkflow) | ||
| form_mode = models.CharField(max_length=255, choices=FORM_MODE_CHOICES) |
There was a problem hiding this comment.
FWIW models.TextChoices is a nice pattern for this sort of thing
| def final_clean_user_id(self): | ||
| domain = self.cleaned_data.get("domain") | ||
| try: | ||
| CommCareUser.get_by_user_id(domain, self.cleaned_data.get("user_id")) |
There was a problem hiding this comment.
Does it have to be a CommCareUser? USH is working to have web users submitting data too.
There was a problem hiding this comment.
It doesn't have to be but for this use case I thought it made more sense for it to be a mobile user since they are easier to manage.
|
|
||
| class AppWorkflowManager(models.Manager): | ||
| def get_due(self): | ||
| cutoff = functions.Now() - MakeInterval("mins", models.F("run_every")) |
There was a problem hiding this comment.
I've done this a few times now in different projects: https://www.snopoke.com/2023/03/11/django-intervals/
esoergel
left a comment
There was a problem hiding this comment.
Finished. There are some parts I could've gone more in depth reviewing, so please let me know if there's anything in particular you want a more detailed review
| build_on = "Latest Version" | ||
| if app.built_on: | ||
| build_on = app.built_on.strftime("%B %d, %Y") | ||
| print(f"Using app '{app.name}' ({app._id} - {build_on})", file=self.log) |
There was a problem hiding this comment.
Might be useful to log the app version number too - easier to cross reference on the releases page.
Suspect IssuesThis pull request was deployed and Sentry observed the following issues:
Did you find this useful? React with a 👍 or 👎 |
Technical Summary
This is a new admin feature which allows configuration of application workflows which will be executed on a schedule e.g. navigate through an app and complete a form.
The purpose of these is to generate 'canary' formplayer traffic for the purposes of monitoring and metrics.
It is expected that a small number of these will be configured per environment using a standardised application. There may also be additional configurations for monitoring enterprise apps.
Safety Assurance
Safety story
This is predominantly new functionality. There are some changes to existing code, notably
jsonattrswhich is used by repeaters. These changes are tested independently.Automated test coverage
Some test coverage is added in this PR.
QA Plan
None
Migrations
Rollback instructions
The DB migrations should be rolled back if the code is removed permanently.
Labels & Review