Age calc with moment in task.js

We’ve recently up-streamed our date-diff functions to xpath.
This allowed us to remove the moment library from our custom global namespace, defined in the main.js in the webapp folder, and remove the surrogate extension lib that originally performed age related calculations (ageInYears, -Months, -Days).

Today we realized we need to show a task for a newly registered patient, if they are below 5 years of age.
Importing the xpath functions did not work as anticipated, and after a little digging it seems tasks have a known issue with not having scope of the moment library:

We then tried to import the moment lib via nools, but that just seemingly broke the app outright. Perhaps due to us still being on 4.3.x?

We’ve also found this thread, but it lacks context around HOW the age is calculated.

Is there a way around this?

You should have no problem importing luxon into tasks.js and using its DateTime object. Would that work for you?

Luxon for Moment Users

There is a way to use moment in tasks.js and I can share that with you, but this is my actual recommendation because its just easy and lightweight. If it doesn’t work, I’ll happily take you through the steps to use moment.

Thank you for highlighting luxon, it worked flawlessly!
image
image

My question is why did it work without fail where moment failed?
Also, why is moment still being used in CHT when luxon is available and objectively “better”?
After all, Moment’s website & their github repo suggests using a different library.


I would still like to see your implementation of moment in tasks.js.
Did it also require global namespace as part of the solution?

All of the above being said, I would like not to repeat existing functionality (as noted with the xpath functions). Could the xpath functions be updated to also use luxon?
That way one could depend on the xpath file to perform checks in tasks.js.

You should be able to use moment and your moment-dependant libraries in tasks.js using these steps:

  1. Use latest cht-conf (at least 3.15.1)
  2. Use latest cht-core (at least 4.2)

If you want to know why this is crashing, you’ll have to read through this fix. But the basic concept is that we previously parsed your compiled javascript code using a library called nools and this was very fragile. nools is a relic of the past, but some CHT partners still rely on its functionality. Many (most?) libraries (like moment) cause a crash, but (by luck) luxon does not.

I believe cht-core’s use of moment is simply a relic of the past. CHT is maintained by a community with limited time and resources and moment is causing no harm or issues for users. Contributions are always welcome if you’re motiviated to replaced Moment with Luxon!

1 Like

Thank you for conveying the requirements.
While we are on the newer cht-conf tool, 3.18.3, we’re unfortunately still busy with our upgrade to 4.2.

Thank you for also providing the link, and putting in the work, that fixed the issue.
The endeavor to part ways with nools makes sense, we’ll be sure to add it to our docs for further reading.
Why was nools originally used in favor of a more code direct approach?
We’ll… glad luck has smiled upon us with that library as we definitely need it :sweat_smile:.

That’s sound reasoning. Since our tasks are going to depend on luxon, we plan to update our dateDiff upstream contribution to do the same. With time we hope to increase our contribution footprint.

Thank you again for all the info.

@jkuester @kenn @diana just a few follow up questions for my own understanding.

  1. While the luxon impl works in dev, will it work in a production build?

The only mention of luxon I can find is in the project level package.lock file (under the config folder - in our case copc).
It’s only mentioned there because cht-conf-test-harness depends on it:

"node_modules/cht-conf-test-harness": {
            "version": "2.4.3",
            "resolved": "https://registry.npmjs.org/cht-conf-test-harness/-/cht-conf-test-harness-2.4.3.tgz",
            "integrity": "sha512-wpZnoo0IkC0zlGDtzKdYXdRCimrIi60gvQegb1vwbU4tq/uExHx61Qf4WehxovFBTebQWr5EqOE+stK2Ul6LEA==",
            "dev": true,
            "dependencies": {
                "lodash": "^4.17.15",
                "luxon": "^1.27.0",

As you can see it is a dev dependency, hence my worries if it will be available in a prod build.
Is it perhaps imported anywhere else?

  1. Where do tasks run/how does it work?
    If I understand correctly, the server, via sentinel, is responsible for scheduling tasks. These are then uploaded to couchDB in order to be replicated to the user devices. Enabling them to complete tasks even while offline.
    The webapp is then responsible for executing the resolvedIf block in order to determine if a task should still be shown.
    This is where we then use the luxon lib in order to calculate if the person is less than 5 years old, and display the task for them.
    The webapp itself only has the moment dependency, does this mean we’re depending on a library of a library?

Not 100% sure, but I think that since Webpack is going to do its thing from the dev environment, it technically might not matter that it is a transitive dev dependency. The necessary luxon code will be wrapped up with your task code when it gets uploaded to the server. (Once again, this is just how things work in my head, :sweat_smile: I have not confirmed this to be true.) All that being said, if you are going to depend on luxon in your implementation code, then semantically you really should add luxon to your list of dependencies in your package.json. You do not really want to simply depend on whatever version the test harness happens to use.

So, as I understand it, Sentinel does not have anything to do with generating/scheduling “tasks” (aka the user tasks configured in the tasks.json). For these tasks to work offline (and be responsive to data that only exists on the client and has not been synced to the server), they have to function completely within the client-side webapp. To dig in more to the code flow of tasks, the rules-engine.service getTaskDocsFor function is probably a decent place to start. The client uses the rules engine to interpret the provided tasks.js config and generate tasks based on the client’s current data. If you really want to get into the guts, here is where the code from tasks.js is loaded. Then, that function is called here when getting/initing the tasks data for a user/contact.

I think the piece you are missing here is that webapp does not actually need to depend directly on luxon just to run your task code. As I mentioned above, webpack will cram all your code and its dependencies together before uploading it as part of your app_settings. Then, when the webapp actually goes to run your task code, it does not know or care about luxon, but your packaged app_settings have all the code needed for your logic to work (regardless of what webapp depends on).

This was part of the trickiness that Kenn was getting at earlier. Your webpacked task code (prior to 4.2) would get automatically parsed by nools. The reason this limited the dependencies that could be used in the task code was because nools would also parse the packed dependency code as well (since webpack would bundle it all up together).

1 Like

That seems to be the behaviour we experienced. From what we can tell, and as you noted, the tasks.js file gets consumed by cht-conf, taking the code and the parts of libraries it depends on, and saves it as a string within the “tasks” block within your project root level app_settings.json file. Interestingly we saw “targets” mentioned in there as well:

"tasks": {
    "rules": "define Target { _id: null, contact: null, deleted: null, type: null, pass: null, date: null, groupBy: null }\ndefine Contact { contact: null, reports: null, tasks: null }\ndefine Task { _id: null, deleted: null, doc: null, contact: null, icon: null, date: null, readyStart: null, readyEnd: null, title: null, fields: null, resolved: null, priority: null, priorityLabel: null, 
...
[{id:'initial-follow-up',days:1,start:0,end:30}],resolvedIf:function(e,t,n,i){const o='chw'===user.contact_type,a=e.contact

The conf tool then uploads your project settings to the couchDB medic database:

const updateAppSettings = (settings) => {
  return request.put({
    url: `${environment.apiUrl}/_design/medic/_rewrite/update_settings/medic?replace=1`,
    headers: {'Content-Type': 'application/json'},
    body: settings,
  });
};

cht-conf/src/lib/api.js at main · medic/cht-conf (github.com)

Finally, the uploaded string content, of the “tasks”, then gets evaluated by the rules-engine.service.

Still, as you’ve noted, we added the luxon library as an explicit dependency in our project level package.json just to be safe.
Thank you for the additional usage links, and for being patient with our understanding!

2 Likes