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

feat(9586): implement freetext search in cht datasource #9625

Open
wants to merge 37 commits into
base: master
Choose a base branch
from

Conversation

sugat009
Copy link
Member

@sugat009 sugat009 commented Nov 7, 2024

Description

Closes: #9586

Code review checklist

  • Readable: Concise, well named, follows the style guide, documented if necessary.
  • Documented: Configuration and user documentation on cht-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 the release notes.

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.

@sugat009 sugat009 linked an issue Nov 7, 2024 that may be closed by this pull request
@sugat009 sugat009 force-pushed the 9586-implement-freetext-search-in-cht-datasource branch from eba7aac to 43efbef Compare November 18, 2024 08:59
@sugat009 sugat009 marked this pull request as ready for review November 29, 2024 09:59
@sugat009
Copy link
Member Author

@jkuester PR is ready for review.

Copy link
Member

@dianabarsan dianabarsan left a comment

Choose a reason for hiding this comment

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

I did a quick partial review and overall this is quite cool. I did leave some requests and questions inline.

api/src/controllers/report.js Outdated Show resolved Hide resolved

module.exports = {
v1: {
get: serverUtils.doOrError(async (req, res) => {
Copy link
Member

Choose a reason for hiding this comment

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

I'm not a big fan of this callback style.

Copy link
Member Author

Choose a reason for hiding this comment

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

We've been doing this pattern for all the REST endpoints that call cht-datasource. What's the alternative?

Copy link
Contributor

Choose a reason for hiding this comment

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

IMHO doOrError is a nice way to reduce duplicated code and ensure we are handling errors consistently.

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 a try-catch block is not so much duplication, and it's more transparent than a nested callback.
I understand this already exists. I'm not a fan.

Object.assign(qualifier, Qualifier.byContactType(req.query.type));
}

const limit = req.query.limit ? Number(req.query.limit) : req.query.limit;
Copy link
Member

Choose a reason for hiding this comment

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

this seems strange that we assign a random non-truthy value (as in: whatever is in req.query.limit) instead of being specific.
Same applies to the reports controller.

Suggested change
const limit = req.query.limit ? Number(req.query.limit) : req.query.limit;
const limit = req.query.limit ? Number(req.query.limit) : false;

The false is a random pick.

Copy link
Member Author

Choose a reason for hiding this comment

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

The reason why req.query.limit is being passed when the conditional is falsy is that in the cht-datasource there is already a validation for this and also a default value. This is to ensure that the validation does not happen twice and also the default value.
Reference.

Copy link
Member

Choose a reason for hiding this comment

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

Then why not have cht-datasource also do the Number conversion then? Why have this validation here? Is limit ever expected to not be a number?

Copy link
Member Author

Choose a reason for hiding this comment

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

Is limit ever expected to not be a number?

Yeah, in cases like the one above, where it is passed as a query param in REST API, it is expected to be a stringified number. However, cht-datasource can also be used in non-REST API codes where end-users will have to pass in a number to cht-datasource because PouchDB expects the limit value to be a number. I think that's a reasonable approach to make the limit variable an explicit Number type, as that would align with the expected input for the PouchDB Adapter. This would provide better type safety and clarity in the code. The validation being present in cht-datasource still makes sense, and whether to apply the same validation elsewhere should be at the discretion of the end-user, based on their specific use case.

Copy link
Member

Choose a reason for hiding this comment

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

So even if it's a stringified number or a number, we still only ever evaluate it as a number. so it makes sense to only have validation in one spot, 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.

Yes. Are you considering a type conversion from string to number a validation?

Copy link
Member

Choose a reason for hiding this comment

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

Ok, I'll use a different word than validation: processing.
It's possible to only do processing of the limit parameter in a single place: this includes validation, conversion and all the other things. We should do processing in a single place.

Copy link
Member Author

@sugat009 sugat009 Dec 3, 2024

Choose a reason for hiding this comment

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

In most cases, I agree but I'm not sure why the conversion of limit from string to number should be designated to cht-datasource, it's not its concern.
Edit 1:
I might have misunderstood the above. Are you suggesting we do both conversion and validation in another place than both the API controller and cht-datasource?

Copy link
Member

Choose a reason for hiding this comment

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

I'm suggesting that conversion and validation can both be done in a single place: cht-datasource.

getIds: serverUtils.doOrError(async (req, res) => {
await checkUserPermissions(req);

const qualifier = Qualifier.byFreetext(req.query.freetext);
Copy link
Member

Choose a reason for hiding this comment

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

So ... this endpoint .. if it doesn't get neither a freetext query param or a limit query param, it will end up returning ALL reports?

Copy link
Member Author

Choose a reason for hiding this comment

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

nope. it returns a 400 - Bad request error because freetext is required whereas limit is set to a default of 10000.
image

@@ -492,6 +494,12 @@ app.postJson('/api/v1/people', function(req, res) {
app.get('/api/v1/person', person.v1.getAll);
app.get('/api/v1/person/:uuid', person.v1.get);

app.get('/api/v1/contact/id', contact.v1.getIds);
Copy link
Member

Choose a reason for hiding this comment

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

maybe /api/v1/contact/ids is more suitable.
The idea is that the URL isn't suggestive at all, without reading the implementation, I would never guess what this endpoint does.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, REST API conventions are to name the API endpoint in a plural way like /api/v1/contacts or /api/v1/contacts/ids but this design decision had already been taken even before this ticket. I couldn't find the link to the conversation for this though.

Copy link
Contributor

Choose a reason for hiding this comment

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

That discussion happened in the parent ticket before we spun off the child isssue: #9544 (comment)

Copy link
Member

Choose a reason for hiding this comment

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

Thanks @jkuester . Your argument here is that "we've already decided and your input is not welcome?"

Copy link
Member

Choose a reason for hiding this comment

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

Sorry for being aggressive and confrontational in the above comment.

I maintain my comment about /api/v1/contact/id being quite unsuggestive, we shouldn't need thorough explanations and reasoning behind the naming choice in order for an api name to make sense.

Copy link
Contributor

Choose a reason for hiding this comment

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

I was just trying to provide the context for the discussion that Sugat referenced. 😬

I am happy to continue the design discussion here to come to an agreed upon approach. It will just be most efficient if we all understand what was already said to get us here. When starting work on new REST endpoints for the cht-datasource code, we chose to go with the pattern of singular entity names (so /api/v1/person instead of /api/v1/persons). When the endpoint can return 0-n entities I do not really see a compelling reason to prefer either singular or plural (since either might make more sense depending on the context). Two things seem clear to me though:

  • Under normal circumstances, we should not duplicate endpoints for the same resource (e.g. having both /api/v1/contact/id and /api/v1/contact/ids).
  • We should be consistent with our naming across our go-forward REST endpoints. Either using singular or plural, but not mixing both.

shared-libs/cht-datasource/src/remote/report.ts Outdated Show resolved Hide resolved
Comment on lines +122 to +123
expect(getLineageDocsByIdOuter.calledOnceWithExactly(localContext.medicDb)).to.be.true;
expect(getDocsByIdsOuter.calledOnceWithExactly(localContext.medicDb)).to.be.true;
Copy link
Member

Choose a reason for hiding this comment

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

Same comment about assertions in afterEach and afterEach run order.

const expectedPlaces = [place0, clinic1, clinic2];
const expectedPlacesIds = expectedPlaces.map(place => place._id);

before(async () => {
Copy link
Member

Choose a reason for hiding this comment

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

All these tests should call the API endpoints, instead of requesting datasource code.

Copy link
Contributor

Choose a reason for hiding this comment

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

This was heavily discussed with QA when laying down the original pattern for these tests. The summary of the the IRL convos is here: #9090 (comment)

TLDR is we could duplicate tests, but the value of that seems nearly non-existant (while adding a non-zero cost in terms of additional tests to run).

Copy link
Member

Choose a reason for hiding this comment

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

Pleaaaase duplicate the tests :)

Copy link
Contributor

Choose a reason for hiding this comment

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

@sugat009 How feasible would it be to parameterize the tests where we are currently using cht-datasource so that we run the same test with both the cht-datasource remote wrapper and with the test utils making the same REST call?

I am thinking of something like this (but have not actually tried to run this yet):

    [
      (placeId) => Place.v1.get(dataContext)(Qualifier.byUuid(placeId)),
      (placeId) => {
        const opts = {
          path: `/api/v1/place/${placeId}`,
        };
        return utils.request(opts);
      }
    ].forEach((getPlace) => {
      it('returns the place matching the provided UUID', async () => {
        const place = await getPlace(place0._id);
        expect(place).excluding(['_rev', 'reported_date']).to.deep.equal(place0);
      });
    });

Copy link
Contributor

Choose a reason for hiding this comment

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

That would ensure we test both the cht-datasource Remote adapter as well as just using the test utils to hit the REST api "directly" without actually needing to duplicate every test...

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 is a nice workaround and would probably be feasible and runnable but let's just duplicate the test code, it probably will be more maintainable and readable, and usually, test code doesn't need reusability.

const getReport = Report.v1.get(dataContext);

it('should return the report matching the provided UUID', async () => {
const resReport = await getReport(Qualifier.byUuid(report0._id));
Copy link
Member

Choose a reason for hiding this comment

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

All these tests should call the api endpoints, instead of request datasource code.

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.

Implement freetext search in cht-datasource
3 participants