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

[BACK-3177] Allow specifying custom cadence for EHR reports #769

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

Conversation

toddkazakov
Copy link
Contributor

No description provided.

@toddkazakov toddkazakov changed the title Allow specifying custom cadence for EHR reports [BACK-3177] Allow specifying custom cadence for EHR reports Sep 21, 2024
@toddkazakov toddkazakov force-pushed the tk-ehr-disable-scheduled-reports branch from d16802a to fe0cf08 Compare September 21, 2024 07:56
ewollesen
ewollesen previously approved these changes Sep 23, 2024
Copy link
Contributor

@ewollesen ewollesen left a comment

Choose a reason for hiding this comment

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

LGTM. No blockers, but I'll reiterate that I'm against taking a dependency for the duration parsing we're doing here.

clinics = test.RandomArrayWithLength(3, clinicsTest.NewRandomClinic)
tasks = make(map[string]task.Task)
for _, clinic := range clinics {
clinic := clinic
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't necessary here is it? I ran the test both ways and it succeeded, but sometimes these things are tricky, right?


It("returns a task for deletion when the report settings are disabled", func() {
settings := clinicsTest.NewRandomEHRSettings()
settings.ScheduledReports.Cadence = api.N0d
Copy link
Contributor

Choose a reason for hiding this comment

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

Hah! Here's the bit from the clinic PR that I needed to know this was a disable setting! :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed this to DISABLED. I'm not a big fan of the resulting code, but at least it makes it obvious to the reader when a task is disabled.

cadence := data["cadence"].(string)
parsed, err := time.ParseDuration(cadence)
if err != nil {
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would simplify the logic if instead of returning nil, you returned sync.DefaultCadence. Both ScheduleNextExecution and GetReconciliationPlan check if the response is nil, and if so, use the default value.

To go one step further, you could instead return the error (func GetCandence(...) (time.Duration, error)) so the caller (who presumably has more context) could decide how to handle that (whether that means using the default, or maybe logging the error before using the default) or maybe using some other value. Just returning nil (or a default) hides the error entirely. If it's really not something we care about, that's fine. If you think there's ever a time you'd want to use that error, consider returning it.

But a minor thing to be sure, and fine how it is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Both ScheduleNextExecution and GetReconciliationPlan check if the response is nil, and if so, use the default value.

This statement is not true. Planner doesn't use the default value, it check whether the cadence in the task is difference that the one in the settings, and if that's the case it updates the task:

// Update the task if the configured cadence has been changed
cadenceFromTask := sync.GetCadence(tsk.Data)
if cadenceFromTask == nil || *cadenceFromTask != cadenceFromSettings {
    sync.SetCadence(tsk.Data, cadenceFromSettings)
    update := task.NewTaskUpdate()
    update.Data = &tsk.Data
    toUpdate[tsk.ID] = update
}

This function returns exactly what the name implies. If parsing fails, then the cadence is not correctly set so it will be updated as configured. I could return the parsing error, but it will be ignored by the caller.

Copy link
Contributor

Choose a reason for hiding this comment

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

This statement is not true.

You're right. I missed a case where the default might not be used.

Many minutes of reading the code later...

OK, I'm good with this now.

}

func GetCadence(data map[string]interface{}) *time.Duration {
cadence := data["cadence"].(string)
Copy link
Contributor

Choose a reason for hiding this comment

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

Uck. I get why this is here, but it's unfortunate. :( I guess someday we could update the task service now that Generics in Go are a thing.

import (
"context"

duration "github.com/xhit/go-str2duration/v2"
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not a fan of taking a dependency for this. We support literally four values, I don't think we need a 3rd-party dependency to handle that now, or in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed the dependency. I don't want to have to update 2 services if we decide to support 60 day reports one day, so I had to implement the parsing logic myself. I think this results in less readable code.

return nil, err
}
if response.StatusCode() != http.StatusOK || response.StatusCode() != http.StatusOK {
return nil, fmt.Errorf("unexpected response status code %v from %v", response.StatusCode(), response.HTTPResponse.Request.URL)
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider if this should use the existing platform errors code.

func (d *defaultClient) GetEHRSettings(ctx context.Context, clinicId string) (*clinic.EHRSettings, error) {
response, err := d.httpClient.GetEHRSettingsWithResponse(ctx, clinicId)
if err != nil {
return nil, err
Copy link
Contributor

Choose a reason for hiding this comment

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

This might be a place to wrap with the existing platform error code to gain the benefits it brings.

clinicId := *clinic.Id
settings, err := p.clinicsClient.GetEHRSettings(ctx, clinicId)
if err != nil {
return nil, err
Copy link
Contributor

Choose a reason for hiding this comment

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

The clinicsClient appears to be generated code that doesn't return platform errors. Therefore you might consider if these should use errors.Wrap. (I won't mention this any more for this PR, but there are other places I haven't pointed out.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you please clarify what are the benefits of using the platform errors.Wrap, because I'm not familiar with those.

Copy link
Contributor

Choose a reason for hiding this comment

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

@darinkrauss is for sure the expert on that. But I believe he'd say they bring a consistent format to errors, and sometimes they add line numbers for finding the source of the error.

I thought the errors package was still in use for new code in platform, no? If not, I'll make a note and try to stop using/suggesting it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've been using the standard library most recently. This may be incorrect, but it's not obvious or documented why the platform errors package should be used instead, hence my question.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, what do folks think about me writing a "Guide to Writing Consistent Code in the Platform Repo"? 😉

Yes, there is a reason to use the Platform errors package and, more explicitly, the errors.Wrap functionality. The errors package adds a lot more context to the error that chains correctly with multiple wraps. Now, while the current Golang errors package does some of that, the rest of Platform isn't built to handle that in any way.

So, in short, if you want errors to propagate correctly and do the "right thing" in the rest of Platform, then use the Platform errors package and use errors.Wrap when you want to add further context or explanation to the error.

I'm fine if we want to intentionally revisit this and make appropriate changes, but IMO, we should stick with how it is currently done so it works and is written as expected.

My 2 cents...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the explanation - I'll make the change, because those errors might eventually bubble up to the task error handler.

ewollesen
ewollesen previously approved these changes Oct 1, 2024
Copy link
Contributor

@ewollesen ewollesen left a comment

Choose a reason for hiding this comment

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

Thank you for pointing out my misunderstanding. Still no blockers IMO. See @darinkrauss re platform/errors usage, if my explanation isn't satisfactory.

clinicId := *clinic.Id
settings, err := p.clinicsClient.GetEHRSettings(ctx, clinicId)
if err != nil {
return nil, err
Copy link
Contributor

Choose a reason for hiding this comment

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

@darinkrauss is for sure the expert on that. But I believe he'd say they bring a consistent format to errors, and sometimes they add line numbers for finding the source of the error.

I thought the errors package was still in use for new code in platform, no? If not, I'll make a note and try to stop using/suggesting it.

cadence := data["cadence"].(string)
parsed, err := time.ParseDuration(cadence)
if err != nil {
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

This statement is not true.

You're right. I missed a case where the default might not be used.

Many minutes of reading the code later...

OK, I'm good with this now.

@ewollesen
Copy link
Contributor

Oh, looks like there's a go import re-ordering that needs to be fixed to make the linter happy.

https://app.travis-ci.com/github/tidepool-org/platform/builds/272549249#L442

ewollesen
ewollesen previously approved these changes Oct 2, 2024
@toddkazakov toddkazakov force-pushed the tk-ehr-disable-scheduled-reports branch from a311e45 to 2b21ec7 Compare October 8, 2024 22:18
@toddkazakov toddkazakov force-pushed the tk-ehr-disable-scheduled-reports branch from a3ee552 to 4404cc9 Compare October 25, 2024 09:41
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