Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enables running transitions synchronously #5476

Merged
merged 12 commits into from
May 2, 2019

Conversation

dianabarsan
Copy link
Member

@dianabarsan dianabarsan commented Mar 6, 2019

Description

Pulls sentinel transitions out into a shared library
Enables usage of new shared library @medic/transitions in Sentinel and API.
API sms and records endpoints now run transitions over docs before saving.

#4857

Review checklist

  • Readable: Concise, well named, follows the style guide, documented if necessary.
  • Documented: Configuration and user documentation on medic-docs
  • Tested: Unit and/or e2e where appropriate
  • Internationalised: All user facing text
  • Backwards compatible: Works with existing data and configuration or includes a migration. Any breaking changes documented in Changes.md.

License

The software is provided under AGPL-3.0. Contributions to this project are accepted under the same license.

);
return callback();
}

Copy link
Member Author

Choose a reason for hiding this comment

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

I moved the call to canRun from the applyTransitions map function.
The reason was to test canRun over the document after it has been updated by previous transitions.

As an example, it enables the possibility of running registration over a fresh doc: currently registration requires either the form to be public or doc.contact to exist, while update_clinics is responsible for creating doc.contact.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is a good idea.

We should also formalise transition run order to make this less fragile.

Copy link
Member Author

Choose a reason for hiding this comment

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

});
});
async.series(operations, (err, results) => {
return err ? reject(err) : resolve(results);
});
Copy link
Member Author

Choose a reason for hiding this comment

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

Just thought about this.. do you guys think I should delete the just-created info-docs for docs that are just echo-ed back?

Copy link
Member

Choose a reason for hiding this comment

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

I think this is fine for now - deleting a doc is probably worse for performance than just leaving it there forever...

I think eventually we want the transitions function to return an array of docs to save, so all the docs are saved once all transitions have executed, but that would need careful consideration...

Copy link
Member Author

Choose a reason for hiding this comment

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

I would very much agree with the transaction-like approach.

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess I don't understand this part of the codebase anymore: why would you want to delete them? Even if the transitions don't touch the document we still want an infodoc for replication dates and stuff right?

Copy link
Member Author

Choose a reason for hiding this comment

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

If the transition returns "not changed" or has an error, I return the untouched version of the document that I received, which will not even have an _id property.
The reason is that some transitions add errors on the docs, but still return as "not changed".
The doc will receive another _id upon save, so the info-doc we just created is useless.
I guess I could only copy the _id property over in this case, so it wouldn't be useless?

Copy link
Contributor

Choose a reason for hiding this comment

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

Might as well right? The upside I presume being that when these docs are written Sentinel won't re-run those transactions over them. And the main downside is that we don't generate "smart" UUIDs that CouchDB likes, but honestly we don't do that for 90% of our data right now anyway (cause of PouchDB), and if we really wanted to we could replace the uuid generation used here with a cache that sources UUIDs from CouchDB if we considered it an issue.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sentinel would run all transitions over that doc either way.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's a shame. Maybe not in the future though (like I feel like we should be able to take advantage of this even if we aren't now)!

Also, it's probably better for Impact to not have orphaned info docs. I may not matter depending on their impl, but it's a bug waiting to happen in any case.

Copy link
Member Author

Choose a reason for hiding this comment

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

This thread is outdated because docs are saved in series within the library immediately after running transitions, same as old sentinel.

@dianabarsan
Copy link
Member Author

I guess this is it.. new and improved along with as much git history.
The branch itself now uses the shared lib published under another name.

Could you please have a look, @garethbowen & @SCdF . Thank you!

Copy link
Member

@garethbowen garethbowen left a comment

Choose a reason for hiding this comment

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

Wow. Awesome!

A few questions and comments about minor details but the general approach is great.

shared-libs/transitions/src/config.js Outdated Show resolved Hide resolved
shared-libs/transitions/src/db.js Outdated Show resolved Hide resolved
shared-libs/transitions/src/lib/logger.js Outdated Show resolved Hide resolved
shared-libs/transitions/src/schedule/due_tasks.js Outdated Show resolved Hide resolved
});
});
async.series(operations, (err, results) => {
return err ? reject(err) : resolve(results);
});
Copy link
Member

Choose a reason for hiding this comment

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

I think this is fine for now - deleting a doc is probably worse for performance than just leaving it there forever...

I think eventually we want the transitions function to return an array of docs to save, so all the docs are saved once all transitions have executed, but that would need careful consideration...

