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
feat(#9065): add cht-datasource to support getting contact by uuid #9090
base: master
Are you sure you want to change the base?
Conversation
…nabled `composite`.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 on what Gareth said about the API design it makes more sense to have the two functions implement the same interface. I wish the TS team had implemented microsoft/TypeScript#420, it would have been pretty nice in our scenario but we can surely make cht-datasource
work without!
shared-libs/cht-datasource/src/remote/libs/remote-environment.ts
Outdated
Show resolved
Hide resolved
…atasource object is a nested hierarchy.
…5_get_contact_by_id
… have app-settings service be injected
# Conflicts: # sentinel/src/lib/purging.js
… to be more useful.
👍 This has been all part of my evil plan to start with crazy amounts of jsdoc and then agree to "moderate" things back to where I actually wanted them.... 😆 But seriously, 💯 agree regarding the unnecessary redundant docs. Most of those got added late on Friday afternoon in my feverish zeal for consistency 😅, but are actually not required by current linting rules. I have deleted those. I also removed the linting requirement for documenting
I think the big question here is if folks agree with my assessment on the value of the On the other hand, if we are okay with at least some
|
@jkuester Not to be the variable name reviewer guy 😅 but usage-wise would it be possible to have the version access name consistent? Meaning that for the declarative one, we do: import { getDatasource } from '@medic/cht-datasource';
const myPerson = await getDatasource().v1.person.getByUuid(myUuid); Notice the lowercase import { Person, Qualifier } from '@medic/cht-datasource';
const getPerson = Person.V1.get(dataContext); An uppercase And as for the JSdoc conversation, IMO having additional/extra comments/docs is always useful than having fewer docs, and IDEs and editors these days can always fold them out if the concern is that they take up screen space. 😀 |
Switched back to fetch api for now. Also made url optional for remote datasoure (allowing for relative requests from webapp/admin).
😆 @sugat009 the names are one of the the most important parts of a new API, so please, by all means, be the variable name reviewer guy! 💯 I agree that more consistency here would be nice, so I have updated the namespaces to be a lowercase |
…s that need a data context.
.mocharc.js
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting this in the top level so we can have nice things in places like our api unit tests....
* @param fn the function to execute | ||
* @returns the result of the function | ||
*/ | ||
get: <T>(fn: (ctx: DataContext) => T) => T |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added this method after much pain and suffering. It is not obvious, but using this function dramatically simplifies unit testing for code that consumes the imperative-style api. You can see the unit tests for the person API controller as an example, but basically this function provides the abstraction point where sinon can be used to easily inject a stub implementation.
There are other approaches that could be used (and if you can think of a better one that allows for simple testing, I am all ears), but after many hours of tinkering this has the best balance of simplicity, flexibility, and testability of everything I have tried. (Though I am not super happy with the name get
. It is short and does make semantic sense with how we intend this api to be used (e.g. to access the inner function by currying the context), but I don't love it. Very interested to hear if anyone has a better name!)
export abstract class AbstractDataContext implements DataContext { | ||
readonly get = <T>(fn: (ctx: DataContext) => T): T => fn(this); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may think it would make more sense to include this class in the src/libs/data-context.ts
file and you would be correct. Unfortunately, putting it there (in the same file as the DataContext
and other supporting functions) causes some very weird circular dependency issues that all get magically resolved when I move this here....
}); | ||
|
||
describe('GET /api/v1/person/:uuid', async () => { | ||
const getPerson = Person.v1.get(dataContext); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lorerod my initial approach here is to end up with the minimal amount of new integration tests by combining tests for the cht-datasource with tests for the person API controller. Basically, these tests use cht-datasource with the remote
data context to call the controller via the /api/v1/person/:uuid
REST endpoint. Then the api controller code uses cht-datasource with the local
data context (aka Pouch) to do the data interactions with Couch. So we end up being about to test the following "integration":
cht-datasource (remote) -> api Person controller -> cht-datasource (local) -> Couch
To me, this seems like a valid way to test everything at the same time without compromising the effectiveness/fidelity of the tests at all. Using the remote cht-datasource to make the REST calls is functionally not much different than using the utils.request
function to make the REST calls... But, IDK, let me know what you think about all this!
path: `/api/v1/person/${patient._id}`, | ||
auth: { username: userNoPerms.username, password: userNoPerms.password }, | ||
}; | ||
await expect(utils.request(opts)).to.be.rejectedWith('403 - {"code":403,"error":"Insufficient privileges"}'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, just because we use cht-datasource for some of the tests does not mean we need to use it for all of them. With the way things are setup now, it would be tricky to use cht-datasource with a different user, so I just opted for the utils.request
function here.
// Cookies from the jar will be included on Node `fetch` calls | ||
global.fetch = require('fetch-cookie').default(global.fetch, cookieJar); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After some tinkering and thinking, I realized we are in a pretty stable place for authentication in cht-datasource.
Webapp (online) and the admin app are using the remote adapter which makes fetch
calls from the browser. These fetch
calls can be made relative to the "current page" (so the URL does not need to be provided) and they inherit the cookies for the browser session (so we don't need to worry about authentication).
Sentinel, api, and Webapp (offline) are using the local adapter which is just Pouch
under the hood and the injected Pouch instances are already authenticated.
However, our integration tests (and any future 3rd-party usage of cht-datasource) need to provide a root URL to use for calling the api server (done via the getRemoteDataContext
function). Where things get tricky for the tests is the NodeJS implementation of the fetch
api does not supply cookies from the Set-Cookie
of previous responses when it makes new requests (this happens automatically in the browser environment). So, the choices were either:
- update the cht-datasource code to make it possible to inject the session cookie just so the tests would work
- or get a bit creative with
fetch
during the test runs to be sure the cookies are there whenfetch
is called.
I have opted for the latter option for now. It keeps the impl code the most clean. This will probably need to be re-visited in the future, either when we go to replace all our request-promise-native
with fetch
or when we release cht-datasource for 3rd-party consumption and we need a more proper way to do authentication. But, for now, this seems like a reasonable approach especially since we can just piggy-back on our existing rpn cookie jar....
@m5r @sugat009 This is ready for another review! Actually, IMHO this code is "complete" in that I would be comfortable merging it to
(These other three should be much quicker to add now that the patterns/utils are stabilizing). |
Description
Closes #9065
cht-datasource
This PR converts
shared-libs/cht-script-api
intoshared-libs/cht-datasource
as a general purpose data-access layer intended for use within cht-core both in server and the client-side code as well as within custom config code for (tasks, targets, etc).There are two modes of functionality exported from
cht-datasource
. Both require you to first acquire aDataContext
(eitherremote
orlocal
depending on if you can interact directly with the CHT api server or if offline functionality is required).Imperative
The imperative mode provides a hierarchical factory object that you can drill down into and interact with the datasource.
This mode is useful for exposing functionality to custom configuration code (tasks, targets, etc) by passing the datasource object directly into the custom function.
Declarative
The declarative mode provides more flexible access to API functions. The
dataContext
must be curried when using the functions.Code review checklist
Compose URLs
If Build CI hasn't passed, these may 404:
License
The software is provided under AGPL-3.0. Contributions to this project are accepted under the same license.