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

🚀 Imports 2.0 #358

Merged
merged 12 commits into from
Sep 30, 2020
Merged

🚀 Imports 2.0 #358

merged 12 commits into from
Sep 30, 2020

Conversation

rowanseymour
Copy link
Contributor

@rowanseymour rowanseymour commented Sep 23, 2020

No description provided.

@codecov
Copy link

codecov bot commented Sep 23, 2020

Codecov Report

Merging #358 into master will increase coverage by 1.01%.
The diff coverage is 68.84%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #358      +/-   ##
==========================================
+ Coverage   53.00%   54.01%   +1.01%     
==========================================
  Files         109      111       +2     
  Lines        7856     8010     +154     
==========================================
+ Hits         4164     4327     +163     
+ Misses       3007     2959      -48     
- Partials      685      724      +39     
Impacted Files Coverage Δ
models/msgs.go 20.80% <0.00%> (ø)
models/utils.go 79.31% <ø> (ø)
tasks/starts/worker.go 55.68% <0.00%> (ø)
tasks/contacts/import_contact_batch.go 25.00% <25.00%> (ø)
models/imports.go 67.44% <67.44%> (ø)
models/contacts.go 61.02% <70.68%> (+2.92%) ⬆️
models/events.go 48.35% <77.77%> (+48.35%) ⬆️
hooks/session_triggered.go 81.48% <100.00%> (ø)
tasks/broadcasts/worker.go 57.33% <100.00%> (ø)
web/contact/contact.go 61.70% <100.00%> (ø)
... and 10 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 1ad2a3c...81e279b. Read the comment docs.

@rowanseymour rowanseymour marked this pull request as ready for review September 28, 2020 22:18
// * If URNs exists and belongs to a single contact it returns that contact (other URNs are not assigned to the contact).
// * If URNs exists and belongs to multiple contacts it will return an error.
//
func GetOrCreateContact(ctx context.Context, db *sqlx.DB, oa *OrgAssets, urnz []urns.URN) (*Contact, *flows.Contact, error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Contract is a bit different than what we've had in RP - we don't promise that the returned contact owns all of the passed in URNs - instead the import code assigns any missing URNs using modifiers. I like this because:

  • Unless we added locking - that stuff isn't going to be atomic anyway
  • We don't duplicate the logic around creating new URNs vs attaching orphaned URNs in that modifier
  • Using modifiers means there's an event to say that a URN was added to a contact... and one day we'll show all events for all contacts... one day

Copy link
Contributor Author

Choose a reason for hiding this comment

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

// CreateContact creates a new contact for the passed in org with the passed in URNs
func CreateContact(ctx context.Context, db *sqlx.DB, oa *OrgAssets, userID UserID, name string, language envs.Language, urnz []urns.URN) (*Contact, *flows.Contact, error) {
// find current owners of these URNs
owners, err := contactIDsFromURNs(ctx, db, oa.OrgID(), urnz)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

same check to see if URNs are taken added here to avoid even more tx rollbacks

return contactID, true, nil
}

if dbutil.IsUniqueViolation(err) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

thinking of ways to test this... could have another interface like Queryer that supports transactions (QueryerWithTx).. use that everywhere instead of *sqlx.DB, and make an implementation for testing that we can configure to blow up when needed

Copy link
Member

Choose a reason for hiding this comment

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

Don't know if this is the best, or even a good way, but in Courier we do in goroutines by having a flag we can set on the test to slow things down:
https://github.com/nyaruka/courier/blob/master/backends/rapidpro/backend_test.go#L254
https://github.com/nyaruka/courier/blob/master/backends/rapidpro/contact.go#L180

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Interesting - I like the idea of being able to test an actual race - tho I think I'd still want to keep that out of the production code. I.e. could use the db interface as per nyaruka#360 but make one of those calls take an extra second.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

if err != nil {
return nil, errors.Wrapf(err, "error selecting contact ids by uuid")
return nil, err
Copy link
Member

Choose a reason for hiding this comment

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

Remember go annoyingly treats no rows as an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So the code that it replaces wasn't checking for sql.NoRows because it's only only sued in places where you're expecting get back ids for things that should exist.. so I went back and forth on whether this being a generic utility method should allow that.. and couldn't make up my mind.. but this morning I'm leaning toward it allowing no rows, and the calling code should check that it got as many ids as it expected

Copy link
Member

Choose a reason for hiding this comment

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

Nice

}
defer rows.Close()

var id ContactID
for rows.Next() {
err := rows.Scan(&id)
if err != nil {
return nil, errors.Wrapf(err, "error scanning contact id")
return nil, err
Copy link
Member

Choose a reason for hiding this comment

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

What's your rule for wrapping or not wrapping? I kinda just defer to always wrapping because hey, more info when looking at a trace never seems like a bad thing but totally admit maybe that's overkill. Just wondering what rule you use.

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 generally leaned toward always wrap - not even sure why I didn't here - fine with more wrapping

return contactID, true, nil
}

if dbutil.IsUniqueViolation(err) {
Copy link
Member

Choose a reason for hiding this comment

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

Don't know if this is the best, or even a good way, but in Courier we do in goroutines by having a flag we can set on the test to slow things down:
https://github.com/nyaruka/courier/blob/master/backends/rapidpro/backend_test.go#L254
https://github.com/nyaruka/courier/blob/master/backends/rapidpro/contact.go#L180

// import status constants
const (
ContactImportStatusPending ContactImportStatus = "P"
ContactImportStatusProcessing ContactImportStatus = "O"
Copy link
Member

Choose a reason for hiding this comment

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

Would Running be better maybe?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe but these are the same codes that we use in the 3 export models

func (b *ContactImportBatch) Import(ctx context.Context, db *sqlx.DB, orgID OrgID) error {
// if any error occurs this batch should be marked as failed
if err := b.tryImport(ctx, db, orgID); err != nil {
b.markFailed(ctx, db)
Copy link
Member

Choose a reason for hiding this comment

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

Any benefit to logging the error in the task itself too?

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 think this is an error for us (it's a a bunch of wrapped err's) rather than the user facing errors what we collect in batch.errors and relate to problems in the user's data, so it's fine if it just goes to sentry.

}

// and apply in bulk
_, err = ApplyModifiers(ctx, db, nil, oa, modifiersByContact)
Copy link
Member

Choose a reason for hiding this comment

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

May not be worth it but one thing we do in batch starts is if we fail we retry in individual transactions for each contact.. this helps with deadlocks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So that would be something like reworking HandleAndCommitEvents to catch any error when committing the transaction that wraps ApplyEventPreCommitHooks.. and if it fails, try each scene one by one? That sounds pretty straightforward.. tho inclined to try without first.

b.FinishedOn = &now
_, err = db.ExecContext(ctx,
`UPDATE contacts_contactimportbatch SET status = $2, num_created = $3, num_updated = $4, num_errored = $5, errors = $6, finished_on = $7 WHERE id = $1`,
b.ID, b.Status, b.NumCreated, b.NumUpdated, b.NumErrored, b.Errors, b.FinishedOn,
Copy link
Member

Choose a reason for hiding this comment

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

Since you have a batch at this point maybe cleaner / less error prone to use NamedQuery?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah will update

@rowanseymour rowanseymour merged commit 14ec817 into master Sep 30, 2020
@rowanseymour rowanseymour deleted the imports2 branch September 30, 2020 19:02
rasoro pushed a commit to Ilhasoft/mailroom that referenced this pull request Mar 10, 2023
add is_resend to Msg payload to allow for resending messages manually
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.

2 participants