shared-libs/transitions/src/transitions/index.js Outdated Show resolved Hide resolved
@@ -262,11 +262,11 @@ describe('registration transition', () => {
const checkAutoResponse = () => {
const taskElement = element(by.css('#reports-content .details > ul'));
expect(taskElement.element(by.css('.task-list > li:nth-child(1) > ul > li')).getText()).toBe('Thank you '+ CAROL.name +' for registering Siobhan');
expect(taskElement.element(by.css('.task-list > li:nth-child(1) .task-state .state.pending')).isDisplayed()).toBeTruthy();
expect(taskElement.element(by.css('.task-list > li:nth-child(1) .task-state .state.forwarded-to-gateway')).isDisplayed()).toBeTruthy();
Copy link
Member

Choose a reason for hiding this comment

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

WOOHOO!

@@ -2,7 +2,7 @@ const utils = require('../../../utils'),
sentinelUtils = require('../utils'),
uuid = require('uuid');

describe('death_reporting', () => {
describe('generate_patient_id_on_people', () => {
Copy link
Member

Choose a reason for hiding this comment

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

Hah

Copy link
Contributor

@SCdF SCdF left a comment

Choose a reason for hiding this comment

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

Mammoth!

Various comments, but the general gist is looking awesome! I'm excited for this distinction to open up opportunities to improve Sentinel's performance as well as our SMS response times

}

if (!changed) {
return updateMetadata(change, callback);
Copy link
Contributor

Choose a reason for hiding this comment

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

Not your code I imagine, but it's weird that we call declare a call to updateMetadata twice even though we do it regardless (either we do it and return, or we do something else then do it).

return;
}
let changes;
const clonedDocs = JSON.parse(JSON.stringify(docs)),
Copy link
Contributor

Choose a reason for hiding this comment

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

I haven't kept up and I'm cool if it is, but is this the state of the art for JSON cloning these days, perf-wise?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah every time I check this is what it comes back to. Last time I checked was a couple of months ago.

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess it depends on what you're cloning.
As a result of Stefan's comment, I tried a couple of reliable alternatives : http://jsben.ch/SJghM

Do you guys think lodash.cloneDeep would be worth including? It can be required as a standalone.

Copy link
Member

Choose a reason for hiding this comment

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

In my Chromium, JSON is faster than lodash but angular is way faster. Do you get lodash as fastest?

screenshot from 2019-03-08 09-32-34

Copy link
Member Author

@dianabarsan dianabarsan Mar 7, 2019

Choose a reason for hiding this comment

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

slightly but consistently faster

image

Copy link
Member

Choose a reason for hiding this comment

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

Mine's consistently slower. Strange. Different browser versions maybe?

I wonder how angular copy is doing it?

I don't think it's worth switching to lodash but it may be worth ripping out the angular version and using that throughout in a separate PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

I was planning on looking at the Angular version tomorrow, it got me curious.

The lodash seems to be the complete solution to deep copy and tries to treat all possible cases, while angular's solution is clearly stated to be riddled with caveats, albeit faster.

}
let changes;
const clonedDocs = JSON.parse(JSON.stringify(docs)),
idsByIdx = [];
Copy link
Contributor

Choose a reason for hiding this comment

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

Might as well

Suggested change
idsByIdx = [];
idsByIdx = new Array(clonedDocs.length);

// - hydrate the docs and generate info docs
// - run loaded transitions over all docs
// - returns docs, either updated or their original version
const processDocs = docs => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can processDocs wrap and so re-use processChange?

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess we could, but then everything would be done one doc at a time:

  • fetching lineage
  • creating the infodoc

Perf wise, it would be much worse than bulking them.

});
});
async.series(operations, (err, results) => {
return err ? reject(err) : resolve(results);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess I don't understand this part of the codebase anymore: why would you want to delete them? Even if the transitions don't touch the document we still want an infodoc for replication dates and stuff right?

shared-libs/transitions/src/transitions/index.js Outdated Show resolved Hide resolved
shared-libs/transitions/src/transitions/index.js Outdated Show resolved Hide resolved
shared-libs/transitions/src/transitions/index.js Outdated Show resolved Hide resolved
);
return callback();
}

Copy link
Contributor

Choose a reason for hiding this comment

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

This is a good idea.

We should also formalise transition run order to make this less fragile.

@@ -1,7 +1,6 @@
// TODO: this doesn't need to exist. We can just work this out dynamically when
// gateway queries, it's not slow or complicated.
const async = require('async'),
_ = require('underscore'),
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the reasoning behind moving the schedule code into the transitions shared lib? AFAICT it's only used by sentinel.

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

@dianabarsan Could you please raise an issue so this actually gets fixed? I'm happy to do it but you're the sentinel expert now!

Copy link
Member Author

Choose a reason for hiding this comment

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

sure!

Copy link
Member

@garethbowen garethbowen left a comment

Choose a reason for hiding this comment

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

Awesome.

I haven't gone through each line again but reviewed the new commits and it looks good to me. If you think it's ready for AT go for it!

@dianabarsan
Copy link
Member Author

I've rebased to resolve a conflict. Also published the shared-lib under the correct name and updated the dependencies in the apps. Had to do some minor updates to get a couple of unit tests passing.
All changes included in this commit

Copy link
Member

@garethbowen garethbowen left a comment

Choose a reason for hiding this comment

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

LGTM.

@dianabarsan dianabarsan merged commit df888af into master May 2, 2019
dianabarsan added a commit that referenced this pull request May 3, 2019
Pulls sentinel transitions out into a shared library
Enables usage of new shared library @medic/transitions in Sentinel and API.
API sms and records endpoints now run transitions over docs before saving.

#4857
abbyad pushed a commit that referenced this pull request May 10, 2019
Pulls sentinel transitions out into a shared library
Enables usage of new shared library @medic/transitions in Sentinel and API.
API sms and records endpoints now run transitions over docs before saving.

#4857
@dianabarsan dianabarsan deleted the 4857-detached-sentinel-bis branch May 12, 2020 21:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants