From e5b7928287ff4a269d05f3a40ebc602b515309bd Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 26 Jul 2024 10:42:51 -0500 Subject: [PATCH 001/216] Use minio for local dev and tests --- .github/workflows/ci.yml | 9 +++++++++ .gitignore | 1 - core/models/orgs.go | 4 +--- core/models/orgs_test.go | 4 +--- core/runner/runner_test.go | 2 +- mailroom.go | 38 ++++++++++++++----------------------- runtime/config.go | 37 +++++++++++++++--------------------- testsuite/testsuite.go | 39 +++++++++++++++++++------------------- 8 files changed, 61 insertions(+), 73 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29da74e7b..d29729640 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,15 @@ jobs: discovery.type: single-node xpack.security.enabled: false options: --health-cmd "curl http://localhost:9200/_cluster/health" --health-interval 10s --health-timeout 5s --health-retries 5 + minio: + image: bitnami/minio:latest + env: + MINIO_ROOT_USER: root + MINIO_ROOT_PASSWORD: tembatemba + MINIO_DEFAULT_BUCKETS: temba-attachments,temba-sessions,temba-logs + ports: + - 9000:9000 + options: --health-cmd "mc ready local" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Checkout code diff --git a/.gitignore b/.gitignore index f39bf7c00..8b4cfbc0a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,6 @@ docs/* docs .DS_Store -_storage/ # Test binary, build with `go test -c` *.test diff --git a/core/models/orgs.go b/core/models/orgs.go index f9bdce43e..ff03292bf 100644 --- a/core/models/orgs.go +++ b/core/models/orgs.go @@ -157,8 +157,6 @@ func (o *Org) AirtimeService(httpClient *http.Client, httpRetries *httpx.RetryCo // StoreAttachment saves an attachment to storage func (o *Org) StoreAttachment(ctx context.Context, rt *runtime.Runtime, filename string, contentType string, content io.ReadCloser) (utils.Attachment, error) { - prefix := rt.Config.S3AttachmentsPrefix - // read the content contentBytes, err := io.ReadAll(content) if err != nil { @@ -171,7 +169,7 @@ func (o *Org) StoreAttachment(ctx context.Context, rt *runtime.Runtime, filename contentType, _, _ = mime.ParseMediaType(contentType) } - path := o.attachmentPath(prefix, filename) + path := o.attachmentPath("attachments", filename) url, err := rt.AttachmentStorage.Put(ctx, path, contentType, contentBytes) if err != nil { diff --git a/core/models/orgs_test.go b/core/models/orgs_test.go index 14340d021..794202a71 100644 --- a/core/models/orgs_test.go +++ b/core/models/orgs_test.go @@ -107,8 +107,6 @@ func TestEmailService(t *testing.T) { func TestStoreAttachment(t *testing.T) { ctx, rt := testsuite.Runtime() - defer testsuite.Reset(testsuite.ResetStorage) - image, err := os.Open("testdata/test.jpg") require.NoError(t, err) @@ -118,7 +116,7 @@ func TestStoreAttachment(t *testing.T) { attachment, err := org.StoreAttachment(context.Background(), rt, "668383ba-387c-49bc-b164-1213ac0ea7aa.jpg", "image/jpeg", image) require.NoError(t, err) - assert.Equal(t, utils.Attachment("image/jpeg:_test_attachments_storage/attachments/1/6683/83ba/668383ba-387c-49bc-b164-1213ac0ea7aa.jpg"), attachment) + assert.Equal(t, utils.Attachment("image/jpeg:https://temba-attachments.s3.us-east-1.amazonaws.com/attachments/1/6683/83ba/668383ba-387c-49bc-b164-1213ac0ea7aa.jpg"), attachment) // err trying to read from same reader again _, err = org.StoreAttachment(context.Background(), rt, "668383ba-387c-49bc-b164-1213ac0ea7aa.jpg", "image/jpeg", image) diff --git a/core/runner/runner_test.go b/core/runner/runner_test.go index 354779643..66be1602a 100644 --- a/core/runner/runner_test.go +++ b/core/runner/runner_test.go @@ -83,7 +83,7 @@ func TestStartFlowBatch(t *testing.T) { func TestResume(t *testing.T) { ctx, rt := testsuite.Runtime() - defer testsuite.Reset(testsuite.ResetData | testsuite.ResetStorage) + defer testsuite.Reset(testsuite.ResetData) // write sessions to s3 storage rt.Config.SessionStorage = "s3" diff --git a/mailroom.go b/mailroom.go index 5700adb93..bfc90acf6 100644 --- a/mailroom.go +++ b/mailroom.go @@ -92,31 +92,21 @@ func (mr *Mailroom) Start() error { log.Warn("fcm not configured, no android syncing") } - // create our storage (S3 or file system) - if mr.rt.Config.AWSAccessKeyID != "" || mr.rt.Config.AWSUseCredChain { - s3config := &storage.S3Options{ - Endpoint: c.S3Endpoint, - Region: c.S3Region, - DisableSSL: c.S3DisableSSL, - ForcePathStyle: c.S3ForcePathStyle, - MaxRetries: 3, - } - if mr.rt.Config.AWSAccessKeyID != "" && !mr.rt.Config.AWSUseCredChain { - s3config.AWSAccessKeyID = c.AWSAccessKeyID - s3config.AWSSecretAccessKey = c.AWSSecretAccessKey - } - s3Client, err := storage.NewS3Client(s3config) - if err != nil { - return err - } - mr.rt.AttachmentStorage = storage.NewS3(s3Client, mr.rt.Config.S3AttachmentsBucket, c.S3Region, s3.BucketCannedACLPublicRead, 32) - mr.rt.SessionStorage = storage.NewS3(s3Client, mr.rt.Config.S3SessionsBucket, c.S3Region, s3.ObjectCannedACLPrivate, 32) - mr.rt.LogStorage = storage.NewS3(s3Client, mr.rt.Config.S3LogsBucket, c.S3Region, s3.ObjectCannedACLPrivate, 32) - } else { - mr.rt.AttachmentStorage = storage.NewFS("_storage/attachments", 0766) - mr.rt.SessionStorage = storage.NewFS("_storage/sessions", 0766) - mr.rt.LogStorage = storage.NewFS("_storage/logs", 0766) + s3config := &storage.S3Options{ + AWSAccessKeyID: c.AWSAccessKeyID, + AWSSecretAccessKey: c.AWSSecretAccessKey, + Region: c.AWSRegion, + Endpoint: c.S3Endpoint, + ForcePathStyle: c.S3ForcePathStyle, + MaxRetries: 3, + } + s3Client, err := storage.NewS3Client(s3config) + if err != nil { + return err } + mr.rt.AttachmentStorage = storage.NewS3(s3Client, mr.rt.Config.S3AttachmentsBucket, c.AWSRegion, s3.BucketCannedACLPublicRead, 32) + mr.rt.SessionStorage = storage.NewS3(s3Client, mr.rt.Config.S3SessionsBucket, c.AWSRegion, s3.ObjectCannedACLPrivate, 32) + mr.rt.LogStorage = storage.NewS3(s3Client, mr.rt.Config.S3LogsBucket, c.AWSRegion, s3.ObjectCannedACLPrivate, 32) // check our storages if err := checkStorage(mr.rt.AttachmentStorage); err != nil { diff --git a/runtime/config.go b/runtime/config.go index ce86e2c11..02d2a24ed 100644 --- a/runtime/config.go +++ b/runtime/config.go @@ -57,18 +57,15 @@ type Config struct { ElasticPassword string `help:"the password for ElasticSearch if using basic auth"` ElasticContactsIndex string `help:"the name of index alias for contacts"` - S3Endpoint string `help:"the S3 endpoint we will write attachments to"` - S3Region string `help:"the S3 region we will write attachments to"` - S3AttachmentsBucket string `help:"the S3 bucket we will write attachments to"` - S3AttachmentsPrefix string `help:"the prefix that will be added to attachment filenames"` - S3SessionsBucket string `help:"the S3 bucket we will write attachments to"` - S3LogsBucket string `help:"the S3 bucket we will write logs to"` - S3DisableSSL bool `help:"whether we disable SSL when accessing S3. Should always be set to False unless you're hosting an S3 compatible service within a secure internal network"` - S3ForcePathStyle bool `help:"whether we force S3 path style. Should generally need to default to False unless you're hosting an S3 compatible service"` - - AWSAccessKeyID string `help:"the access key id to use when authenticating S3"` - AWSSecretAccessKey string `help:"the secret access key id to use when authenticating S3"` - AWSUseCredChain bool `help:"whether to use the AWS credentials chain. Defaults to false."` + AWSAccessKeyID string `help:"access key ID to use for AWS services"` + AWSSecretAccessKey string `help:"secret access key to use for AWS services"` + AWSRegion string `help:"region to use for AWS services, e.g. us-east-1"` + + S3Endpoint string `help:"S3 service endpoint, e.g. https://s3.amazonaws.com"` + S3AttachmentsBucket string `help:"S3 bucket to write attachments to"` + S3SessionsBucket string `help:"S3 bucket to write flow sessions to"` + S3LogsBucket string `help:"S3 bucket to write channel logs to"` + S3ForcePathStyle bool `help:"S3 should used /bucket/path style URLs"` CourierAuthToken string `help:"the authentication token used for requests to Courier"` LibratoUsername string `help:"the username that will be used to authenticate to Librato"` @@ -118,18 +115,14 @@ func NewDefaultConfig() *Config { ElasticPassword: "", ElasticContactsIndex: "contacts", - S3Endpoint: "https://s3.amazonaws.com", - S3Region: "us-east-1", - S3AttachmentsBucket: "attachments-bucket", - S3AttachmentsPrefix: "attachments/", - S3SessionsBucket: "sessions-bucket", - S3LogsBucket: "logs-bucket", - S3DisableSSL: false, - S3ForcePathStyle: false, - AWSAccessKeyID: "", AWSSecretAccessKey: "", - AWSUseCredChain: false, + AWSRegion: "us-east-1", + + S3Endpoint: "https://s3.amazonaws.com", + S3AttachmentsBucket: "temba-attachments", + S3SessionsBucket: "temba-sessions", + S3LogsBucket: "temba-logs", InstanceID: hostname, LogLevel: slog.LevelWarn, diff --git a/testsuite/testsuite.go b/testsuite/testsuite.go index 1935b91b6..3a7c79279 100644 --- a/testsuite/testsuite.go +++ b/testsuite/testsuite.go @@ -8,6 +8,7 @@ import ( "os/exec" "path" + "github.com/aws/aws-sdk-go/service/s3" "github.com/elastic/go-elasticsearch/v8" "github.com/gomodule/redigo/redis" "github.com/jmoiron/sqlx" @@ -25,10 +26,6 @@ const elasticURL = "http://localhost:9200" const elasticContactsIndex = "test_contacts" const postgresContainerName = "textit-postgres-1" -const attachmentStorageDir = "_test_attachments_storage" -const sessionStorageDir = "_test_session_storage" -const logStorageDir = "_test_log_storage" - // Refresh is our type for the pieces of org assets we want fresh (not cached) type ResetFlag int @@ -38,7 +35,6 @@ const ( ResetDB = ResetFlag(1 << 1) ResetData = ResetFlag(1 << 2) ResetRedis = ResetFlag(1 << 3) - ResetStorage = ResetFlag(1 << 4) ResetElastic = ResetFlag(1 << 5) ) @@ -54,9 +50,6 @@ func Reset(what ResetFlag) { if what&ResetRedis > 0 { resetRedis() } - if what&ResetStorage > 0 { - resetStorage() - } if what&ResetElastic > 0 { resetElastic(ctx) } @@ -67,8 +60,23 @@ func Reset(what ResetFlag) { // Runtime returns the various runtime things a test might need func Runtime() (context.Context, *runtime.Runtime) { cfg := runtime.NewDefaultConfig() - cfg.ElasticContactsIndex = elasticContactsIndex cfg.Port = 8091 + cfg.ElasticContactsIndex = elasticContactsIndex + cfg.AWSAccessKeyID = "root" + cfg.AWSSecretAccessKey = "tembatemba" + cfg.S3Endpoint = "http://localhost:9000" + cfg.S3ForcePathStyle = true + + s3config := &storage.S3Options{ + AWSAccessKeyID: cfg.AWSAccessKeyID, + AWSSecretAccessKey: cfg.AWSSecretAccessKey, + Region: cfg.AWSRegion, + Endpoint: cfg.S3Endpoint, + ForcePathStyle: cfg.S3ForcePathStyle, + MaxRetries: 3, + } + s3Client, err := storage.NewS3Client(s3config) + noError(err) dbx := getDB() rt := &runtime.Runtime{ @@ -77,9 +85,9 @@ func Runtime() (context.Context, *runtime.Runtime) { RP: getRP(), ES: getES(), FCM: &MockFCMClient{ValidTokens: []string{"FCMID3", "FCMID4", "FCMID5"}}, - AttachmentStorage: storage.NewFS(attachmentStorageDir, 0766), - SessionStorage: storage.NewFS(sessionStorageDir, 0766), - LogStorage: storage.NewFS(logStorageDir, 0766), + AttachmentStorage: storage.NewS3(s3Client, cfg.S3AttachmentsBucket, cfg.AWSRegion, s3.BucketCannedACLPublicRead, 32), + SessionStorage: storage.NewS3(s3Client, cfg.S3SessionsBucket, cfg.AWSRegion, s3.ObjectCannedACLPrivate, 32), + LogStorage: storage.NewS3(s3Client, cfg.S3LogsBucket, cfg.AWSRegion, s3.ObjectCannedACLPrivate, 32), Config: cfg, } @@ -191,13 +199,6 @@ func resetRedis() { assertredis.FlushDB() } -// clears our storage for tests -func resetStorage() { - must(os.RemoveAll(attachmentStorageDir)) - must(os.RemoveAll(sessionStorageDir)) - must(os.RemoveAll(logStorageDir)) -} - // clears indexed data in Elastic func resetElastic(ctx context.Context) { es := getES() From 11098f1712edab175d1321cf249610d5a2158fd5 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 29 Jul 2024 16:40:11 -0500 Subject: [PATCH 002/216] Add S3 to runtime, replace storages --- .github/workflows/ci.yml | 2 +- core/ivr/ivr.go | 2 +- core/models/channel_logs.go | 13 +++-- core/models/msgs_test.go | 2 +- core/models/orgs.go | 3 +- core/models/orgs_test.go | 4 +- core/models/sessions.go | 19 ++++--- core/models/sessions_test.go | 2 +- core/msgio/courier_test.go | 2 +- core/runner/runner_test.go | 2 +- core/tasks/handler/ctasks/msg_event.go | 2 +- core/tasks/handler/ctasks/wait_expiration.go | 2 +- core/tasks/handler/ctasks/wait_timeout.go | 2 +- go.mod | 8 +-- go.sum | 16 +++--- mailroom.go | 44 +++++--------- runtime/config.go | 2 +- runtime/runtime.go | 18 +++--- testsuite/testsuite.go | 60 ++++++++++---------- web/ivr/ivr_test.go | 2 +- 20 files changed, 98 insertions(+), 109 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d29729640..70b90fdc2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: env: MINIO_ROOT_USER: root MINIO_ROOT_PASSWORD: tembatemba - MINIO_DEFAULT_BUCKETS: temba-attachments,temba-sessions,temba-logs + MINIO_DEFAULT_BUCKETS: test-attachments,test-sessions,test-logs ports: - 9000:9000 options: --health-cmd "mc ready local" --health-interval 10s --health-timeout 5s --health-retries 5 diff --git a/core/ivr/ivr.go b/core/ivr/ivr.go index 2190900c9..83d21e530 100644 --- a/core/ivr/ivr.go +++ b/core/ivr/ivr.go @@ -410,7 +410,7 @@ func ResumeIVRFlow( return fmt.Errorf("error creating flow contact: %w", err) } - session, err := models.FindWaitingSessionForContact(ctx, rt.DB, rt.SessionStorage, oa, models.FlowTypeVoice, contact) + session, err := models.FindWaitingSessionForContact(ctx, rt, oa, models.FlowTypeVoice, contact) if err != nil { return fmt.Errorf("error loading session for contact: %w", err) } diff --git a/core/models/channel_logs.go b/core/models/channel_logs.go index f862fcf77..c0dc3176f 100644 --- a/core/models/channel_logs.go +++ b/core/models/channel_logs.go @@ -7,10 +7,11 @@ import ( "path" "time" + "github.com/aws/aws-sdk-go/service/s3" "github.com/nyaruka/gocommon/dates" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/jsonx" - "github.com/nyaruka/gocommon/storage" + "github.com/nyaruka/gocommon/s3x" "github.com/nyaruka/gocommon/stringsx" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" @@ -185,15 +186,17 @@ func InsertChannelLogs(ctx context.Context, rt *runtime.Runtime, logs []*Channel } if len(attached) > 0 { - uploads := make([]*storage.Upload, len(attached)) + uploads := make([]*s3x.Upload, len(attached)) for i, l := range attached { - uploads[i] = &storage.Upload{ - Path: l.path(), + uploads[i] = &s3x.Upload{ + Bucket: rt.Config.S3LogsBucket, + Key: l.path(), ContentType: "application/json", Body: jsonx.MustMarshal(l), + ACL: s3.ObjectCannedACLPrivate, } } - if err := rt.LogStorage.BatchPut(ctx, uploads); err != nil { + if err := rt.S3.BatchPut(ctx, uploads, 32); err != nil { return fmt.Errorf("error writing attached channel logs to storage: %w", err) } } diff --git a/core/models/msgs_test.go b/core/models/msgs_test.go index 567c43075..bdc0653a5 100644 --- a/core/models/msgs_test.go +++ b/core/models/msgs_test.go @@ -626,7 +626,7 @@ func insertTestSession(t *testing.T, ctx context.Context, rt *runtime.Runtime, o _, flowContact, _ := contact.Load(rt, oa) - session, err := models.FindWaitingSessionForContact(ctx, rt.DB, rt.SessionStorage, oa, models.FlowTypeMessaging, flowContact) + session, err := models.FindWaitingSessionForContact(ctx, rt, oa, models.FlowTypeMessaging, flowContact) require.NoError(t, err) return session diff --git a/core/models/orgs.go b/core/models/orgs.go index ff03292bf..6fe88154e 100644 --- a/core/models/orgs.go +++ b/core/models/orgs.go @@ -13,6 +13,7 @@ import ( "path/filepath" "time" + "github.com/aws/aws-sdk-go/service/s3" "github.com/nyaruka/gocommon/dbutil" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/jsonx" @@ -171,7 +172,7 @@ func (o *Org) StoreAttachment(ctx context.Context, rt *runtime.Runtime, filename path := o.attachmentPath("attachments", filename) - url, err := rt.AttachmentStorage.Put(ctx, path, contentType, contentBytes) + url, err := rt.S3.PutObject(ctx, rt.Config.S3AttachmentsBucket, path, contentType, contentBytes, s3.BucketCannedACLPublicRead) if err != nil { return "", fmt.Errorf("unable to store attachment content: %w", err) } diff --git a/core/models/orgs_test.go b/core/models/orgs_test.go index 794202a71..b03881850 100644 --- a/core/models/orgs_test.go +++ b/core/models/orgs_test.go @@ -107,6 +107,8 @@ func TestEmailService(t *testing.T) { func TestStoreAttachment(t *testing.T) { ctx, rt := testsuite.Runtime() + defer testsuite.Reset(testsuite.ResetStorage) + image, err := os.Open("testdata/test.jpg") require.NoError(t, err) @@ -116,7 +118,7 @@ func TestStoreAttachment(t *testing.T) { attachment, err := org.StoreAttachment(context.Background(), rt, "668383ba-387c-49bc-b164-1213ac0ea7aa.jpg", "image/jpeg", image) require.NoError(t, err) - assert.Equal(t, utils.Attachment("image/jpeg:https://temba-attachments.s3.us-east-1.amazonaws.com/attachments/1/6683/83ba/668383ba-387c-49bc-b164-1213ac0ea7aa.jpg"), attachment) + assert.Equal(t, utils.Attachment("image/jpeg:http://localhost:9000/test-attachments/attachments/1/6683/83ba/668383ba-387c-49bc-b164-1213ac0ea7aa.jpg"), attachment) // err trying to read from same reader again _, err = org.StoreAttachment(context.Background(), rt, "668383ba-387c-49bc-b164-1213ac0ea7aa.jpg", "image/jpeg", image) diff --git a/core/models/sessions.go b/core/models/sessions.go index 3ebce7b3d..501d0ec4b 100644 --- a/core/models/sessions.go +++ b/core/models/sessions.go @@ -11,10 +11,11 @@ import ( "path" "time" + "github.com/aws/aws-sdk-go/service/s3" "github.com/gomodule/redigo/redis" "github.com/jmoiron/sqlx" "github.com/lib/pq" - "github.com/nyaruka/gocommon/storage" + "github.com/nyaruka/gocommon/s3x" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/envs" @@ -751,8 +752,8 @@ LIMIT 1 ` // FindWaitingSessionForContact returns the waiting session for the passed in contact, if any -func FindWaitingSessionForContact(ctx context.Context, db *sqlx.DB, st storage.Storage, oa *OrgAssets, sessionType FlowType, contact *flows.Contact) (*Session, error) { - rows, err := db.QueryxContext(ctx, sqlSelectWaitingSessionForContact, sessionType, contact.ID()) +func FindWaitingSessionForContact(ctx context.Context, rt *runtime.Runtime, oa *OrgAssets, sessionType FlowType, contact *flows.Contact) (*Session, error) { + rows, err := rt.DB.QueryxContext(ctx, sqlSelectWaitingSessionForContact, sessionType, contact.ID()) if err != nil { return nil, fmt.Errorf("error selecting waiting session: %w", err) } @@ -783,7 +784,7 @@ func FindWaitingSessionForContact(ctx context.Context, db *sqlx.DB, st storage.S start := time.Now() - _, output, err := st.Get(ctx, u.Path) + _, output, err := rt.S3.GetObject(ctx, rt.Config.S3SessionsBucket, u.Path) if err != nil { return nil, fmt.Errorf("error reading session from storage: %s: %w", session.OutputURL(), err) } @@ -800,16 +801,18 @@ func FindWaitingSessionForContact(ctx context.Context, db *sqlx.DB, st storage.S func WriteSessionOutputsToStorage(ctx context.Context, rt *runtime.Runtime, sessions []*Session) error { start := time.Now() - uploads := make([]*storage.Upload, len(sessions)) + uploads := make([]*s3x.Upload, len(sessions)) for i, s := range sessions { - uploads[i] = &storage.Upload{ - Path: s.StoragePath(), + uploads[i] = &s3x.Upload{ + Bucket: rt.Config.S3SessionsBucket, + Key: s.StoragePath(), Body: []byte(s.Output()), ContentType: "application/json", + ACL: s3.ObjectCannedACLPrivate, } } - err := rt.SessionStorage.BatchPut(ctx, uploads) + err := rt.S3.BatchPut(ctx, uploads, 32) if err != nil { return fmt.Errorf("error writing sessions to storage: %w", err) } diff --git a/core/models/sessions_test.go b/core/models/sessions_test.go index e20562e86..71dcd2f80 100644 --- a/core/models/sessions_test.go +++ b/core/models/sessions_test.go @@ -474,7 +474,7 @@ func TestClearWaitTimeout(t *testing.T) { timeoutOn := time.Now().Add(time.Minute) testdata.InsertWaitingSession(rt, testdata.Org1, testdata.Cathy, models.FlowTypeMessaging, testdata.Favorites, models.NilCallID, time.Now(), expiresOn, true, &timeoutOn) - session, err := models.FindWaitingSessionForContact(ctx, rt.DB, nil, oa, models.FlowTypeMessaging, cathy) + session, err := models.FindWaitingSessionForContact(ctx, rt, oa, models.FlowTypeMessaging, cathy) require.NoError(t, err) // can be called without db connection to clear without updating db diff --git a/core/msgio/courier_test.go b/core/msgio/courier_test.go index eb576c1fd..6fe794458 100644 --- a/core/msgio/courier_test.go +++ b/core/msgio/courier_test.go @@ -66,7 +66,7 @@ func TestNewCourierMsg(t *testing.T) { // create a non-priority flow message.. i.e. the session isn't responding to an incoming message testdata.InsertWaitingSession(rt, testdata.Org1, testdata.Cathy, models.FlowTypeMessaging, testdata.Favorites, models.NilCallID, time.Now(), time.Now(), false, nil) - session, err := models.FindWaitingSessionForContact(ctx, rt.DB, rt.SessionStorage, oa, models.FlowTypeMessaging, cathy) + session, err := models.FindWaitingSessionForContact(ctx, rt, oa, models.FlowTypeMessaging, cathy) require.NoError(t, err) msg1, err := models.NewOutgoingFlowMsg(rt, oa.Org(), facebook, session, flow, flowMsg1, time.Date(2021, 11, 9, 14, 3, 30, 0, time.UTC)) diff --git a/core/runner/runner_test.go b/core/runner/runner_test.go index 66be1602a..354779643 100644 --- a/core/runner/runner_test.go +++ b/core/runner/runner_test.go @@ -83,7 +83,7 @@ func TestStartFlowBatch(t *testing.T) { func TestResume(t *testing.T) { ctx, rt := testsuite.Runtime() - defer testsuite.Reset(testsuite.ResetData) + defer testsuite.Reset(testsuite.ResetData | testsuite.ResetStorage) // write sessions to s3 storage rt.Config.SessionStorage = "s3" diff --git a/core/tasks/handler/ctasks/msg_event.go b/core/tasks/handler/ctasks/msg_event.go index d2cb8a3f6..0ab4604db 100644 --- a/core/tasks/handler/ctasks/msg_event.go +++ b/core/tasks/handler/ctasks/msg_event.go @@ -123,7 +123,7 @@ func (t *MsgEventTask) Perform(ctx context.Context, rt *runtime.Runtime, oa *mod trigger, keyword := models.FindMatchingMsgTrigger(oa, channel, flowContact, t.Text) // look for a waiting session for this contact - session, err := models.FindWaitingSessionForContact(ctx, rt.DB, rt.SessionStorage, oa, models.FlowTypeMessaging, flowContact) + session, err := models.FindWaitingSessionForContact(ctx, rt, oa, models.FlowTypeMessaging, flowContact) if err != nil { return fmt.Errorf("error loading active session for contact: %w", err) } diff --git a/core/tasks/handler/ctasks/wait_expiration.go b/core/tasks/handler/ctasks/wait_expiration.go index 3a85a33a3..84d3280a9 100644 --- a/core/tasks/handler/ctasks/wait_expiration.go +++ b/core/tasks/handler/ctasks/wait_expiration.go @@ -46,7 +46,7 @@ func (t *WaitExpirationTask) Perform(ctx context.Context, rt *runtime.Runtime, o } // look for a waiting session for this contact - session, err := models.FindWaitingSessionForContact(ctx, rt.DB, rt.SessionStorage, oa, models.FlowTypeMessaging, flowContact) + session, err := models.FindWaitingSessionForContact(ctx, rt, oa, models.FlowTypeMessaging, flowContact) if err != nil { return fmt.Errorf("error loading waiting session for contact: %w", err) } diff --git a/core/tasks/handler/ctasks/wait_timeout.go b/core/tasks/handler/ctasks/wait_timeout.go index 07fe368f2..a0802498b 100644 --- a/core/tasks/handler/ctasks/wait_timeout.go +++ b/core/tasks/handler/ctasks/wait_timeout.go @@ -48,7 +48,7 @@ func (t *WaitTimeoutTask) Perform(ctx context.Context, rt *runtime.Runtime, oa * } // look for a waiting session for this contact - session, err := models.FindWaitingSessionForContact(ctx, rt.DB, rt.SessionStorage, oa, models.FlowTypeMessaging, flowContact) + session, err := models.FindWaitingSessionForContact(ctx, rt, oa, models.FlowTypeMessaging, flowContact) if err != nil { return fmt.Errorf("error loading waiting session for contact: %w", err) } diff --git a/go.mod b/go.mod index 205a4ddee..2c071a590 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( firebase.google.com/go/v4 v4.14.1 github.com/Masterminds/semver v1.5.0 github.com/appleboy/go-fcm v1.2.1 - github.com/aws/aws-sdk-go v1.54.19 + github.com/aws/aws-sdk-go v1.55.3 github.com/buger/jsonparser v1.1.1 github.com/elastic/go-elasticsearch/v8 v8.14.0 github.com/getsentry/sentry-go v0.28.1 @@ -19,7 +19,7 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 - github.com/nyaruka/gocommon v1.55.8 + github.com/nyaruka/gocommon v1.56.4 github.com/nyaruka/goflow v0.219.2 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 @@ -31,7 +31,7 @@ require ( github.com/samber/slog-sentry v1.2.2 github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.9.0 - golang.org/x/exp v0.0.0-20240716175740-e3f259677ff7 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 google.golang.org/api v0.188.0 ) @@ -52,7 +52,7 @@ require ( github.com/elastic/elastic-transport-go/v8 v8.6.0 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/gabriel-vasile/mimetype v1.4.4 // indirect + github.com/gabriel-vasile/mimetype v1.4.5 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect diff --git a/go.sum b/go.sum index a00abee98..c220fa31b 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,8 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/appleboy/go-fcm v1.2.1 h1:NhpACabtRuAplYg6bTNfSr3LBwsSuutP55HsphzLU/g= github.com/appleboy/go-fcm v1.2.1/go.mod h1:5FzMN+9J2sxnkoys9h3y48GQH8HnI637Q/ro/uP2Qsk= -github.com/aws/aws-sdk-go v1.54.19 h1:tyWV+07jagrNiCcGRzRhdtVjQs7Vy41NwsuOcl0IbVI= -github.com/aws/aws-sdk-go v1.54.19/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go v1.55.3 h1:0B5hOX+mIx7I5XPOrjrHlKSDQV/+ypFZpIHOx5LOk3E= +github.com/aws/aws-sdk-go v1.55.3/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -54,8 +54,8 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= -github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= +github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= +github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k= github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= @@ -154,8 +154,8 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= -github.com/nyaruka/gocommon v1.55.8 h1:vMMnwCt/P5D7kWic13g3TkALp6uTpR/a9JIW22DITh0= -github.com/nyaruka/gocommon v1.55.8/go.mod h1:ZiVNGrpzkv8str/Tjblddl2tmR0NCifv1mAvjTOKXQI= +github.com/nyaruka/gocommon v1.56.4 h1:wE6dnqpFBRRwNO5CLFVb4mnU/uf5g3AX9Ivh7WwxfZQ= +github.com/nyaruka/gocommon v1.56.4/go.mod h1:XDCG6LNrYCH7XfRBDmwue1WMqoJXyltmhTvmBQy2XIk= github.com/nyaruka/goflow v0.219.2 h1:4Armcryiigg6TOleOf6hmT/yek1/PRKliXnt2gnLyzU= github.com/nyaruka/goflow v0.219.2/go.mod h1:svhvRr+3TPczWfRyUTFY3TC5KGswMdf/OTUA/X7u/9Y= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= @@ -223,8 +223,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240716175740-e3f259677ff7 h1:wDLEX9a7YQoKdKNQt88rtydkqDxeGaBUTnIYc3iG/mA= -golang.org/x/exp v0.0.0-20240716175740-e3f259677ff7/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= diff --git a/mailroom.go b/mailroom.go index bfc90acf6..9d3601e9b 100644 --- a/mailroom.go +++ b/mailroom.go @@ -9,11 +9,10 @@ import ( "time" "github.com/appleboy/go-fcm" - "github.com/aws/aws-sdk-go/service/s3" "github.com/elastic/go-elasticsearch/v8" "github.com/jmoiron/sqlx" "github.com/nyaruka/gocommon/analytics" - "github.com/nyaruka/gocommon/storage" + "github.com/nyaruka/gocommon/s3x" "github.com/nyaruka/mailroom/core/tasks" "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/mailroom/web" @@ -92,37 +91,27 @@ func (mr *Mailroom) Start() error { log.Warn("fcm not configured, no android syncing") } - s3config := &storage.S3Options{ - AWSAccessKeyID: c.AWSAccessKeyID, - AWSSecretAccessKey: c.AWSSecretAccessKey, - Region: c.AWSRegion, - Endpoint: c.S3Endpoint, - ForcePathStyle: c.S3ForcePathStyle, - MaxRetries: 3, - } - s3Client, err := storage.NewS3Client(s3config) + // setup S3 storage + mr.rt.S3, err = s3x.NewService(c.AWSAccessKeyID, c.AWSSecretAccessKey, c.AWSRegion, c.S3Endpoint, c.S3Minio) if err != nil { return err } - mr.rt.AttachmentStorage = storage.NewS3(s3Client, mr.rt.Config.S3AttachmentsBucket, c.AWSRegion, s3.BucketCannedACLPublicRead, 32) - mr.rt.SessionStorage = storage.NewS3(s3Client, mr.rt.Config.S3SessionsBucket, c.AWSRegion, s3.ObjectCannedACLPrivate, 32) - mr.rt.LogStorage = storage.NewS3(s3Client, mr.rt.Config.S3LogsBucket, c.AWSRegion, s3.ObjectCannedACLPrivate, 32) - // check our storages - if err := checkStorage(mr.rt.AttachmentStorage); err != nil { - log.Error(mr.rt.AttachmentStorage.Name()+" attachment storage not available", "error", err) + // check buckets + if err := mr.rt.S3.Test(mr.ctx, c.S3AttachmentsBucket); err != nil { + log.Error("attachments bucket not accessible", "error", err) } else { - log.Info(mr.rt.AttachmentStorage.Name() + " attachment storage ok") + log.Info("attachments bucket ok") } - if err := checkStorage(mr.rt.SessionStorage); err != nil { - log.Error(mr.rt.SessionStorage.Name()+" session storage not available", "error", err) + if err := mr.rt.S3.Test(mr.ctx, c.S3SessionsBucket); err != nil { + log.Error("sessions bucket not accessible", "error", err) } else { - log.Info(mr.rt.SessionStorage.Name() + " session storage ok") + log.Info("sessions bucket ok") } - if err := checkStorage(mr.rt.LogStorage); err != nil { - log.Error(mr.rt.LogStorage.Name()+" log storage not available", "error", err) + if err := mr.rt.S3.Test(mr.ctx, c.S3LogsBucket); err != nil { + log.Error("logs bucket not accessible", "error", err) } else { - log.Info(mr.rt.LogStorage.Name() + " log storage ok") + log.Info("logs bucket ok") } // initialize our elastic client @@ -193,10 +182,3 @@ func openAndCheckDBConnection(url string, maxOpenConns int) (*sql.DB, *sqlx.DB, return db.DB, db, err } - -func checkStorage(s storage.Storage) error { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - err := s.Test(ctx) - cancel() - return err -} diff --git a/runtime/config.go b/runtime/config.go index 02d2a24ed..554a27da9 100644 --- a/runtime/config.go +++ b/runtime/config.go @@ -65,7 +65,7 @@ type Config struct { S3AttachmentsBucket string `help:"S3 bucket to write attachments to"` S3SessionsBucket string `help:"S3 bucket to write flow sessions to"` S3LogsBucket string `help:"S3 bucket to write channel logs to"` - S3ForcePathStyle bool `help:"S3 should used /bucket/path style URLs"` + S3Minio bool `help:"S3 is actually Minio or other compatible service"` CourierAuthToken string `help:"the authentication token used for requests to Courier"` LibratoUsername string `help:"the username that will be used to authenticate to Librato"` diff --git a/runtime/runtime.go b/runtime/runtime.go index ad05b59c9..f145ffdd5 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -8,21 +8,19 @@ import ( "github.com/elastic/go-elasticsearch/v8" "github.com/gomodule/redigo/redis" "github.com/jmoiron/sqlx" - "github.com/nyaruka/gocommon/storage" + "github.com/nyaruka/gocommon/s3x" ) // Runtime represents the set of services required to run many Mailroom functions. Used as a wrapper for // those services to simplify call signatures but not create a direct dependency to Mailroom or Server type Runtime struct { - DB *sqlx.DB - ReadonlyDB *sql.DB - RP *redis.Pool - ES *elasticsearch.TypedClient - FCM FCMClient - AttachmentStorage storage.Storage - SessionStorage storage.Storage - LogStorage storage.Storage - Config *Config + DB *sqlx.DB + ReadonlyDB *sql.DB + RP *redis.Pool + S3 *s3x.Service + ES *elasticsearch.TypedClient + FCM FCMClient + Config *Config } // FCMClient is an interface to allow mocking in tests diff --git a/testsuite/testsuite.go b/testsuite/testsuite.go index 3a7c79279..e79df0623 100644 --- a/testsuite/testsuite.go +++ b/testsuite/testsuite.go @@ -8,11 +8,10 @@ import ( "os/exec" "path" - "github.com/aws/aws-sdk-go/service/s3" "github.com/elastic/go-elasticsearch/v8" "github.com/gomodule/redigo/redis" "github.com/jmoiron/sqlx" - "github.com/nyaruka/gocommon/storage" + "github.com/nyaruka/gocommon/s3x" "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/redisx/assertredis" @@ -35,12 +34,13 @@ const ( ResetDB = ResetFlag(1 << 1) ResetData = ResetFlag(1 << 2) ResetRedis = ResetFlag(1 << 3) + ResetStorage = ResetFlag(1 << 4) ResetElastic = ResetFlag(1 << 5) ) // Reset clears out both our database and redis DB func Reset(what ResetFlag) { - ctx := context.TODO() + ctx, rt := Runtime() // TODO pass rt from test? if what&ResetDB > 0 { resetDB() @@ -50,8 +50,11 @@ func Reset(what ResetFlag) { if what&ResetRedis > 0 { resetRedis() } + if what&ResetStorage > 0 { + resetStorage(ctx, rt) + } if what&ResetElastic > 0 { - resetElastic(ctx) + resetElastic(ctx, rt) } models.FlushCache() @@ -65,30 +68,23 @@ func Runtime() (context.Context, *runtime.Runtime) { cfg.AWSAccessKeyID = "root" cfg.AWSSecretAccessKey = "tembatemba" cfg.S3Endpoint = "http://localhost:9000" - cfg.S3ForcePathStyle = true - - s3config := &storage.S3Options{ - AWSAccessKeyID: cfg.AWSAccessKeyID, - AWSSecretAccessKey: cfg.AWSSecretAccessKey, - Region: cfg.AWSRegion, - Endpoint: cfg.S3Endpoint, - ForcePathStyle: cfg.S3ForcePathStyle, - MaxRetries: 3, - } - s3Client, err := storage.NewS3Client(s3config) + cfg.S3AttachmentsBucket = "test-attachments" + cfg.S3SessionsBucket = "test-sessions" + cfg.S3LogsBucket = "test-logs" + cfg.S3Minio = true + + s3svc, err := s3x.NewService(cfg.AWSAccessKeyID, cfg.AWSSecretAccessKey, cfg.AWSRegion, cfg.S3Endpoint, cfg.S3Minio) noError(err) dbx := getDB() rt := &runtime.Runtime{ - DB: dbx, - ReadonlyDB: dbx.DB, - RP: getRP(), - ES: getES(), - FCM: &MockFCMClient{ValidTokens: []string{"FCMID3", "FCMID4", "FCMID5"}}, - AttachmentStorage: storage.NewS3(s3Client, cfg.S3AttachmentsBucket, cfg.AWSRegion, s3.BucketCannedACLPublicRead, 32), - SessionStorage: storage.NewS3(s3Client, cfg.S3SessionsBucket, cfg.AWSRegion, s3.ObjectCannedACLPrivate, 32), - LogStorage: storage.NewS3(s3Client, cfg.S3LogsBucket, cfg.AWSRegion, s3.ObjectCannedACLPrivate, 32), - Config: cfg, + DB: dbx, + ReadonlyDB: dbx.DB, + RP: getRP(), + ES: getES(), + S3: s3svc, + FCM: &MockFCMClient{ValidTokens: []string{"FCMID3", "FCMID4", "FCMID5"}}, + Config: cfg, } slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))) @@ -199,21 +195,25 @@ func resetRedis() { assertredis.FlushDB() } -// clears indexed data in Elastic -func resetElastic(ctx context.Context) { - es := getES() +func resetStorage(ctx context.Context, rt *runtime.Runtime) { + rt.S3.EmptyBucket(ctx, rt.Config.S3AttachmentsBucket) + rt.S3.EmptyBucket(ctx, rt.Config.S3SessionsBucket) + rt.S3.EmptyBucket(ctx, rt.Config.S3LogsBucket) +} - exists, err := es.Indices.ExistsAlias(elasticContactsIndex).Do(ctx) +// clears indexed data in Elastic +func resetElastic(ctx context.Context, rt *runtime.Runtime) { + exists, err := rt.ES.Indices.ExistsAlias(elasticContactsIndex).Do(ctx) noError(err) if exists { // get any indexes for the contacts alias - ar, err := es.Indices.GetAlias().Name(elasticContactsIndex).Do(ctx) + ar, err := rt.ES.Indices.GetAlias().Name(elasticContactsIndex).Do(ctx) noError(err) // and delete them for _, index := range maps.Keys(ar) { - _, err := es.Indices.Delete(index).Do(ctx) + _, err := rt.ES.Indices.Delete(index).Do(ctx) noError(err) } } diff --git a/web/ivr/ivr_test.go b/web/ivr/ivr_test.go index 65bc245c4..e2917dcfa 100644 --- a/web/ivr/ivr_test.go +++ b/web/ivr/ivr_test.go @@ -647,7 +647,7 @@ func getCallLogs(t *testing.T, rt *runtime.Runtime, channelUUID assets.ChannelUU logs := make([][]byte, len(logUUIDs)) for i, logUUID := range logUUIDs { - _, body, err := rt.LogStorage.Get(context.Background(), fmt.Sprintf("channels/%s/%s/%s.json", channelUUID, logUUID[0:4], logUUID)) + _, body, err := rt.S3.GetObject(context.Background(), rt.Config.S3LogsBucket, fmt.Sprintf("channels/%s/%s/%s.json", channelUUID, logUUID[0:4], logUUID)) require.NoError(t, err) logs[i] = body } From 8432100f250cc5a44cc2fc7ecb896ccaa6767eb4 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 29 Jul 2024 17:31:26 -0500 Subject: [PATCH 003/216] Update CHANGELOG.md for v9.3.0 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28e04c3fa..64f070e24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.0 (2024-07-29) +------------------------- + * Add S3 to runtime, replace storages + * Use minio for local dev and tests + v9.2.2 (2024-07-23) ------------------------- * Simplify config param name to configure FCM relayer syncing From a1b81cd2ed0046a418273eeffa6d3cbfd80020d3 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 2 Aug 2024 10:35:15 -0500 Subject: [PATCH 004/216] Update to latest goflow/gocommon --- cmd/mailroom/main.go | 2 +- core/goflow/flows_test.go | 5 +++-- core/handlers/base_test.go | 18 +++++++++--------- core/handlers/campaigns_test.go | 2 +- core/handlers/session_triggered_test.go | 3 ++- core/handlers/webhook_called_test.go | 4 ++-- core/ivr/ivr.go | 2 +- core/models/broadcasts_test.go | 2 +- core/models/channel_logs.go | 4 ++-- core/models/contacts.go | 2 +- core/models/imports_test.go | 3 ++- core/models/msgs.go | 4 ++-- core/models/msgs_test.go | 4 ++-- core/models/schedules_test.go | 4 ++-- core/models/sessions.go | 2 +- core/models/starts.go | 2 +- core/models/starts_test.go | 3 ++- core/models/tickets_test.go | 4 ++-- core/models/triggers_test.go | 2 +- core/runner/runner_test.go | 4 ++-- core/search/queries_test.go | 4 ++-- core/search/search_test.go | 8 ++++---- core/tasks/campaigns/cron_test.go | 2 +- core/tasks/handler/cron_test.go | 2 +- .../handler/ctasks/wait_expiration_test.go | 2 +- go.mod | 9 ++++----- go.sum | 18 ++++++++---------- services/ivr/twiml/service_test.go | 2 +- services/ivr/vonage/service.go | 4 ++-- services/ivr/vonage/service_test.go | 2 +- testsuite/testdata/campaigns.go | 4 ++-- testsuite/testdata/channels.go | 2 +- testsuite/testdata/flows.go | 8 ++++---- testsuite/testdata/msgs.go | 6 +++--- testsuite/testdata/tickets.go | 2 +- testsuite/web.go | 6 +++--- utils/queues/fair_sorted_test.go | 4 ++-- 37 files changed, 81 insertions(+), 80 deletions(-) diff --git a/cmd/mailroom/main.go b/cmd/mailroom/main.go index a501d37d3..3dfd726a1 100644 --- a/cmd/mailroom/main.go +++ b/cmd/mailroom/main.go @@ -81,7 +81,7 @@ func main() { log.Info("starting mailroom", "version", version, "released", date) if config.UUIDSeed != 0 { - uuids.SetGenerator(uuids.NewSeededGenerator(int64(config.UUIDSeed))) + uuids.SetGenerator(uuids.NewSeededGenerator(int64(config.UUIDSeed), time.Now)) log.Warn("using seeded UUID generation", "uuid-seed", config.UUIDSeed) } diff --git a/core/goflow/flows_test.go b/core/goflow/flows_test.go index a0e64a9f2..344954de4 100644 --- a/core/goflow/flows_test.go +++ b/core/goflow/flows_test.go @@ -2,6 +2,7 @@ package goflow_test import ( "testing" + "time" "github.com/Masterminds/semver" "github.com/nyaruka/gocommon/i18n" @@ -43,7 +44,7 @@ func TestReadFlow(t *testing.T) { } func TestCloneDefinition(t *testing.T) { - uuids.SetGenerator(uuids.NewSeededGenerator(12345)) + uuids.SetGenerator(uuids.NewSeededGenerator(12345, time.Now)) defer uuids.SetGenerator(uuids.DefaultGenerator) cloned, err := goflow.CloneDefinition([]byte(`{"uuid": "502c3ee4-3249-4dee-8e71-c62070667d52", "name": "New", "spec_version": "13.0.0", "type": "messaging", "language": "eng", "nodes": []}`), nil) @@ -54,7 +55,7 @@ func TestCloneDefinition(t *testing.T) { func TestMigrateDefinition(t *testing.T) { _, rt := testsuite.Runtime() - uuids.SetGenerator(uuids.NewSeededGenerator(12345)) + uuids.SetGenerator(uuids.NewSeededGenerator(12345, time.Now)) defer uuids.SetGenerator(uuids.DefaultGenerator) v13_0_0 := testsuite.ReadFile("testdata/migrate/13.0.0.json") diff --git a/core/handlers/base_test.go b/core/handlers/base_test.go index e3e51615a..67eea177c 100644 --- a/core/handlers/base_test.go +++ b/core/handlers/base_test.go @@ -54,7 +54,7 @@ type SQLAssertion struct { } func NewActionUUID() flows.ActionUUID { - return flows.ActionUUID(uuids.New()) + return flows.ActionUUID(uuids.NewV4()) } // createTestFlow creates a flow that starts with a split by contact id @@ -67,12 +67,12 @@ func createTestFlow(t *testing.T, uuid assets.FlowUUID, tc TestCase) flows.Flow exitUUIDs := make([]flows.ExitUUID, len(tc.Actions)) i := 0 for range tc.Actions { - categoryUUIDs[i] = flows.CategoryUUID(uuids.New()) - exitUUIDs[i] = flows.ExitUUID(uuids.New()) + categoryUUIDs[i] = flows.CategoryUUID(uuids.NewV4()) + exitUUIDs[i] = flows.ExitUUID(uuids.NewV4()) i++ } - defaultCategoryUUID := flows.CategoryUUID(uuids.New()) - defaultExitUUID := flows.ExitUUID(uuids.New()) + defaultCategoryUUID := flows.CategoryUUID(uuids.NewV4()) + defaultExitUUID := flows.ExitUUID(uuids.NewV4()) cases := make([]*routers.Case, len(tc.Actions)) categories := make([]flows.Category, len(tc.Actions)) @@ -81,17 +81,17 @@ func createTestFlow(t *testing.T, uuid assets.FlowUUID, tc TestCase) flows.Flow i = 0 for contact, actions := range tc.Actions { cases[i] = routers.NewCase( - uuids.New(), + uuids.NewV4(), "has_any_word", []string{fmt.Sprintf("%d", contact.ID)}, categoryUUIDs[i], ) exitNodes[i] = definition.NewNode( - flows.NodeUUID(uuids.New()), + flows.NodeUUID(uuids.NewV4()), actions, nil, - []flows.Exit{definition.NewExit(flows.ExitUUID(uuids.New()), "")}, + []flows.Exit{definition.NewExit(flows.ExitUUID(uuids.NewV4()), "")}, ) categories[i] = routers.NewCategory( @@ -122,7 +122,7 @@ func createTestFlow(t *testing.T, uuid assets.FlowUUID, tc TestCase) flows.Flow // and our entry node entry := definition.NewNode( - flows.NodeUUID(uuids.New()), + flows.NodeUUID(uuids.NewV4()), nil, router, exits, diff --git a/core/handlers/campaigns_test.go b/core/handlers/campaigns_test.go index efc1c6a7f..72bc51962 100644 --- a/core/handlers/campaigns_test.go +++ b/core/handlers/campaigns_test.go @@ -39,7 +39,7 @@ func TestCampaigns(t *testing.T) { tcs := []handlers.TestCase{ { Msgs: handlers.ContactMsgMap{ - testdata.Cathy: flows.NewMsgIn(flows.MsgUUID(uuids.New()), testdata.Cathy.URN, nil, "Hi there", nil), + testdata.Cathy: flows.NewMsgIn(flows.MsgUUID(uuids.NewV4()), testdata.Cathy.URN, nil, "Hi there", nil), }, Actions: handlers.ContactActionMap{ testdata.Cathy: []flows.Action{ diff --git a/core/handlers/session_triggered_test.go b/core/handlers/session_triggered_test.go index 28bd139dc..19bae9185 100644 --- a/core/handlers/session_triggered_test.go +++ b/core/handlers/session_triggered_test.go @@ -3,6 +3,7 @@ package handlers_test import ( "encoding/json" "testing" + "time" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" @@ -36,7 +37,7 @@ func TestSessionTriggered(t *testing.T) { UUID: testdata.TestersGroup.UUID, } - uuids.SetGenerator(uuids.NewSeededGenerator(1234567)) + uuids.SetGenerator(uuids.NewSeededGenerator(1234567, time.Now)) defer uuids.SetGenerator(uuids.DefaultGenerator) tcs := []handlers.TestCase{ diff --git a/core/handlers/webhook_called_test.go b/core/handlers/webhook_called_test.go index 2a886c815..7d19bfe18 100644 --- a/core/handlers/webhook_called_test.go +++ b/core/handlers/webhook_called_test.go @@ -122,9 +122,9 @@ func TestUnhealthyWebhookCalls(t *testing.T) { defer rc.Close() defer testsuite.Reset(testsuite.ResetData | testsuite.ResetRedis) - defer dates.SetNowSource(dates.DefaultNowSource) + defer dates.SetNowFunc(time.Now) - dates.SetNowSource(dates.NewSequentialNowSource(time.Date(2021, 11, 17, 7, 0, 0, 0, time.UTC))) + dates.SetNowFunc(dates.NewSequentialNow(time.Date(2021, 11, 17, 7, 0, 0, 0, time.UTC), time.Second)) flowDef, err := os.ReadFile("testdata/webhook_flow.json") require.NoError(t, err) diff --git a/core/ivr/ivr.go b/core/ivr/ivr.go index 83d21e530..53f9e9e9b 100644 --- a/core/ivr/ivr.go +++ b/core/ivr/ivr.go @@ -531,7 +531,7 @@ func buildMsgResume( svc Service, channel *models.Channel, contact *flows.Contact, urn urns.URN, call *models.Call, oa *models.OrgAssets, r *http.Request, resume InputResume) (flows.Resume, error, error) { // our msg UUID - msgUUID := flows.MsgUUID(uuids.New()) + msgUUID := flows.MsgUUID(uuids.NewV4()) // we have an attachment, download it locally if resume.Attachment != NilAttachment { diff --git a/core/models/broadcasts_test.go b/core/models/broadcasts_test.go index c26c90f31..415b5a28b 100644 --- a/core/models/broadcasts_test.go +++ b/core/models/broadcasts_test.go @@ -238,7 +238,7 @@ func TestBroadcastBatchCreateMessage(t *testing.T) { } for i, tc := range tcs { - contact := testdata.InsertContact(rt, testdata.Org1, flows.ContactUUID(uuids.New()), "Felix", tc.contactLanguage, models.ContactStatusActive) + contact := testdata.InsertContact(rt, testdata.Org1, flows.ContactUUID(uuids.NewV4()), "Felix", tc.contactLanguage, models.ContactStatusActive) testdata.InsertContactURN(rt, testdata.Org1, contact, tc.contactURN, 1000, nil) batch := &models.BroadcastBatch{ diff --git a/core/models/channel_logs.go b/core/models/channel_logs.go index c0dc3176f..5731b201f 100644 --- a/core/models/channel_logs.go +++ b/core/models/channel_logs.go @@ -70,7 +70,7 @@ func NewChannelLogForIncoming(t ChannelLogType, ch *Channel, r *httpx.Recorder, func newChannelLog(t ChannelLogType, ch *Channel, r *httpx.Recorder, redactVals []string) *ChannelLog { return &ChannelLog{ - uuid: ChannelLogUUID(uuids.New()), + uuid: ChannelLogUUID(uuids.NewV4()), type_: t, channel: ch, httpLogs: []*httpx.Log{}, @@ -173,7 +173,7 @@ func InsertChannelLogs(ctx context.Context, rt *runtime.Runtime, logs []*Channel } else { // otherwise write to database so it's retrievable unattached = append(unattached, &dbChannelLog{ - UUID: ChannelLogUUID(uuids.New()), + UUID: ChannelLogUUID(uuids.NewV4()), ChannelID: l.channel.ID(), Type: l.type_, HTTPLogs: jsonx.MustMarshal(l.httpLogs), diff --git a/core/models/contacts.go b/core/models/contacts.go index 77cd53599..bd535b96b 100644 --- a/core/models/contacts.go +++ b/core/models/contacts.go @@ -877,7 +877,7 @@ func insertContactAndURNs(ctx context.Context, db DBorTx, orgID OrgID, userID Us `INSERT INTO contacts_contact (org_id, is_active, uuid, name, language, status, ticket_count, created_on, modified_on, created_by_id, modified_by_id) VALUES($1, TRUE, $2, $3, $4, $5, 0, $6, $6, $7, $7) RETURNING id`, - orgID, uuids.New(), null.String(name), null.String(string(language)), status, dates.Now(), userID, + orgID, uuids.NewV4(), null.String(name), null.String(string(language)), status, dates.Now(), userID, ) if err != nil { return NilContactID, fmt.Errorf("error inserting new contact: %w", err) diff --git a/core/models/imports_test.go b/core/models/imports_test.go index ea24fa678..738ee7e06 100644 --- a/core/models/imports_test.go +++ b/core/models/imports_test.go @@ -7,6 +7,7 @@ import ( "sort" "strings" "testing" + "time" "github.com/nyaruka/gocommon/dbutil" "github.com/nyaruka/gocommon/dbutil/assertdb" @@ -63,7 +64,7 @@ func TestContactImports(t *testing.T) { oa, err := models.GetOrgAssetsWithRefresh(ctx, rt, testdata.Org1.ID, models.RefreshOrg|models.RefreshChannels|models.RefreshGroups) require.NoError(t, err) - uuids.SetGenerator(uuids.NewSeededGenerator(12345)) + uuids.SetGenerator(uuids.NewSeededGenerator(12345, time.Now)) defer uuids.SetGenerator(uuids.DefaultGenerator) for i, tc := range tcs { diff --git a/core/models/msgs.go b/core/models/msgs.go index 3ec6e5ea4..4f8b8e8d3 100644 --- a/core/models/msgs.go +++ b/core/models/msgs.go @@ -240,7 +240,7 @@ func (m *Msg) Attachments() []utils.Attachment { func NewIncomingAndroid(orgID OrgID, channelID ChannelID, contactID ContactID, urnID URNID, text string, receivedOn time.Time) *Msg { msg := &Msg{} m := &msg.m - m.UUID = flows.MsgUUID(uuids.New()) + m.UUID = flows.MsgUUID(uuids.NewV4()) m.OrgID = orgID m.ChannelID = channelID m.ContactID = contactID @@ -321,7 +321,7 @@ func NewOutgoingIVR(cfg *runtime.Config, orgID OrgID, call *Call, out *flows.Msg func NewOutgoingOptInMsg(rt *runtime.Runtime, session *Session, flow *Flow, optIn *OptIn, channel *Channel, urn urns.URN, createdOn time.Time) *Msg { msg := &Msg{} m := &msg.m - m.UUID = flows.MsgUUID(uuids.New()) + m.UUID = flows.MsgUUID(uuids.NewV4()) m.OrgID = session.OrgID() m.ContactID = session.ContactID() m.HighPriority = session.IncomingMsgID() != NilMsgID diff --git a/core/models/msgs_test.go b/core/models/msgs_test.go index bdc0653a5..7055ae0f3 100644 --- a/core/models/msgs_test.go +++ b/core/models/msgs_test.go @@ -386,9 +386,9 @@ func TestGetMsgRepetitions(t *testing.T) { defer rc.Close() defer testsuite.Reset(testsuite.ResetRedis) - defer dates.SetNowSource(dates.DefaultNowSource) + defer dates.SetNowFunc(time.Now) - dates.SetNowSource(dates.NewFixedNowSource(time.Date(2021, 11, 18, 12, 13, 3, 234567, time.UTC))) + dates.SetNowFunc(dates.NewFixedNow(time.Date(2021, 11, 18, 12, 13, 3, 234567, time.UTC))) oa := testdata.Org1.Load(rt) _, cathy, _ := testdata.Cathy.Load(rt, oa) diff --git a/core/models/schedules_test.go b/core/models/schedules_test.go index 3206f2ca5..fce5f3f77 100644 --- a/core/models/schedules_test.go +++ b/core/models/schedules_test.go @@ -20,8 +20,8 @@ func TestNewSchedule(t *testing.T) { oa := testdata.Org1.Load(rt) - dates.SetNowSource(dates.NewFixedNowSource(time.Date(2024, 6, 20, 14, 30, 0, 0, time.UTC))) - defer dates.SetNowSource(dates.DefaultNowSource) + dates.SetNowFunc(dates.NewFixedNow(time.Date(2024, 6, 20, 14, 30, 0, 0, time.UTC))) + defer dates.SetNowFunc(time.Now) _, err := models.NewSchedule(oa, time.Date(2024, 6, 20, 14, 46, 30, 0, time.UTC), "Z", "") assert.EqualError(t, err, "invalid repeat period: Z") diff --git a/core/models/sessions.go b/core/models/sessions.go index 501d0ec4b..7d0a6b1e4 100644 --- a/core/models/sessions.go +++ b/core/models/sessions.go @@ -513,7 +513,7 @@ func NewSession(ctx context.Context, tx *sqlx.Tx, oa *OrgAssets, fs flows.Sessio uuid := fs.UUID() if uuid == "" { - uuid = flows.SessionUUID(uuids.New()) + uuid = flows.SessionUUID(uuids.NewV4()) } // create our session object diff --git a/core/models/starts.go b/core/models/starts.go index 6f6a16f2d..b47c45a1e 100644 --- a/core/models/starts.go +++ b/core/models/starts.go @@ -98,7 +98,7 @@ type FlowStart struct { // NewFlowStart creates a new flow start objects for the passed in parameters func NewFlowStart(orgID OrgID, startType StartType, flowID FlowID) *FlowStart { - return &FlowStart{UUID: uuids.New(), OrgID: orgID, StartType: startType, FlowID: flowID} + return &FlowStart{UUID: uuids.NewV4(), OrgID: orgID, StartType: startType, FlowID: flowID} } func (s *FlowStart) WithGroupIDs(groupIDs []GroupID) *FlowStart { diff --git a/core/models/starts_test.go b/core/models/starts_test.go index 3202cd8f7..11e8c4a90 100644 --- a/core/models/starts_test.go +++ b/core/models/starts_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "testing" + "time" "github.com/nyaruka/gocommon/dbutil/assertdb" "github.com/nyaruka/gocommon/jsonx" @@ -95,7 +96,7 @@ func TestStarts(t *testing.T) { } func TestStartsBuilding(t *testing.T) { - uuids.SetGenerator(uuids.NewSeededGenerator(12345)) + uuids.SetGenerator(uuids.NewSeededGenerator(12345, time.Now)) defer uuids.SetGenerator(uuids.DefaultGenerator) start := models.NewFlowStart(testdata.Org1.ID, models.StartTypeManual, testdata.Favorites.ID). diff --git a/core/models/tickets_test.go b/core/models/tickets_test.go index 93f750a79..763f0bc03 100644 --- a/core/models/tickets_test.go +++ b/core/models/tickets_test.go @@ -95,8 +95,8 @@ func TestUpdateTicketLastActivity(t *testing.T) { now := time.Date(2021, 6, 22, 15, 59, 30, 123456000, time.UTC) - defer dates.SetNowSource(dates.DefaultNowSource) - dates.SetNowSource(dates.NewFixedNowSource(now)) + defer dates.SetNowFunc(time.Now) + dates.SetNowFunc(dates.NewFixedNow(now)) ticket := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Where my shoes", time.Now(), nil) modelTicket := ticket.Load(rt) diff --git a/core/models/triggers_test.go b/core/models/triggers_test.go index 20a803fbc..4fd94ca3f 100644 --- a/core/models/triggers_test.go +++ b/core/models/triggers_test.go @@ -20,7 +20,7 @@ func TestLoadTriggers(t *testing.T) { defer testsuite.Reset(testsuite.ResetAll) rt.DB.MustExec(`DELETE FROM triggers_trigger`) - farmersGroup := testdata.InsertContactGroup(rt, testdata.Org1, assets.GroupUUID(uuids.New()), "Farmers", "") + farmersGroup := testdata.InsertContactGroup(rt, testdata.Org1, assets.GroupUUID(uuids.NewV4()), "Farmers", "") // create trigger for other org to ensure it isn't loaded testdata.InsertCatchallTrigger(rt, testdata.Org2, testdata.Org2Favorites, nil, nil, nil) diff --git a/core/runner/runner_test.go b/core/runner/runner_test.go index 354779643..8a4176986 100644 --- a/core/runner/runner_test.go +++ b/core/runner/runner_test.go @@ -126,7 +126,7 @@ func TestResume(t *testing.T) { session := sessions[0] for i, tc := range tcs { // answer our first question - msg := flows.NewMsgIn(flows.MsgUUID(uuids.New()), testdata.Cathy.URN, nil, tc.Message, nil) + msg := flows.NewMsgIn(flows.MsgUUID(uuids.NewV4()), testdata.Cathy.URN, nil, tc.Message, nil) msg.SetID(10) resume := resumes.NewMsg(oa.Env(), flowContact, msg) @@ -172,7 +172,7 @@ func TestStartFlowConcurrency(t *testing.T) { // create a lot of contacts... contacts := make([]*testdata.Contact, 100) for i := range contacts { - contacts[i] = testdata.InsertContact(rt, testdata.Org1, flows.ContactUUID(uuids.New()), "Jim", i18n.NilLanguage, models.ContactStatusActive) + contacts[i] = testdata.InsertContact(rt, testdata.Org1, flows.ContactUUID(uuids.NewV4()), "Jim", i18n.NilLanguage, models.ContactStatusActive) } options := &runner.StartOptions{ diff --git a/core/search/queries_test.go b/core/search/queries_test.go index 2c4684c26..beaed687c 100644 --- a/core/search/queries_test.go +++ b/core/search/queries_test.go @@ -17,8 +17,8 @@ import ( func TestBuildRecipientsQuery(t *testing.T) { _, rt := testsuite.Runtime() - dates.SetNowSource(dates.NewFixedNowSource(time.Date(2022, 4, 20, 15, 30, 45, 0, time.UTC))) - defer dates.SetNowSource(dates.DefaultNowSource) + dates.SetNowFunc(dates.NewFixedNow(time.Date(2022, 4, 20, 15, 30, 45, 0, time.UTC))) + defer dates.SetNowFunc(time.Now) oa := testdata.Org1.Load(rt) flow, err := oa.FlowByID(testdata.Favorites.ID) diff --git a/core/search/search_test.go b/core/search/search_test.go index 565fabe87..7388d411e 100644 --- a/core/search/search_test.go +++ b/core/search/search_test.go @@ -131,13 +131,13 @@ func TestGetContactIDsForQuery(t *testing.T) { // so that we can test queries that span multiple responses cylonIDs := make([]models.ContactID, 10003) for i := range 10003 { - cylonIDs[i] = testdata.InsertContact(rt, testdata.Org1, flows.ContactUUID(uuids.New()), fmt.Sprintf("Cylon %d", i), i18n.NilLanguage, models.ContactStatusActive).ID + cylonIDs[i] = testdata.InsertContact(rt, testdata.Org1, flows.ContactUUID(uuids.NewV4()), fmt.Sprintf("Cylon %d", i), i18n.NilLanguage, models.ContactStatusActive).ID } // create some extra contacts in the other org to be sure we're filtering correctly - testdata.InsertContact(rt, testdata.Org2, flows.ContactUUID(uuids.New()), "George", i18n.NilLanguage, models.ContactStatusActive) - testdata.InsertContact(rt, testdata.Org2, flows.ContactUUID(uuids.New()), "Bob", i18n.NilLanguage, models.ContactStatusActive) - testdata.InsertContact(rt, testdata.Org2, flows.ContactUUID(uuids.New()), "Cylon 0", i18n.NilLanguage, models.ContactStatusActive) + testdata.InsertContact(rt, testdata.Org2, flows.ContactUUID(uuids.NewV4()), "George", i18n.NilLanguage, models.ContactStatusActive) + testdata.InsertContact(rt, testdata.Org2, flows.ContactUUID(uuids.NewV4()), "Bob", i18n.NilLanguage, models.ContactStatusActive) + testdata.InsertContact(rt, testdata.Org2, flows.ContactUUID(uuids.NewV4()), "Cylon 0", i18n.NilLanguage, models.ContactStatusActive) testsuite.ReindexElastic(ctx) diff --git a/core/tasks/campaigns/cron_test.go b/core/tasks/campaigns/cron_test.go index a7c1648d9..2b67e913a 100644 --- a/core/tasks/campaigns/cron_test.go +++ b/core/tasks/campaigns/cron_test.go @@ -71,7 +71,7 @@ func TestQueueEventFires(t *testing.T) { // add 110 scheduled event fires to test batch limits for i := 0; i < 110; i++ { - contact := testdata.InsertContact(rt, testdata.Org1, flows.ContactUUID(uuids.New()), fmt.Sprintf("Jim %d", i), i18n.NilLanguage, models.ContactStatusActive) + contact := testdata.InsertContact(rt, testdata.Org1, flows.ContactUUID(uuids.NewV4()), fmt.Sprintf("Jim %d", i), i18n.NilLanguage, models.ContactStatusActive) testdata.InsertEventFire(rt, contact, testdata.RemindersEvent1, time.Now().Add(-time.Minute)) } diff --git a/core/tasks/handler/cron_test.go b/core/tasks/handler/cron_test.go index 90deae6e5..f51cf7bf0 100644 --- a/core/tasks/handler/cron_test.go +++ b/core/tasks/handler/cron_test.go @@ -41,7 +41,7 @@ func TestRetryMsgs(t *testing.T) { rt.DB.MustExec( `INSERT INTO msgs_msg(uuid, org_id, channel_id, contact_id, contact_urn_id, text, direction, msg_type, status, created_on, modified_on, visibility, msg_count, error_count, next_attempt) VALUES($1, $2, $3, $4, $5, $6, $7, 'T', $8, $9, $9, 'V', 1, 0, NOW())`, - uuids.New(), testdata.Org1.ID, testdata.TwilioChannel.ID, testdata.Cathy.ID, testdata.Cathy.URNID, msg.Text, models.DirectionIn, msg.Status, msg.CreatedOn) + uuids.NewV4(), testdata.Org1.ID, testdata.TwilioChannel.ID, testdata.Cathy.ID, testdata.Cathy.URNID, msg.Text, models.DirectionIn, msg.Status, msg.CreatedOn) } res, err := cron.Run(ctx, rt) diff --git a/core/tasks/handler/ctasks/wait_expiration_test.go b/core/tasks/handler/ctasks/wait_expiration_test.go index 4c1b3d6f3..76a2f8b0e 100644 --- a/core/tasks/handler/ctasks/wait_expiration_test.go +++ b/core/tasks/handler/ctasks/wait_expiration_test.go @@ -88,7 +88,7 @@ func TestTimedEvents(t *testing.T) { ctask = &ctasks.MsgEventTask{ ChannelID: tc.Channel.ID, MsgID: models.MsgID(1), - MsgUUID: flows.MsgUUID(uuids.New()), + MsgUUID: flows.MsgUUID(uuids.NewV4()), URN: tc.Contact.URN, URNID: tc.Contact.URNID, Text: tc.Message, diff --git a/go.mod b/go.mod index 2c071a590..9ada37d7f 100644 --- a/go.mod +++ b/go.mod @@ -19,11 +19,11 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 - github.com/nyaruka/gocommon v1.56.4 - github.com/nyaruka/goflow v0.219.2 + github.com/nyaruka/gocommon v1.57.1 + github.com/nyaruka/goflow v0.220.0 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 - github.com/nyaruka/rp-indexer/v9 v9.1.9 + github.com/nyaruka/rp-indexer/v9 v9.2.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_model v0.6.1 github.com/prometheus/common v0.55.0 @@ -57,7 +57,6 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/s2a-go v0.1.7 // indirect @@ -74,7 +73,7 @@ require ( github.com/nyaruka/null/v2 v2.0.3 // indirect github.com/nyaruka/phonenumbers v1.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/samber/lo v1.45.0 // indirect + github.com/samber/lo v1.46.0 // indirect github.com/sergi/go-diff v1.3.1 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect diff --git a/go.sum b/go.sum index c220fa31b..007604ab6 100644 --- a/go.sum +++ b/go.sum @@ -77,8 +77,6 @@ github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4 github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= -github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= @@ -154,10 +152,10 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= -github.com/nyaruka/gocommon v1.56.4 h1:wE6dnqpFBRRwNO5CLFVb4mnU/uf5g3AX9Ivh7WwxfZQ= -github.com/nyaruka/gocommon v1.56.4/go.mod h1:XDCG6LNrYCH7XfRBDmwue1WMqoJXyltmhTvmBQy2XIk= -github.com/nyaruka/goflow v0.219.2 h1:4Armcryiigg6TOleOf6hmT/yek1/PRKliXnt2gnLyzU= -github.com/nyaruka/goflow v0.219.2/go.mod h1:svhvRr+3TPczWfRyUTFY3TC5KGswMdf/OTUA/X7u/9Y= +github.com/nyaruka/gocommon v1.57.1 h1:s/9WLZAOUNg0dQHZkgpx8qQMUtNJ2mqzxQhdk1+UBn4= +github.com/nyaruka/gocommon v1.57.1/go.mod h1:wa++Yu/8PEP/HwfvXZrGlKZUCPSJAtSJHeuVsWtLOPM= +github.com/nyaruka/goflow v0.220.0 h1:z9uaJyl1AdTgyRKJ0kPqCRhszkXD/1ZRHJNJkiKXcGA= +github.com/nyaruka/goflow v0.220.0/go.mod h1:NK8xsonXqdipHPDgze4f4L9BHWUGYwDCj5Xg9UVM6Ec= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= @@ -168,8 +166,8 @@ github.com/nyaruka/phonenumbers v1.4.0 h1:ddhWiHnHCIX3n6ETDA58Zq5dkxkjlvgrDWM2OH github.com/nyaruka/phonenumbers v1.4.0/go.mod h1:gv+CtldaFz+G3vHHnasBSirAi3O2XLqZzVWz4V1pl2E= github.com/nyaruka/redisx v0.8.1 h1:d9Hc8nfSKTSEU+bx+YrB13d6bzAgiiHygk4jg/Q4nb4= github.com/nyaruka/redisx v0.8.1/go.mod h1:2TUmkDvprPInnmInR5AEbCm0zRRewkvSDVLsO+Do6iI= -github.com/nyaruka/rp-indexer/v9 v9.1.9 h1:LFRhudwhlnJvTuDslJPDk+jCf++jOks2rXtfKQBQ8OY= -github.com/nyaruka/rp-indexer/v9 v9.1.9/go.mod h1:VKa9eaJ0+XnV90DYCkC3XHU0PPJRXFyDcZw+VuZOtTI= +github.com/nyaruka/rp-indexer/v9 v9.2.0 h1:YisG0GiQNg15LDQtA0m7SNnEHlxzIbmimu95SVXxSVI= +github.com/nyaruka/rp-indexer/v9 v9.2.0/go.mod h1:NzcuE4Zxrzde7gQinlWfwq2jeyEbamBj8hqVkm+eQLg= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= @@ -183,8 +181,8 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= -github.com/samber/lo v1.45.0 h1:TPK85Y30Lv9Jh8s3TrJeA94u1hwcbFA9JObx/vT6lYU= -github.com/samber/lo v1.45.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= +github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ= +github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/samber/slog-multi v1.2.0 h1:JIebVdmeGkCMd5/ticlmU+aDYl4tdAZBmp5uLaSzr0k= github.com/samber/slog-multi v1.2.0/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo= github.com/samber/slog-sentry v1.2.2 h1:S0glIVITlGCCfSvIOte2Sh63HMHJpYN3hDr+97hILIk= diff --git a/services/ivr/twiml/service_test.go b/services/ivr/twiml/service_test.go index ed520a8e5..0f98e5aed 100644 --- a/services/ivr/twiml/service_test.go +++ b/services/ivr/twiml/service_test.go @@ -27,7 +27,7 @@ func TestResponseForSprint(t *testing.T) { urn := urns.URN("tel:+12067799294") expiresOn := time.Now().Add(time.Hour) - channelRef := assets.NewChannelReference(assets.ChannelUUID(uuids.New()), "Twilio Channel") + channelRef := assets.NewChannelReference(assets.ChannelUUID(uuids.NewV4()), "Twilio Channel") env := envs.NewBuilder().WithAllowedLanguages("eng", "spa").WithDefaultCountry("US").Build() resumeURL := "http://temba.io/resume?session=1" diff --git a/services/ivr/vonage/service.go b/services/ivr/vonage/service.go index ec019eaaf..d980b4a5e 100644 --- a/services/ivr/vonage/service.go +++ b/services/ivr/vonage/service.go @@ -767,7 +767,7 @@ func (s *service) responseForSprint(ctx context.Context, rp *redis.Pool, channel // with a 1 second timeout in the script that will get called / repeated until the UUID is // populated at which time we will actually continue. - recordingUUID := string(uuids.New()) + recordingUUID := string(uuids.NewV4()) eventURL := resumeURL + "&wait_type=recording_url&recording_uuid=" + recordingUUID eventURL = eventURL + "&sig=" + url.QueryEscape(s.calculateSignature(eventURL)) record := &Record{ @@ -805,7 +805,7 @@ func (s *service) responseForSprint(ctx context.Context, rp *redis.Pool, channel // // We then track the state of that call, restarting NCCO control of the original call when // the transfer has completed. - conversationUUID := string(uuids.New()) + conversationUUID := string(uuids.NewV4()) connect := &Conversation{ Action: "conversation", Name: conversationUUID, diff --git a/services/ivr/vonage/service_test.go b/services/ivr/vonage/service_test.go index 6f2a192d7..346019474 100644 --- a/services/ivr/vonage/service_test.go +++ b/services/ivr/vonage/service_test.go @@ -51,7 +51,7 @@ func TestResponseForSprint(t *testing.T) { rt.DB.MustExec(`UPDATE channels_channel SET config = config || '{"callback_domain": "localhost:8091"}'::jsonb, role='SRCA' WHERE id = $1`, testdata.VonageChannel.ID) // set our UUID generator - uuids.SetGenerator(uuids.NewSeededGenerator(0)) + uuids.SetGenerator(uuids.NewSeededGenerator(0, time.Now)) oa, err := models.GetOrgAssets(ctx, rt, testdata.Org1.ID) require.NoError(t, err) diff --git a/testsuite/testdata/campaigns.go b/testsuite/testdata/campaigns.go index f1e3a9ab2..765db9c91 100644 --- a/testsuite/testdata/campaigns.go +++ b/testsuite/testdata/campaigns.go @@ -19,7 +19,7 @@ type CampaignEvent struct { } func InsertCampaign(rt *runtime.Runtime, org *Org, name string, group *Group) *Campaign { - uuid := models.CampaignUUID(uuids.New()) + uuid := models.CampaignUUID(uuids.NewV4()) var id models.CampaignID must(rt.DB.Get(&id, `INSERT INTO campaigns_campaign(uuid, org_id, name, group_id, is_archived, is_system, is_active, created_on, modified_on, created_by_id, modified_by_id) @@ -29,7 +29,7 @@ func InsertCampaign(rt *runtime.Runtime, org *Org, name string, group *Group) *C } func InsertCampaignFlowEvent(rt *runtime.Runtime, campaign *Campaign, flow *Flow, relativeTo *Field, offset int, unit string) *CampaignEvent { - uuid := models.CampaignEventUUID(uuids.New()) + uuid := models.CampaignEventUUID(uuids.NewV4()) var id models.CampaignEventID must(rt.DB.Get(&id, `INSERT INTO campaigns_campaignevent( diff --git a/testsuite/testdata/channels.go b/testsuite/testdata/channels.go index de7df73d4..976bb288f 100644 --- a/testsuite/testdata/channels.go +++ b/testsuite/testdata/channels.go @@ -16,7 +16,7 @@ type Channel struct { // InsertChannel inserts a channel func InsertChannel(rt *runtime.Runtime, org *Org, channelType models.ChannelType, name, address string, schemes []string, role string, config map[string]any) *Channel { - uuid := assets.ChannelUUID(uuids.New()) + uuid := assets.ChannelUUID(uuids.NewV4()) var id models.ChannelID must(rt.DB.Get(&id, `INSERT INTO channels_channel(uuid, org_id, channel_type, name, address, schemes, role, config, last_seen, is_system, log_policy, is_active, created_on, modified_on, created_by_id, modified_by_id) diff --git a/testsuite/testdata/flows.go b/testsuite/testdata/flows.go index e2a9cb705..45d8f1aa4 100644 --- a/testsuite/testdata/flows.go +++ b/testsuite/testdata/flows.go @@ -82,7 +82,7 @@ func InsertFlowStart(rt *runtime.Runtime, org *Org, flow *Flow, contacts []*Cont var id models.StartID must(rt.DB.Get(&id, `INSERT INTO flows_flowstart(uuid, org_id, flow_id, start_type, exclusions, created_on, modified_on, contact_count, status, created_by_id) - VALUES($1, $2, $3, 'M', '{}', NOW(), NOW(), 2, 'P', 1) RETURNING id`, uuids.New(), org.ID, flow.ID, + VALUES($1, $2, $3, 'M', '{}', NOW(), NOW(), 2, 'P', 1) RETURNING id`, uuids.NewV4(), org.ID, flow.ID, )) for _, c := range contacts { @@ -108,7 +108,7 @@ func InsertFlowSession(rt *runtime.Runtime, org *Org, contact *Contact, sessionT var id models.SessionID must(rt.DB.Get(&id, `INSERT INTO flows_flowsession(uuid, org_id, contact_id, status, output, responded, created_on, session_type, current_flow_id, call_id, wait_started_on, wait_expires_on, wait_resume_on_expire, ended_on) - VALUES($1, $2, $3, $4, '{}', TRUE, NOW(), $5, $6, $7, $8, $9, FALSE, $10) RETURNING id`, uuids.New(), org.ID, contact.ID, status, sessionType, currentFlow.ID, callID, waitStartedOn, waitExpiresOn, endedOn, + VALUES($1, $2, $3, $4, '{}', TRUE, NOW(), $5, $6, $7, $8, $9, FALSE, $10) RETURNING id`, uuids.NewV4(), org.ID, contact.ID, status, sessionType, currentFlow.ID, callID, waitStartedOn, waitExpiresOn, endedOn, )) return id } @@ -118,7 +118,7 @@ func InsertWaitingSession(rt *runtime.Runtime, org *Org, contact *Contact, sessi var id models.SessionID must(rt.DB.Get(&id, `INSERT INTO flows_flowsession(uuid, org_id, contact_id, status, output, responded, created_on, session_type, current_flow_id, call_id, wait_started_on, wait_expires_on, wait_resume_on_expire, timeout_on) - VALUES($1, $2, $3, 'W', '{"status":"waiting"}', TRUE, NOW(), $4, $5, $6, $7, $8, $9, $10) RETURNING id`, uuids.New(), org.ID, contact.ID, sessionType, currentFlow.ID, callID, waitStartedOn, waitExpiresOn, waitResumeOnExpire, waitTimeoutOn, + VALUES($1, $2, $3, 'W', '{"status":"waiting"}', TRUE, NOW(), $4, $5, $6, $7, $8, $9, $10) RETURNING id`, uuids.NewV4(), org.ID, contact.ID, sessionType, currentFlow.ID, callID, waitStartedOn, waitExpiresOn, waitResumeOnExpire, waitTimeoutOn, )) return id } @@ -135,7 +135,7 @@ func InsertFlowRun(rt *runtime.Runtime, org *Org, sessionID models.SessionID, co var id models.FlowRunID must(rt.DB.Get(&id, `INSERT INTO flows_flowrun(uuid, org_id, session_id, contact_id, flow_id, status, responded, current_node_uuid, created_on, modified_on, exited_on) - VALUES($1, $2, $3, $4, $5, $6, TRUE, $7, NOW(), NOW(), $8) RETURNING id`, uuids.New(), org.ID, sessionID, contact.ID, flow.ID, status, null.String(currentNodeUUID), exitedOn, + VALUES($1, $2, $3, $4, $5, $6, TRUE, $7, NOW(), NOW(), $8) RETURNING id`, uuids.NewV4(), org.ID, sessionID, contact.ID, flow.ID, status, null.String(currentNodeUUID), exitedOn, )) return id } diff --git a/testsuite/testdata/msgs.go b/testsuite/testdata/msgs.go index 65b5bccdc..75dd0616a 100644 --- a/testsuite/testdata/msgs.go +++ b/testsuite/testdata/msgs.go @@ -51,7 +51,7 @@ type Template struct { // InsertIncomingMsg inserts an incoming text message func InsertIncomingMsg(rt *runtime.Runtime, org *Org, channel *Channel, contact *Contact, text string, status models.MsgStatus) *MsgIn { - msgUUID := flows.MsgUUID(uuids.New()) + msgUUID := flows.MsgUUID(uuids.NewV4()) var id models.MsgID must(rt.DB.Get(&id, `INSERT INTO msgs_msg(uuid, text, created_on, modified_on, direction, msg_type, status, visibility, msg_count, error_count, next_attempt, contact_id, contact_urn_id, org_id, channel_id) @@ -129,7 +129,7 @@ func InsertBroadcast(rt *runtime.Runtime, org *Org, baseLanguage i18n.Language, // InsertOptIn inserts an opt in func InsertOptIn(rt *runtime.Runtime, org *Org, name string) *OptIn { - uuid := assets.OptInUUID(uuids.New()) + uuid := assets.OptInUUID(uuids.NewV4()) var id models.OptInID must(rt.DB.Get(&id, `INSERT INTO msgs_optin(uuid, org_id, name, created_on, modified_on, created_by_id, modified_by_id, is_active, is_system) @@ -140,7 +140,7 @@ func InsertOptIn(rt *runtime.Runtime, org *Org, name string) *OptIn { // InsertTemplate inserts a template func InsertTemplate(rt *runtime.Runtime, org *Org, name string) *Template { - uuid := assets.TemplateUUID(uuids.New()) + uuid := assets.TemplateUUID(uuids.NewV4()) var id models.TemplateID must(rt.DB.Get(&id, `INSERT INTO templates_template(uuid, org_id, name, created_on, modified_on) diff --git a/testsuite/testdata/tickets.go b/testsuite/testdata/tickets.go index 6827b71c5..fa4609e64 100644 --- a/testsuite/testdata/tickets.go +++ b/testsuite/testdata/tickets.go @@ -44,7 +44,7 @@ func InsertClosedTicket(rt *runtime.Runtime, org *Org, contact *Contact, topic * } func insertTicket(rt *runtime.Runtime, org *Org, contact *Contact, status models.TicketStatus, topic *Topic, body string, openedOn time.Time, assignee *User) *Ticket { - uuid := flows.TicketUUID(uuids.New()) + uuid := flows.TicketUUID(uuids.NewV4()) lastActivityOn := openedOn var closedOn *time.Time diff --git a/testsuite/web.go b/testsuite/web.go index 8985044f8..a1f6ad758 100644 --- a/testsuite/web.go +++ b/testsuite/web.go @@ -33,9 +33,9 @@ func RunWebTests(t *testing.T, ctx context.Context, rt *runtime.Runtime, truthFi wg := &sync.WaitGroup{} defer uuids.SetGenerator(uuids.DefaultGenerator) - uuids.SetGenerator(uuids.NewSeededGenerator(123456)) + uuids.SetGenerator(uuids.NewSeededGenerator(123456, time.Now)) - defer dates.SetNowSource(dates.DefaultNowSource) + defer dates.SetNowFunc(time.Now) server := web.NewServer(ctx, rt, wg) server.Start() @@ -73,7 +73,7 @@ func RunWebTests(t *testing.T, ctx context.Context, rt *runtime.Runtime, truthFi var err error for i, tc := range tcs { - dates.SetNowSource(dates.NewSequentialNowSource(time.Date(2018, 7, 6, 12, 30, 0, 123456789, time.UTC))) + dates.SetNowFunc(dates.NewSequentialNow(time.Date(2018, 7, 6, 12, 30, 0, 123456789, time.UTC), time.Second)) var clonedMocks *httpx.MockRequestor if tc.HTTPMocks != nil { diff --git a/utils/queues/fair_sorted_test.go b/utils/queues/fair_sorted_test.go index 5ea1f9a23..9c535ae8f 100644 --- a/utils/queues/fair_sorted_test.go +++ b/utils/queues/fair_sorted_test.go @@ -17,8 +17,8 @@ func TestQueues(t *testing.T) { rc := rt.RP.Get() defer rc.Close() - dates.SetNowSource(dates.NewSequentialNowSource(time.Date(2022, 1, 1, 12, 1, 2, 123456789, time.UTC))) - defer dates.SetNowSource(dates.DefaultNowSource) + dates.SetNowFunc(dates.NewSequentialNow(time.Date(2022, 1, 1, 12, 1, 2, 123456789, time.UTC), time.Second)) + defer dates.SetNowFunc(time.Now) defer testsuite.Reset(testsuite.ResetRedis) From dc2f004d4e4499112c7864c891698fa42b0eb7f2 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 2 Aug 2024 12:43:13 -0500 Subject: [PATCH 005/216] Update CHANGELOG.md for v9.3.1 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64f070e24..2cf0862c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.1 (2024-08-02) +------------------------- + * Update to latest goflow/gocommon + v9.3.0 (2024-07-29) ------------------------- * Add S3 to runtime, replace storages From 865df00b71b1d7665ea8cbe14a58c70121f97f17 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 6 Aug 2024 17:15:55 -0500 Subject: [PATCH 006/216] Replace ticket bodies with notes on the open event --- core/handlers/ticket_opened.go | 3 +- core/hooks/insert_tickets.go | 14 +++-- core/models/contacts.go | 2 +- core/models/contacts_test.go | 6 +-- core/models/notifications_test.go | 4 +- core/models/ticket_events.go | 4 +- core/models/ticket_events_test.go | 6 +-- core/models/tickets.go | 14 ++--- core/models/tickets_test.go | 50 +++++++++--------- core/tasks/handler/ctasks/msg_event_test.go | 6 +-- .../handler/ctasks/ticket_closed_test.go | 2 +- go.mod | 4 +- go.sum | 8 +-- mailroom_test.dump | Bin 1759482 -> 1763419 bytes testsuite/testdata/tickets.go | 14 ++--- web/contact/testdata/modify.json | 7 +-- web/msg/base_test.go | 2 +- web/ticket/base_test.go | 34 ++++++------ 18 files changed, 88 insertions(+), 92 deletions(-) diff --git a/core/handlers/ticket_opened.go b/core/handlers/ticket_opened.go index d2d9c50ba..dd646e616 100644 --- a/core/handlers/ticket_opened.go +++ b/core/handlers/ticket_opened.go @@ -57,11 +57,10 @@ func handleTicketOpened(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, o openedInID, scene.ContactID(), topicID, - event.Ticket.Body, assigneeID, ) - scene.AppendToEventPreCommitHook(hooks.InsertTicketsHook, ticket) + scene.AppendToEventPreCommitHook(hooks.InsertTicketsHook, hooks.TicketAndNote{Ticket: ticket, Note: event.Note}) return nil } diff --git a/core/hooks/insert_tickets.go b/core/hooks/insert_tickets.go index 5c30f985d..6de90bae2 100644 --- a/core/hooks/insert_tickets.go +++ b/core/hooks/insert_tickets.go @@ -10,6 +10,11 @@ import ( "github.com/jmoiron/sqlx" ) +type TicketAndNote struct { + Ticket *models.Ticket + Note string +} + // InsertTicketsHook is our hook for inserting tickets var InsertTicketsHook models.EventCommitHook = &insertTicketsHook{} @@ -17,12 +22,15 @@ type insertTicketsHook struct{} // Apply inserts all the airtime transfers that were created func (h *insertTicketsHook) Apply(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, oa *models.OrgAssets, scenes map[*models.Scene][]any) error { - // gather all our tickets + // gather all our tickets and notes tickets := make([]*models.Ticket, 0, len(scenes)) + notes := make(map[*models.Ticket]string, len(scenes)) for _, ts := range scenes { for _, t := range ts { - tickets = append(tickets, t.(*models.Ticket)) + open := t.(TicketAndNote) + tickets = append(tickets, open.Ticket) + notes[open.Ticket] = open.Note } } @@ -36,7 +44,7 @@ func (h *insertTicketsHook) Apply(ctx context.Context, rt *runtime.Runtime, tx * openEvents := make([]*models.TicketEvent, len(tickets)) eventsByTicket := make(map[*models.Ticket]*models.TicketEvent, len(tickets)) for i, ticket := range tickets { - evt := models.NewTicketOpenedEvent(ticket, ticket.OpenedByID(), ticket.AssigneeID()) + evt := models.NewTicketOpenedEvent(ticket, ticket.OpenedByID(), ticket.AssigneeID(), notes[ticket]) openEvents[i] = evt eventsByTicket[ticket] = evt } diff --git a/core/models/contacts.go b/core/models/contacts.go index bd535b96b..600714d24 100644 --- a/core/models/contacts.go +++ b/core/models/contacts.go @@ -393,7 +393,7 @@ func LoadContacts(ctx context.Context, db Queryer, oa *OrgAssets, ids []ContactI // grab the last opened open ticket if len(e.Tickets) > 0 { t := e.Tickets[0] - contact.ticket = NewTicket(t.UUID, oa.OrgID(), NilUserID, NilFlowID, contact.ID(), t.TopicID, t.Body, t.AssigneeID) + contact.ticket = NewTicket(t.UUID, oa.OrgID(), NilUserID, NilFlowID, contact.ID(), t.TopicID, t.AssigneeID) } contacts = append(contacts, contact) diff --git a/core/models/contacts_test.go b/core/models/contacts_test.go index cf90e8e36..00149f052 100644 --- a/core/models/contacts_test.go +++ b/core/models/contacts_test.go @@ -29,11 +29,11 @@ func TestContacts(t *testing.T) { defer testsuite.Reset(testsuite.ResetAll) // for now it's still possible to have more than one open ticket in the database - testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.SupportTopic, "Where are my shoes?", time.Now(), testdata.Agent) - testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.SalesTopic, "Where are my pants?", time.Now(), nil) + testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.SupportTopic, time.Now(), testdata.Agent) + testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.SalesTopic, time.Now(), nil) testdata.InsertContactURN(rt, testdata.Org1, testdata.Bob, "whatsapp:250788373373", 999, nil) - testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Bob, testdata.DefaultTopic, "His name is Bob", time.Now(), testdata.Editor) + testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Bob, testdata.DefaultTopic, time.Now(), testdata.Editor) org, err := models.GetOrgAssetsWithRefresh(ctx, rt, testdata.Org1.ID, models.RefreshAll) assert.NoError(t, err) diff --git a/core/models/notifications_test.go b/core/models/notifications_test.go index 616f776c4..d615f1a01 100644 --- a/core/models/notifications_test.go +++ b/core/models/notifications_test.go @@ -166,10 +166,10 @@ func assertNotifications(t *testing.T, ctx context.Context, db *sqlx.DB, after t } func openTicket(t *testing.T, ctx context.Context, rt *runtime.Runtime, openedBy *testdata.User, assignee *testdata.User) (*models.Ticket, *models.TicketEvent) { - ticket := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.SupportTopic, "Where my pants", time.Now(), assignee) + ticket := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.SupportTopic, time.Now(), assignee) modelTicket := ticket.Load(rt) - openedEvent := models.NewTicketOpenedEvent(modelTicket, openedBy.SafeID(), assignee.SafeID()) + openedEvent := models.NewTicketOpenedEvent(modelTicket, openedBy.SafeID(), assignee.SafeID(), "") err := models.InsertTicketEvents(ctx, rt.DB, []*models.TicketEvent{openedEvent}) require.NoError(t, err) diff --git a/core/models/ticket_events.go b/core/models/ticket_events.go index ea108e327..af3e3103c 100644 --- a/core/models/ticket_events.go +++ b/core/models/ticket_events.go @@ -36,8 +36,8 @@ type TicketEvent struct { } } -func NewTicketOpenedEvent(t *Ticket, userID UserID, assigneeID UserID) *TicketEvent { - return newTicketEvent(t, userID, TicketEventTypeOpened, "", NilTopicID, assigneeID) +func NewTicketOpenedEvent(t *Ticket, userID UserID, assigneeID UserID, note string) *TicketEvent { + return newTicketEvent(t, userID, TicketEventTypeOpened, note, NilTopicID, assigneeID) } func NewTicketAssignedEvent(t *Ticket, userID UserID, assigneeID UserID) *TicketEvent { diff --git a/core/models/ticket_events_test.go b/core/models/ticket_events_test.go index 8e2f2a4fd..9fbb7c767 100644 --- a/core/models/ticket_events_test.go +++ b/core/models/ticket_events_test.go @@ -18,15 +18,15 @@ func TestTicketEvents(t *testing.T) { defer testsuite.Reset(testsuite.ResetData) - ticket := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Have you seen my cookies?", time.Now(), nil) + ticket := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), nil) modelTicket := ticket.Load(rt) - e1 := models.NewTicketOpenedEvent(modelTicket, testdata.Admin.ID, testdata.Agent.ID) + e1 := models.NewTicketOpenedEvent(modelTicket, testdata.Admin.ID, testdata.Agent.ID, "this is a note") assert.Equal(t, testdata.Org1.ID, e1.OrgID()) assert.Equal(t, testdata.Cathy.ID, e1.ContactID()) assert.Equal(t, ticket.ID, e1.TicketID()) assert.Equal(t, models.TicketEventTypeOpened, e1.EventType()) - assert.Equal(t, null.NullString, e1.Note()) + assert.Equal(t, null.String("this is a note"), e1.Note()) assert.Equal(t, testdata.Admin.ID, e1.CreatedByID()) e2 := models.NewTicketAssignedEvent(modelTicket, testdata.Admin.ID, testdata.Agent.ID) diff --git a/core/models/tickets.go b/core/models/tickets.go index f6e53781a..ffffd6216 100644 --- a/core/models/tickets.go +++ b/core/models/tickets.go @@ -50,7 +50,6 @@ type Ticket struct { ContactID ContactID `db:"contact_id"` Status TicketStatus `db:"status"` TopicID TopicID `db:"topic_id"` - Body string `db:"body"` AssigneeID UserID `db:"assignee_id"` OpenedOn time.Time `db:"opened_on"` OpenedByID UserID `db:"opened_by_id"` @@ -63,7 +62,7 @@ type Ticket struct { } // NewTicket creates a new open ticket -func NewTicket(uuid flows.TicketUUID, orgID OrgID, userID UserID, flowID FlowID, contactID ContactID, topicID TopicID, body string, assigneeID UserID) *Ticket { +func NewTicket(uuid flows.TicketUUID, orgID OrgID, userID UserID, flowID FlowID, contactID ContactID, topicID TopicID, assigneeID UserID) *Ticket { t := &Ticket{} t.t.UUID = uuid t.t.OrgID = orgID @@ -72,7 +71,6 @@ func NewTicket(uuid flows.TicketUUID, orgID OrgID, userID UserID, flowID FlowID, t.t.ContactID = contactID t.t.Status = TicketStatusOpen t.t.TopicID = topicID - t.t.Body = body t.t.AssigneeID = assigneeID return t } @@ -83,7 +81,6 @@ func (t *Ticket) OrgID() OrgID { return t.t.OrgID } func (t *Ticket) ContactID() ContactID { return t.t.ContactID } func (t *Ticket) Status() TicketStatus { return t.t.Status } func (t *Ticket) TopicID() TopicID { return t.t.TopicID } -func (t *Ticket) Body() string { return t.t.Body } func (t *Ticket) AssigneeID() UserID { return t.t.AssigneeID } func (t *Ticket) RepliedOn() *time.Time { return t.t.RepliedOn } func (t *Ticket) LastActivityOn() time.Time { return t.t.LastActivityOn } @@ -106,7 +103,7 @@ func (t *Ticket) FlowTicket(oa *OrgAssets) *flows.Ticket { } } - return flows.NewTicket(t.UUID(), topic, t.Body(), assignee) + return flows.NewTicket(t.UUID(), topic, assignee) } const sqlSelectLastOpenTicket = ` @@ -117,7 +114,6 @@ SELECT contact_id, status, topic_id, - body, assignee_id, opened_on, opened_by_id, @@ -151,7 +147,6 @@ SELECT contact_id, status, topic_id, - body, assignee_id, opened_on, opened_by_id, @@ -197,7 +192,6 @@ SELECT t.contact_id, t.status, t.topic_id, - t.body, t.assignee_id, t.opened_on, t.opened_by_id, @@ -238,8 +232,8 @@ func lookupTicket(ctx context.Context, db *sqlx.DB, query string, params ...any) const sqlInsertTicket = ` INSERT INTO - tickets_ticket(uuid, org_id, contact_id, status, topic_id, body, assignee_id, opened_on, opened_by_id, opened_in_id, modified_on, last_activity_on) - VALUES( :uuid, :org_id, :contact_id, :status, :topic_id, :body, :assignee_id, NOW(), :opened_by_id, :opened_in_id, NOW() , NOW()) + tickets_ticket(uuid, org_id, contact_id, status, topic_id, assignee_id, opened_on, opened_by_id, opened_in_id, modified_on, last_activity_on) + VALUES( :uuid, :org_id, :contact_id, :status, :topic_id, :assignee_id, NOW(), :opened_by_id, :opened_in_id, NOW() , NOW()) RETURNING id ` diff --git a/core/models/tickets_test.go b/core/models/tickets_test.go index 763f0bc03..c444f0347 100644 --- a/core/models/tickets_test.go +++ b/core/models/tickets_test.go @@ -30,7 +30,6 @@ func TestTickets(t *testing.T) { models.NilFlowID, testdata.Cathy.ID, testdata.DefaultTopic.ID, - "Where are my cookies?", testdata.Admin.ID, ) ticket2 := models.NewTicket( @@ -40,7 +39,6 @@ func TestTickets(t *testing.T) { models.NilFlowID, testdata.Bob.ID, testdata.SalesTopic.ID, - "Where are my trousers?", models.NilUserID, ) ticket3 := models.NewTicket( @@ -50,7 +48,6 @@ func TestTickets(t *testing.T) { testdata.Favorites.ID, testdata.Alexandria.ID, testdata.SupportTopic.ID, - "Where are my pants?", testdata.Admin.ID, ) @@ -73,19 +70,20 @@ func TestTickets(t *testing.T) { assertTicketDailyCount(t, rt, models.TicketDailyCountAssignment, fmt.Sprintf("o:%d:u:%d", testdata.Org1.ID, testdata.Editor.ID), 0) // can lookup a ticket by UUID - tk1, err := models.LookupTicketByUUID(ctx, rt.DB, "2ef57efc-d85f-4291-b330-e4afe68af5fe") + tk, err := models.LookupTicketByUUID(ctx, rt.DB, "64f81be1-00ff-48ef-9e51-97d6f924c1a4") assert.NoError(t, err) - assert.Equal(t, "Where are my cookies?", tk1.Body()) + assert.Equal(t, flows.TicketUUID("64f81be1-00ff-48ef-9e51-97d6f924c1a4"), tk.UUID()) + assert.Equal(t, testdata.Bob.ID, tk.ContactID()) // can lookup open tickets by contact org1, _ := models.GetOrgAssets(ctx, rt, testdata.Org1.ID) cathy, err := models.LoadContact(ctx, rt.DB, org1, testdata.Cathy.ID) require.NoError(t, err) - tk, err := models.LoadOpenTicketForContact(ctx, rt.DB, cathy) + tk, err = models.LoadOpenTicketForContact(ctx, rt.DB, cathy) assert.NoError(t, err) - assert.NotNil(t, tk) - assert.Equal(t, "Where are my cookies?", tk.Body()) + assert.Equal(t, flows.TicketUUID("2ef57efc-d85f-4291-b330-e4afe68af5fe"), tk.UUID()) + assert.Equal(t, testdata.Cathy.ID, tk.ContactID()) } func TestUpdateTicketLastActivity(t *testing.T) { @@ -98,7 +96,7 @@ func TestUpdateTicketLastActivity(t *testing.T) { defer dates.SetNowFunc(time.Now) dates.SetNowFunc(dates.NewFixedNow(now)) - ticket := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Where my shoes", time.Now(), nil) + ticket := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), nil) modelTicket := ticket.Load(rt) models.UpdateTicketLastActivity(ctx, rt.DB, []*models.Ticket{modelTicket}) @@ -117,17 +115,17 @@ func TestTicketsAssign(t *testing.T) { oa, err := models.GetOrgAssets(ctx, rt, testdata.Org1.ID) require.NoError(t, err) - ticket1 := testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Where my shoes", nil) + ticket1 := testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, nil) modelTicket1 := ticket1.Load(rt) - ticket2 := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Where my pants", time.Now(), nil) + ticket2 := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), nil) modelTicket2 := ticket2.Load(rt) // create ticket already assigned to a user - ticket3 := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Where my glasses", time.Now(), testdata.Admin) + ticket3 := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), testdata.Admin) modelTicket3 := ticket3.Load(rt) - testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "", time.Now(), nil) + testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), nil) evts, err := models.TicketsAssign(ctx, rt.DB, oa, testdata.Admin.ID, []*models.Ticket{modelTicket1, modelTicket2, modelTicket3}, testdata.Agent.ID) require.NoError(t, err) @@ -158,13 +156,13 @@ func TestTicketsAddNote(t *testing.T) { oa, err := models.GetOrgAssets(ctx, rt, testdata.Org1.ID) require.NoError(t, err) - ticket1 := testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Where my shoes", nil) + ticket1 := testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, nil) modelTicket1 := ticket1.Load(rt) - ticket2 := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Where my pants", time.Now(), testdata.Agent) + ticket2 := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), testdata.Agent) modelTicket2 := ticket2.Load(rt) - testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "", time.Now(), nil) + testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), nil) evts, err := models.TicketsAddNote(ctx, rt.DB, oa, testdata.Admin.ID, []*models.Ticket{modelTicket1, modelTicket2}, "spam") require.NoError(t, err) @@ -186,16 +184,16 @@ func TestTicketsChangeTopic(t *testing.T) { oa, err := models.GetOrgAssets(ctx, rt, testdata.Org1.ID) require.NoError(t, err) - ticket1 := testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.SalesTopic, "Where my shoes", nil) + ticket1 := testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.SalesTopic, nil) modelTicket1 := ticket1.Load(rt) - ticket2 := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.SupportTopic, "Where my pants", time.Now(), nil) + ticket2 := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.SupportTopic, time.Now(), nil) modelTicket2 := ticket2.Load(rt) - ticket3 := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Where my pants", time.Now(), nil) + ticket3 := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), nil) modelTicket3 := ticket3.Load(rt) - testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "", time.Now(), nil) + testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), nil) evts, err := models.TicketsChangeTopic(ctx, rt.DB, oa, testdata.Admin.ID, []*models.Ticket{modelTicket1, modelTicket2, modelTicket3}, testdata.SupportTopic.ID) require.NoError(t, err) @@ -216,10 +214,10 @@ func TestCloseTickets(t *testing.T) { oa, err := models.GetOrgAssets(ctx, rt, testdata.Org1.ID) require.NoError(t, err) - ticket1 := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Where my shoes", time.Now(), nil) + ticket1 := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), nil) modelTicket1 := ticket1.Load(rt) - ticket2 := testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Where my pants", nil) + ticket2 := testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, nil) modelTicket2 := ticket2.Load(rt) _, cathy, _ := testdata.Cathy.Load(rt, oa) @@ -251,7 +249,7 @@ func TestCloseTickets(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT count(*) FROM tickets_ticketevent WHERE ticket_id = $1 AND event_type = 'C'`, ticket2.ID).Returns(0) // can close tickets without a user - ticket3 := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Where my shoes", time.Now(), nil) + ticket3 := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), nil) modelTicket3 := ticket3.Load(rt) evts, err = models.CloseTickets(ctx, rt, oa, models.NilUserID, []*models.Ticket{modelTicket3}) @@ -270,10 +268,10 @@ func TestReopenTickets(t *testing.T) { oa, err := models.GetOrgAssets(ctx, rt, testdata.Org1.ID) require.NoError(t, err) - ticket1 := testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Where my shoes", nil) + ticket1 := testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, nil) modelTicket1 := ticket1.Load(rt) - ticket2 := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Where my pants", time.Now(), nil) + ticket2 := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), nil) modelTicket2 := ticket2.Load(rt) evts, err := models.ReopenTickets(ctx, rt, oa, testdata.Admin.ID, []*models.Ticket{modelTicket1, modelTicket2}) @@ -308,7 +306,7 @@ func TestTicketRecordReply(t *testing.T) { openedOn := time.Date(2022, 5, 18, 14, 21, 0, 0, time.UTC) repliedOn := time.Date(2022, 5, 18, 15, 0, 0, 0, time.UTC) - ticket := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Where my shoes", openedOn, nil) + ticket := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, openedOn, nil) timing, err := models.TicketRecordReplied(ctx, rt.DB, ticket.ID, repliedOn) assert.NoError(t, err) diff --git a/core/tasks/handler/ctasks/msg_event_test.go b/core/tasks/handler/ctasks/msg_event_test.go index ad4f00187..17763419d 100644 --- a/core/tasks/handler/ctasks/msg_event_test.go +++ b/core/tasks/handler/ctasks/msg_event_test.go @@ -31,15 +31,15 @@ func TestMsgEvents(t *testing.T) { // give Cathy and Bob some tickets... openTickets := map[*testdata.Contact][]*testdata.Ticket{ testdata.Cathy: { - testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Ok", time.Now(), nil), + testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), nil), }, } closedTickets := map[*testdata.Contact][]*testdata.Ticket{ testdata.Cathy: { - testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "", nil), + testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, nil), }, testdata.Bob: { - testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Bob, testdata.DefaultTopic, "Ok", nil), + testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Bob, testdata.DefaultTopic, nil), }, } diff --git a/core/tasks/handler/ctasks/ticket_closed_test.go b/core/tasks/handler/ctasks/ticket_closed_test.go index 2ac4463fe..4f518c918 100644 --- a/core/tasks/handler/ctasks/ticket_closed_test.go +++ b/core/tasks/handler/ctasks/ticket_closed_test.go @@ -24,7 +24,7 @@ func TestTicketClosed(t *testing.T) { // add a ticket closed trigger testdata.InsertTicketClosedTrigger(rt, testdata.Org1, testdata.Favorites) - ticket := testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Where are my shoes?", nil) + ticket := testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, nil) modelTicket := ticket.Load(rt) models.NewTicketClosedEvent(modelTicket, testdata.Admin.ID) diff --git a/go.mod b/go.mod index 9ada37d7f..d5346c5ae 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( firebase.google.com/go/v4 v4.14.1 github.com/Masterminds/semver v1.5.0 github.com/appleboy/go-fcm v1.2.1 - github.com/aws/aws-sdk-go v1.55.3 + github.com/aws/aws-sdk-go v1.55.5 github.com/buger/jsonparser v1.1.1 github.com/elastic/go-elasticsearch/v8 v8.14.0 github.com/getsentry/sentry-go v0.28.1 @@ -20,7 +20,7 @@ require ( github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 github.com/nyaruka/gocommon v1.57.1 - github.com/nyaruka/goflow v0.220.0 + github.com/nyaruka/goflow v0.221.1 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 github.com/nyaruka/rp-indexer/v9 v9.2.0 diff --git a/go.sum b/go.sum index 007604ab6..ed09f7b9a 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,8 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/appleboy/go-fcm v1.2.1 h1:NhpACabtRuAplYg6bTNfSr3LBwsSuutP55HsphzLU/g= github.com/appleboy/go-fcm v1.2.1/go.mod h1:5FzMN+9J2sxnkoys9h3y48GQH8HnI637Q/ro/uP2Qsk= -github.com/aws/aws-sdk-go v1.55.3 h1:0B5hOX+mIx7I5XPOrjrHlKSDQV/+ypFZpIHOx5LOk3E= -github.com/aws/aws-sdk-go v1.55.3/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -154,8 +154,8 @@ github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= github.com/nyaruka/gocommon v1.57.1 h1:s/9WLZAOUNg0dQHZkgpx8qQMUtNJ2mqzxQhdk1+UBn4= github.com/nyaruka/gocommon v1.57.1/go.mod h1:wa++Yu/8PEP/HwfvXZrGlKZUCPSJAtSJHeuVsWtLOPM= -github.com/nyaruka/goflow v0.220.0 h1:z9uaJyl1AdTgyRKJ0kPqCRhszkXD/1ZRHJNJkiKXcGA= -github.com/nyaruka/goflow v0.220.0/go.mod h1:NK8xsonXqdipHPDgze4f4L9BHWUGYwDCj5Xg9UVM6Ec= +github.com/nyaruka/goflow v0.221.1 h1:IEXC4fI9FYWWbL0kqd0/jE9WMCn3ph/ZzR30M76V2eo= +github.com/nyaruka/goflow v0.221.1/go.mod h1:NK8xsonXqdipHPDgze4f4L9BHWUGYwDCj5Xg9UVM6Ec= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= diff --git a/mailroom_test.dump b/mailroom_test.dump index 690d2c3dc3ad637a418325107731ff02a44d3969..9c26fac9165a7b7a23f02a2729d221d082a530d5 100644 GIT binary patch delta 81017 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¥JfvYIvTUkBq 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

BI>i)+xRW@QSzWn%=}_=IqY9%S=WvwCiT*k} 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 wuq9)vW6Jy-T(~c;7>01A)WY z>c}+2MD61f-Fkz_x00K3dE3(|30h*T%@Lxl35c&j(e;Jbv!fXq@0h2VB9c@6*7)6$ zsz8wodtBi3;Ry|b*YG~%(|+ZSC8qCgA~R!vj~5Z`!Is&Q_htv$u{dLN=5GS!a!94d zOZe)M;%&%8Mz}1mC05nnPJHo0oP9?7aX|_)(TWHfJWG6awT}FlrYlt3LZF2Ve)0x- z2nXGZdlp&481SLa86sYl@iBTw)1fV2sdhU_JR>vW(`apIiy2$i**)OGNzcO6fHRc##ztl(9@DZW+7kqOVXWUnZ zw1>&i4DuC*+7BfSEN(j4$D9OOZWG2-M0>4n$l9I;&H?*McNlkPT!`+-GEg#|(3FC&?#gr!j0mnAps^cX`K{~R=L~nA6_R^i^&#M4Ze(+D0ifH`ne(I7PzS;* z65(i{$v$ur3}`ybd2a{INM5l+XA&KK4Db3oSSQ?Y%1O@vJ99j?6TM+sx-z+sjs`|fzc|q9 z(jZ9N;LoAE3-&S#9@?@i*S86ZkNPZoaJ!!<7=bQ{i#x7?EDte$uTr^BLG|1829C8{ z3QzqZ96!4$_CXKM*R^6iKq-2Fghnrn%JwjkdSPW7mL3>!z!&}or`f2;hMz)~y|w}& z4&TL%Y?P9<57CLH5Bp18kM>T-ek7Q26bt)!4bT&Q%G?`h8Ab<$kya46wi0G5XHr^w z@Yq9R@tEnr?fJ#n_?1%`k>aytVCG8t0mZQ|_YmR77eSfF^a_K^U?RNGm3mCzZpJGT>L)im0*m}?>!^^5dv$??r@0}ky1-R_ zKa(_DQBU`IpN0(wR_$p$#yaU8nbM~?C9O3LEb z>o3v_xS#_VY!*HXvNxkH;NKbe}f~#?RV|U2Hu%%f=ck&L3B;eDiNZ zK+k`+D=HR3;xMW<<`x#hb#8$IUdDpb_OS~{3?Nmvh})2b!R;kI`e&T6XX&p|e{n8~x_SKp|1GOFC-`R$#x1G;b z>Qmj?P0PiM^3X0e@wGs0|MeQ*%`Eobju8x>-kT$W@@`BGdIotVecf|Cu$T)w7#z_( z5yY06sAvLgAL83ZGXr|*WXR)VQ+PR_D)$_CL7~Cy9HIa09<-5R*-dk;`_(fX@-ofu znkV3}0If>>c}^)v91_c_nKA`p_X<~ow}DHr?i?t?o0znO8={JZgeZVzMN!%d1%1j)aBfWr-m{geDhnN@#<8CCfNPwlQevP0 z{&d}B4|$v7gx&oSNl~56q7z!?_fybcsz07Lb%^Xiq+V`W0M)XF?fuc4pxJ1FD! z=`3WSV<}H`HNlma%zcQvY(`8>uJcY-R89zjN->ig9^Kk1xy;1vn8c$nti=mCo{7rt zI^FrrwS3fmoAB}VdcSUd;Mm|PA>F5oVWaLW{dq<0U_vqCM`+dT`2%V$op3`!Xf0#@NA` zqf8gA%hUPv8#?rFM3hRCSQPcPqnlV*FiCW_?tGpUCMYSlUbE`kYq@pMKY`@18hE z*H1{v!73iFTZ=8@G(iu2)hlig2Q%cAq2rKjvD&LNy6n5yzJcv{;U^Ol<_8t7z5Y3@ z4awdazp(wUMyo7Q9(3oZjrEZ^-d#x`=HgGPXz4sHG4v1m@~~2+Gt9LeG#8bq=RQtn zY_49Xs}~xdLMB#s%&vvCs-p=SNqhOKLlE!e@QQPrX3N4iTS4$v6k`ip_0xV5L|;xG zTPsmf9j*M7u83 zTQ`xQVW@DTNI+O#fq%H{mxZAXKN<44Df5D-?ovhWj3qzfp%x_^q~kAYT)-~kh&4TpmuBt)cv zu4rqf5mET z@Q#e;32~{o5)H0A?!Q3{DWmAy*jOTB1}$mx~UI24ovgCR`)y5JIf8U40{Zl3u@5CbYH{js0{@3r0UAPFb?Wed3X~*ma zT3TBKXnvxxQdqMUPexuL#q6R+!TB~t3s|k&t^9vhnyGO?=&YG&L zz*oU;PiA?{L}22;4!OlgRkA3*@nLO${1b;M^S)YMNpyFV#qs{>;?O;L67pP9kd|eX zycD{3_j+vll$E^Rdv=Oh?aRZ-H-?lA`>k^=anZBhtVEmJ;rn@@PY|Ljm5M^hYTkQ2 zHwZh5b1*`luP-B{qQax0N7J!9VRbo`8uiLN9!LMMsPxpkp3lNwIQr&t_5Eu^vvp?h zpXfz(@Z}e`3wc>0Z!%Y?t}aRQyF2YWBYhV~}6wgrFp0>MlDIY|3;&PaCie4Y|dtDyI@w~9rBP67}VnK?40@EY8D zVW6Z-CKH8=Hs^E)rsWI|@6>YQ388jx?#D%O9@A10o6y0|Bf`i0phE1z>=x|cspr%a zFEsH}FO~ZeEn)QfYi5+~d~So7K=ni(a}wRV$?i_qRaAa>6-Y}L&XH!d1IwHU(%bsZ zS+eUt`fI+@JjPRD%TGz)>soHdMmBcD#ttJMjLtAOlZhE&q1+Xjg^B;PD=GxKBSuBe z5+3(Y{+anwVls+6d5~Tq$W|K>LT797%G!_%t-DlTg*$S6{)4{uEZV3=3?i+y@y>{IyI`2`u^eRetGUlsy?s%bu4kRSt1M7*b5;vZyM$V`thl_@(Kk5> z;l5tzn^F)-R|#33r+7W~-q=9p7Yz}=9;40E0Du$+Vk8EkB{kS;3OT*1tAosluddb? z1*y}P4CVAeE)=(q&YcSi9H6=lrX0ucw|cnmF*p=Z`*31?=g=b>TZ_H+CB~vLVHb&x zJh^p|nWQr-{yL)Rkjq$pkov-XiCBp85NR}u4rjR?XVk%W4;K*kSi$ty-ldlWD-cSY zT^`84q1DVyP&bA3D+g%_qUWLcBoRZ>iNmUR@nkl|b--~2a80YU6+mU*_j#fQm0nw; zceaL@#L{sit9+$|W=SnPkw$ac*2_iOs=!YU&?Dr#y|57!%tu=2ltY*`FTw2rlb$GY ztzz*?*Dd{-@-FfRCJD_h_J)Qr2cM{2zWqMr_Ghr1$wcc*Dzn+$?jh7pR827radsHi znG9fZpbfGN3L7{&ivro@7@i1yW5ufmUv<-M#Z}D1fN{;Jz3duO`=i2laIIoK?;NHw zZ=9;+I4{5p6Aq_Hqm_^UGxAp}g1YQ%P|hjY2q7ytr!j(;lz<{lD`kn>EM{=WTPO>` zb^V{L0Cg9?uQMh27n$#gK1Y3}vzk>Ci$*BIh)7;?U|50a#O$DxY5)zojO4!JJ+l=+ z5$I_XWBPDbHZlDWp%m*+Y&PG>qkhC+M)V1=cP@6zxfRQEQpLM68XjD=JKL>&8e3C@ z-KbsOs2jaYh{&)vZr_bRG;ORq)Eta`dsauF^508>szXElT?q#PtZH%H;uFy3?gIW% z4lyIQcY$RgC`?HFs-(o_ktN$uLPbw2|96LP_a03)*FjfT$;6LEXAWW&+p}M8ein8` z8}3yB5HrV7^7oJHCSlr`!ENVJ;2nzvik$(O8|Us7c6sI`Ok=A@I~%Y@5NCeHxsp#g_N8%txBszTC#@q2 zp%<^W;0g~0NzB0dfDkK2Kj*afQEyf_oy9Ob%2jP^UD;>z>u5H;Fg=Z}773o=eIPrZ z*Ra@HbD!|fKSOPG1iB1e5-)-W+$=|-Fy)Z;%+KgA)QZ>nqv#afxN39D?JWn4dd{4x zN<0#3Af8!d%26oZq)NAJVyaq~lJ$9o9|i+qNQ3D6(VquD`FyKgiylkbmJiZ{bEVm} zbxLL)56N1du07~Xvz1|!L>28v!Irz|sGYv-v{dt~5=VK_%|U>S{&2%6o1B@J0!zql zNQ@YRrTeUA0nopCKw$?|3<$eRhvCKYeAFu8-yMj_lt!xB8_&=uac)`VMFXZ! zWf$bdxd`kJHQIEE`}X9?a+q5mU?BK@6H6)Uat<$fSysRZ@U;fbR1CN8hNJRx*hWKpXk;EbxROJDp|d&TD}e4;^lJn zX+3_mY(>Hv!2x2Dh>qezfRhGR_s|_ef<{KV43`hGi%Jh|Seb;ZxuEJ^-w1{!9qP#) zJm_2#i|pI5;4XNN2(=E{ncgpUtRT#2-Ur;amn<DTfL^CE*BDMLG+`@t5JnD^|AR85-=W;fgYK8 zY2o7hy3~h~Do|1$&Y|vNYnD+VvvbrO^ktwMu4IKmc{kWsMnr&dsq#DvEclz6qBP0} zi6UL9ag}=|5E=06(yxaClhd#^I+;MmCrQ%2Hzgpk=H`NOR72O8GkKrAXXptBp0#ID z0K9=2mZQt1xQZb!DajcGp;0g%X!{RcYLT(za3SBfVzP<2(oRs=O1t}YVUQ4!W@VqB zHY;V&7j`h^2E6bzOx%Snk4Ls}YBS%%CDGV}qby~m88jC$z+k}k?s_yPyQ*ii-<7eR ziljL?UC!c*symEneLXB~kJmCYNl$;uZc|2USoE_uo2a@oMV?tl~rPlZ&DAtu&R>jBGw!9e7P6oz#Mha>eoPa&FDE}q?t zF&j}zeV3l?4uk#qGNJZ=MuZOy(|wArJO2hM*8RL2#Lnfh@~t71uR~IrcGC(nnvi|5bowr!^13F0t|)9`Tpg+k!e#t!tRdF%B?(sk zoO9YS?Xcl!N}b>V!T5%sN=`4AYFqK1>gr2TRg?!W%E*uI9{XrQ?tEXiy)|EcsRt)} z>(M(4(l>8zm>L|+fHvdy_$SHS@U{z2Vl=$GLbvCR*1n!?ML2148?PY`m6#?7m`0A> zxau0a9OQN#M4Z7<6k`f5qZ$B=1OeHr#Bs&ZS_1g|u z^jH$=#xjiXHB-#|L~ej_$K`I^sN07k(fBcI%+)1+0?MZR2Ewe^N>rJogPz`(QW_8k zUxAdL2E%dtbQt@K2(AyQcf88Jf~{PChj8TRxTAOe6YRtg8MJr4=RMrew(vwZu+Q0W_;zS!7TjTeY-xkP>L76bX~b8dren{W+0=|9c2;5={bk)0)#XSl%;c zx`c|8JG$0E$x_o6Wxef-wk{p5aMtdA>k^xJ{>+Q!9|U?41aK zt39wGZ7acw*%!tF;1|c<)K#pqQd;<=7^AvKEvjfQo`ST(_n#c1 z*LN8q8XyOt;c=g^#p9te#N$>wRSKaDwNBDgVn=Zt7!uxPw$sV9?%79o@|HNC(M&W7 ztp!fp&Wrt#>6>-iHBXOY-?523_~a#w7iPPl5C9+%%wQDV-=atScsFVx4i2#`reN7& zEP}oU?)Tjfi}slQD4Fo4%;V$YtMexYh?`LkBoNI|$?_Fxvv)D@fLhx$rfYB{x@5|f zcd+KmJ}j1NRs$jX7o=Yd-C7%1sG-)4D(!a!h)?0z@)vF1V40;=+e3z5cT?$7zlE{F zVx=c$QYq_XN3$a{jS~HSeCo;FeF73cUC1-}%w2GPEVW-OVo>4HV6?fPYv9|yY?O0cQ^7~n|0LpjltCU;Bg&HBaCR5so zZpY(PxBOSuhMlQ_4DIjM8shs7Jq+WHrf$?6 zMLe}D_NZ?TQW-6UoYcxwbG(osfQAz5%OR*BdsE&R)0TEX)MLnG-_e9}IdwiQljtt{ zx)gQ@;ixmNfi3d}VIs!h;!FP#0}cv3kRLNaS|)5Dn?{hMX)1Qa`Shce`bQO22>D@} ze)-b=)rC0qXHiA-2OYbPSWO3Y7uda{8O(((|MWi|V8%JRqP04FDt)oZ zQ#y+FGD805y2k*l%eGfckFy6I%U@=Gm|)2ocvLRIk{OY-X5RGkH@(@jV_7gob4L~q z9DCY5+S^o}G@MFlJS6LMNUoy zbPlS}Z`qj9vz<_CGWn*iiu%>DZn!=$T5)7%8-l5bg7tX zK!1pfrj%+G1tDU_XaZAle5sI}^T(T9&_Gm3&Go%s> z?Fq4u69Cep_Y3T$->g0FIgUVRRRm#vNNGinUEp>-PT+y)&)_xi_OK$-&4rsNi4|+W zGCl`WyCT!!vZg-ieWmDzDoFZ*r|u)3fozQ+K?fx#;`w4EvRG(Lev;)Cd?p3#~=X84>W{C z2AsK=c{V)O94B=1{4@OP4E!895$>11K{*yPAOx7{0Ne4&>*MqY@^@Q`0@{)rhZVF% z7Ax+`*nBR4r>lI`ijRD-Uj!JqIJM@^*zJ1n0Tf^t8>zXPZ^3cJ?ewOduX?h@&3rLY zwMyn|n;|zRsgPa<_6UedkC$Th2R|$On*TTCJH(E1Cq5NS-zOcdU#zIKfs_{nD|XyR z))&K`$|G$*E$TBTN0=aFg~Uzj;gl0Aw^X7MeaI75?rEK%!*>fPN-@OY!*b5U;I9=` zTAs!3T;ARU;o@^FjX%@6SBx*^a96PM6vv^y&XQo%JE9s~kn4fEUsF*|O;axU(DpMa zQWz2v_?;HL0pfL0wP`-a#yY>sfw|4!g5ex@3&@9IptYsca$lpMHE0>~N#}}+csCDo8j5W<(-faI^?Fr9v!QwJ^N0-D)mQL|@1APb%{Ugy}u{4;L+wJD(}9x!7~6hI8VEgh9f93ue_U_#T^Mi7uTN11GobqMuig zT6!tm`(!yeOm_+2ze2b%G{nt(g$;}dj$n1l8%Lahd@u%OOnpqeeR?VO9dDr9*QB-k8R=?$F0OFY;6S8eO0%L6~w^+G1y)OR4e3k+0CJu z2kT}R(^QbQI9xCcumUllY&eCS=9}3abh}N~4G3nWd{WgZuXLuJ~pDRJ{R*$w{F5@YGsy=Y=fAJ7TV2~1p zXSFZVErNIBhAh~cmb%q+9=aE&#JKG{;3+zsZI7zOeJ0t;IZs|fxrYgVq+tX`irV6@ z!`c46oV)8eSZAlF@=o$dQfs!eZv8Y&T9WM40~kB*`3$fBt*NknE27F^W4X)c2VKSB zmp{Frsr9R*m82GyYpZ%C&cxkqNDA5xM8Vkpc|DW8GXlNK5wsGCuiwZ{kOZwLWH|e? zSHSH}@2K{C{;VAz1MCn4dy^B^Om@(bJJ5I?MA=jl&M3KfZsy5X>+%DUiq?y*IjPZe zX7SX(FP+&`)8b0_#m1)*pZMWVwSS2Z+;HrJ@arWSqKS(f7O!Fv&%wONe_AYF$;>yC z(L;bYqii8Bmunm(5KbJ8h2C_WyAGQJAr_PUr8BL>l7V%*&nH>Zy&spqV-%oyp=?6Z zaRZyX^AfNQP`ag9{b`Vm3cu*?fN*edBE;0j+O!2$y9E`36TFCjJqYXBtDY5~Zn@Zk z{HpY{0dF6L?W#UcuiAqp!1 zlVE&H0R)$cA8DAAuirM58nY6h9Z>oxsQm=j6;rS*B+Y$*P9u*c4$ycF$Ah ze=EP$#TV#&YCd4jLDZK@vi{lH7C1%(8HfN0nHF%3H{g1Jw?bOS&wn7x0oDb&S7XCy z{_Wio%+fZ9&2*l8v0n2S2Bom8XZXZ)dPeJncdJB~hj5vDFbbV5P2#zHr2fNJ0#FU; zogW2uy@>x>T-n;#998eCOXguA4vOJ?FtKWnIY0_p`a($nYx=oohpUnS8azoUqwsT* z{~vE-K!Lj|r$R?4mDB}S8Kx2leK1if4_P}TX;L(plJb1eSbDz%J94M{&dmaeWsr-ZY=zQ4OdnqQXMd{|5JPN%Tq#yHXgadZ2IY`L!(L^ z0C0}ILA+8TAJ>1V3^^m?e}=f}DymjzidHc?xGN?0l; z=4J7J52EpZ)IQcer%;6zMXjVEEX*_U}j}Gr13R8{2U*+>F4mIIL;F7;sKwRO3%sKF!ke zj2XwATU1p=h?{yDo%xTxW8EIDe!__#mC1}(zOUT68`*Q&?vdE|tP|#64Krp7-e#*p z)rqB?bp{1hXqSWWmK)T@NRb!>1`fWYmN0KgIl1rV#GI0%V}rNX1}Ng$qXp+@wTKC* z-QKvr^6I`Vm(11Eb4^Vs0RNx5+<&56r#{vK_}@Y2ukVz23ZECO_Mhzff7JMonEz4r zKVtolK6(7%$(ndk>TI_tMISty(e={aU4b(Nu6UR08HRn<+Kn_5yl&+lPN;{fZ&u#03P`{Ygxxy7n-AL9&G z#y}sJS8^Ni!$n@qQ}x+q$sq-A*h$X7Av_4)jj{HWVYD)^X~YNO0l)*yKI9q#aRB zFE&Dld9zzKX&{gwxttMIeL{J({M!1P)#_?VL;vC9t z5EDJFU>uA;S&?_{E${@51rC15$XT}q!8p|_$4rst`B^#E1mR$DC)ICtD9;XC8~O3@m2`yXOm_v`-Pl4GjXzhU{WZikPHBB@ITKc!%ovC*7n6%9N{; z#nz~57>M$oUny3s{ST(ys*V9L%@A2=5wdzLkY6i)eh;h&Hw$7sx$7JM;bX7%{n@yERX_My?ja{oz|UZSyiSiK;mLT1 z2vWRvB6!Oofj?603) z>NM&Duz>`4W(qnw#BgZ|wB@EWTZ_+5y6+Q1s75bQ(#W?D=<9{()JsHV>bz0Ovh7oji@Iav(U!L`6IaV znO5pO*YHb=;WrcrG;eq4pVPUnEQk3&GsXMAk6oR3zjdto)^+rU=a!z=HP8lmsaP*4 zBS~d~OzHoq76PiGdASVbSKm9hD=&VxRc~ydntwP=KR#f9v%h+kKj5Cp#ts0doJ5x+ zhakt$fk4oKV1ptTrZZxr!(bCIVw0A}FtW+zXVj36EleR=#>d5r;)IzIWK@eJ`Z$aN g`gyTY{yy>Jkr)3Gdi9U~{G;1{bpMYY{}TTH0IA{LLjV8( delta 79995 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%0Dk9i`JJu`4nmK|KUlaqpzhw$MeRhs!l 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�X{O54*{`Q9muTcclkbyg5v4UG#bw$Nh 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 diff --git a/testsuite/testdata/tickets.go b/testsuite/testdata/tickets.go index fa4609e64..88408bafb 100644 --- a/testsuite/testdata/tickets.go +++ b/testsuite/testdata/tickets.go @@ -34,16 +34,16 @@ func (k *Ticket) Load(rt *runtime.Runtime) *models.Ticket { } // InsertOpenTicket inserts an open ticket -func InsertOpenTicket(rt *runtime.Runtime, org *Org, contact *Contact, topic *Topic, body string, openedOn time.Time, assignee *User) *Ticket { - return insertTicket(rt, org, contact, models.TicketStatusOpen, topic, body, openedOn, assignee) +func InsertOpenTicket(rt *runtime.Runtime, org *Org, contact *Contact, topic *Topic, openedOn time.Time, assignee *User) *Ticket { + return insertTicket(rt, org, contact, models.TicketStatusOpen, topic, openedOn, assignee) } // InsertClosedTicket inserts a closed ticket -func InsertClosedTicket(rt *runtime.Runtime, org *Org, contact *Contact, topic *Topic, body string, assignee *User) *Ticket { - return insertTicket(rt, org, contact, models.TicketStatusClosed, topic, body, dates.Now(), assignee) +func InsertClosedTicket(rt *runtime.Runtime, org *Org, contact *Contact, topic *Topic, assignee *User) *Ticket { + return insertTicket(rt, org, contact, models.TicketStatusClosed, topic, dates.Now(), assignee) } -func insertTicket(rt *runtime.Runtime, org *Org, contact *Contact, status models.TicketStatus, topic *Topic, body string, openedOn time.Time, assignee *User) *Ticket { +func insertTicket(rt *runtime.Runtime, org *Org, contact *Contact, status models.TicketStatus, topic *Topic, openedOn time.Time, assignee *User) *Ticket { uuid := flows.TicketUUID(uuids.NewV4()) lastActivityOn := openedOn @@ -56,8 +56,8 @@ func insertTicket(rt *runtime.Runtime, org *Org, contact *Contact, status models var id models.TicketID must(rt.DB.Get(&id, - `INSERT INTO tickets_ticket(uuid, org_id, contact_id, status, topic_id, body, opened_on, modified_on, closed_on, last_activity_on, assignee_id) - VALUES($1, $2, $3, $4, $5, $6, $7, NOW(), $8, $9, $10) RETURNING id`, uuid, org.ID, contact.ID, status, topic.ID, body, openedOn, closedOn, lastActivityOn, assignee.SafeID(), + `INSERT INTO tickets_ticket(uuid, org_id, contact_id, status, topic_id, opened_on, modified_on, closed_on, last_activity_on, assignee_id) + VALUES($1, $2, $3, $4, $5, $6, NOW(), $7, $8, $9) RETURNING id`, uuid, org.ID, contact.ID, status, topic.ID, openedOn, closedOn, lastActivityOn, assignee.SafeID(), )) return &Ticket{id, uuid} } diff --git a/web/contact/testdata/modify.json b/web/contact/testdata/modify.json index b3698a385..3439bb387 100644 --- a/web/contact/testdata/modify.json +++ b/web/contact/testdata/modify.json @@ -1399,7 +1399,6 @@ "uuid": "0a8f2e00-fef6-402c-bd79-d789446ec0e0", "name": "Support" }, - "body": "Need help", "assignee": { "email": "admin1@nyaruka.com", "name": "Andy Admin" @@ -1416,12 +1415,12 @@ "uuid": "0a8f2e00-fef6-402c-bd79-d789446ec0e0", "name": "Support" }, - "body": "Need help", "assignee": { "email": "admin1@nyaruka.com", "name": "Andy Admin" } - } + }, + "note": "Need help" }, { "type": "contact_groups_changed", @@ -1508,7 +1507,6 @@ "uuid": "0a8f2e00-fef6-402c-bd79-d789446ec0e0", "name": "Support" }, - "body": "Need help", "assignee": { "email": "admin1@nyaruka.com", "name": "Andy Admin" @@ -1598,7 +1596,6 @@ "uuid": "0a8f2e00-fef6-402c-bd79-d789446ec0e0", "name": "Support" }, - "body": "Need help", "assignee": { "email": "admin1@nyaruka.com", "name": "Andy Admin" diff --git a/web/msg/base_test.go b/web/msg/base_test.go index 540080457..1a528008f 100644 --- a/web/msg/base_test.go +++ b/web/msg/base_test.go @@ -17,7 +17,7 @@ func TestSend(t *testing.T) { defer testsuite.Reset(testsuite.ResetData | testsuite.ResetRedis) - cathyTicket := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "help", time.Date(2015, 1, 1, 12, 30, 45, 0, time.UTC), nil) + cathyTicket := testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Date(2015, 1, 1, 12, 30, 45, 0, time.UTC), nil) testsuite.RunWebTests(t, ctx, rt, "testdata/send.json", map[string]string{ "cathy_ticket_id": fmt.Sprintf("%d", cathyTicket.ID), diff --git a/web/ticket/base_test.go b/web/ticket/base_test.go index 65db898ec..1c48895f7 100644 --- a/web/ticket/base_test.go +++ b/web/ticket/base_test.go @@ -13,10 +13,10 @@ func TestTicketAssign(t *testing.T) { defer testsuite.Reset(testsuite.ResetData) - testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Have you seen my cookies?", time.Now(), testdata.Admin) - testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Have you seen my cookies?", time.Now(), testdata.Agent) - testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Have you seen my cookies?", nil) - testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Bob, testdata.DefaultTopic, "", nil) + testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), testdata.Admin) + testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), testdata.Agent) + testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, nil) + testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Bob, testdata.DefaultTopic, nil) testsuite.RunWebTests(t, ctx, rt, "testdata/assign.json", nil) } @@ -26,9 +26,9 @@ func TestTicketAddNote(t *testing.T) { defer testsuite.Reset(testsuite.ResetData) - testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Have you seen my cookies?", time.Now(), testdata.Admin) - testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Have you seen my cookies?", time.Now(), testdata.Agent) - testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Have you seen my cookies?", nil) + testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), testdata.Admin) + testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), testdata.Agent) + testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, nil) testsuite.RunWebTests(t, ctx, rt, "testdata/add_note.json", nil) } @@ -38,9 +38,9 @@ func TestTicketChangeTopic(t *testing.T) { defer testsuite.Reset(testsuite.ResetData) - testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Have you seen my cookies?", time.Now(), testdata.Admin) - testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.SupportTopic, "Have you seen my cookies?", time.Now(), testdata.Agent) - testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.SalesTopic, "Have you seen my cookies?", nil) + testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), testdata.Admin) + testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.SupportTopic, time.Now(), testdata.Agent) + testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.SalesTopic, nil) testsuite.RunWebTests(t, ctx, rt, "testdata/change_topic.json", nil) } @@ -51,9 +51,9 @@ func TestTicketClose(t *testing.T) { defer testsuite.Reset(testsuite.ResetData) // create 2 open tickets and 1 closed one for Cathy - testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Have you seen my cookies?", time.Now(), testdata.Admin) - testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Have you seen my cookies?", time.Now(), nil) - testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Have you seen my cookies?", testdata.Editor) + testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), testdata.Admin) + testdata.InsertOpenTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, time.Now(), nil) + testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, testdata.Editor) testsuite.RunWebTests(t, ctx, rt, "testdata/close.json", nil) } @@ -64,13 +64,13 @@ func TestTicketReopen(t *testing.T) { defer testsuite.Reset(testsuite.ResetData | testsuite.ResetRedis) // we should be able to reopen ticket #1 because Cathy has no other tickets open - testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Have you seen my cookies?", testdata.Admin) + testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, testdata.Admin) // but then we won't be able to open ticket #2 - testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, "Have you seen my cookies?", nil) + testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Cathy, testdata.DefaultTopic, nil) - testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Bob, testdata.DefaultTopic, "Have you seen my cookies?", testdata.Editor) - testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Alexandria, testdata.DefaultTopic, "Have you seen my cookies?", testdata.Editor) + testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Bob, testdata.DefaultTopic, testdata.Editor) + testdata.InsertClosedTicket(rt, testdata.Org1, testdata.Alexandria, testdata.DefaultTopic, testdata.Editor) testsuite.RunWebTests(t, ctx, rt, "testdata/reopen.json", nil) } From fb73c5b067c3670b136e12e88dec30d4816b1162 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 7 Aug 2024 17:31:25 -0500 Subject: [PATCH 007/216] Update test database --- core/models/assets_test.go | 4 +- core/models/users_test.go | 1 - mailroom_test.dump | Bin 1763419 -> 1764514 bytes web/po/testdata/favorites.po | 38 ++-- web/po/testdata/import.json | 256 +++++++++++++-------------- web/po/testdata/multiple_flows.es.po | 48 ++--- 6 files changed, 172 insertions(+), 175 deletions(-) diff --git a/core/models/assets_test.go b/core/models/assets_test.go index 8646df9d8..a63664629 100644 --- a/core/models/assets_test.go +++ b/core/models/assets_test.go @@ -62,8 +62,6 @@ func TestAssets(t *testing.T) { func TestCloneForSimulation(t *testing.T) { ctx, rt := testsuite.Runtime() - defer testsuite.Reset(0) - oa, err := models.GetOrgAssets(ctx, rt, testdata.Org1.ID) require.NoError(t, err) @@ -103,7 +101,7 @@ func TestCloneForSimulation(t *testing.T) { // original assets still has original flow definition flow, err = oa.FlowByUUID(testdata.Favorites.UUID) require.NoError(t, err) - assert.Equal(t, "{\"_ui\": {\"nodes\": {\"1b828e78-e478-4357-9472-47a30ec1f60b\": {\"typ", string(flow.Definition())[:64]) + assert.Equal(t, "{\"_ui\": {\"nodes\": {\"10c9c241-777f-4010-a841-6e87abed8520\": {\"typ", string(flow.Definition())[:64]) // and doesn't have the test channels testChannel1 = oa.SessionAssets().Channels().Get("d7be3965-4c76-4abd-af78-ebc0b84ab621") diff --git a/core/models/users_test.go b/core/models/users_test.go index 2f8f30e9a..615c5c15b 100644 --- a/core/models/users_test.go +++ b/core/models/users_test.go @@ -32,7 +32,6 @@ func TestLoadUsers(t *testing.T) { {id: testdata.Admin.ID, email: testdata.Admin.Email, name: "Andy Admin", role: models.UserRoleAdministrator, team: office}, {id: testdata.Agent.ID, email: testdata.Agent.Email, name: "Ann D'Agent", role: models.UserRoleAgent, team: partners}, {id: testdata.Editor.ID, email: testdata.Editor.Email, name: "Ed McEditor", role: models.UserRoleEditor, team: office}, - {id: testdata.Surveyor.ID, email: testdata.Surveyor.Email, name: "Steve Surveys", role: models.UserRoleSurveyor, team: nil}, {id: testdata.Viewer.ID, email: testdata.Viewer.Email, name: "Veronica Views", role: models.UserRoleViewer, team: nil}, } diff --git a/mailroom_test.dump b/mailroom_test.dump index 9c26fac9165a7b7a23f02a2729d221d082a530d5..a524a885eebf74686491ebf46a85523680d1ef12 100644 GIT binary patch delta 70922 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 delta 68915 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 A;Q#;t diff --git a/web/po/testdata/favorites.po b/web/po/testdata/favorites.po index 556320c3b..fb01f60aa 100644 --- a/web/po/testdata/favorites.po +++ b/web/po/testdata/favorites.po @@ -10,76 +10,76 @@ msgstr "" "Language-3: \n" "Source-Flows: 9de3663f-c5c5-4c92-9f45-ecbc09abcc85\n" -#: Favorites/e92b12c5-1817-468e-aa2f-8791fb6247e9/name:0 +#: Favorites/e87aeeab-8ede-4173-bc76-8f5583ea7207/name:0 msgid "All Responses" msgstr "" -#: Favorites/8d2e259c-bc3c-464f-8c15-985bc736e212/name:0 +#: Favorites/c102acfc-8cc5-41fa-89ed-41cbfa362ba6/name:0 msgid "Blue" msgstr "" -#: Favorites/3e2dcf45-ffc0-4197-b5ab-25ed974ea612/name:0 +#: Favorites/8d2e259c-bc3c-464f-8c15-985bc736e212/name:0 msgid "Cyan" msgstr "" -#: Favorites/aac779a9-e2a6-4a11-9efa-9670e081a33a/text:0 +#: Favorites/3e2dcf45-ffc0-4197-b5ab-25ed974ea612/text:0 msgid "Good choice, I like @results.color.category_localized too! What is your favorite beer?" msgstr "" -#: Favorites/34a421ac-34cb-49d8-a2a5-534f52c60851/name:0 +#: Favorites/58284598-805a-4740-8966-dcb09e3b670a/name:0 msgid "Green" msgstr "" -#: Favorites/eb048bdf-17ee-4334-a52b-5e82a20189ac/text:0 +#: Favorites/943f85bb-50bc-40c3-8d6f-57dbe34c87f7/text:0 msgid "I don't know that color. Try again." msgstr "" -#: Favorites/0891f63c-9e82-42bb-a815-8b44aff33046/text:0 +#: Favorites/4cadf512-1299-468f-85e4-26af9edec193/text:0 msgid "I don't know that one, try again please." msgstr "" -#: Favorites/e87aeeab-8ede-4173-bc76-8f5583ea7207/text:0 +#: Favorites/52d7a9ab-52b7-4e82-ba7f-672fb8d6ec91/text:0 msgid "Mmmmm... delicious @results.beer.category_localized. If only they made @(lower(results.color)) @results.beer.category_localized! Lastly, what is your name?" msgstr "" -#: Favorites/a03dceb1-7ac1-491d-93ef-23d3e099633b/name:0 +#: Favorites/87b850ff-ddc5-4add-8a4f-c395c3a9ac38/name:0 msgid "Mutzig" msgstr "" -#: Favorites/4cadf512-1299-468f-85e4-26af9edec193/name:0 +#: Favorites/6e367c0c-65ab-479a-82e3-c597d8e35eef/name:0 msgid "No Response" msgstr "" -#: Favorites/58284598-805a-4740-8966-dcb09e3b670a/name:0 -#: Favorites/b9d718d3-b5e0-4d26-998e-2da31b24f2f9/name:0 +#: Favorites/c169352e-1944-4451-8d32-eb39c41cb3ae/name:0 +#: Favorites/e0ec2076-2746-43b4-a410-c3af47d6a121/name:0 msgid "Other" msgstr "" -#: Favorites/58119801-ed31-4538-888d-23779a01707f/name:0 +#: Favorites/b9d718d3-b5e0-4d26-998e-2da31b24f2f9/name:0 msgid "Primus" msgstr "" -#: Favorites/b0c29972-6fd4-485e-83c2-057a3f7a04da/name:0 +#: Favorites/5563a722-9680-419c-a792-b1fa9df92e06/name:0 msgid "Red" msgstr "" -#: Favorites/ada3d96a-a1a2-41eb-aac7-febdb98a9b4c/name:0 +#: Favorites/dbc3b9d2-e6ce-4ebe-9552-8ddce482c1d1/name:0 msgid "Skol" msgstr "" -#: Favorites/cc711204-3dd4-499d-9d37-b477bf5c5458/text:0 +#: Favorites/e92b12c5-1817-468e-aa2f-8791fb6247e9/text:0 msgid "Sorry you can't participate right now, I'll try again later." msgstr "" -#: Favorites/cb6fc9b4-d6e9-4ed3-8a11-3f4d19654a48/text:0 +#: Favorites/491f3ed1-9154-4acb-8fdd-0a37567e0574/text:0 msgid "Thanks @results.name, we are all done!" msgstr "" -#: Favorites/2ba89eb6-6981-4c0d-a19d-3cf1fde52a43/name:0 +#: Favorites/f1ca9ac8-d0aa-4758-a969-195be7330267/name:0 msgid "Turbo King" msgstr "" -#: Favorites/9631dddf-0dd7-4310-b263-5f7cad4795e0/text:0 +#: Favorites/8c2504ef-0acc-405f-9efe-d5fc2c434a93/text:0 msgid "What is your favorite color?" msgstr "" diff --git a/web/po/testdata/import.json b/web/po/testdata/import.json index dbcc48f41..62f8cb5ad 100644 --- a/web/po/testdata/import.json +++ b/web/po/testdata/import.json @@ -45,7 +45,7 @@ "expire_after_minutes": 720, "localization": { "spa": { - "8d2e259c-bc3c-464f-8c15-985bc736e212": { + "c102acfc-8cc5-41fa-89ed-41cbfa362ba6": { "name": [ "Azul" ] @@ -54,186 +54,186 @@ }, "nodes": [ { - "uuid": "333fa9a0-85a3-47c5-817e-153a1a124991", + "uuid": "b4664fbd-3495-4fc6-aa8b-b397857dcd68", "actions": [ { "type": "send_msg", - "uuid": "9631dddf-0dd7-4310-b263-5f7cad4795e0", + "uuid": "8c2504ef-0acc-405f-9efe-d5fc2c434a93", "text": "What is your favorite color?" } ], "exits": [ { - "uuid": "66c38ec3-0acd-4bf7-a5d5-278af1bee492", - "destination_uuid": "48fd5325-d660-4404-bdf3-05ad1b024cc0" + "uuid": "f4495f19-37ee-4e51-a7d5-d99ef6be147a", + "destination_uuid": "10c9c241-777f-4010-a841-6e87abed8520" } ] }, { - "uuid": "943f85bb-50bc-40c3-8d6f-57dbe34c87f7", + "uuid": "1b828e78-e478-4357-9472-47a30ec1f60b", "actions": [ { "type": "send_msg", - "uuid": "eb048bdf-17ee-4334-a52b-5e82a20189ac", + "uuid": "943f85bb-50bc-40c3-8d6f-57dbe34c87f7", "text": "I don't know that color. Try again." } ], "exits": [ { - "uuid": "1349bebf-4653-407a-ad25-9fa60e7d7464", - "destination_uuid": "48fd5325-d660-4404-bdf3-05ad1b024cc0" + "uuid": "9631dddf-0dd7-4310-b263-5f7cad4795e0", + "destination_uuid": "10c9c241-777f-4010-a841-6e87abed8520" } ] }, { - "uuid": "48fd5325-d660-4404-bdf3-05ad1b024cc0", + "uuid": "10c9c241-777f-4010-a841-6e87abed8520", "router": { "type": "switch", "wait": { "type": "msg", "timeout": { "seconds": 300, - "category_uuid": "4cadf512-1299-468f-85e4-26af9edec193" + "category_uuid": "6e367c0c-65ab-479a-82e3-c597d8e35eef" } }, "result_name": "Color", "categories": [ { - "uuid": "b0c29972-6fd4-485e-83c2-057a3f7a04da", + "uuid": "5563a722-9680-419c-a792-b1fa9df92e06", "name": "Red", - "exit_uuid": "37491e99-f4d3-40ae-9ed1-bff62b0e2529" + "exit_uuid": "66c38ec3-0acd-4bf7-a5d5-278af1bee492" }, { - "uuid": "34a421ac-34cb-49d8-a2a5-534f52c60851", + "uuid": "58284598-805a-4740-8966-dcb09e3b670a", "name": "Green", - "exit_uuid": "456e75bd-32cc-40c1-a5ef-ffef2e57642c" + "exit_uuid": "eb048bdf-17ee-4334-a52b-5e82a20189ac" }, { - "uuid": "8d2e259c-bc3c-464f-8c15-985bc736e212", + "uuid": "c102acfc-8cc5-41fa-89ed-41cbfa362ba6", "name": "Blue", - "exit_uuid": "405cf157-1e43-46d8-a0d1-49adcb539267" + "exit_uuid": "1349bebf-4653-407a-ad25-9fa60e7d7464" }, { - "uuid": "3e2dcf45-ffc0-4197-b5ab-25ed974ea612", + "uuid": "8d2e259c-bc3c-464f-8c15-985bc736e212", "name": "Cyan", - "exit_uuid": "c169352e-1944-4451-8d32-eb39c41cb3ae" + "exit_uuid": "37491e99-f4d3-40ae-9ed1-bff62b0e2529" }, { - "uuid": "58284598-805a-4740-8966-dcb09e3b670a", + "uuid": "c169352e-1944-4451-8d32-eb39c41cb3ae", "name": "Other", - "exit_uuid": "5563a722-9680-419c-a792-b1fa9df92e06" + "exit_uuid": "456e75bd-32cc-40c1-a5ef-ffef2e57642c" }, { - "uuid": "4cadf512-1299-468f-85e4-26af9edec193", + "uuid": "6e367c0c-65ab-479a-82e3-c597d8e35eef", "name": "No Response", - "exit_uuid": "3ffb6f24-2ed8-4fd5-bcc0-b2e2668672a8" + "exit_uuid": "405cf157-1e43-46d8-a0d1-49adcb539267" } ], "operand": "@input", "cases": [ { - "uuid": "c102acfc-8cc5-41fa-89ed-41cbfa362ba6", + "uuid": "3ffb6f24-2ed8-4fd5-bcc0-b2e2668672a8", "type": "has_any_word", "arguments": [ "Red" ], - "category_uuid": "b0c29972-6fd4-485e-83c2-057a3f7a04da" + "category_uuid": "5563a722-9680-419c-a792-b1fa9df92e06" }, { - "uuid": "baf07ebb-8a2a-4e63-aa08-d19aa408cd45", + "uuid": "b0c29972-6fd4-485e-83c2-057a3f7a04da", "type": "has_any_word", "arguments": [ "Green" ], - "category_uuid": "34a421ac-34cb-49d8-a2a5-534f52c60851" + "category_uuid": "58284598-805a-4740-8966-dcb09e3b670a" }, { - "uuid": "3b400f91-db69-42b9-9fe2-24ad556b067a", + "uuid": "34a421ac-34cb-49d8-a2a5-534f52c60851", "type": "has_any_word", "arguments": [ "Blue" ], - "category_uuid": "8d2e259c-bc3c-464f-8c15-985bc736e212" + "category_uuid": "c102acfc-8cc5-41fa-89ed-41cbfa362ba6" }, { - "uuid": "6e367c0c-65ab-479a-82e3-c597d8e35eef", + "uuid": "baf07ebb-8a2a-4e63-aa08-d19aa408cd45", "type": "has_any_word", "arguments": [ "Navy" ], - "category_uuid": "8d2e259c-bc3c-464f-8c15-985bc736e212" + "category_uuid": "c102acfc-8cc5-41fa-89ed-41cbfa362ba6" }, { - "uuid": "7624633a-01a9-48f0-abca-957e7290df0a", + "uuid": "3b400f91-db69-42b9-9fe2-24ad556b067a", "type": "has_any_word", "arguments": [ "Cyan" ], - "category_uuid": "3e2dcf45-ffc0-4197-b5ab-25ed974ea612" + "category_uuid": "8d2e259c-bc3c-464f-8c15-985bc736e212" } ], - "default_category_uuid": "58284598-805a-4740-8966-dcb09e3b670a" + "default_category_uuid": "c169352e-1944-4451-8d32-eb39c41cb3ae" }, "exits": [ { - "uuid": "37491e99-f4d3-40ae-9ed1-bff62b0e2529", - "destination_uuid": "48f2ecb3-8e8e-4f7b-9510-1ee08bd6a434" + "uuid": "66c38ec3-0acd-4bf7-a5d5-278af1bee492", + "destination_uuid": "5253c207-46e8-42a9-998e-a3e54e0e0542" }, { - "uuid": "456e75bd-32cc-40c1-a5ef-ffef2e57642c", - "destination_uuid": "48f2ecb3-8e8e-4f7b-9510-1ee08bd6a434" + "uuid": "eb048bdf-17ee-4334-a52b-5e82a20189ac", + "destination_uuid": "5253c207-46e8-42a9-998e-a3e54e0e0542" }, { - "uuid": "405cf157-1e43-46d8-a0d1-49adcb539267", - "destination_uuid": "48f2ecb3-8e8e-4f7b-9510-1ee08bd6a434" + "uuid": "1349bebf-4653-407a-ad25-9fa60e7d7464", + "destination_uuid": "5253c207-46e8-42a9-998e-a3e54e0e0542" }, { - "uuid": "c169352e-1944-4451-8d32-eb39c41cb3ae" + "uuid": "37491e99-f4d3-40ae-9ed1-bff62b0e2529" }, { - "uuid": "5563a722-9680-419c-a792-b1fa9df92e06", - "destination_uuid": "943f85bb-50bc-40c3-8d6f-57dbe34c87f7" + "uuid": "456e75bd-32cc-40c1-a5ef-ffef2e57642c", + "destination_uuid": "1b828e78-e478-4357-9472-47a30ec1f60b" }, { - "uuid": "3ffb6f24-2ed8-4fd5-bcc0-b2e2668672a8", - "destination_uuid": "f4495f19-37ee-4e51-a7d5-d99ef6be147a" + "uuid": "405cf157-1e43-46d8-a0d1-49adcb539267", + "destination_uuid": "b0ae4ad9-5def-4778-8b0a-818d0f4bd3cf" } ] }, { - "uuid": "48f2ecb3-8e8e-4f7b-9510-1ee08bd6a434", + "uuid": "5253c207-46e8-42a9-998e-a3e54e0e0542", "actions": [ { "type": "send_msg", - "uuid": "aac779a9-e2a6-4a11-9efa-9670e081a33a", + "uuid": "3e2dcf45-ffc0-4197-b5ab-25ed974ea612", "text": "Good choice, I like @results.color.category_localized too! What is your favorite beer?" } ], "exits": [ { - "uuid": "0f0e66a8-9062-444f-b636-3d5374466e31", - "destination_uuid": "1b828e78-e478-4357-9472-47a30ec1f60b" + "uuid": "7624633a-01a9-48f0-abca-957e7290df0a", + "destination_uuid": "48f2ecb3-8e8e-4f7b-9510-1ee08bd6a434" } ] }, { - "uuid": "b0ae4ad9-5def-4778-8b0a-818d0f4bd3cf", + "uuid": "48fd5325-d660-4404-bdf3-05ad1b024cc0", "actions": [ { "type": "send_msg", - "uuid": "0891f63c-9e82-42bb-a815-8b44aff33046", + "uuid": "4cadf512-1299-468f-85e4-26af9edec193", "text": "I don't know that one, try again please." } ], "exits": [ { - "uuid": "b341b58e-58fe-41bf-b26e-6274765ccc0e", - "destination_uuid": "1b828e78-e478-4357-9472-47a30ec1f60b" + "uuid": "aac779a9-e2a6-4a11-9efa-9670e081a33a", + "destination_uuid": "48f2ecb3-8e8e-4f7b-9510-1ee08bd6a434" } ] }, { - "uuid": "1b828e78-e478-4357-9472-47a30ec1f60b", + "uuid": "48f2ecb3-8e8e-4f7b-9510-1ee08bd6a434", "router": { "type": "switch", "wait": { @@ -242,109 +242,109 @@ "result_name": "Beer", "categories": [ { - "uuid": "a03dceb1-7ac1-491d-93ef-23d3e099633b", + "uuid": "87b850ff-ddc5-4add-8a4f-c395c3a9ac38", "name": "Mutzig", - "exit_uuid": "e4697b6f-12a9-47ae-a927-96d95d9f8f77" + "exit_uuid": "0f0e66a8-9062-444f-b636-3d5374466e31" }, { - "uuid": "58119801-ed31-4538-888d-23779a01707f", + "uuid": "b9d718d3-b5e0-4d26-998e-2da31b24f2f9", "name": "Primus", - "exit_uuid": "d03c8f97-9f3b-4a6a-8ba9-bdc82a6f09b8" + "exit_uuid": "0891f63c-9e82-42bb-a815-8b44aff33046" }, { - "uuid": "2ba89eb6-6981-4c0d-a19d-3cf1fde52a43", + "uuid": "f1ca9ac8-d0aa-4758-a969-195be7330267", "name": "Turbo King", - "exit_uuid": "e0ec2076-2746-43b4-a410-c3af47d6a121" + "exit_uuid": "b341b58e-58fe-41bf-b26e-6274765ccc0e" }, { - "uuid": "ada3d96a-a1a2-41eb-aac7-febdb98a9b4c", + "uuid": "dbc3b9d2-e6ce-4ebe-9552-8ddce482c1d1", "name": "Skol", - "exit_uuid": "87b850ff-ddc5-4add-8a4f-c395c3a9ac38" + "exit_uuid": "e4697b6f-12a9-47ae-a927-96d95d9f8f77" }, { - "uuid": "b9d718d3-b5e0-4d26-998e-2da31b24f2f9", + "uuid": "e0ec2076-2746-43b4-a410-c3af47d6a121", "name": "Other", - "exit_uuid": "a813de57-c92a-4128-804d-56e80b332142" + "exit_uuid": "d03c8f97-9f3b-4a6a-8ba9-bdc82a6f09b8" } ], "operand": "@input", "cases": [ { - "uuid": "f1ca9ac8-d0aa-4758-a969-195be7330267", + "uuid": "a813de57-c92a-4128-804d-56e80b332142", "type": "has_any_word", "arguments": [ "Mutzig" ], - "category_uuid": "a03dceb1-7ac1-491d-93ef-23d3e099633b" + "category_uuid": "87b850ff-ddc5-4add-8a4f-c395c3a9ac38" }, { - "uuid": "dbc3b9d2-e6ce-4ebe-9552-8ddce482c1d1", + "uuid": "a03dceb1-7ac1-491d-93ef-23d3e099633b", "type": "has_any_word", "arguments": [ "Primus" ], - "category_uuid": "58119801-ed31-4538-888d-23779a01707f" + "category_uuid": "b9d718d3-b5e0-4d26-998e-2da31b24f2f9" }, { - "uuid": "52d7a9ab-52b7-4e82-ba7f-672fb8d6ec91", + "uuid": "58119801-ed31-4538-888d-23779a01707f", "type": "has_any_word", "arguments": [ "Turbo King" ], - "category_uuid": "2ba89eb6-6981-4c0d-a19d-3cf1fde52a43" + "category_uuid": "f1ca9ac8-d0aa-4758-a969-195be7330267" }, { - "uuid": "fc551cb4-e797-4076-b40a-433c44ad492b", + "uuid": "2ba89eb6-6981-4c0d-a19d-3cf1fde52a43", "type": "has_any_word", "arguments": [ "Skol" ], - "category_uuid": "ada3d96a-a1a2-41eb-aac7-febdb98a9b4c" + "category_uuid": "dbc3b9d2-e6ce-4ebe-9552-8ddce482c1d1" } ], - "default_category_uuid": "b9d718d3-b5e0-4d26-998e-2da31b24f2f9" + "default_category_uuid": "e0ec2076-2746-43b4-a410-c3af47d6a121" }, "exits": [ { - "uuid": "e4697b6f-12a9-47ae-a927-96d95d9f8f77", - "destination_uuid": "a84399b1-0e7b-42ee-8759-473137b510db" + "uuid": "0f0e66a8-9062-444f-b636-3d5374466e31", + "destination_uuid": "333fa9a0-85a3-47c5-817e-153a1a124991" }, { - "uuid": "d03c8f97-9f3b-4a6a-8ba9-bdc82a6f09b8", - "destination_uuid": "a84399b1-0e7b-42ee-8759-473137b510db" + "uuid": "0891f63c-9e82-42bb-a815-8b44aff33046", + "destination_uuid": "333fa9a0-85a3-47c5-817e-153a1a124991" }, { - "uuid": "e0ec2076-2746-43b4-a410-c3af47d6a121", - "destination_uuid": "a84399b1-0e7b-42ee-8759-473137b510db" + "uuid": "b341b58e-58fe-41bf-b26e-6274765ccc0e", + "destination_uuid": "333fa9a0-85a3-47c5-817e-153a1a124991" }, { - "uuid": "87b850ff-ddc5-4add-8a4f-c395c3a9ac38", - "destination_uuid": "a84399b1-0e7b-42ee-8759-473137b510db" + "uuid": "e4697b6f-12a9-47ae-a927-96d95d9f8f77", + "destination_uuid": "333fa9a0-85a3-47c5-817e-153a1a124991" }, { - "uuid": "a813de57-c92a-4128-804d-56e80b332142", - "destination_uuid": "b0ae4ad9-5def-4778-8b0a-818d0f4bd3cf" + "uuid": "d03c8f97-9f3b-4a6a-8ba9-bdc82a6f09b8", + "destination_uuid": "48fd5325-d660-4404-bdf3-05ad1b024cc0" } ] }, { - "uuid": "a84399b1-0e7b-42ee-8759-473137b510db", + "uuid": "333fa9a0-85a3-47c5-817e-153a1a124991", "actions": [ { "type": "send_msg", - "uuid": "e87aeeab-8ede-4173-bc76-8f5583ea7207", + "uuid": "52d7a9ab-52b7-4e82-ba7f-672fb8d6ec91", "text": "Mmmmm... delicious @results.beer.category_localized. If only they made @(lower(results.color)) @results.beer.category_localized! Lastly, what is your name?" } ], "exits": [ { - "uuid": "491f3ed1-9154-4acb-8fdd-0a37567e0574", - "destination_uuid": "8c2504ef-0acc-405f-9efe-d5fc2c434a93" + "uuid": "ada3d96a-a1a2-41eb-aac7-febdb98a9b4c", + "destination_uuid": "a84399b1-0e7b-42ee-8759-473137b510db" } ] }, { - "uuid": "8c2504ef-0acc-405f-9efe-d5fc2c434a93", + "uuid": "a84399b1-0e7b-42ee-8759-473137b510db", "router": { "type": "switch", "wait": { @@ -353,123 +353,123 @@ "result_name": "Name", "categories": [ { - "uuid": "e92b12c5-1817-468e-aa2f-8791fb6247e9", + "uuid": "e87aeeab-8ede-4173-bc76-8f5583ea7207", "name": "All Responses", - "exit_uuid": "a602e75e-0814-4034-bb95-770906ddfe34" + "exit_uuid": "fc551cb4-e797-4076-b40a-433c44ad492b" } ], "operand": "@input", "cases": [], - "default_category_uuid": "e92b12c5-1817-468e-aa2f-8791fb6247e9" + "default_category_uuid": "e87aeeab-8ede-4173-bc76-8f5583ea7207" }, "exits": [ { - "uuid": "a602e75e-0814-4034-bb95-770906ddfe34", - "destination_uuid": "5253c207-46e8-42a9-998e-a3e54e0e0542" + "uuid": "fc551cb4-e797-4076-b40a-433c44ad492b", + "destination_uuid": "5456940a-d3f7-481a-bffe-debdb02c2108" } ] }, { - "uuid": "5253c207-46e8-42a9-998e-a3e54e0e0542", + "uuid": "5456940a-d3f7-481a-bffe-debdb02c2108", "actions": [ { "type": "send_msg", - "uuid": "cb6fc9b4-d6e9-4ed3-8a11-3f4d19654a48", + "uuid": "491f3ed1-9154-4acb-8fdd-0a37567e0574", "text": "Thanks @results.name, we are all done!" } ], "exits": [ { - "uuid": "1470d5e6-08dd-479b-a207-9b2b27b924d3" + "uuid": "a602e75e-0814-4034-bb95-770906ddfe34" } ] }, { - "uuid": "f4495f19-37ee-4e51-a7d5-d99ef6be147a", + "uuid": "b0ae4ad9-5def-4778-8b0a-818d0f4bd3cf", "actions": [ { "type": "send_msg", - "uuid": "cc711204-3dd4-499d-9d37-b477bf5c5458", + "uuid": "e92b12c5-1817-468e-aa2f-8791fb6247e9", "text": "Sorry you can't participate right now, I'll try again later." } ], "exits": [ { - "uuid": "96940d27-44e1-49e5-afd3-a00c27bd3914" + "uuid": "cb6fc9b4-d6e9-4ed3-8a11-3f4d19654a48" } ] } ], "_ui": { "nodes": { - "1b828e78-e478-4357-9472-47a30ec1f60b": { + "10c9c241-777f-4010-a841-6e87abed8520": { "type": "wait_for_response", "position": { - "top": 387, - "left": 112 + "top": 129, + "left": 98 } }, - "333fa9a0-85a3-47c5-817e-153a1a124991": { + "1b828e78-e478-4357-9472-47a30ec1f60b": { "type": "execute_actions", "position": { - "top": 0, - "left": 100 + "top": 8, + "left": 456 } }, - "48f2ecb3-8e8e-4f7b-9510-1ee08bd6a434": { + "333fa9a0-85a3-47c5-817e-153a1a124991": { "type": "execute_actions", "position": { - "top": 237, - "left": 131 + "top": 535, + "left": 191 } }, - "48fd5325-d660-4404-bdf3-05ad1b024cc0": { + "48f2ecb3-8e8e-4f7b-9510-1ee08bd6a434": { "type": "wait_for_response", "position": { - "top": 129, - "left": 98 + "top": 387, + "left": 112 } }, - "5253c207-46e8-42a9-998e-a3e54e0e0542": { + "48fd5325-d660-4404-bdf3-05ad1b024cc0": { "type": "execute_actions", "position": { - "top": 805, - "left": 191 + "top": 265, + "left": 512 } }, - "8c2504ef-0acc-405f-9efe-d5fc2c434a93": { - "type": "wait_for_response", + "5253c207-46e8-42a9-998e-a3e54e0e0542": { + "type": "execute_actions", "position": { - "top": 702, - "left": 191 + "top": 237, + "left": 131 } }, - "943f85bb-50bc-40c3-8d6f-57dbe34c87f7": { + "5456940a-d3f7-481a-bffe-debdb02c2108": { "type": "execute_actions", "position": { - "top": 8, - "left": 456 + "top": 805, + "left": 191 } }, "a84399b1-0e7b-42ee-8759-473137b510db": { - "type": "execute_actions", + "type": "wait_for_response", "position": { - "top": 535, + "top": 702, "left": 191 } }, "b0ae4ad9-5def-4778-8b0a-818d0f4bd3cf": { "type": "execute_actions", "position": { - "top": 265, - "left": 512 + "top": 1278, + "left": 752 } }, - "f4495f19-37ee-4e51-a7d5-d99ef6be147a": { + "b4664fbd-3495-4fc6-aa8b-b397857dcd68": { "type": "execute_actions", "position": { - "top": 1278, - "left": 752 + "top": 0, + "left": 100 } } }, diff --git a/web/po/testdata/multiple_flows.es.po b/web/po/testdata/multiple_flows.es.po index fb5796330..c54321a08 100644 --- a/web/po/testdata/multiple_flows.es.po +++ b/web/po/testdata/multiple_flows.es.po @@ -10,90 +10,90 @@ msgstr "" "Language-3: spa\n" "Source-Flows: 9de3663f-c5c5-4c92-9f45-ecbc09abcc85; 5890fe3a-f204-4661-b74d-025be4ee019c\n" -#: Pick+a+Number/b634f07f-7b2d-47bd-8795-051e56cf2609/name:0 +#: Pick+a+Number/0d15ae52-5ad9-4d64-9c64-e27545d48a19/name:0 msgid "1-10" msgstr "" -#: Favorites/e92b12c5-1817-468e-aa2f-8791fb6247e9/name:0 -#: Pick+a+Number/0c873115-191b-41fd-9fc5-6acfe556041a/name:0 +#: Favorites/e87aeeab-8ede-4173-bc76-8f5583ea7207/name:0 +#: Pick+a+Number/225915f1-fb26-48a5-b457-d2ea4300b575/name:0 msgid "All Responses" msgstr "" -#: Favorites/8d2e259c-bc3c-464f-8c15-985bc736e212/name:0 +#: Favorites/c102acfc-8cc5-41fa-89ed-41cbfa362ba6/name:0 msgid "Blue" msgstr "" -#: Favorites/3e2dcf45-ffc0-4197-b5ab-25ed974ea612/name:0 +#: Favorites/8d2e259c-bc3c-464f-8c15-985bc736e212/name:0 msgid "Cyan" msgstr "" -#: Favorites/aac779a9-e2a6-4a11-9efa-9670e081a33a/text:0 +#: Favorites/3e2dcf45-ffc0-4197-b5ab-25ed974ea612/text:0 msgid "Good choice, I like @results.color.category_localized too! What is your favorite beer?" msgstr "" -#: Favorites/34a421ac-34cb-49d8-a2a5-534f52c60851/name:0 +#: Favorites/58284598-805a-4740-8966-dcb09e3b670a/name:0 msgid "Green" msgstr "" -#: Favorites/eb048bdf-17ee-4334-a52b-5e82a20189ac/text:0 +#: Favorites/943f85bb-50bc-40c3-8d6f-57dbe34c87f7/text:0 msgid "I don't know that color. Try again." msgstr "" -#: Favorites/0891f63c-9e82-42bb-a815-8b44aff33046/text:0 +#: Favorites/4cadf512-1299-468f-85e4-26af9edec193/text:0 msgid "I don't know that one, try again please." msgstr "" -#: Favorites/e87aeeab-8ede-4173-bc76-8f5583ea7207/text:0 +#: Favorites/52d7a9ab-52b7-4e82-ba7f-672fb8d6ec91/text:0 msgid "Mmmmm... delicious @results.beer.category_localized. If only they made @(lower(results.color)) @results.beer.category_localized! Lastly, what is your name?" msgstr "" -#: Favorites/a03dceb1-7ac1-491d-93ef-23d3e099633b/name:0 +#: Favorites/87b850ff-ddc5-4add-8a4f-c395c3a9ac38/name:0 msgid "Mutzig" msgstr "" -#: Favorites/4cadf512-1299-468f-85e4-26af9edec193/name:0 +#: Favorites/6e367c0c-65ab-479a-82e3-c597d8e35eef/name:0 msgid "No Response" msgstr "" -#: Favorites/58284598-805a-4740-8966-dcb09e3b670a/name:0 -#: Favorites/b9d718d3-b5e0-4d26-998e-2da31b24f2f9/name:0 -#: Pick+a+Number/f90c9734-3e58-4c07-96cc-315266c8ecfd/name:0 +#: Favorites/c169352e-1944-4451-8d32-eb39c41cb3ae/name:0 +#: Favorites/e0ec2076-2746-43b4-a410-c3af47d6a121/name:0 +#: Pick+a+Number/34ef666f-24a0-41cc-b364-e8b08a6e89ff/name:0 msgid "Other" msgstr "" -#: Pick+a+Number/7210f035-0591-45e1-832b-d27c943eb402/text:0 +#: Pick+a+Number/a39e1d4b-ceda-45e5-b889-11ddcbc77fde/text:0 msgid "Pick a number between 1-10." msgstr "" -#: Favorites/58119801-ed31-4538-888d-23779a01707f/name:0 +#: Favorites/b9d718d3-b5e0-4d26-998e-2da31b24f2f9/name:0 msgid "Primus" msgstr "" -#: Favorites/b0c29972-6fd4-485e-83c2-057a3f7a04da/name:0 +#: Favorites/5563a722-9680-419c-a792-b1fa9df92e06/name:0 msgid "Red" msgstr "" -#: Favorites/ada3d96a-a1a2-41eb-aac7-febdb98a9b4c/name:0 +#: Favorites/dbc3b9d2-e6ce-4ebe-9552-8ddce482c1d1/name:0 msgid "Skol" msgstr "" -#: Favorites/cc711204-3dd4-499d-9d37-b477bf5c5458/text:0 +#: Favorites/e92b12c5-1817-468e-aa2f-8791fb6247e9/text:0 msgid "Sorry you can't participate right now, I'll try again later." msgstr "" -#: Favorites/cb6fc9b4-d6e9-4ed3-8a11-3f4d19654a48/text:0 +#: Favorites/491f3ed1-9154-4acb-8fdd-0a37567e0574/text:0 msgid "Thanks @results.name, we are all done!" msgstr "" -#: Favorites/2ba89eb6-6981-4c0d-a19d-3cf1fde52a43/name:0 +#: Favorites/f1ca9ac8-d0aa-4758-a969-195be7330267/name:0 msgid "Turbo King" msgstr "" -#: Favorites/9631dddf-0dd7-4310-b263-5f7cad4795e0/text:0 +#: Favorites/8c2504ef-0acc-405f-9efe-d5fc2c434a93/text:0 msgid "What is your favorite color?" msgstr "" -#: Pick+a+Number/225915f1-fb26-48a5-b457-d2ea4300b575/text:0 +#: Pick+a+Number/f90c9734-3e58-4c07-96cc-315266c8ecfd/text:0 msgid "You picked @results.number!" msgstr "" From 507470f081ede3b3f90b3a8d50cb693240e4c37e Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 8 Aug 2024 11:57:24 -0500 Subject: [PATCH 008/216] Update CHANGELOG.md for v9.3.2 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cf0862c1..e83c9ad9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.2 (2024-08-08) +------------------------- + * Update test database + * Replace ticket bodies with notes on the open event + v9.3.1 (2024-08-02) ------------------------- * Update to latest goflow/gocommon From 971245b9451e3d5844e27be4b444413fbe5d8e80 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 7 Aug 2024 16:46:31 -0500 Subject: [PATCH 009/216] Authenticate metrics endpoint using org.prometheus_token instead of an API token --- core/models/assets.go | 2 +- core/models/orgs.go | 88 ++++++++++++++++++++------------ core/models/orgs_test.go | 14 ++--- core/models/tokens.go | 51 ------------------ core/models/users_test.go | 1 - mailroom_test.dump | Bin 1763419 -> 1764514 bytes testsuite/testdata/constants.go | 1 - web/org/metrics.go | 39 ++++++++------ web/org/metrics_test.go | 22 +++----- 9 files changed, 93 insertions(+), 125 deletions(-) delete mode 100644 core/models/tokens.go diff --git a/core/models/assets.go b/core/models/assets.go index 7da56214d..f20572d28 100644 --- a/core/models/assets.go +++ b/core/models/assets.go @@ -167,7 +167,7 @@ func NewOrgAssets(ctx context.Context, rt *runtime.Runtime, orgID OrgID, prev *O var err error if prev == nil || refresh&RefreshOrg > 0 { - oa.org, err = LoadOrg(ctx, rt.Config, db, orgID) + oa.org, err = LoadOrg(ctx, db, orgID) if err != nil { return nil, fmt.Errorf("error loading environment for org %d: %w", orgID, err) } diff --git a/core/models/orgs.go b/core/models/orgs.go index 6fe88154e..01f7fe8c7 100644 --- a/core/models/orgs.go +++ b/core/models/orgs.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "io" - "log/slog" "mime" "net/http" "path/filepath" @@ -17,6 +16,7 @@ import ( "github.com/nyaruka/gocommon/dbutil" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/jsonx" + "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/engine" @@ -56,9 +56,12 @@ func airtimeServiceFactory(rt *runtime.Runtime) engine.AirtimeServiceFactory { } } -// OrgID is our type for orgs ids +// OrgID is our type for org ids type OrgID int +// OrgUUID is our type for org UUIDs +type OrgUUID uuids.UUID + const ( // NilOrgID is the id 0 considered as nil org id NilOrgID = OrgID(0) @@ -70,11 +73,14 @@ const ( // Org is mailroom's type for RapidPro orgs. It also implements the envs.Environment interface for GoFlow type Org struct { o struct { - ID OrgID `json:"id"` - ParentID OrgID `json:"parent_id"` - Suspended bool `json:"is_suspended"` - FlowSMTP null.String `json:"flow_smtp"` - Config null.Map[any] `json:"config"` + UUID OrgUUID `json:"uuid"` + ID OrgID `json:"id"` + ParentID OrgID `json:"parent_id"` + Name string `json:"name"` + Suspended bool `json:"is_suspended"` + FlowSMTP null.String `json:"flow_smtp"` + PrometheusToken null.String `json:"prometheus_token"` + Config null.Map[any] `json:"config"` } env envs.Environment } @@ -82,12 +88,18 @@ type Org struct { // ID returns the id of the org func (o *Org) ID() OrgID { return o.o.ID } +// Name returns the name of the org +func (o *Org) Name() string { return o.o.Name } + // Suspended returns whether the org has been suspended func (o *Org) Suspended() bool { return o.o.Suspended } // FlowSMTP provides custom SMTP settings for flow sessions func (o *Org) FlowSMTP() string { return string(o.o.FlowSMTP) } +// FlowSMTP provides custom SMTP settings for flow sessions +func (o *Org) PrometheusToken() string { return string(o.o.PrometheusToken) } + // Environment returns this org as an engine environment func (o *Org) Environment() envs.Environment { return o.env } @@ -202,36 +214,15 @@ func orgFromAssets(sa flows.SessionAssets) *Org { return sa.Source().(*OrgAssets).Org() } -// LoadOrg loads the org for the passed in id, returning any error encountered -func LoadOrg(ctx context.Context, cfg *runtime.Config, db *sql.DB, orgID OrgID) (*Org, error) { - start := time.Now() - - org := &Org{} - rows, err := db.QueryContext(ctx, selectOrgByID, orgID) - if err != nil { - return nil, fmt.Errorf("error loading org: %d: %w", orgID, err) - } - defer rows.Close() - if !rows.Next() { - return nil, fmt.Errorf("no org with id: %d", orgID) - } - - err = dbutil.ScanJSON(rows, org) - if err != nil { - return nil, fmt.Errorf("error unmarshalling org: %w", err) - } - - slog.Debug("loaded org environment", "elapsed", time.Since(start), "org_id", orgID) - - return org, nil -} - -const selectOrgByID = ` +const sqlSelectOrgByID = ` SELECT ROW_TO_JSON(o) FROM (SELECT + uuid, id, parent_id, + name, is_suspended, flow_smtp, + prometheus_token, o.config AS config, (SELECT CASE date_format WHEN 'D' THEN 'DD-MM-YYYY' WHEN 'M' THEN 'MM-DD-YYYY' ELSE 'YYYY-MM-DD' END) AS date_format, 'tt:mm' AS time_format, @@ -247,5 +238,36 @@ SELECT ROW_TO_JSON(o) FROM (SELECT ), '' ) AS default_country FROM orgs_org o - WHERE o.id = $1 + WHERE id = $1 ) o` + +// LoadOrg loads the org for the passed in id, returning any error encountered +func LoadOrg(ctx context.Context, db *sql.DB, orgID OrgID) (*Org, error) { + org := &Org{} + rows, err := db.QueryContext(ctx, sqlSelectOrgByID, orgID) + if err != nil { + return nil, fmt.Errorf("error loading org: %d: %w", orgID, err) + } + defer rows.Close() + if !rows.Next() { + return nil, fmt.Errorf("no org with id: %d", orgID) + } + + err = dbutil.ScanJSON(rows, org) + if err != nil { + return nil, fmt.Errorf("error unmarshalling org: %w", err) + } + + return org, nil +} + +// GetOrgIDFromUUID gets an org ID from a UUID (returns NilOrgID if not found) +func GetOrgIDFromUUID(ctx context.Context, db *sql.DB, orgUUID OrgUUID) (OrgID, error) { + var orgID OrgID + err := db.QueryRowContext(ctx, `SELECT id FROM orgs_org WHERE uuid = $1`, orgUUID).Scan(&orgID) + if err != nil && err != sql.ErrNoRows { + return NilOrgID, fmt.Errorf("error getting org id by uuid: %w", err) + } + + return orgID, nil +} diff --git a/core/models/orgs_test.go b/core/models/orgs_test.go index b03881850..1c8439840 100644 --- a/core/models/orgs_test.go +++ b/core/models/orgs_test.go @@ -33,7 +33,7 @@ func TestLoadOrg(t *testing.T) { rt.DB.MustExec(`UPDATE orgs_org SET flow_languages = '{}' WHERE id = $1`, testdata.Org2.ID) rt.DB.MustExec(`UPDATE orgs_org SET date_format = 'M' WHERE id = $1`, testdata.Org2.ID) - org, err := models.LoadOrg(ctx, rt.Config, rt.DB.DB, testdata.Org1.ID) + org, err := models.LoadOrg(ctx, rt.DB.DB, testdata.Org1.ID) assert.NoError(t, err) assert.Equal(t, models.OrgID(1), org.ID()) @@ -48,7 +48,7 @@ func TestLoadOrg(t *testing.T) { assert.Equal(t, i18n.Language("fra"), org.Environment().DefaultLanguage()) assert.Equal(t, i18n.Locale("fra-US"), org.Environment().DefaultLocale()) - org, err = models.LoadOrg(ctx, rt.Config, rt.DB.DB, testdata.Org2.ID) + org, err = models.LoadOrg(ctx, rt.DB.DB, testdata.Org2.ID) assert.NoError(t, err) assert.True(t, org.Suspended()) assert.Equal(t, "", org.FlowSMTP()) @@ -57,7 +57,7 @@ func TestLoadOrg(t *testing.T) { assert.Equal(t, i18n.NilLanguage, org.Environment().DefaultLanguage()) assert.Equal(t, i18n.NilLocale, org.Environment().DefaultLocale()) - _, err = models.LoadOrg(ctx, rt.Config, rt.DB.DB, 99) + _, err = models.LoadOrg(ctx, rt.DB.DB, 99) assert.EqualError(t, err, "no org with id: 99") } @@ -70,9 +70,9 @@ func TestEmailService(t *testing.T) { rt.DB.MustExec(`UPDATE orgs_org SET parent_id = $2 WHERE id = $1`, testdata.Org2.ID, testdata.Org1.ID) models.FlushCache() - org1, err := models.LoadOrg(ctx, rt.Config, rt.DB.DB, testdata.Org1.ID) + org1, err := models.LoadOrg(ctx, rt.DB.DB, testdata.Org1.ID) require.NoError(t, err) - org2, err := models.LoadOrg(ctx, rt.Config, rt.DB.DB, testdata.Org2.ID) + org2, err := models.LoadOrg(ctx, rt.DB.DB, testdata.Org2.ID) require.NoError(t, err) // no SMTP config by default.. no email service @@ -91,7 +91,7 @@ func TestEmailService(t *testing.T) { rt.DB.MustExec(`UPDATE orgs_org SET flow_smtp = 'smtp://zed:123@flows.com?from=foo%40flows.com' WHERE id = $1`, testdata.Org1.ID) models.FlushCache() - org1, err = models.LoadOrg(ctx, rt.Config, rt.DB.DB, testdata.Org1.ID) + org1, err = models.LoadOrg(ctx, rt.DB.DB, testdata.Org1.ID) require.NoError(t, err) svc, err = org1.EmailService(ctx, rt, nil) @@ -112,7 +112,7 @@ func TestStoreAttachment(t *testing.T) { image, err := os.Open("testdata/test.jpg") require.NoError(t, err) - org, err := models.LoadOrg(ctx, rt.Config, rt.DB.DB, testdata.Org1.ID) + org, err := models.LoadOrg(ctx, rt.DB.DB, testdata.Org1.ID) assert.NoError(t, err) attachment, err := org.StoreAttachment(context.Background(), rt, "668383ba-387c-49bc-b164-1213ac0ea7aa.jpg", "image/jpeg", image) diff --git a/core/models/tokens.go b/core/models/tokens.go deleted file mode 100644 index dac8e19cf..000000000 --- a/core/models/tokens.go +++ /dev/null @@ -1,51 +0,0 @@ -package models - -import ( - "context" - "database/sql" - - "github.com/nyaruka/gocommon/uuids" -) - -// OrgReference is just a reference for an org, containing the id, uuid and name for the org -type OrgReference struct { - ID OrgID `db:"id"` - UUID uuids.UUID `db:"uuid"` - Name string `db:"name"` -} - -const lookupOrgByUUIDAndTokenSQL = ` -SELECT - o.id AS id, - o.uuid as uuid, - o.name AS name -FROM - orgs_org o -JOIN - api_apitoken a -ON - a.org_id = o.id -JOIN - auth_group g -ON - a.role_id = g.id -WHERE - a.is_active = TRUE AND - o.is_active = TRUE AND - o.uuid = $1::uuid AND - g.name = $2 AND - a.key = $3; -` - -// LookupOrgByUUIDAndToken looks up an OrgReference for the given UUID and token -func LookupOrgByUUIDAndToken(ctx context.Context, db DBorTx, orgUUID uuids.UUID, permission string, token string) (*OrgReference, error) { - org := &OrgReference{} - err := db.GetContext(ctx, org, lookupOrgByUUIDAndTokenSQL, orgUUID, permission, token) - if err == sql.ErrNoRows { - return nil, nil - } - if err != nil { - return nil, err - } - return org, nil -} diff --git a/core/models/users_test.go b/core/models/users_test.go index 2f8f30e9a..615c5c15b 100644 --- a/core/models/users_test.go +++ b/core/models/users_test.go @@ -32,7 +32,6 @@ func TestLoadUsers(t *testing.T) { {id: testdata.Admin.ID, email: testdata.Admin.Email, name: "Andy Admin", role: models.UserRoleAdministrator, team: office}, {id: testdata.Agent.ID, email: testdata.Agent.Email, name: "Ann D'Agent", role: models.UserRoleAgent, team: partners}, {id: testdata.Editor.ID, email: testdata.Editor.Email, name: "Ed McEditor", role: models.UserRoleEditor, team: office}, - {id: testdata.Surveyor.ID, email: testdata.Surveyor.Email, name: "Steve Surveys", role: models.UserRoleSurveyor, team: nil}, {id: testdata.Viewer.ID, email: testdata.Viewer.Email, name: "Veronica Views", role: models.UserRoleViewer, team: nil}, } diff --git a/mailroom_test.dump b/mailroom_test.dump index 9c26fac9165a7b7a23f02a2729d221d082a530d5..a524a885eebf74686491ebf46a85523680d1ef12 100644 GIT binary patch delta 70922 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 delta 68915 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 A;Q#;t diff --git a/testsuite/testdata/constants.go b/testsuite/testdata/constants.go index 5cff490f7..566fa72dd 100644 --- a/testsuite/testdata/constants.go +++ b/testsuite/testdata/constants.go @@ -21,7 +21,6 @@ var Admin = &User{3, "admin1@nyaruka.com"} var Editor = &User{4, "editor1@nyaruka.com"} var Viewer = &User{5, "viewer1@nyaruka.com"} var Agent = &User{6, "agent1@nyaruka.com"} -var Surveyor = &User{7, "surveyor1@nyaruka.com"} var TwilioChannel = &Channel{10000, "74729f45-7f29-4868-9dc4-90e491e3c7d8", "T"} var VonageChannel = &Channel{10001, "19012bfd-3ce3-4cae-9bb9-76cf92c73d49", "NX"} diff --git a/web/org/metrics.go b/web/org/metrics.go index cdb0b2450..f473a6d6e 100644 --- a/web/org/metrics.go +++ b/web/org/metrics.go @@ -6,7 +6,6 @@ import ( "net/http" "github.com/golang/protobuf/proto" - "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/runtime" @@ -34,8 +33,8 @@ type groupCountRow struct { Count int64 `db:"count"` } -func calculateGroupCounts(ctx context.Context, rt *runtime.Runtime, org *models.OrgReference) (*dto.MetricFamily, error) { - rows, err := rt.DB.QueryxContext(ctx, groupCountsSQL, org.ID) +func calculateGroupCounts(ctx context.Context, rt *runtime.Runtime, org *models.Org) (*dto.MetricFamily, error) { + rows, err := rt.DB.QueryxContext(ctx, groupCountsSQL, org.ID()) if err != nil { return nil, fmt.Errorf("error querying group counts for org: %w", err) } @@ -77,7 +76,7 @@ func calculateGroupCounts(ctx context.Context, rt *runtime.Runtime, org *models. }, { Name: proto.String("org"), - Value: proto.String(org.Name), + Value: proto.String(org.Name()), }, }, Gauge: &dto.Gauge{ @@ -116,8 +115,8 @@ type channelStats struct { Counts map[string]int64 } -func calculateChannelCounts(ctx context.Context, rt *runtime.Runtime, org *models.OrgReference) (*dto.MetricFamily, error) { - rows, err := rt.DB.QueryxContext(ctx, channelCountsSQL, org.ID) +func calculateChannelCounts(ctx context.Context, rt *runtime.Runtime, org *models.Org) (*dto.MetricFamily, error) { + rows, err := rt.DB.QueryxContext(ctx, channelCountsSQL, org.ID()) if err != nil { return nil, fmt.Errorf("error querying channel counts for org: %w", err) } @@ -218,7 +217,7 @@ func calculateChannelCounts(ctx context.Context, rt *runtime.Runtime, org *model }, { Name: proto.String("org"), - Value: proto.String(org.Name), + Value: proto.String(org.Name()), }, }, Gauge: &dto.Gauge{ @@ -241,26 +240,36 @@ func handleMetrics(ctx context.Context, rt *runtime.Runtime, r *http.Request, ra return nil } - orgUUID := uuids.UUID(r.PathValue("uuid")) - org, err := models.LookupOrgByUUIDAndToken(ctx, rt.DB, orgUUID, "Prometheus", token) + orgID, err := models.GetOrgIDFromUUID(ctx, rt.DB.DB, models.OrgUUID(r.PathValue("uuid"))) if err != nil { - return fmt.Errorf("error looking up org for token: %w", err) + return fmt.Errorf("error looking up org by UUID: %w", err) } - if org == nil { + if orgID == models.NilOrgID { rawW.WriteHeader(http.StatusUnauthorized) rawW.Write([]byte(`{"error": "invalid authentication"}`)) return nil } - groups, err := calculateGroupCounts(ctx, rt, org) + oa, err := models.GetOrgAssets(ctx, rt, orgID) if err != nil { - return fmt.Errorf("error calculating group counts for org: %d: %w", org.ID, err) + return fmt.Errorf("unable to load org assets: %w", err) } - channels, err := calculateChannelCounts(ctx, rt, org) + if oa.Org().PrometheusToken() != token { + rawW.WriteHeader(http.StatusUnauthorized) + rawW.Write([]byte(`{"error": "invalid authentication"}`)) + return nil + } + + groups, err := calculateGroupCounts(ctx, rt, oa.Org()) + if err != nil { + return fmt.Errorf("error calculating group counts for org: %d: %w", oa.OrgID(), err) + } + + channels, err := calculateChannelCounts(ctx, rt, oa.Org()) if err != nil { - return fmt.Errorf("error calculating channel counts for org: %d: %w", org.ID, err) + return fmt.Errorf("error calculating channel counts for org: %d: %w", oa.OrgID(), err) } rawW.WriteHeader(http.StatusOK) diff --git a/web/org/metrics_test.go b/web/org/metrics_test.go index 91784de4a..e0647d845 100644 --- a/web/org/metrics_test.go +++ b/web/org/metrics_test.go @@ -20,10 +20,7 @@ func TestMetrics(t *testing.T) { defer testsuite.Reset(testsuite.ResetAll) promToken := "2d26a50841ff48237238bbdd021150f6a33a4196" - rt.DB.MustExec(`INSERT INTO api_apitoken(is_active, org_id, created, key, role_id, user_id) VALUES(TRUE, $1, NOW(), $2, $3, 1);`, testdata.Org1.ID, promToken, testdata.AuthGroupIDs["Prometheus"]) - - adminToken := "5c26a50841ff48237238bbdd021150f6a33a4199" - rt.DB.MustExec(`INSERT INTO api_apitoken(is_active, org_id, created, key, role_id, user_id) VALUES(TRUE, $1, NOW(), $2, $3, 1);`, testdata.Org1.ID, adminToken, testdata.AuthGroupIDs["Administrators"]) + rt.DB.MustExec(`UPDATE orgs_org SET prometheus_token = $1 WHERE id = $2`, promToken, testdata.Org1.ID) wg := &sync.WaitGroup{} server := web.NewServer(ctx, rt, wg) @@ -42,42 +39,35 @@ func TestMetrics(t *testing.T) { Contains []string }{ { - Label: "no username", + Label: "no auth provided", URL: fmt.Sprintf("http://localhost:8091/mr/org/%s/metrics", testdata.Org1.UUID), Username: "", Password: "", Response: `{"error": "invalid authentication"}`, }, { - Label: "invalid password", + Label: "invalid password (token)", URL: fmt.Sprintf("http://localhost:8091/mr/org/%s/metrics", testdata.Org1.UUID), Username: "metrics", Password: "invalid", Response: `{"error": "invalid authentication"}`, }, { - Label: "invalid username", + Label: "invalid username (always metrics)", URL: fmt.Sprintf("http://localhost:8091/mr/org/%s/metrics", testdata.Org1.UUID), Username: "invalid", Password: promToken, Response: `{"error": "invalid authentication"}`, }, { - Label: "valid login, wrong org", + Label: "valid token but wrong org", URL: fmt.Sprintf("http://localhost:8091/mr/org/%s/metrics", testdata.Org2.UUID), Username: "metrics", Password: promToken, Response: `{"error": "invalid authentication"}`, }, { - Label: "valid login, invalid user", - URL: fmt.Sprintf("http://localhost:8091/mr/org/%s/metrics", testdata.Org1.UUID), - Username: "metrics", - Password: adminToken, - Response: `{"error": "invalid authentication"}`, - }, - { - Label: "valid", + Label: "valid auth", URL: fmt.Sprintf("http://localhost:8091/mr/org/%s/metrics", testdata.Org1.UUID), Username: "metrics", Password: promToken, From e19dd46b39a107a7688f45e7473db39d48cc5afe Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 8 Aug 2024 14:08:51 -0500 Subject: [PATCH 010/216] Re-add test database --- mailroom_test.dump | Bin 1764514 -> 1763441 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/mailroom_test.dump b/mailroom_test.dump index a524a885eebf74686491ebf46a85523680d1ef12..4d43736dab624a46bdc4292a44ae520d315e9ae1 100644 GIT binary patch delta 66220 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¥|Q7~cWX<1UyvNGZp1KjWF3Db{Fk7|>_e??Bv} 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 Date: Thu, 8 Aug 2024 14:31:04 -0500 Subject: [PATCH 011/216] Don't try to load ticket body when loading tickets with a contact --- core/models/contacts.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/models/contacts.go b/core/models/contacts.go index 600714d24..1845bb146 100644 --- a/core/models/contacts.go +++ b/core/models/contacts.go @@ -531,7 +531,6 @@ type contactEnvelope struct { Tickets []struct { UUID flows.TicketUUID `json:"uuid"` TopicID TopicID `json:"topic_id"` - Body string `json:"body"` AssigneeID UserID `json:"assignee_id"` } `json:"tickets"` CreatedOn time.Time `json:"created_on"` @@ -587,7 +586,7 @@ LEFT JOIN ( SELECT contact_id, array_agg( - json_build_object('uuid', t.uuid, 'body', t.body, 'topic_id', t.topic_id, 'assignee_id', t.assignee_id) ORDER BY t.opened_on DESC, t.id DESC + json_build_object('uuid', t.uuid, 'topic_id', t.topic_id, 'assignee_id', t.assignee_id) ORDER BY t.opened_on DESC, t.id DESC ) as tickets FROM tickets_ticket t From 55e855664fa8d1cf1fb3b10630123cba8233819f Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 8 Aug 2024 15:17:06 -0500 Subject: [PATCH 012/216] Update CHANGELOG.md for v9.3.3 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e83c9ad9e..661ef76c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.3 (2024-08-08) +------------------------- + * Don't try to load ticket body when loading tickets with a contact + * Authenticate metrics endpoint using org.prometheus_token instead of an API token + v9.3.2 (2024-08-08) ------------------------- * Update test database From 328c2e45bdd2a78423a1d4f88d666223922707f3 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 19 Aug 2024 15:40:34 -0500 Subject: [PATCH 013/216] Update to latest goflow --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d5346c5ae..ead307f19 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 github.com/nyaruka/gocommon v1.57.1 - github.com/nyaruka/goflow v0.221.1 + github.com/nyaruka/goflow v0.222.0 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 github.com/nyaruka/rp-indexer/v9 v9.2.0 diff --git a/go.sum b/go.sum index ed09f7b9a..d16fe1e23 100644 --- a/go.sum +++ b/go.sum @@ -154,8 +154,8 @@ github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= github.com/nyaruka/gocommon v1.57.1 h1:s/9WLZAOUNg0dQHZkgpx8qQMUtNJ2mqzxQhdk1+UBn4= github.com/nyaruka/gocommon v1.57.1/go.mod h1:wa++Yu/8PEP/HwfvXZrGlKZUCPSJAtSJHeuVsWtLOPM= -github.com/nyaruka/goflow v0.221.1 h1:IEXC4fI9FYWWbL0kqd0/jE9WMCn3ph/ZzR30M76V2eo= -github.com/nyaruka/goflow v0.221.1/go.mod h1:NK8xsonXqdipHPDgze4f4L9BHWUGYwDCj5Xg9UVM6Ec= +github.com/nyaruka/goflow v0.222.0 h1:VcpgtzZSdXME2dk0VFXS2qSd1/p5mOppoYOb+mxn6Zs= +github.com/nyaruka/goflow v0.222.0/go.mod h1:NK8xsonXqdipHPDgze4f4L9BHWUGYwDCj5Xg9UVM6Ec= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= From 190911dd83ec628e4eb488ee526cb4a8dce89ad1 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 19 Aug 2024 15:40:54 -0500 Subject: [PATCH 014/216] Update CHANGELOG.md for v9.3.4 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 661ef76c7..e895eb762 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.4 (2024-08-19) +------------------------- + * Update to latest goflow + v9.3.3 (2024-08-08) ------------------------- * Don't try to load ticket body when loading tickets with a contact From 3690095dcee5de74d521f00caa11de84bc255ab6 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 19 Aug 2024 16:37:23 -0500 Subject: [PATCH 015/216] Fix ticket modifier test --- web/contact/testdata/modify.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/contact/testdata/modify.json b/web/contact/testdata/modify.json index 3439bb387..3ffcea58f 100644 --- a/web/contact/testdata/modify.json +++ b/web/contact/testdata/modify.json @@ -1368,7 +1368,7 @@ "uuid": "0a8f2e00-fef6-402c-bd79-d789446ec0e0", "name": "Support" }, - "body": "Need help", + "note": "Need help", "assignee": { "email": "admin1@nyaruka.com", "name": "Andy Admin" From d801a5eb619a42987939dc5f49e91334388252f0 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 19 Aug 2024 16:49:13 -0500 Subject: [PATCH 016/216] Update CHANGELOG.md for v9.3.5 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e895eb762..151db837e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.5 (2024-08-19) +------------------------- + * Fix ticket modifier test + v9.3.4 (2024-08-19) ------------------------- * Update to latest goflow From 742b12a81e1556fd1e7d33d6234e5ac2c803a9ad Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 21 Aug 2024 10:03:58 -0500 Subject: [PATCH 017/216] Update to latest goflow (with phone parsing workaround) --- go.mod | 44 ++++++++++++++-------------- go.sum | 92 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 68 insertions(+), 68 deletions(-) diff --git a/go.mod b/go.mod index ead307f19..5f35b97ff 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 github.com/nyaruka/gocommon v1.57.1 - github.com/nyaruka/goflow v0.222.0 + github.com/nyaruka/goflow v0.222.1 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 github.com/nyaruka/rp-indexer/v9 v9.2.0 @@ -31,18 +31,18 @@ require ( github.com/samber/slog-sentry v1.2.2 github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.9.0 - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 - google.golang.org/api v0.188.0 + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa + google.golang.org/api v0.193.0 ) require ( - cloud.google.com/go v0.115.0 // indirect - cloud.google.com/go/auth v0.7.1 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect + cloud.google.com/go v0.115.1 // indirect + cloud.google.com/go/auth v0.9.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect cloud.google.com/go/compute/metadata v0.5.0 // indirect - cloud.google.com/go/firestore v1.15.0 // indirect - cloud.google.com/go/iam v1.1.11 // indirect - cloud.google.com/go/longrunning v0.5.10 // indirect + cloud.google.com/go/firestore v1.16.0 // indirect + cloud.google.com/go/iam v1.2.0 // indirect + cloud.google.com/go/longrunning v0.6.0 // indirect cloud.google.com/go/storage v1.43.0 // indirect github.com/MicahParks/keyfunc v1.9.0 // indirect github.com/Shopify/gomail v0.0.0-20220729171026-0784ece65e69 // indirect @@ -59,10 +59,10 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/google/s2a-go v0.1.7 // indirect + github.com/google/s2a-go v0.1.8 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.5 // indirect + github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect @@ -73,7 +73,7 @@ require ( github.com/nyaruka/null/v2 v2.0.3 // indirect github.com/nyaruka/phonenumbers v1.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/samber/lo v1.46.0 // indirect + github.com/samber/lo v1.47.0 // indirect github.com/sergi/go-diff v1.3.1 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect @@ -81,17 +81,17 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.5.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/oauth2 v0.22.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/time v0.6.0 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect - google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect + google.golang.org/genproto v0.0.0-20240820151423-278611b39280 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240820151423-278611b39280 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240820151423-278611b39280 // indirect google.golang.org/grpc v1.65.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect diff --git a/go.sum b/go.sum index d16fe1e23..a17f6ca3e 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,18 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= -cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= -cloud.google.com/go/auth v0.7.1 h1:Iv1bbpzJ2OIg16m94XI9/tlzZZl3cdeR3nGVGj78N7s= -cloud.google.com/go/auth v0.7.1/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs= -cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI= -cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I= +cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ= +cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc= +cloud.google.com/go/auth v0.9.0 h1:cYhKl1JUhynmxjXfrk4qdPc6Amw7i+GC9VLflgT0p5M= +cloud.google.com/go/auth v0.9.0/go.mod h1:2HsApZBr9zGZhC9QAXsYVYaWk8kNUt37uny+XVKi7wM= +cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= +cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= -cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8= -cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk= -cloud.google.com/go/iam v1.1.11 h1:0mQ8UKSfdHLut6pH9FM3bI55KWR46ketn0PuXleDyxw= -cloud.google.com/go/iam v1.1.11/go.mod h1:biXoiLWYIKntto2joP+62sd9uW5EpkZmKIvfNcTWlnQ= -cloud.google.com/go/longrunning v0.5.10 h1:eB/BniENNRKhjz/xgiillrdcH3G74TGSl3BXinGlI7E= -cloud.google.com/go/longrunning v0.5.10/go.mod h1:tljz5guTr5oc/qhlUjBlk7UAIFMOGuPNxkNDZXlLics= +cloud.google.com/go/firestore v1.16.0 h1:YwmDHcyrxVRErWcgxunzEaZxtNbc8QoFYA/JOEwDPgc= +cloud.google.com/go/firestore v1.16.0/go.mod h1:+22v/7p+WNBSQwdSwP57vz47aZiY+HrDkrOsJNhk7rg= +cloud.google.com/go/iam v1.2.0 h1:kZKMKVNk/IsSSc/udOb83K0hL/Yh/Gcqpz+oAkoIFN8= +cloud.google.com/go/iam v1.2.0/go.mod h1:zITGuWgsLZxd8OwAlX+eMFgZDXzBm7icj1PVTYG766Q= +cloud.google.com/go/longrunning v0.6.0 h1:mM1ZmaNsQsnb+5n1DNPeL0KwQd9jQRqSqSDEkBZr+aI= +cloud.google.com/go/longrunning v0.6.0/go.mod h1:uHzSZqW89h7/pasCWNYdUpwGz3PcVWhrWupreVPYLts= cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= @@ -112,15 +112,15 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= -github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= +github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= @@ -154,8 +154,8 @@ github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= github.com/nyaruka/gocommon v1.57.1 h1:s/9WLZAOUNg0dQHZkgpx8qQMUtNJ2mqzxQhdk1+UBn4= github.com/nyaruka/gocommon v1.57.1/go.mod h1:wa++Yu/8PEP/HwfvXZrGlKZUCPSJAtSJHeuVsWtLOPM= -github.com/nyaruka/goflow v0.222.0 h1:VcpgtzZSdXME2dk0VFXS2qSd1/p5mOppoYOb+mxn6Zs= -github.com/nyaruka/goflow v0.222.0/go.mod h1:NK8xsonXqdipHPDgze4f4L9BHWUGYwDCj5Xg9UVM6Ec= +github.com/nyaruka/goflow v0.222.1 h1:QrqHStCp1dNf5JmT2oxGry2sTv+UV2uGOkZ3DxytGqY= +github.com/nyaruka/goflow v0.222.1/go.mod h1:NK8xsonXqdipHPDgze4f4L9BHWUGYwDCj5Xg9UVM6Ec= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= @@ -181,8 +181,8 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= -github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ= -github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= +github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= +github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/samber/slog-multi v1.2.0 h1:JIebVdmeGkCMd5/ticlmU+aDYl4tdAZBmp5uLaSzr0k= github.com/samber/slog-multi v1.2.0/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo= github.com/samber/slog-sentry v1.2.2 h1:S0glIVITlGCCfSvIOte2Sh63HMHJpYN3hDr+97hILIk= @@ -211,18 +211,18 @@ go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= -go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -236,17 +236,17 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -255,18 +255,18 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -276,8 +276,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.188.0 h1:51y8fJ/b1AaaBRJr4yWm96fPcuxSo0JcegXE3DaHQHw= -google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag= +google.golang.org/api v0.193.0 h1:eOGDoJFsLU+HpCBaDJex2fWiYujAw9KbXgpOAMePoUs= +google.golang.org/api v0.193.0/go.mod h1:Po3YMV1XZx+mTku3cfJrlIYR03wiGrCOsdpC67hjZvw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw= @@ -285,12 +285,12 @@ google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d h1:/hmn0Ku5kWij/kjGsrcJeC1T/MrJi2iNWwgAqrihFwc= -google.golang.org/genproto v0.0.0-20240711142825-46eb208f015d/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= -google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY= -google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d h1:JU0iKnSg02Gmb5ZdV8nYsKEKsP6o/FGVWTrw4i1DA9A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto v0.0.0-20240820151423-278611b39280 h1:oKt8r1ZvaPqBe3oeGTdyx1iNjuBS+VJcc9QdU1CD3d8= +google.golang.org/genproto v0.0.0-20240820151423-278611b39280/go.mod h1:wxEc5TmU9JSLs1rSqG4z1YzeSNigp/9yIojIPuZVvKQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240820151423-278611b39280 h1:YDFM9oOjiFhaMAVgbDxfxW+66nRrsvzQzJ51wp3OxC0= +google.golang.org/genproto/googleapis/api v0.0.0-20240820151423-278611b39280/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240820151423-278611b39280 h1:XQMA2e105XNlEZ8NRF0HqnUOZzP14sUSsgL09kpdNnU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240820151423-278611b39280/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= From 56eef3e780af0c4893f0c8d23821d68758ad3728 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 21 Aug 2024 10:19:43 -0500 Subject: [PATCH 018/216] Update CHANGELOG.md for v9.3.6 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 151db837e..e1c908955 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.6 (2024-08-21) +------------------------- + * Update to latest goflow (with phone parsing workaround) + v9.3.5 (2024-08-19) ------------------------- * Fix ticket modifier test From 7be96532cb391027511ec88f6ffa8b752a10a77d Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 22 Aug 2024 13:37:31 -0500 Subject: [PATCH 019/216] WIP --- .github/workflows/ci.yml | 5 +++++ runtime/config.go | 6 ++++++ testsuite/testdata/data/dynamo.json | 18 ++++++++++++++++++ .../testdata/data/postgres.dump | Bin testsuite/testsuite.go | 12 +++++++++++- 5 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 testsuite/testdata/data/dynamo.json rename mailroom_test.dump => testsuite/testdata/data/postgres.dump (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 70b90fdc2..e5ac39354 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,11 @@ jobs: psql -h localhost -U postgres --no-password -c "ALTER ROLE mailroom_test WITH SUPERUSER;" psql -h localhost -U postgres --no-password -c "CREATE DATABASE mailroom_test;" + - name: Install and start DynamoDB + uses: rrainn/dynamodb-action@v2.0.1 + with: + port: 6000 + - name: Install Go uses: actions/setup-go@v5 with: diff --git a/runtime/config.go b/runtime/config.go index 554a27da9..ee0634bf5 100644 --- a/runtime/config.go +++ b/runtime/config.go @@ -67,6 +67,9 @@ type Config struct { S3LogsBucket string `help:"S3 bucket to write channel logs to"` S3Minio bool `help:"S3 is actually Minio or other compatible service"` + DynamoEndpoint string `help:"DynamoDB service endpoint, e.g. https://dynamodb.us-east-1.amazonaws.com"` + DynamoTablePrefix string `help:"prefix to use for DynamoDB tables"` + CourierAuthToken string `help:"the authentication token used for requests to Courier"` LibratoUsername string `help:"the username that will be used to authenticate to Librato"` LibratoToken string `help:"the token that will be used to authenticate to Librato"` @@ -124,6 +127,9 @@ func NewDefaultConfig() *Config { S3SessionsBucket: "temba-sessions", S3LogsBucket: "temba-logs", + DynamoEndpoint: "", // let library generate it + DynamoTablePrefix: "Temba", + InstanceID: hostname, LogLevel: slog.LevelWarn, UUIDSeed: 0, diff --git a/testsuite/testdata/data/dynamo.json b/testsuite/testdata/data/dynamo.json new file mode 100644 index 000000000..4bfdc081b --- /dev/null +++ b/testsuite/testdata/data/dynamo.json @@ -0,0 +1,18 @@ +[ + { + "TableName": "ChannelLogsAttached", + "KeySchema": [ + { + "AttributeName": "UUID", + "KeyType": "HASH" + } + ], + "AttributeDefinitions": [ + { + "AttributeName": "UUID", + "AttributeType": "S" + } + ], + "BillingMode": "PAY_PER_REQUEST" + } +] \ No newline at end of file diff --git a/mailroom_test.dump b/testsuite/testdata/data/postgres.dump similarity index 100% rename from mailroom_test.dump rename to testsuite/testdata/data/postgres.dump diff --git a/testsuite/testsuite.go b/testsuite/testsuite.go index e79df0623..e8c029493 100644 --- a/testsuite/testsuite.go +++ b/testsuite/testsuite.go @@ -36,6 +36,7 @@ const ( ResetRedis = ResetFlag(1 << 3) ResetStorage = ResetFlag(1 << 4) ResetElastic = ResetFlag(1 << 5) + ResetDynamo = ResetFlag(1 << 6) ) // Reset clears out both our database and redis DB @@ -56,6 +57,9 @@ func Reset(what ResetFlag) { if what&ResetElastic > 0 { resetElastic(ctx, rt) } + if what&ResetDynamo > 0 { + resetDynamo(ctx, rt) + } models.FlushCache() } @@ -72,6 +76,8 @@ func Runtime() (context.Context, *runtime.Runtime) { cfg.S3SessionsBucket = "test-sessions" cfg.S3LogsBucket = "test-logs" cfg.S3Minio = true + cfg.DynamoEndpoint = "http://localhost:6000" + cfg.DynamoTablePrefix = "Test" s3svc, err := s3x.NewService(cfg.AWSAccessKeyID, cfg.AWSSecretAccessKey, cfg.AWSRegion, cfg.S3Endpoint, cfg.S3Minio) noError(err) @@ -157,7 +163,7 @@ func resetDB() { } func loadTestDump() { - dump, err := os.Open(absPath("./mailroom_test.dump")) + dump, err := os.Open(absPath("./testdata/data/postgres.dump")) must(err) defer dump.Close() @@ -221,6 +227,10 @@ func resetElastic(ctx context.Context, rt *runtime.Runtime) { ReindexElastic(ctx) } +func resetDynamo(ctx context.Context, rt *runtime.Runtime) { + +} + var sqlResetTestData = ` UPDATE contacts_contact SET current_flow_id = NULL; From 0d72521bb46567e098dfb0db6ce7b3adc57ab0c8 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 22 Aug 2024 13:40:47 -0500 Subject: [PATCH 020/216] Update to aws-sdk-go-v2 --- core/models/channel_logs.go | 6 ++--- core/models/orgs.go | 4 ++-- core/models/sessions.go | 6 ++--- go.mod | 22 ++++++++++++++--- go.sum | 48 +++++++++++++++++++++++++++++-------- mailroom.go | 2 +- runtime/runtime.go | 2 +- testsuite/testsuite.go | 2 +- 8 files changed, 68 insertions(+), 24 deletions(-) diff --git a/core/models/channel_logs.go b/core/models/channel_logs.go index 5731b201f..13c2b12d8 100644 --- a/core/models/channel_logs.go +++ b/core/models/channel_logs.go @@ -7,11 +7,11 @@ import ( "path" "time" - "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/nyaruka/gocommon/aws/s3x" "github.com/nyaruka/gocommon/dates" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/jsonx" - "github.com/nyaruka/gocommon/s3x" "github.com/nyaruka/gocommon/stringsx" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" @@ -193,7 +193,7 @@ func InsertChannelLogs(ctx context.Context, rt *runtime.Runtime, logs []*Channel Key: l.path(), ContentType: "application/json", Body: jsonx.MustMarshal(l), - ACL: s3.ObjectCannedACLPrivate, + ACL: types.ObjectCannedACLPrivate, } } if err := rt.S3.BatchPut(ctx, uploads, 32); err != nil { diff --git a/core/models/orgs.go b/core/models/orgs.go index 01f7fe8c7..8051637ee 100644 --- a/core/models/orgs.go +++ b/core/models/orgs.go @@ -12,7 +12,7 @@ import ( "path/filepath" "time" - "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/nyaruka/gocommon/dbutil" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/jsonx" @@ -184,7 +184,7 @@ func (o *Org) StoreAttachment(ctx context.Context, rt *runtime.Runtime, filename path := o.attachmentPath("attachments", filename) - url, err := rt.S3.PutObject(ctx, rt.Config.S3AttachmentsBucket, path, contentType, contentBytes, s3.BucketCannedACLPublicRead) + url, err := rt.S3.PutObject(ctx, rt.Config.S3AttachmentsBucket, path, contentType, contentBytes, types.ObjectCannedACLPublicRead) if err != nil { return "", fmt.Errorf("unable to store attachment content: %w", err) } diff --git a/core/models/sessions.go b/core/models/sessions.go index 7d0a6b1e4..5b905d8ad 100644 --- a/core/models/sessions.go +++ b/core/models/sessions.go @@ -11,11 +11,11 @@ import ( "path" "time" - "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/gomodule/redigo/redis" "github.com/jmoiron/sqlx" "github.com/lib/pq" - "github.com/nyaruka/gocommon/s3x" + "github.com/nyaruka/gocommon/aws/s3x" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/envs" @@ -808,7 +808,7 @@ func WriteSessionOutputsToStorage(ctx context.Context, rt *runtime.Runtime, sess Key: s.StoragePath(), Body: []byte(s.Output()), ContentType: "application/json", - ACL: s3.ObjectCannedACLPrivate, + ACL: types.ObjectCannedACLPrivate, } } diff --git a/go.mod b/go.mod index 5f35b97ff..8ad46f51f 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( firebase.google.com/go/v4 v4.14.1 github.com/Masterminds/semver v1.5.0 github.com/appleboy/go-fcm v1.2.1 - github.com/aws/aws-sdk-go v1.55.5 + github.com/aws/aws-sdk-go-v2/service/s3 v1.60.0 github.com/buger/jsonparser v1.1.1 github.com/elastic/go-elasticsearch/v8 v8.14.0 github.com/getsentry/sentry-go v0.28.1 @@ -19,7 +19,7 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 - github.com/nyaruka/gocommon v1.57.1 + github.com/nyaruka/gocommon v1.58.0 github.com/nyaruka/goflow v0.222.1 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 @@ -47,6 +47,23 @@ require ( github.com/MicahParks/keyfunc v1.9.0 // indirect github.com/Shopify/gomail v0.0.0-20220729171026-0784ece65e69 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/aws/aws-sdk-go-v2 v1.30.4 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect + github.com/aws/aws-sdk-go-v2/config v1.27.28 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.28 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.30.4 // indirect + github.com/aws/smithy-go v1.20.4 // indirect github.com/blevesearch/segment v0.9.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/elastic/elastic-transport-go/v8 v8.6.0 // indirect @@ -64,7 +81,6 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/naoina/go-stringutil v0.1.0 // indirect diff --git a/go.sum b/go.sum index a17f6ca3e..9a9293bd7 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,42 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/appleboy/go-fcm v1.2.1 h1:NhpACabtRuAplYg6bTNfSr3LBwsSuutP55HsphzLU/g= github.com/appleboy/go-fcm v1.2.1/go.mod h1:5FzMN+9J2sxnkoys9h3y48GQH8HnI637Q/ro/uP2Qsk= -github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= -github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go-v2 v1.30.4 h1:frhcagrVNrzmT95RJImMHgabt99vkXGslubDaDagTk8= +github.com/aws/aws-sdk-go-v2 v1.30.4/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 h1:70PVAiL15/aBMh5LThwgXdSQorVr91L127ttckI9QQU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4/go.mod h1:/MQxMqci8tlqDH+pjmoLu1i0tbWCUP1hhyMRuFxpQCw= +github.com/aws/aws-sdk-go-v2/config v1.27.28 h1:OTxWGW/91C61QlneCtnD62NLb4W616/NM1jA8LhJqbg= +github.com/aws/aws-sdk-go-v2/config v1.27.28/go.mod h1:uzVRVtJSU5EFv6Fu82AoVFKozJi2ZCY6WRCXj06rbvs= +github.com/aws/aws-sdk-go-v2/credentials v1.17.28 h1:m8+AHY/ND8CMHJnPoH7PJIRakWGa4gbfbxuY9TGTUXM= +github.com/aws/aws-sdk-go-v2/credentials v1.17.28/go.mod h1:6TF7dSc78ehD1SL6KpRIPKMA1GyyWflIkjqg+qmf4+c= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 h1:yjwoSyDZF8Jth+mUk5lSPJCkMC0lMy6FaCD51jm6ayE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12/go.mod h1:fuR57fAgMk7ot3WcNQfb6rSEn+SUffl7ri+aa8uKysI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 h1:TNyt/+X43KJ9IJJMjKfa3bNTiZbUP7DeCxfbTROESwY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16/go.mod h1:2DwJF39FlNAUiX5pAc0UNeiz16lK2t7IaFcm0LFHEgc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 h1:jYfy8UPmd+6kJW5YhY0L1/KftReOGxI/4NtVSTh9O/I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16/go.mod h1:7ZfEPZxkW42Afq4uQB8H2E2e6ebh6mXTueEpYzjCzcs= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16 h1:mimdLQkIX1zr8GIPY1ZtALdBQGxcASiBd2MOp8m/dMc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16/go.mod h1:YHk6owoSwrIsok+cAH9PENCOGoH5PU2EllX4vLtSrsY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18 h1:GckUnpm4EJOAio1c8o25a+b3lVfwVzC9gnSBqiiNmZM= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18/go.mod h1:Br6+bxfG33Dk3ynmkhsW2Z/t9D4+lRqdLDNCKi85w0U= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 h1:tJ5RnkHCiSH0jyd6gROjlJtNwov0eGYNz8s8nFcR0jQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18/go.mod h1:++NHzT+nAF7ZPrHPsA+ENvsXkOO8wEu+C6RXltAG4/c= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 h1:jg16PhLPUiHIj8zYIW6bqzeQSuHVEiWnGA0Brz5Xv2I= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16/go.mod h1:Uyk1zE1VVdsHSU7096h/rwnXDzOzYQVl+FNPhPw7ShY= +github.com/aws/aws-sdk-go-v2/service/s3 v1.60.0 h1:2QXGJvG19QwqXUvgcdoCOZPyLuvZf8LiXPCN4P53TdI= +github.com/aws/aws-sdk-go-v2/service/s3 v1.60.0/go.mod h1:BSPI0EfnYUuNHPS0uqIo5VrRwzie+Fp+YhQOUs16sKI= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 h1:zCsFCKvbj25i7p1u94imVoO447I/sFv8qq+lGJhRN0c= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.5/go.mod h1:ZeDX1SnKsVlejeuz41GiajjZpRSWR7/42q/EyA/QEiM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 h1:SKvPgvdvmiTWoi0GAJ7AsJfOz3ngVkD/ERbs5pUnHNI= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5/go.mod h1:20sz31hv/WsPa3HhU3hfrIet2kxM4Pe0r20eBZ20Tac= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.4 h1:iAckBT2OeEK/kBDyN/jDtpEExhjeeA/Im2q4X0rJZT8= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.4/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0= +github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= +github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -125,10 +159,6 @@ github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -152,8 +182,8 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= -github.com/nyaruka/gocommon v1.57.1 h1:s/9WLZAOUNg0dQHZkgpx8qQMUtNJ2mqzxQhdk1+UBn4= -github.com/nyaruka/gocommon v1.57.1/go.mod h1:wa++Yu/8PEP/HwfvXZrGlKZUCPSJAtSJHeuVsWtLOPM= +github.com/nyaruka/gocommon v1.58.0 h1:q8/YCWQOEiFHVnYW+f5N+HjI0R4gjV93bOpVWqImr8k= +github.com/nyaruka/gocommon v1.58.0/go.mod h1:u0n6zC7AxmrUZxzY4VtZU1d26QJ8nhqRRb5IFOw/Lig= github.com/nyaruka/goflow v0.222.1 h1:QrqHStCp1dNf5JmT2oxGry2sTv+UV2uGOkZ3DxytGqY= github.com/nyaruka/goflow v0.222.1/go.mod h1:NK8xsonXqdipHPDgze4f4L9BHWUGYwDCj5Xg9UVM6Ec= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= @@ -320,8 +350,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/mailroom.go b/mailroom.go index 9d3601e9b..73eb99884 100644 --- a/mailroom.go +++ b/mailroom.go @@ -12,7 +12,7 @@ import ( "github.com/elastic/go-elasticsearch/v8" "github.com/jmoiron/sqlx" "github.com/nyaruka/gocommon/analytics" - "github.com/nyaruka/gocommon/s3x" + "github.com/nyaruka/gocommon/aws/s3x" "github.com/nyaruka/mailroom/core/tasks" "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/mailroom/web" diff --git a/runtime/runtime.go b/runtime/runtime.go index f145ffdd5..547a12888 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -8,7 +8,7 @@ import ( "github.com/elastic/go-elasticsearch/v8" "github.com/gomodule/redigo/redis" "github.com/jmoiron/sqlx" - "github.com/nyaruka/gocommon/s3x" + "github.com/nyaruka/gocommon/aws/s3x" ) // Runtime represents the set of services required to run many Mailroom functions. Used as a wrapper for diff --git a/testsuite/testsuite.go b/testsuite/testsuite.go index e79df0623..460a45c82 100644 --- a/testsuite/testsuite.go +++ b/testsuite/testsuite.go @@ -11,7 +11,7 @@ import ( "github.com/elastic/go-elasticsearch/v8" "github.com/gomodule/redigo/redis" "github.com/jmoiron/sqlx" - "github.com/nyaruka/gocommon/s3x" + "github.com/nyaruka/gocommon/aws/s3x" "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/redisx/assertredis" From 5e749676dd888ab8ca282f4230c8a58f3b3ee100 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 22 Aug 2024 13:54:45 -0500 Subject: [PATCH 021/216] Delete unused test-smtp command --- .gitignore | 1 - cmd/test-smtp/main.go | 48 ------------------------------------------- 2 files changed, 49 deletions(-) delete mode 100644 cmd/test-smtp/main.go diff --git a/.gitignore b/.gitignore index 8b4cfbc0a..7cf51226c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ *.so *.dylib mailroom -test-smtp dist .vscode .envrc diff --git a/cmd/test-smtp/main.go b/cmd/test-smtp/main.go deleted file mode 100644 index f9e0c1ea5..000000000 --- a/cmd/test-smtp/main.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "fmt" - "log/slog" - "os" - - "github.com/nyaruka/ezconf" - "github.com/nyaruka/goflow/utils/smtpx" -) - -type config struct { - URL string `help:"the SMTP formatted URL to use to test sending"` - To string `help:"the email address to send to"` - Subject string `help:"the email subject to send"` - Body string `help:"the email body to send"` -} - -func main() { - // get our smtp server config - options := &config{ - URL: "smtp://foo%40zap.com:opensesame@smtp.gmail.com:587/?from=foo%40zap.com&tls=true", - To: "test@temba.io", - Subject: "Test Email", - Body: "Test Body", - } - loader := ezconf.NewLoader( - options, - "test-smtp", "SMTP Tester - test SMTP settings by sending an email", - nil, - ) - loader.MustLoad() - - client, err := smtpx.NewClientFromURL(options.URL) - if err != nil { - slog.Error(fmt.Sprintf("unable to parse smtp config: %s", options.URL), "error", err) - os.Exit(1) - } - - m := smtpx.NewMessage([]string{options.To}, options.Subject, options.Body, "") - - err = smtpx.Send(client, m, nil) - if err != nil { - slog.Error("error sending email", "error", err) - } - - slog.Info("email sent") -} From d8f8e5576f3943de410891b239533d7b1e6a1eda Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 22 Aug 2024 14:11:32 -0500 Subject: [PATCH 022/216] Update CHANGELOG.md for v9.3.7 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1c908955..cf42474e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.7 (2024-08-22) +------------------------- + * Delete unused test-smtp command + * Update to aws-sdk-go-v2 + v9.3.6 (2024-08-21) ------------------------- * Update to latest goflow (with phone parsing workaround) From 70e0df9e0f65e050975e708185b49fae25cf9259 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 22 Aug 2024 14:49:01 -0500 Subject: [PATCH 023/216] Add Dynamo client to runtime --- go.mod | 3 ++ go.sum | 10 ++++++ mailroom.go | 7 ++++ runtime/config.go | 12 +++---- runtime/runtime.go | 2 ++ .../{testdata/data => testfiles}/dynamo.json | 0 .../data => testfiles}/postgres.dump | Bin testsuite/testsuite.go | 31 ++++++++++++++++-- 8 files changed, 57 insertions(+), 8 deletions(-) rename testsuite/{testdata/data => testfiles}/dynamo.json (100%) rename testsuite/{testdata/data => testfiles}/postgres.dump (100%) diff --git a/go.mod b/go.mod index 8ad46f51f..60873e69f 100644 --- a/go.mod +++ b/go.mod @@ -56,8 +56,10 @@ require ( github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.5 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.17 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect @@ -81,6 +83,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/naoina/go-stringutil v0.1.0 // indirect diff --git a/go.sum b/go.sum index 9a9293bd7..6a81a3872 100644 --- a/go.sum +++ b/go.sum @@ -48,10 +48,14 @@ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvK github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16 h1:mimdLQkIX1zr8GIPY1ZtALdBQGxcASiBd2MOp8m/dMc= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16/go.mod h1:YHk6owoSwrIsok+cAH9PENCOGoH5PU2EllX4vLtSrsY= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.5 h1:Cm77yt+/CV7A6DglkENsWA3H1hq8+4ItJnFKrhxHkvg= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.5/go.mod h1:s2fYaueBuCnwv1XQn6T8TfShxJWusv5tWPMcL+GY6+g= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18 h1:GckUnpm4EJOAio1c8o25a+b3lVfwVzC9gnSBqiiNmZM= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18/go.mod h1:Br6+bxfG33Dk3ynmkhsW2Z/t9D4+lRqdLDNCKi85w0U= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.17 h1:HDJGz1jlV7RokVgTPfx1UHBHANC0N5Uk++xgyYgz5E0= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.17/go.mod h1:5szDu6TWdRDytfDxUQVv2OYfpTQMKApVFyqpm+TcA98= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 h1:tJ5RnkHCiSH0jyd6gROjlJtNwov0eGYNz8s8nFcR0jQ= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18/go.mod h1:++NHzT+nAF7ZPrHPsA+ENvsXkOO8wEu+C6RXltAG4/c= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 h1:jg16PhLPUiHIj8zYIW6bqzeQSuHVEiWnGA0Brz5Xv2I= @@ -159,6 +163,10 @@ github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -350,6 +358,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/mailroom.go b/mailroom.go index 73eb99884..66fac4788 100644 --- a/mailroom.go +++ b/mailroom.go @@ -12,6 +12,7 @@ import ( "github.com/elastic/go-elasticsearch/v8" "github.com/jmoiron/sqlx" "github.com/nyaruka/gocommon/analytics" + "github.com/nyaruka/gocommon/aws/dynamo" "github.com/nyaruka/gocommon/aws/s3x" "github.com/nyaruka/mailroom/core/tasks" "github.com/nyaruka/mailroom/runtime" @@ -91,6 +92,12 @@ func (mr *Mailroom) Start() error { log.Warn("fcm not configured, no android syncing") } + // setup DynamoDB + mr.rt.Dynamo, err = dynamo.NewService(c.AWSAccessKeyID, c.AWSSecretAccessKey, c.AWSRegion, c.S3Endpoint, c.DynamoTablePrefix) + if err != nil { + return err + } + // setup S3 storage mr.rt.S3, err = s3x.NewService(c.AWSAccessKeyID, c.AWSSecretAccessKey, c.AWSRegion, c.S3Endpoint, c.S3Minio) if err != nil { diff --git a/runtime/config.go b/runtime/config.go index ee0634bf5..aaa838c69 100644 --- a/runtime/config.go +++ b/runtime/config.go @@ -61,15 +61,15 @@ type Config struct { AWSSecretAccessKey string `help:"secret access key to use for AWS services"` AWSRegion string `help:"region to use for AWS services, e.g. us-east-1"` + DynamoEndpoint string `help:"DynamoDB service endpoint, e.g. https://dynamodb.us-east-1.amazonaws.com"` + DynamoTablePrefix string `help:"prefix to use for DynamoDB tables"` + S3Endpoint string `help:"S3 service endpoint, e.g. https://s3.amazonaws.com"` S3AttachmentsBucket string `help:"S3 bucket to write attachments to"` S3SessionsBucket string `help:"S3 bucket to write flow sessions to"` S3LogsBucket string `help:"S3 bucket to write channel logs to"` S3Minio bool `help:"S3 is actually Minio or other compatible service"` - DynamoEndpoint string `help:"DynamoDB service endpoint, e.g. https://dynamodb.us-east-1.amazonaws.com"` - DynamoTablePrefix string `help:"prefix to use for DynamoDB tables"` - CourierAuthToken string `help:"the authentication token used for requests to Courier"` LibratoUsername string `help:"the username that will be used to authenticate to Librato"` LibratoToken string `help:"the token that will be used to authenticate to Librato"` @@ -122,14 +122,14 @@ func NewDefaultConfig() *Config { AWSSecretAccessKey: "", AWSRegion: "us-east-1", + DynamoEndpoint: "", // let library generate it + DynamoTablePrefix: "Temba", + S3Endpoint: "https://s3.amazonaws.com", S3AttachmentsBucket: "temba-attachments", S3SessionsBucket: "temba-sessions", S3LogsBucket: "temba-logs", - DynamoEndpoint: "", // let library generate it - DynamoTablePrefix: "Temba", - InstanceID: hostname, LogLevel: slog.LevelWarn, UUIDSeed: 0, diff --git a/runtime/runtime.go b/runtime/runtime.go index 547a12888..81b43e046 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -8,6 +8,7 @@ import ( "github.com/elastic/go-elasticsearch/v8" "github.com/gomodule/redigo/redis" "github.com/jmoiron/sqlx" + "github.com/nyaruka/gocommon/aws/dynamo" "github.com/nyaruka/gocommon/aws/s3x" ) @@ -17,6 +18,7 @@ type Runtime struct { DB *sqlx.DB ReadonlyDB *sql.DB RP *redis.Pool + Dynamo *dynamo.Service S3 *s3x.Service ES *elasticsearch.TypedClient FCM FCMClient diff --git a/testsuite/testdata/data/dynamo.json b/testsuite/testfiles/dynamo.json similarity index 100% rename from testsuite/testdata/data/dynamo.json rename to testsuite/testfiles/dynamo.json diff --git a/testsuite/testdata/data/postgres.dump b/testsuite/testfiles/postgres.dump similarity index 100% rename from testsuite/testdata/data/postgres.dump rename to testsuite/testfiles/postgres.dump diff --git a/testsuite/testsuite.go b/testsuite/testsuite.go index cb805d235..ecbc459a2 100644 --- a/testsuite/testsuite.go +++ b/testsuite/testsuite.go @@ -3,15 +3,19 @@ package testsuite import ( "context" "fmt" + "io" "log/slog" "os" "os/exec" "path" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/elastic/go-elasticsearch/v8" "github.com/gomodule/redigo/redis" "github.com/jmoiron/sqlx" + "github.com/nyaruka/gocommon/aws/dynamo" "github.com/nyaruka/gocommon/aws/s3x" + "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/redisx/assertredis" @@ -79,6 +83,9 @@ func Runtime() (context.Context, *runtime.Runtime) { cfg.DynamoEndpoint = "http://localhost:6000" cfg.DynamoTablePrefix = "Test" + dyna, err := dynamo.NewService(cfg.AWSAccessKeyID, cfg.AWSSecretAccessKey, cfg.AWSRegion, cfg.DynamoEndpoint, cfg.DynamoTablePrefix) + noError(err) + s3svc, err := s3x.NewService(cfg.AWSAccessKeyID, cfg.AWSSecretAccessKey, cfg.AWSRegion, cfg.S3Endpoint, cfg.S3Minio) noError(err) @@ -87,8 +94,9 @@ func Runtime() (context.Context, *runtime.Runtime) { DB: dbx, ReadonlyDB: dbx.DB, RP: getRP(), - ES: getES(), + Dynamo: dyna, S3: s3svc, + ES: getES(), FCM: &MockFCMClient{ValidTokens: []string{"FCMID3", "FCMID4", "FCMID5"}}, Config: cfg, } @@ -163,7 +171,7 @@ func resetDB() { } func loadTestDump() { - dump, err := os.Open(absPath("./testdata/data/postgres.dump")) + dump, err := os.Open(absPath("./testsuite/testfiles/postgres.dump")) must(err) defer dump.Close() @@ -228,7 +236,26 @@ func resetElastic(ctx context.Context, rt *runtime.Runtime) { } func resetDynamo(ctx context.Context, rt *runtime.Runtime) { + tablesFile, err := os.Open(absPath("./testsuite/testfiles/dynamo.json")) + must(err) + defer tablesFile.Close() + tablesJSON, err := io.ReadAll(tablesFile) + must(err) + + inputs := []*dynamodb.CreateTableInput{} + jsonx.MustUnmarshal(tablesJSON, &inputs) + + for _, input := range inputs { + // delete table if it exists + if _, err := rt.Dynamo.Client.DescribeTable(ctx, &dynamodb.DescribeTableInput{TableName: input.TableName}); err == nil { + _, err := rt.Dynamo.Client.DeleteTable(ctx, &dynamodb.DeleteTableInput{TableName: input.TableName}) + must(err) + } + + _, err := rt.Dynamo.Client.CreateTable(ctx, input) + must(err) + } } var sqlResetTestData = ` From b4f555bece6cd28ea93f94625bb46bc1c1c9fec6 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 22 Aug 2024 15:43:54 -0500 Subject: [PATCH 024/216] Update CHANGELOG.md for v9.3.8 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf42474e2..1334a5be4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.8 (2024-08-22) +------------------------- + * Add Dynamo client to runtime + v9.3.7 (2024-08-22) ------------------------- * Delete unused test-smtp command From cfa78075d987262d2cc0febb6807ef51a825eb2a Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 22 Aug 2024 16:19:29 -0500 Subject: [PATCH 025/216] Add test of dynamodb reachability to mailroom startup --- mailroom.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mailroom.go b/mailroom.go index 66fac4788..1fbe1c4a0 100644 --- a/mailroom.go +++ b/mailroom.go @@ -93,10 +93,15 @@ func (mr *Mailroom) Start() error { } // setup DynamoDB - mr.rt.Dynamo, err = dynamo.NewService(c.AWSAccessKeyID, c.AWSSecretAccessKey, c.AWSRegion, c.S3Endpoint, c.DynamoTablePrefix) + mr.rt.Dynamo, err = dynamo.NewService(c.AWSAccessKeyID, c.AWSSecretAccessKey, c.AWSRegion, c.DynamoEndpoint, c.DynamoTablePrefix) if err != nil { return err } + if err := mr.rt.Dynamo.Test(mr.ctx, "ChannelLogsAttached"); err != nil { + log.Error("dynamodb not reachable", "error", err) + } else { + log.Info("dynamodb ok") + } // setup S3 storage mr.rt.S3, err = s3x.NewService(c.AWSAccessKeyID, c.AWSSecretAccessKey, c.AWSRegion, c.S3Endpoint, c.S3Minio) From 2cb8e29f2d31dd4aa26e69bdeed533d30554a7bd Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 22 Aug 2024 16:20:24 -0500 Subject: [PATCH 026/216] Update CHANGELOG.md for v9.3.9 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1334a5be4..580e5c826 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.9 (2024-08-22) +------------------------- + * Add test of dynamodb reachability to mailroom startup + v9.3.8 (2024-08-22) ------------------------- * Add Dynamo client to runtime From 397309fb5dbdc55c1474cc62bb5501c80ae0bc0f Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 22 Aug 2024 17:42:53 -0500 Subject: [PATCH 027/216] Tweak error message when loading session from S3 --- core/models/sessions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/models/sessions.go b/core/models/sessions.go index 5b905d8ad..45c0dd7fa 100644 --- a/core/models/sessions.go +++ b/core/models/sessions.go @@ -786,7 +786,7 @@ func FindWaitingSessionForContact(ctx context.Context, rt *runtime.Runtime, oa * _, output, err := rt.S3.GetObject(ctx, rt.Config.S3SessionsBucket, u.Path) if err != nil { - return nil, fmt.Errorf("error reading session from storage: %s: %w", session.OutputURL(), err) + return nil, fmt.Errorf("error reading session from s3 bucket=%s key=%s: %w", rt.Config.S3SessionsBucket, u.Path, err) } slog.Debug("loaded session from storage", "elapsed", time.Since(start), "output_url", session.OutputURL()) From dd869c93a69f55407120b966806a8353e6cec8fc Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 22 Aug 2024 17:43:43 -0500 Subject: [PATCH 028/216] Update CHANGELOG.md for v9.3.10 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 580e5c826..6871728fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.10 (2024-08-22) +------------------------- + * Tweak error message when loading session from S3 + v9.3.9 (2024-08-22) ------------------------- * Add test of dynamodb reachability to mailroom startup From 85493023766eae5016439dcf3b64269e37c431cf Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 22 Aug 2024 18:04:37 -0500 Subject: [PATCH 029/216] Always strip / from session object keys --- core/models/sessions.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/models/sessions.go b/core/models/sessions.go index 45c0dd7fa..760f8c7ea 100644 --- a/core/models/sessions.go +++ b/core/models/sessions.go @@ -9,6 +9,7 @@ import ( "log/slog" "net/url" "path" + "strings" "time" "github.com/aws/aws-sdk-go-v2/service/s3/types" @@ -781,12 +782,13 @@ func FindWaitingSessionForContact(ctx context.Context, rt *runtime.Runtime, oa * if err != nil { return nil, fmt.Errorf("error parsing output URL: %s: %w", session.OutputURL(), err) } + key := strings.TrimPrefix(u.Path, "/") start := time.Now() - _, output, err := rt.S3.GetObject(ctx, rt.Config.S3SessionsBucket, u.Path) + _, output, err := rt.S3.GetObject(ctx, rt.Config.S3SessionsBucket, key) if err != nil { - return nil, fmt.Errorf("error reading session from s3 bucket=%s key=%s: %w", rt.Config.S3SessionsBucket, u.Path, err) + return nil, fmt.Errorf("error reading session from s3 bucket=%s key=%s: %w", rt.Config.S3SessionsBucket, key, err) } slog.Debug("loaded session from storage", "elapsed", time.Since(start), "output_url", session.OutputURL()) From eb46a55980153baba1c642b7b170467afc003d70 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 22 Aug 2024 18:16:23 -0500 Subject: [PATCH 030/216] Update CHANGELOG.md for v9.3.11 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6871728fe..51a5e9076 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.11 (2024-08-22) +------------------------- + * Always strip / from session object keys + v9.3.10 (2024-08-22) ------------------------- * Tweak error message when loading session from S3 From 3023bb681748b1ca6e9f97b25ffd1a0a3b1a8c10 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 9 Sep 2024 17:30:24 -0500 Subject: [PATCH 031/216] Add outbox count to org --- core/models/orgs.go | 6 +++++- core/models/orgs_test.go | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/core/models/orgs.go b/core/models/orgs.go index 8051637ee..e5ea38c94 100644 --- a/core/models/orgs.go +++ b/core/models/orgs.go @@ -81,6 +81,7 @@ type Org struct { FlowSMTP null.String `json:"flow_smtp"` PrometheusToken null.String `json:"prometheus_token"` Config null.Map[any] `json:"config"` + OutboxCount int `json:"outbox_count"` } env envs.Environment } @@ -103,6 +104,8 @@ func (o *Org) PrometheusToken() string { return string(o.o.PrometheusToken) } // Environment returns this org as an engine environment func (o *Org) Environment() envs.Environment { return o.env } +func (o *Org) OutboxCount() int { return o.o.OutboxCount } + // MarshalJSON is our custom marshaller so that our inner env get output func (o *Org) MarshalJSON() ([]byte, error) { return json.Marshal(o.env) @@ -236,7 +239,8 @@ SELECT ROW_TO_JSON(o) FROM (SELECT WHERE c.org_id = o.id AND c.is_active = TRUE AND c.country IS NOT NULL GROUP BY c.country ORDER BY count(c.country) desc, country LIMIT 1 ), '' - ) AS default_country + ) AS default_country, + (SELECT SUM(count) FROM msgs_systemlabelcount WHERE org_id = $1 AND label_type = 'O') AS outbox_count FROM orgs_org o WHERE id = $1 ) o` diff --git a/core/models/orgs_test.go b/core/models/orgs_test.go index 1c8439840..8f25cd306 100644 --- a/core/models/orgs_test.go +++ b/core/models/orgs_test.go @@ -39,6 +39,7 @@ func TestLoadOrg(t *testing.T) { assert.Equal(t, models.OrgID(1), org.ID()) assert.False(t, org.Suspended()) assert.Equal(t, "smtp://foo:bar", org.FlowSMTP()) + assert.Equal(t, 0, org.OutboxCount()) assert.Equal(t, envs.DateFormatDayMonthYear, org.Environment().DateFormat()) assert.Equal(t, envs.TimeFormatHourMinute, org.Environment().TimeFormat()) assert.Equal(t, envs.RedactionPolicyNone, org.Environment().RedactionPolicy()) From cdb8c86cfc9d4ec60af1ab19ecf9ff836edcf2fa Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 10 Sep 2024 11:54:58 -0500 Subject: [PATCH 032/216] Implement queue pausing and resuming by padding score --- utils/queues/fair_sorted.go | 20 +++++++++++++++++ utils/queues/fair_sorted_test.go | 30 ++++++++++++++++++++++++- utils/queues/lua/fair_sorted_pause.lua | 7 ++++++ utils/queues/lua/fair_sorted_pop.lua | 4 ++-- utils/queues/lua/fair_sorted_resume.lua | 7 ++++++ 5 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 utils/queues/lua/fair_sorted_pause.lua create mode 100644 utils/queues/lua/fair_sorted_resume.lua diff --git a/utils/queues/fair_sorted.go b/utils/queues/fair_sorted.go index 4c9e05c11..f46f2522e 100644 --- a/utils/queues/fair_sorted.go +++ b/utils/queues/fair_sorted.go @@ -134,3 +134,23 @@ var scriptFSSize = redis.NewScript(1, luaFSSize) func (q *FairSorted) Size(rc redis.Conn) (int, error) { return redis.Int(scriptFSSize.Do(rc, q.activeKey(), q.keyBase)) } + +//go:embed lua/fair_sorted_pause.lua +var luaFSPause string +var scriptFSPause = redis.NewScript(1, luaFSPause) + +// Pause marks the given task owner as paused so their tasks are not popped. +func (q *FairSorted) Pause(rc redis.Conn, ownerID int) error { + _, err := scriptFSPause.Do(rc, q.activeKey(), strconv.FormatInt(int64(ownerID), 10)) + return err +} + +//go:embed lua/fair_sorted_resume.lua +var luaFSResume string +var scriptFSResume = redis.NewScript(1, luaFSResume) + +// Resume marks the given task owner as active so their tasks will be popped. +func (q *FairSorted) Resume(rc redis.Conn, ownerID int) error { + _, err := scriptFSResume.Do(rc, q.activeKey(), strconv.FormatInt(int64(ownerID), 10)) + return err +} diff --git a/utils/queues/fair_sorted_test.go b/utils/queues/fair_sorted_test.go index 9c535ae8f..8d7a41324 100644 --- a/utils/queues/fair_sorted_test.go +++ b/utils/queues/fair_sorted_test.go @@ -88,7 +88,35 @@ func TestQueues(t *testing.T) { assertredis.ZGetAll(t, rc, "test:active", map[string]float64{}) - // if we somehow get into a state where an owner is in the active set but doesn't have queued tasks, pop will retry + q.Push(rc, "type1", 1, "task6", queues.DefaultPriority) + q.Push(rc, "type1", 1, "task7", queues.DefaultPriority) + q.Push(rc, "type1", 2, "task8", queues.DefaultPriority) + q.Push(rc, "type1", 2, "task9", queues.DefaultPriority) + + assertPop(1, `"task6"`) + + q.Pause(rc, 1) + q.Pause(rc, 1) // no-op if already paused + + assertredis.ZGetAll(t, rc, "test:active", map[string]float64{"1": 1000001, "2": 0}) + + assertPop(2, `"task8"`) + assertPop(2, `"task9"`) + assertPop(0, "") // no more tasks + + q.Resume(rc, 1) + q.Resume(rc, 1) // no-op if already active + + assertredis.ZGetAll(t, rc, "test:active", map[string]float64{"1": 1}) + + assertPop(1, `"task7"`) + + q.Done(rc, 1) + q.Done(rc, 1) + q.Done(rc, 2) + q.Done(rc, 2) + + // if we somehow get into a state where an owner is in the active set but doesn't have queued tasks, pop will retry q.Push(rc, "type1", 1, "task6", queues.DefaultPriority) q.Push(rc, "type1", 2, "task7", queues.DefaultPriority) diff --git a/utils/queues/lua/fair_sorted_pause.lua b/utils/queues/lua/fair_sorted_pause.lua new file mode 100644 index 000000000..adc923d2e --- /dev/null +++ b/utils/queues/lua/fair_sorted_pause.lua @@ -0,0 +1,7 @@ +local activeSetKey = KEYS[1] +local ownerID = ARGV[1] + +local score = redis.call("ZSCORE", activeSetKey, ownerID) +if score ~= false then + redis.call("ZADD", activeSetKey, 1000000 + score % 1000000, ownerID) +end diff --git a/utils/queues/lua/fair_sorted_pop.lua b/utils/queues/lua/fair_sorted_pop.lua index adce5547b..e4727716d 100644 --- a/utils/queues/lua/fair_sorted_pop.lua +++ b/utils/queues/lua/fair_sorted_pop.lua @@ -1,8 +1,8 @@ local activeSetKey = KEYS[1] local queueBase = ARGV[1] --- first get what is the active queue -local result = redis.call("ZRANGE", activeSetKey, 0, 0) +-- first get the owner with the least workers and not paused +local result = redis.call("ZRANGEBYSCORE", activeSetKey, "-inf", 999999, "LIMIT", 0, 1) -- nothing? return nothing local ownerID = result[1] diff --git a/utils/queues/lua/fair_sorted_resume.lua b/utils/queues/lua/fair_sorted_resume.lua new file mode 100644 index 000000000..82d1a6ed2 --- /dev/null +++ b/utils/queues/lua/fair_sorted_resume.lua @@ -0,0 +1,7 @@ +local activeSetKey = KEYS[1] +local ownerID = ARGV[1] + +local score = redis.call("ZSCORE", activeSetKey, ownerID) +if score ~= false then + redis.call("ZADD", activeSetKey, score % 1000000, ownerID) +end From 8f4578ff11dd26bfd596801cec570578d55dba1a Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 10 Sep 2024 14:13:25 -0500 Subject: [PATCH 033/216] Add dedicated starts queue and a cron to throttle it based on outbox counts --- core/tasks/starts/start_flow.go | 12 ++---- core/tasks/starts/throttle_queue.go | 62 +++++++++++++++++++++++++++++ core/tasks/task.go | 1 + mailroom.go | 14 +++++-- testsuite/tasks.go | 16 ++++---- utils/queues/fair_sorted.go | 15 +++++++ utils/queues/fair_sorted_test.go | 9 +++++ 7 files changed, 109 insertions(+), 20 deletions(-) create mode 100644 core/tasks/starts/throttle_queue.go diff --git a/core/tasks/starts/start_flow.go b/core/tasks/starts/start_flow.go index 3664f6c1c..efa6def5b 100644 --- a/core/tasks/starts/start_flow.go +++ b/core/tasks/starts/start_flow.go @@ -114,18 +114,12 @@ func createFlowStartBatches(ctx context.Context, rt *runtime.Runtime, oa *models // split the contact ids into batches to become batch tasks idBatches := models.ChunkSlice(contactIDs, startBatchSize) - // by default we start in the batch queue unless we have two or fewer contacts - q := tasks.BatchQueue + // by default we start in the starts queue unless we have two or fewer contacts + q := tasks.StartsQueue if len(contactIDs) <= 2 { q = tasks.HandlerQueue } - // if this is a big multi batch blast, give it low priority - priority := queues.DefaultPriority - if len(idBatches) > 1 { - priority = queues.LowPriority - } - rc := rt.RP.Get() defer rc.Close() @@ -142,7 +136,7 @@ func createFlowStartBatches(ctx context.Context, rt *runtime.Runtime, oa *models batchTask = &StartFlowBatchTask{FlowStartBatch: batch} } - err = tasks.Queue(rc, q, start.OrgID, batchTask, priority) + err = tasks.Queue(rc, q, start.OrgID, batchTask, queues.DefaultPriority) if err != nil { if i == 0 { return fmt.Errorf("error queuing flow start batch: %w", err) diff --git a/core/tasks/starts/throttle_queue.go b/core/tasks/starts/throttle_queue.go new file mode 100644 index 000000000..5f664eb54 --- /dev/null +++ b/core/tasks/starts/throttle_queue.go @@ -0,0 +1,62 @@ +package starts + +import ( + "context" + "fmt" + "time" + + "github.com/nyaruka/mailroom/core/models" + "github.com/nyaruka/mailroom/core/tasks" + "github.com/nyaruka/mailroom/runtime" + "github.com/nyaruka/mailroom/utils/queues" +) + +const ( + outboxThreshold = 10_000 +) + +func init() { + tasks.RegisterCron("trottle_queue", &ThrottleQueueCron{queue: tasks.StartsQueue}) +} + +type ThrottleQueueCron struct { + queue *queues.FairSorted +} + +func (c *ThrottleQueueCron) Next(last time.Time) time.Time { + return tasks.CronNext(last, time.Second*10) +} + +func (c *ThrottleQueueCron) AllInstances() bool { + return false +} + +// Run throttles processing of starts based on that org's current outbox size +func (c *ThrottleQueueCron) Run(ctx context.Context, rt *runtime.Runtime) (map[string]any, error) { + rc := rt.RP.Get() + defer rc.Close() + + owners, err := c.queue.Owners(rc) + if err != nil { + return nil, fmt.Errorf("error getting task owners: %w", err) + } + + numPaused, numResumed := 0, 0 + + for ownerID := range owners { + oa, err := models.GetOrgAssets(ctx, rt, models.OrgID(ownerID)) + if err != nil { + return nil, fmt.Errorf("error org assets for org #%d: %w", ownerID, err) + } + + if oa.Org().OutboxCount() >= outboxThreshold { + c.queue.Pause(rc, ownerID) + numPaused++ + } else { + c.queue.Resume(rc, ownerID) + numResumed++ + } + } + + return map[string]any{"paused": numPaused, "resumed": numResumed}, nil +} diff --git a/core/tasks/task.go b/core/tasks/task.go index ad0268e72..f2d15576c 100644 --- a/core/tasks/task.go +++ b/core/tasks/task.go @@ -15,6 +15,7 @@ import ( var HandlerQueue = queues.NewFairSorted("handler") var BatchQueue = queues.NewFairSorted("batch") +var StartsQueue = queues.NewFairSorted("starts") var registeredTypes = map[string](func() Task){} diff --git a/mailroom.go b/mailroom.go index 1fbe1c4a0..77633a896 100644 --- a/mailroom.go +++ b/mailroom.go @@ -29,8 +29,9 @@ type Mailroom struct { wg *sync.WaitGroup quit chan bool - batchForeman *Foreman handlerForeman *Foreman + batchForeman *Foreman + startsForeman *Foreman webserver *web.Server } @@ -43,8 +44,10 @@ func NewMailroom(config *runtime.Config) *Mailroom { wg: &sync.WaitGroup{}, } mr.ctx, mr.cancel = context.WithCancel(context.Background()) - mr.batchForeman = NewForeman(mr.rt, mr.wg, tasks.BatchQueue, config.BatchWorkers) + mr.handlerForeman = NewForeman(mr.rt, mr.wg, tasks.HandlerQueue, config.HandlerWorkers) + mr.batchForeman = NewForeman(mr.rt, mr.wg, tasks.BatchQueue, config.BatchWorkers) + mr.startsForeman = NewForeman(mr.rt, mr.wg, tasks.StartsQueue, config.BatchWorkers) return mr } @@ -142,8 +145,9 @@ func (mr *Mailroom) Start() error { analytics.Start() // init our foremen and start it - mr.batchForeman.Start() mr.handlerForeman.Start() + mr.batchForeman.Start() + mr.startsForeman.Start() // start our web server mr.webserver = web.NewServer(mr.ctx, mr.rt, mr.wg) @@ -161,8 +165,10 @@ func (mr *Mailroom) Stop() error { log := slog.With("comp", "mailroom") log.Info("mailroom stopping") - mr.batchForeman.Stop() mr.handlerForeman.Stop() + mr.batchForeman.Stop() + mr.startsForeman.Stop() + analytics.Stop() close(mr.quit) mr.cancel() diff --git a/testsuite/tasks.go b/testsuite/tasks.go index 937aab5ab..08bccb79a 100644 --- a/testsuite/tasks.go +++ b/testsuite/tasks.go @@ -68,15 +68,17 @@ func FlushTasks(t *testing.T, rt *runtime.Runtime) map[string]int { var err error counts := make(map[string]int) - for { - // look for a task on the handler queue - task, err = tasks.HandlerQueue.Pop(rc) - require.NoError(t, err) + qs := []*queues.FairSorted{tasks.HandlerQueue, tasks.BatchQueue, tasks.StartsQueue} - if task == nil { - // look for a task on the batch queue - task, err = tasks.BatchQueue.Pop(rc) + for { + // look for a task in the queues + for _, q := range qs { + task, err = q.Pop(rc) require.NoError(t, err) + + if task != nil { + break + } } if task == nil { // all done diff --git a/utils/queues/fair_sorted.go b/utils/queues/fair_sorted.go index f46f2522e..7ca26a167 100644 --- a/utils/queues/fair_sorted.go +++ b/utils/queues/fair_sorted.go @@ -65,6 +65,21 @@ func (q *FairSorted) Push(rc redis.Conn, taskType string, ownerID int, task any, return err } +func (q *FairSorted) Owners(rc redis.Conn) ([]int, error) { + strs, err := redis.Strings(rc.Do("ZRANGE", q.activeKey(), 0, -1)) + if err != nil { + return nil, err + } + + actual := make([]int, len(strs)) + for i, s := range strs { + owner, _ := strconv.ParseInt(s, 10, 64) + actual[i] = int(owner) + } + + return actual, nil +} + func (q *FairSorted) activeKey() string { return fmt.Sprintf("%s:active", q.keyBase) } diff --git a/utils/queues/fair_sorted_test.go b/utils/queues/fair_sorted_test.go index 8d7a41324..d44f84a26 100644 --- a/utils/queues/fair_sorted_test.go +++ b/utils/queues/fair_sorted_test.go @@ -42,6 +42,12 @@ func TestQueues(t *testing.T) { assert.Equal(t, expecting, size) } + assertOwners := func(expected []int) { + actual, err := q.Owners(rc) + assert.NoError(t, err) + assert.ElementsMatch(t, expected, actual) + } + assertSize(0) q.Push(rc, "type1", 1, "task1", queues.DefaultPriority) @@ -72,6 +78,7 @@ func TestQueues(t *testing.T) { assertPop(1, `"task1"`) assertredis.ZGetAll(t, rc, "test:active", map[string]float64{"1": 2, "2": 1}) + assertOwners([]int{1, 2}) assertSize(2) // mark task2 and task1 (owner 1) as complete @@ -99,6 +106,7 @@ func TestQueues(t *testing.T) { q.Pause(rc, 1) // no-op if already paused assertredis.ZGetAll(t, rc, "test:active", map[string]float64{"1": 1000001, "2": 0}) + assertOwners([]int{1, 2}) assertPop(2, `"task8"`) assertPop(2, `"task9"`) @@ -108,6 +116,7 @@ func TestQueues(t *testing.T) { q.Resume(rc, 1) // no-op if already active assertredis.ZGetAll(t, rc, "test:active", map[string]float64{"1": 1}) + assertOwners([]int{1}) assertPop(1, `"task7"`) From 83d838858d74c35b447ba77106a0d626c6aa2811 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 10 Sep 2024 16:50:21 -0500 Subject: [PATCH 034/216] Update CHANGELOG.md for v9.3.12 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51a5e9076..8d2fc572a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.12 (2024-09-10) +------------------------- + * Add dedicated starts queue and a cron to throttle it based on outbox counts + v9.3.11 (2024-08-22) ------------------------- * Always strip / from session object keys From b6161bd8df0846df99585eba963456beeefdee8c Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 10 Sep 2024 16:12:05 -0500 Subject: [PATCH 035/216] Update to go 1.23 --- .github/workflows/ci.yml | 2 +- core/models/contacts.go | 3 ++- core/models/contacts_test.go | 7 ++++--- core/models/sessions.go | 3 ++- core/models/utils.go | 21 +++++---------------- core/msgio/send.go | 6 +++--- core/runner/runner.go | 5 +++-- core/tasks/campaigns/cron.go | 2 +- core/tasks/campaigns/fire_campaign_event.go | 13 +++++++------ core/tasks/msgs/send_broadcast.go | 3 ++- core/tasks/starts/start_flow.go | 7 ++++--- go.mod | 6 +++--- testsuite/testsuite.go | 4 ++-- web/contact/modify.go | 5 +++-- web/contact/urns.go | 5 +++-- web/ticket/reopen.go | 7 ++++--- 16 files changed, 49 insertions(+), 50 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5ac39354..b4feb7871 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ name: CI on: [push, pull_request] env: - go-version: "1.22.x" + go-version: "1.23.x" jobs: test: name: Test diff --git a/core/models/contacts.go b/core/models/contacts.go index 1845bb146..d9cb2b0bd 100644 --- a/core/models/contacts.go +++ b/core/models/contacts.go @@ -8,6 +8,7 @@ import ( "fmt" "log/slog" "net/url" + "slices" "strconv" "time" @@ -1183,7 +1184,7 @@ func updateURNChannel(urn urns.URN, channel *Channel) (urns.URN, error) { // UpdateContactModifiedOn updates modified_on the passed in contacts func UpdateContactModifiedOn(ctx context.Context, db DBorTx, contactIDs []ContactID) error { - for _, idBatch := range ChunkSlice(contactIDs, 100) { + for idBatch := range slices.Chunk(contactIDs, 100) { _, err := db.ExecContext(ctx, `UPDATE contacts_contact SET modified_on = NOW() WHERE id = ANY($1)`, pq.Array(idBatch)) if err != nil { return fmt.Errorf("error updating modified_on for contact batch: %w", err) diff --git a/core/models/contacts_test.go b/core/models/contacts_test.go index 00149f052..cb78f6166 100644 --- a/core/models/contacts_test.go +++ b/core/models/contacts_test.go @@ -3,6 +3,8 @@ package models_test import ( "context" "fmt" + "maps" + "slices" "sort" "testing" "time" @@ -20,7 +22,6 @@ import ( "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "golang.org/x/exp/maps" ) func TestContacts(t *testing.T) { @@ -420,7 +421,7 @@ func TestGetOrCreateContactIDsFromURNs(t *testing.T) { fetched, created, err := models.GetOrCreateContactsFromURNs(ctx, rt.DB, oa, tc.urns) assert.NoError(t, err, "%d: error getting contact ids", i) assert.Equal(t, tc.fetched, fetched, "%d: fetched contacts mismatch", i) - assert.Equal(t, tc.created, maps.Keys(created), "%d: created contacts mismatch", i) + assert.Equal(t, tc.created, slices.AppendSeq([]urns.URN{}, maps.Keys(created)), "%d: created contacts mismatch", i) } } @@ -656,7 +657,7 @@ func TestLockContacts(t *testing.T) { // try to get locks for 101, 102, 103 locks, skipped, err := models.LockContacts(ctx, rt, testdata.Org1.ID, []models.ContactID{101, 102, 103}, time.Second) assert.NoError(t, err) - assert.ElementsMatch(t, []models.ContactID{101, 103}, maps.Keys(locks)) + assert.ElementsMatch(t, []models.ContactID{101, 103}, slices.Collect(maps.Keys(locks))) assert.Equal(t, []models.ContactID{102}, skipped) // because it's already locked assertredis.Exists(t, rc, "lock:c:1:101") diff --git a/core/models/sessions.go b/core/models/sessions.go index 760f8c7ea..c47340733 100644 --- a/core/models/sessions.go +++ b/core/models/sessions.go @@ -9,6 +9,7 @@ import ( "log/slog" "net/url" "path" + "slices" "strings" "time" @@ -855,7 +856,7 @@ func ExitSessions(ctx context.Context, db *sqlx.DB, sessionIDs []SessionID, stat } // split into batches and exit each batch in a transaction - for _, idBatch := range ChunkSlice(sessionIDs, 100) { + for idBatch := range slices.Chunk(sessionIDs, 100) { tx, err := db.BeginTxx(ctx, nil) if err != nil { return fmt.Errorf("error starting transaction to exit sessions: %w", err) diff --git a/core/models/utils.go b/core/models/utils.go index 07e6d6d4d..3de4782e7 100644 --- a/core/models/utils.go +++ b/core/models/utils.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "log/slog" + "slices" "time" "github.com/jmoiron/sqlx" @@ -61,32 +62,20 @@ func BulkQuery[T any](ctx context.Context, label string, tx DBorTx, sql string, func BulkQueryBatches[T any](ctx context.Context, label string, tx DBorTx, sql string, batchSize int, structs []T) error { start := time.Now() - batches := ChunkSlice(structs, batchSize) - for i, batch := range batches { + i := 0 + for batch := range slices.Chunk(structs, batchSize) { err := dbutil.BulkQuery(ctx, tx, sql, batch) if err != nil { return fmt.Errorf("error making bulk batch query: %w", err) } - slog.Info(fmt.Sprintf("%s bulk sql batch complete", label), "elapsed", time.Since(start), "rows", len(batch), "batch", i+1) + slog.Info(fmt.Sprintf("%s bulk sql batch complete", label), "elapsed", time.Since(start), "rows", len(batch), "batch", i) + i++ } return nil } -func ChunkSlice[T any](slice []T, size int) [][]T { - chunks := make([][]T, 0, len(slice)/size+1) - - for i := 0; i < len(slice); i += size { - end := i + size - if end > len(slice) { - end = len(slice) - } - chunks = append(chunks, slice[i:end]) - } - return chunks -} - func ScanJSONRows[T any](rows *sql.Rows, f func() T) ([]T, error) { defer rows.Close() diff --git a/core/msgio/send.go b/core/msgio/send.go index 1a01da0e5..fc094778f 100644 --- a/core/msgio/send.go +++ b/core/msgio/send.go @@ -3,11 +3,11 @@ package msgio import ( "context" "log/slog" + "maps" "slices" "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/runtime" - "golang.org/x/exp/maps" ) type Send struct { @@ -48,7 +48,7 @@ func tryToQueue(ctx context.Context, rt *runtime.Runtime, db models.DBorTx, msgs // fetch URNs and organize by id urnIDs := getMessageURNIDs(msgs) urnsByID := make(map[models.URNID]*models.ContactURN, len(urnIDs)) - for _, batch := range models.ChunkSlice(urnIDs, 1000) { + for batch := range slices.Chunk(urnIDs, 1000) { urns, err := models.LoadContactURNs(ctx, db, batch) if err != nil { slog.Error("error getting contact URNs", "error", err) @@ -155,7 +155,7 @@ func getMessageURNIDs(msgs []*models.Msg) []models.URNID { ids[*uid] = true } } - return maps.Keys(ids) + return slices.Collect(maps.Keys(ids)) } func assert(c bool, m string) { diff --git a/core/runner/runner.go b/core/runner/runner.go index f075bafa2..1e0ef6c58 100644 --- a/core/runner/runner.go +++ b/core/runner/runner.go @@ -5,6 +5,8 @@ import ( "encoding/json" "fmt" "log/slog" + "maps" + "slices" "time" "github.com/gomodule/redigo/redis" @@ -16,7 +18,6 @@ import ( "github.com/nyaruka/mailroom/core/goflow" "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/runtime" - "golang.org/x/exp/maps" ) const ( @@ -264,7 +265,7 @@ func tryToStartWithLock(ctx context.Context, rt *runtime.Runtime, oa *models.Org if err != nil { return nil, nil, err } - locked := maps.Keys(locks) + locked := slices.Collect(maps.Keys(locks)) // whatever happens, we need to unlock the contacts defer models.UnlockContacts(rt, oa.OrgID(), locks) diff --git a/core/tasks/campaigns/cron.go b/core/tasks/campaigns/cron.go index 9bfd3af44..8e2e5552c 100644 --- a/core/tasks/campaigns/cron.go +++ b/core/tasks/campaigns/cron.go @@ -3,6 +3,7 @@ package campaigns import ( "context" "fmt" + "log/slog" "time" "github.com/gomodule/redigo/redis" @@ -12,7 +13,6 @@ import ( "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/mailroom/utils/queues" "github.com/nyaruka/redisx" - "golang.org/x/exp/slog" ) const ( diff --git a/core/tasks/campaigns/fire_campaign_event.go b/core/tasks/campaigns/fire_campaign_event.go index 03dfa69b2..7716690f5 100644 --- a/core/tasks/campaigns/fire_campaign_event.go +++ b/core/tasks/campaigns/fire_campaign_event.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log/slog" + "maps" "slices" "time" @@ -18,7 +19,6 @@ import ( "github.com/nyaruka/mailroom/core/tasks" "github.com/nyaruka/mailroom/core/tasks/handler" "github.com/nyaruka/mailroom/runtime" - "golang.org/x/exp/maps" ) // TypeFireCampaignEvent is the type of the fire event task @@ -173,18 +173,19 @@ func FireCampaignEvents(ctx context.Context, rt *runtime.Runtime, oa *models.Org } // mark the skipped fires as skipped and record as handled - err = models.MarkEventsFired(ctx, rt.DB, maps.Values(firesToSkip), time.Now(), models.FireResultSkipped) + skipped := slices.Collect(maps.Values(firesToSkip)) + err = models.MarkEventsFired(ctx, rt.DB, skipped, time.Now(), models.FireResultSkipped) if err != nil { return nil, fmt.Errorf("error marking events skipped: %w", err) } - handled := maps.Values(firesToSkip) + handled := skipped // if this is an ivr flow, we need to create a task to perform the start there if dbFlow.FlowType() == models.FlowTypeVoice { - fired := maps.Values(firesToFire) + fired := slices.Collect(maps.Values(firesToFire)) - err := handler.TriggerIVRFlow(ctx, rt, oa.OrgID(), dbFlow.ID(), maps.Keys(firesToFire), func(ctx context.Context, tx *sqlx.Tx) error { + err := handler.TriggerIVRFlow(ctx, rt, oa.OrgID(), dbFlow.ID(), slices.Collect(maps.Keys(firesToFire)), func(ctx context.Context, tx *sqlx.Tx) error { return models.MarkEventsFired(ctx, tx, fired, time.Now(), models.FireResultFired) }) if err != nil { @@ -226,7 +227,7 @@ func FireCampaignEvents(ctx context.Context, rt *runtime.Runtime, oa *models.Org CommitHook: markFired, } - _, err = runner.StartFlow(ctx, rt, oa, dbFlow, maps.Keys(firesToFire), options) + _, err = runner.StartFlow(ctx, rt, oa, dbFlow, slices.Collect(maps.Keys(firesToFire)), options) if err != nil { slog.Error("error starting flow for campaign event", "error", err, "event", eventUUID) } diff --git a/core/tasks/msgs/send_broadcast.go b/core/tasks/msgs/send_broadcast.go index d0e4470cd..dc9a57c54 100644 --- a/core/tasks/msgs/send_broadcast.go +++ b/core/tasks/msgs/send_broadcast.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log/slog" + "slices" "time" "github.com/nyaruka/goflow/contactql" @@ -104,7 +105,7 @@ func createBroadcastBatches(ctx context.Context, rt *runtime.Runtime, oa *models defer rc.Close() // create tasks for batches of contacts - idBatches := models.ChunkSlice(contactIDs, startBatchSize) + idBatches := slices.Collect(slices.Chunk(contactIDs, startBatchSize)) for i, idBatch := range idBatches { isLast := (i == len(idBatches)-1) diff --git a/core/tasks/starts/start_flow.go b/core/tasks/starts/start_flow.go index efa6def5b..b1709947e 100644 --- a/core/tasks/starts/start_flow.go +++ b/core/tasks/starts/start_flow.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log/slog" + "slices" "time" "github.com/nyaruka/gocommon/i18n" @@ -111,15 +112,15 @@ func createFlowStartBatches(ctx context.Context, rt *runtime.Runtime, oa *models return nil } - // split the contact ids into batches to become batch tasks - idBatches := models.ChunkSlice(contactIDs, startBatchSize) - // by default we start in the starts queue unless we have two or fewer contacts q := tasks.StartsQueue if len(contactIDs) <= 2 { q = tasks.HandlerQueue } + // split the contact ids into batches to become batch tasks + idBatches := slices.Collect(slices.Chunk(contactIDs, startBatchSize)) + rc := rt.RP.Get() defer rc.Close() diff --git a/go.mod b/go.mod index 60873e69f..45ee04d20 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,12 @@ module github.com/nyaruka/mailroom -go 1.22 +go 1.23 require ( firebase.google.com/go/v4 v4.14.1 github.com/Masterminds/semver v1.5.0 github.com/appleboy/go-fcm v1.2.1 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.5 github.com/aws/aws-sdk-go-v2/service/s3 v1.60.0 github.com/buger/jsonparser v1.1.1 github.com/elastic/go-elasticsearch/v8 v8.14.0 @@ -31,7 +32,6 @@ require ( github.com/samber/slog-sentry v1.2.2 github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.9.0 - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa google.golang.org/api v0.193.0 ) @@ -56,7 +56,6 @@ require ( github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16 // indirect - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.5 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18 // indirect github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.17 // indirect @@ -101,6 +100,7 @@ require ( go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect golang.org/x/crypto v0.26.0 // indirect + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.22.0 // indirect golang.org/x/sync v0.8.0 // indirect diff --git a/testsuite/testsuite.go b/testsuite/testsuite.go index ecbc459a2..f95b50883 100644 --- a/testsuite/testsuite.go +++ b/testsuite/testsuite.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "log/slog" + "maps" "os" "os/exec" "path" @@ -20,7 +21,6 @@ import ( "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/redisx/assertredis" "github.com/nyaruka/rp-indexer/v9/indexers" - "golang.org/x/exp/maps" ) var _db *sqlx.DB @@ -226,7 +226,7 @@ func resetElastic(ctx context.Context, rt *runtime.Runtime) { noError(err) // and delete them - for _, index := range maps.Keys(ar) { + for index := range maps.Keys(ar) { _, err := rt.ES.Indices.Delete(index).Do(ctx) noError(err) } diff --git a/web/contact/modify.go b/web/contact/modify.go index c0224ef59..82ac8c826 100644 --- a/web/contact/modify.go +++ b/web/contact/modify.go @@ -4,7 +4,9 @@ import ( "context" "encoding/json" "fmt" + "maps" "net/http" + "slices" "time" "github.com/nyaruka/goflow/flows" @@ -12,7 +14,6 @@ import ( "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/mailroom/web" - "golang.org/x/exp/maps" ) func init() { @@ -111,7 +112,7 @@ func tryToLockAndModify(ctx context.Context, rt *runtime.Runtime, oa *models.Org return nil, nil, err } - locked := maps.Keys(locks) + locked := slices.Collect(maps.Keys(locks)) defer models.UnlockContacts(rt, oa.OrgID(), locks) diff --git a/web/contact/urns.go b/web/contact/urns.go index d4de6b2c9..e55c5d36a 100644 --- a/web/contact/urns.go +++ b/web/contact/urns.go @@ -3,14 +3,15 @@ package contact import ( "context" "fmt" + "maps" "net/http" + "slices" "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/mailroom/web" - "golang.org/x/exp/maps" ) func init() { @@ -69,7 +70,7 @@ func handleURNs(ctx context.Context, rt *runtime.Runtime, r *urnsRequest) (any, } } - ownerIDs, err := models.GetContactIDsFromURNs(ctx, rt.DB, r.OrgID, maps.Keys(urnsToLookup)) + ownerIDs, err := models.GetContactIDsFromURNs(ctx, rt.DB, r.OrgID, slices.Collect(maps.Keys(urnsToLookup))) if err != nil { return nil, 0, fmt.Errorf("error getting URN owners: %w", err) } diff --git a/web/ticket/reopen.go b/web/ticket/reopen.go index 2ef220bc1..f75f0532e 100644 --- a/web/ticket/reopen.go +++ b/web/ticket/reopen.go @@ -3,13 +3,14 @@ package ticket import ( "context" "fmt" + "maps" "net/http" + "slices" "time" "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/mailroom/web" - "golang.org/x/exp/maps" ) func init() { @@ -67,12 +68,12 @@ func handleReopen(ctx context.Context, rt *runtime.Runtime, r *http.Request) (an } func tryToLockAndReopen(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets, tickets map[models.ContactID]*models.Ticket, userID models.UserID) (map[*models.Ticket]*models.TicketEvent, map[models.ContactID]*models.Ticket, error) { - locks, skipped, err := models.LockContacts(ctx, rt, oa.OrgID(), maps.Keys(tickets), time.Second) + locks, skipped, err := models.LockContacts(ctx, rt, oa.OrgID(), slices.Collect(maps.Keys(tickets)), time.Second) if err != nil { return nil, nil, err } - locked := maps.Keys(locks) + locked := slices.Collect(maps.Keys(locks)) defer models.UnlockContacts(rt, oa.OrgID(), locks) From 15cf85b8aa861d0c36877fecb3e1945d36c710ab Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 10 Sep 2024 18:59:28 -0500 Subject: [PATCH 036/216] Fix throttle queue task --- core/tasks/starts/throttle_queue.go | 12 +++--- core/tasks/starts/throttle_queue_test.go | 50 ++++++++++++++++++++++++ testsuite/testsuite.go | 1 + 3 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 core/tasks/starts/throttle_queue_test.go diff --git a/core/tasks/starts/throttle_queue.go b/core/tasks/starts/throttle_queue.go index 5f664eb54..b7284805d 100644 --- a/core/tasks/starts/throttle_queue.go +++ b/core/tasks/starts/throttle_queue.go @@ -16,11 +16,11 @@ const ( ) func init() { - tasks.RegisterCron("trottle_queue", &ThrottleQueueCron{queue: tasks.StartsQueue}) + tasks.RegisterCron("throttle_queue", &ThrottleQueueCron{Queue: tasks.StartsQueue}) } type ThrottleQueueCron struct { - queue *queues.FairSorted + Queue *queues.FairSorted } func (c *ThrottleQueueCron) Next(last time.Time) time.Time { @@ -36,24 +36,24 @@ func (c *ThrottleQueueCron) Run(ctx context.Context, rt *runtime.Runtime) (map[s rc := rt.RP.Get() defer rc.Close() - owners, err := c.queue.Owners(rc) + owners, err := c.Queue.Owners(rc) if err != nil { return nil, fmt.Errorf("error getting task owners: %w", err) } numPaused, numResumed := 0, 0 - for ownerID := range owners { + for _, ownerID := range owners { oa, err := models.GetOrgAssets(ctx, rt, models.OrgID(ownerID)) if err != nil { return nil, fmt.Errorf("error org assets for org #%d: %w", ownerID, err) } if oa.Org().OutboxCount() >= outboxThreshold { - c.queue.Pause(rc, ownerID) + c.Queue.Pause(rc, ownerID) numPaused++ } else { - c.queue.Resume(rc, ownerID) + c.Queue.Resume(rc, ownerID) numResumed++ } } diff --git a/core/tasks/starts/throttle_queue_test.go b/core/tasks/starts/throttle_queue_test.go new file mode 100644 index 000000000..01086e602 --- /dev/null +++ b/core/tasks/starts/throttle_queue_test.go @@ -0,0 +1,50 @@ +package starts_test + +import ( + "testing" + + "github.com/nyaruka/mailroom/core/models" + "github.com/nyaruka/mailroom/core/tasks/starts" + "github.com/nyaruka/mailroom/testsuite" + "github.com/nyaruka/mailroom/utils/queues" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestThrottleQueue(t *testing.T) { + ctx, rt := testsuite.Runtime() + rc := rt.RP.Get() + defer rc.Close() + + defer testsuite.Reset(testsuite.ResetRedis | testsuite.ResetData) + + queue := queues.NewFairSorted("test") + cron := &starts.ThrottleQueueCron{Queue: queue} + res, err := cron.Run(ctx, rt) + require.NoError(t, err) + assert.Equal(t, map[string]any{"paused": 0, "resumed": 0}, res) + + queue.Push(rc, "type1", 1, "task1", queues.DefaultPriority) + + res, err = cron.Run(ctx, rt) + require.NoError(t, err) + assert.Equal(t, map[string]any{"paused": 0, "resumed": 1}, res) + + // make it look like org 1 has 20,000 messages in its outbox + rt.DB.MustExec(`INSERT INTO msgs_systemlabelcount(org_id, label_type, count, is_squashed) VALUES (1, 'O', 10050, FALSE)`) + + models.FlushCache() + + res, err = cron.Run(ctx, rt) + require.NoError(t, err) + assert.Equal(t, map[string]any{"paused": 1, "resumed": 0}, res) + + // make it look like most of the inbox has cleared + rt.DB.MustExec(`INSERT INTO msgs_systemlabelcount(org_id, label_type, count, is_squashed) VALUES (1, 'O', -10000, FALSE)`) + + models.FlushCache() + + res, err = cron.Run(ctx, rt) + require.NoError(t, err) + assert.Equal(t, map[string]any{"paused": 0, "resumed": 1}, res) +} diff --git a/testsuite/testsuite.go b/testsuite/testsuite.go index ecbc459a2..e741eb3fc 100644 --- a/testsuite/testsuite.go +++ b/testsuite/testsuite.go @@ -290,6 +290,7 @@ DELETE FROM flows_flowrevision WHERE flow_id >= 30000; DELETE FROM flows_flow WHERE id >= 30000; DELETE FROM ivr_call; DELETE FROM campaigns_eventfire; +DELETE FROM msgs_systemlabelcount; DELETE FROM msgs_msg_labels; DELETE FROM msgs_msg; DELETE FROM msgs_broadcast_groups; From ce2bda4eb3d8d7813819be27c352b0b86c3bb782 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 10 Sep 2024 19:10:17 -0500 Subject: [PATCH 037/216] Update CHANGELOG.md for v9.3.13 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d2fc572a..f3e8164e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.13 (2024-09-11) +------------------------- + * Fix throttle queue task + * Update to go 1.23 + v9.3.12 (2024-09-10) ------------------------- * Add dedicated starts queue and a cron to throttle it based on outbox counts From d72f203f2fc2f77505962ff02c7428c1b78c8dd9 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 10 Sep 2024 19:25:15 -0500 Subject: [PATCH 038/216] Update github action for dynamodb --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4feb7871..685b2cad5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,7 @@ jobs: psql -h localhost -U postgres --no-password -c "CREATE DATABASE mailroom_test;" - name: Install and start DynamoDB - uses: rrainn/dynamodb-action@v2.0.1 + uses: rrainn/dynamodb-action@v4.0.0 with: port: 6000 From f4fc5c14b04a3b29013f8b921740647db5363529 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 11 Sep 2024 16:23:54 -0500 Subject: [PATCH 039/216] Update dynamodb table name --- mailroom.go | 2 +- testsuite/testfiles/dynamo.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mailroom.go b/mailroom.go index 77633a896..48038c855 100644 --- a/mailroom.go +++ b/mailroom.go @@ -100,7 +100,7 @@ func (mr *Mailroom) Start() error { if err != nil { return err } - if err := mr.rt.Dynamo.Test(mr.ctx, "ChannelLogsAttached"); err != nil { + if err := mr.rt.Dynamo.Test(mr.ctx, "ChannelLogs"); err != nil { log.Error("dynamodb not reachable", "error", err) } else { log.Info("dynamodb ok") diff --git a/testsuite/testfiles/dynamo.json b/testsuite/testfiles/dynamo.json index 4bfdc081b..6139403af 100644 --- a/testsuite/testfiles/dynamo.json +++ b/testsuite/testfiles/dynamo.json @@ -1,6 +1,6 @@ [ { - "TableName": "ChannelLogsAttached", + "TableName": "ChannelLogs", "KeySchema": [ { "AttributeName": "UUID", From 951e841448b900b5ec40d6f846f07a14d5a0ef86 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 12 Sep 2024 11:36:24 -0500 Subject: [PATCH 040/216] Throttle broadcasts by outbox size too --- core/tasks/msgs/send_broadcast.go | 6 +++--- core/tasks/starts/start_flow.go | 6 +++--- core/tasks/starts/throttle_queue.go | 5 ++++- core/tasks/task.go | 3 +++ mailroom.go | 10 +++++++--- testsuite/tasks.go | 2 +- 6 files changed, 21 insertions(+), 11 deletions(-) diff --git a/core/tasks/msgs/send_broadcast.go b/core/tasks/msgs/send_broadcast.go index dc9a57c54..5aba51811 100644 --- a/core/tasks/msgs/send_broadcast.go +++ b/core/tasks/msgs/send_broadcast.go @@ -95,9 +95,9 @@ func createBroadcastBatches(ctx context.Context, rt *runtime.Runtime, oa *models return nil } - // two or fewer contacts? queue to our handler queue for sending - q := tasks.BatchQueue - if len(contactIDs) <= 2 { + // batches will be processed in the throttled queue unless we're a single contact + q := tasks.ThrottledQueue + if len(contactIDs) == 1 { q = tasks.HandlerQueue } diff --git a/core/tasks/starts/start_flow.go b/core/tasks/starts/start_flow.go index b1709947e..6097f8e54 100644 --- a/core/tasks/starts/start_flow.go +++ b/core/tasks/starts/start_flow.go @@ -112,9 +112,9 @@ func createFlowStartBatches(ctx context.Context, rt *runtime.Runtime, oa *models return nil } - // by default we start in the starts queue unless we have two or fewer contacts - q := tasks.StartsQueue - if len(contactIDs) <= 2 { + // batches will be processed in the throttled queue unless we're a single contact + q := tasks.ThrottledQueue + if len(contactIDs) == 1 { q = tasks.HandlerQueue } diff --git a/core/tasks/starts/throttle_queue.go b/core/tasks/starts/throttle_queue.go index b7284805d..e49924c10 100644 --- a/core/tasks/starts/throttle_queue.go +++ b/core/tasks/starts/throttle_queue.go @@ -16,7 +16,10 @@ const ( ) func init() { - tasks.RegisterCron("throttle_queue", &ThrottleQueueCron{Queue: tasks.StartsQueue}) + tasks.RegisterCron("throttle_queue", &ThrottleQueueCron{Queue: tasks.ThrottledQueue}) + + // TODO remove once starts are using throttled + tasks.RegisterCron("throttle_starts", &ThrottleQueueCron{Queue: tasks.StartsQueue}) } type ThrottleQueueCron struct { diff --git a/core/tasks/task.go b/core/tasks/task.go index f2d15576c..5cc621afa 100644 --- a/core/tasks/task.go +++ b/core/tasks/task.go @@ -15,6 +15,9 @@ import ( var HandlerQueue = queues.NewFairSorted("handler") var BatchQueue = queues.NewFairSorted("batch") +var ThrottledQueue = queues.NewFairSorted("throttled") + +// TODO remove once starts are using throttled var StartsQueue = queues.NewFairSorted("starts") var registeredTypes = map[string](func() Task){} diff --git a/mailroom.go b/mailroom.go index 48038c855..b44f5d054 100644 --- a/mailroom.go +++ b/mailroom.go @@ -29,9 +29,10 @@ type Mailroom struct { wg *sync.WaitGroup quit chan bool - handlerForeman *Foreman - batchForeman *Foreman - startsForeman *Foreman + handlerForeman *Foreman + batchForeman *Foreman + throttledForeman *Foreman + startsForeman *Foreman // TODO remove once starts are using throttled webserver *web.Server } @@ -47,6 +48,7 @@ func NewMailroom(config *runtime.Config) *Mailroom { mr.handlerForeman = NewForeman(mr.rt, mr.wg, tasks.HandlerQueue, config.HandlerWorkers) mr.batchForeman = NewForeman(mr.rt, mr.wg, tasks.BatchQueue, config.BatchWorkers) + mr.throttledForeman = NewForeman(mr.rt, mr.wg, tasks.StartsQueue, config.BatchWorkers) mr.startsForeman = NewForeman(mr.rt, mr.wg, tasks.StartsQueue, config.BatchWorkers) return mr @@ -147,6 +149,7 @@ func (mr *Mailroom) Start() error { // init our foremen and start it mr.handlerForeman.Start() mr.batchForeman.Start() + mr.throttledForeman.Start() mr.startsForeman.Start() // start our web server @@ -167,6 +170,7 @@ func (mr *Mailroom) Stop() error { mr.handlerForeman.Stop() mr.batchForeman.Stop() + mr.throttledForeman.Stop() mr.startsForeman.Stop() analytics.Stop() diff --git a/testsuite/tasks.go b/testsuite/tasks.go index 08bccb79a..75361e69c 100644 --- a/testsuite/tasks.go +++ b/testsuite/tasks.go @@ -68,7 +68,7 @@ func FlushTasks(t *testing.T, rt *runtime.Runtime) map[string]int { var err error counts := make(map[string]int) - qs := []*queues.FairSorted{tasks.HandlerQueue, tasks.BatchQueue, tasks.StartsQueue} + qs := []*queues.FairSorted{tasks.HandlerQueue, tasks.BatchQueue, tasks.ThrottledQueue} for { // look for a task in the queues From 4eb1f7217fde5e19186f0eca8cd0d0cd6bb22123 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 12 Sep 2024 13:16:41 -0500 Subject: [PATCH 041/216] Rename throttled redis queue to tasks:throttled --- core/tasks/task.go | 2 +- mailroom.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/tasks/task.go b/core/tasks/task.go index 5cc621afa..c419693a1 100644 --- a/core/tasks/task.go +++ b/core/tasks/task.go @@ -15,7 +15,7 @@ import ( var HandlerQueue = queues.NewFairSorted("handler") var BatchQueue = queues.NewFairSorted("batch") -var ThrottledQueue = queues.NewFairSorted("throttled") +var ThrottledQueue = queues.NewFairSorted("tasks:throttled") // TODO remove once starts are using throttled var StartsQueue = queues.NewFairSorted("starts") diff --git a/mailroom.go b/mailroom.go index b44f5d054..3e23e67c5 100644 --- a/mailroom.go +++ b/mailroom.go @@ -48,7 +48,7 @@ func NewMailroom(config *runtime.Config) *Mailroom { mr.handlerForeman = NewForeman(mr.rt, mr.wg, tasks.HandlerQueue, config.HandlerWorkers) mr.batchForeman = NewForeman(mr.rt, mr.wg, tasks.BatchQueue, config.BatchWorkers) - mr.throttledForeman = NewForeman(mr.rt, mr.wg, tasks.StartsQueue, config.BatchWorkers) + mr.throttledForeman = NewForeman(mr.rt, mr.wg, tasks.ThrottledQueue, config.BatchWorkers) mr.startsForeman = NewForeman(mr.rt, mr.wg, tasks.StartsQueue, config.BatchWorkers) return mr From c86b0900502796efbf01ef3460d8b32012f50be7 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 12 Sep 2024 13:28:06 -0500 Subject: [PATCH 042/216] Update CHANGELOG.md for v9.3.14 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3e8164e6..73a1ba0e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.14 (2024-09-12) +------------------------- + * Throttle broadcasts by outbox size too + v9.3.13 (2024-09-11) ------------------------- * Fix throttle queue task From 3be3390008ae65db25f960f2fd738d4be3a5342c Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 12 Sep 2024 14:59:20 -0500 Subject: [PATCH 043/216] Remove starts queue that was replaced by tasks:throttled --- core/tasks/starts/throttle_queue.go | 3 --- core/tasks/task.go | 3 --- mailroom.go | 4 ---- 3 files changed, 10 deletions(-) diff --git a/core/tasks/starts/throttle_queue.go b/core/tasks/starts/throttle_queue.go index e49924c10..805c75ad4 100644 --- a/core/tasks/starts/throttle_queue.go +++ b/core/tasks/starts/throttle_queue.go @@ -17,9 +17,6 @@ const ( func init() { tasks.RegisterCron("throttle_queue", &ThrottleQueueCron{Queue: tasks.ThrottledQueue}) - - // TODO remove once starts are using throttled - tasks.RegisterCron("throttle_starts", &ThrottleQueueCron{Queue: tasks.StartsQueue}) } type ThrottleQueueCron struct { diff --git a/core/tasks/task.go b/core/tasks/task.go index c419693a1..1e6595b26 100644 --- a/core/tasks/task.go +++ b/core/tasks/task.go @@ -17,9 +17,6 @@ var HandlerQueue = queues.NewFairSorted("handler") var BatchQueue = queues.NewFairSorted("batch") var ThrottledQueue = queues.NewFairSorted("tasks:throttled") -// TODO remove once starts are using throttled -var StartsQueue = queues.NewFairSorted("starts") - var registeredTypes = map[string](func() Task){} // RegisterType registers a new type of task diff --git a/mailroom.go b/mailroom.go index 3e23e67c5..0cd5567f9 100644 --- a/mailroom.go +++ b/mailroom.go @@ -32,7 +32,6 @@ type Mailroom struct { handlerForeman *Foreman batchForeman *Foreman throttledForeman *Foreman - startsForeman *Foreman // TODO remove once starts are using throttled webserver *web.Server } @@ -49,7 +48,6 @@ func NewMailroom(config *runtime.Config) *Mailroom { mr.handlerForeman = NewForeman(mr.rt, mr.wg, tasks.HandlerQueue, config.HandlerWorkers) mr.batchForeman = NewForeman(mr.rt, mr.wg, tasks.BatchQueue, config.BatchWorkers) mr.throttledForeman = NewForeman(mr.rt, mr.wg, tasks.ThrottledQueue, config.BatchWorkers) - mr.startsForeman = NewForeman(mr.rt, mr.wg, tasks.StartsQueue, config.BatchWorkers) return mr } @@ -150,7 +148,6 @@ func (mr *Mailroom) Start() error { mr.handlerForeman.Start() mr.batchForeman.Start() mr.throttledForeman.Start() - mr.startsForeman.Start() // start our web server mr.webserver = web.NewServer(mr.ctx, mr.rt, mr.wg) @@ -171,7 +168,6 @@ func (mr *Mailroom) Stop() error { mr.handlerForeman.Stop() mr.batchForeman.Stop() mr.throttledForeman.Stop() - mr.startsForeman.Stop() analytics.Stop() close(mr.quit) From 0506e67f548e0d973f4f0b8a5f3e37c628e217fc Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 12 Sep 2024 15:04:04 -0500 Subject: [PATCH 044/216] Add new queues for batch and handler whilst retaining previous ones --- core/tasks/campaigns/cron_test.go | 4 ++-- core/tasks/task.go | 7 +++++-- mailroom.go | 13 +++++++++++++ testsuite/assert.go | 2 +- testsuite/tasks.go | 4 ++-- 5 files changed, 23 insertions(+), 7 deletions(-) diff --git a/core/tasks/campaigns/cron_test.go b/core/tasks/campaigns/cron_test.go index 2b67e913a..002bf6816 100644 --- a/core/tasks/campaigns/cron_test.go +++ b/core/tasks/campaigns/cron_test.go @@ -66,8 +66,8 @@ func TestQueueEventFires(t *testing.T) { assertFireTasks(t, rt, testdata.Org2, [][]models.FireID{{fire3ID}}) // clear queued tasks - rc.Do("DEL", "batch:active") - rc.Do("DEL", "batch:1") + rc.Do("DEL", "tasks:batch:active") + rc.Do("DEL", "tasks:batch:1") // add 110 scheduled event fires to test batch limits for i := 0; i < 110; i++ { diff --git a/core/tasks/task.go b/core/tasks/task.go index 1e6595b26..fbeda51fa 100644 --- a/core/tasks/task.go +++ b/core/tasks/task.go @@ -13,10 +13,13 @@ import ( "github.com/nyaruka/mailroom/utils/queues" ) -var HandlerQueue = queues.NewFairSorted("handler") -var BatchQueue = queues.NewFairSorted("batch") +var HandlerQueue = queues.NewFairSorted("tasks:handler") +var BatchQueue = queues.NewFairSorted("tasks:batch") var ThrottledQueue = queues.NewFairSorted("tasks:throttled") +var OldHandlerQueue = queues.NewFairSorted("handler") +var OldBatchQueue = queues.NewFairSorted("batch") + var registeredTypes = map[string](func() Task){} // RegisterType registers a new type of task diff --git a/mailroom.go b/mailroom.go index 0cd5567f9..33791c7f1 100644 --- a/mailroom.go +++ b/mailroom.go @@ -33,6 +33,10 @@ type Mailroom struct { batchForeman *Foreman throttledForeman *Foreman + // TODO once queues have moved + oldHandlerForeman *Foreman + oldBatchForeman *Foreman + webserver *web.Server } @@ -49,6 +53,9 @@ func NewMailroom(config *runtime.Config) *Mailroom { mr.batchForeman = NewForeman(mr.rt, mr.wg, tasks.BatchQueue, config.BatchWorkers) mr.throttledForeman = NewForeman(mr.rt, mr.wg, tasks.ThrottledQueue, config.BatchWorkers) + mr.oldHandlerForeman = NewForeman(mr.rt, mr.wg, tasks.OldHandlerQueue, config.HandlerWorkers) + mr.oldBatchForeman = NewForeman(mr.rt, mr.wg, tasks.OldBatchQueue, config.BatchWorkers) + return mr } @@ -149,6 +156,9 @@ func (mr *Mailroom) Start() error { mr.batchForeman.Start() mr.throttledForeman.Start() + mr.oldHandlerForeman.Start() + mr.oldBatchForeman.Start() + // start our web server mr.webserver = web.NewServer(mr.ctx, mr.rt, mr.wg) mr.webserver.Start() @@ -169,6 +179,9 @@ func (mr *Mailroom) Stop() error { mr.batchForeman.Stop() mr.throttledForeman.Stop() + mr.oldHandlerForeman.Stop() + mr.oldBatchForeman.Stop() + analytics.Stop() close(mr.quit) mr.cancel() diff --git a/testsuite/assert.go b/testsuite/assert.go index 052bd624f..e7e7197a4 100644 --- a/testsuite/assert.go +++ b/testsuite/assert.go @@ -67,7 +67,7 @@ func AssertBatchTasks(t *testing.T, orgID models.OrgID, expected map[string]int, rc := getRC() defer rc.Close() - tasks, err := redis.Strings(rc.Do("ZRANGE", fmt.Sprintf("batch:%d", orgID), 0, -1)) + tasks, err := redis.Strings(rc.Do("ZRANGE", fmt.Sprintf("tasks:batch:%d", orgID), 0, -1)) require.NoError(t, err) actual := make(map[string]int, 5) diff --git a/testsuite/tasks.go b/testsuite/tasks.go index 75361e69c..04971237d 100644 --- a/testsuite/tasks.go +++ b/testsuite/tasks.go @@ -38,12 +38,12 @@ func CurrentTasks(t *testing.T, rt *runtime.Runtime, qname string) map[models.Or defer rc.Close() // get all active org queues - active, err := redis.Ints(rc.Do("ZRANGE", fmt.Sprintf("%s:active", qname), 0, -1)) + active, err := redis.Ints(rc.Do("ZRANGE", fmt.Sprintf("tasks:%s:active", qname), 0, -1)) require.NoError(t, err) tasks := make(map[models.OrgID][]*queues.Task) for _, orgID := range active { - orgTasksEncoded, err := redis.Strings(rc.Do("ZRANGE", fmt.Sprintf("%s:%d", qname, orgID), 0, -1)) + orgTasksEncoded, err := redis.Strings(rc.Do("ZRANGE", fmt.Sprintf("tasks:%s:%d", qname, orgID), 0, -1)) require.NoError(t, err) orgTasks := make([]*queues.Task, len(orgTasksEncoded)) From 58b118f7613c4a4105823ab8be402db358779a08 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 12 Sep 2024 15:51:18 -0500 Subject: [PATCH 045/216] Update CHANGELOG.md for v9.3.15 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73a1ba0e9..1e8b3cc4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.15 (2024-09-12) +------------------------- + * Add new prefixed queues for batch and handler whilst retaining previous ones + * Remove starts queue that was replaced by tasks:throttled + v9.3.14 (2024-09-12) ------------------------- * Throttle broadcasts by outbox size too From bcd83fef3932e0c2b8995275a9332aebe8110aed Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 12 Sep 2024 16:02:23 -0500 Subject: [PATCH 046/216] Remove old task queues --- core/tasks/task.go | 3 --- mailroom.go | 13 ------------- 2 files changed, 16 deletions(-) diff --git a/core/tasks/task.go b/core/tasks/task.go index fbeda51fa..06a2f454f 100644 --- a/core/tasks/task.go +++ b/core/tasks/task.go @@ -17,9 +17,6 @@ var HandlerQueue = queues.NewFairSorted("tasks:handler") var BatchQueue = queues.NewFairSorted("tasks:batch") var ThrottledQueue = queues.NewFairSorted("tasks:throttled") -var OldHandlerQueue = queues.NewFairSorted("handler") -var OldBatchQueue = queues.NewFairSorted("batch") - var registeredTypes = map[string](func() Task){} // RegisterType registers a new type of task diff --git a/mailroom.go b/mailroom.go index 33791c7f1..0cd5567f9 100644 --- a/mailroom.go +++ b/mailroom.go @@ -33,10 +33,6 @@ type Mailroom struct { batchForeman *Foreman throttledForeman *Foreman - // TODO once queues have moved - oldHandlerForeman *Foreman - oldBatchForeman *Foreman - webserver *web.Server } @@ -53,9 +49,6 @@ func NewMailroom(config *runtime.Config) *Mailroom { mr.batchForeman = NewForeman(mr.rt, mr.wg, tasks.BatchQueue, config.BatchWorkers) mr.throttledForeman = NewForeman(mr.rt, mr.wg, tasks.ThrottledQueue, config.BatchWorkers) - mr.oldHandlerForeman = NewForeman(mr.rt, mr.wg, tasks.OldHandlerQueue, config.HandlerWorkers) - mr.oldBatchForeman = NewForeman(mr.rt, mr.wg, tasks.OldBatchQueue, config.BatchWorkers) - return mr } @@ -156,9 +149,6 @@ func (mr *Mailroom) Start() error { mr.batchForeman.Start() mr.throttledForeman.Start() - mr.oldHandlerForeman.Start() - mr.oldBatchForeman.Start() - // start our web server mr.webserver = web.NewServer(mr.ctx, mr.rt, mr.wg) mr.webserver.Start() @@ -179,9 +169,6 @@ func (mr *Mailroom) Stop() error { mr.batchForeman.Stop() mr.throttledForeman.Stop() - mr.oldHandlerForeman.Stop() - mr.oldBatchForeman.Stop() - analytics.Stop() close(mr.quit) mr.cancel() From 191dd887b5751651454c27b3848583ba1fede95b Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 12 Sep 2024 17:24:06 -0500 Subject: [PATCH 047/216] Update CHANGELOG.md for v9.3.16 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e8b3cc4c..27684519e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.16 (2024-09-12) +------------------------- + * Remove old task queues + v9.3.15 (2024-09-12) ------------------------- * Add new prefixed queues for batch and handler whilst retaining previous ones From d927779ee91498cc9ce2f55aa07bf04f7e374d86 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 13 Sep 2024 10:09:24 -0500 Subject: [PATCH 048/216] Update go version in DockerFile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1d551d3ad..3cdf2ec6f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22 +FROM golang:1.23 # copy our dev certs into the container # WORKDIR /usr/local/share/ca-certificates From 9b35a2a844f14e86685afb58c2a13d1e51992a1a Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 13 Sep 2024 12:19:04 -0500 Subject: [PATCH 049/216] Start writing channel logs to DynamoDB --- core/models/channel_logs.go | 56 ++++++++++++++++++++++++++++++-- core/models/channel_logs_test.go | 13 ++++++++ go.mod | 12 ++++--- go.sum | 24 ++++++++------ testsuite/testsuite.go | 3 ++ 5 files changed, 91 insertions(+), 17 deletions(-) diff --git a/core/models/channel_logs.go b/core/models/channel_logs.go index 13c2b12d8..5385cef66 100644 --- a/core/models/channel_logs.go +++ b/core/models/channel_logs.go @@ -1,13 +1,19 @@ package models import ( + "bytes" + "compress/gzip" "context" "encoding/json" "fmt" "path" + "slices" "time" - "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + dytypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/nyaruka/gocommon/aws/s3x" "github.com/nyaruka/gocommon/dates" "github.com/nyaruka/gocommon/httpx" @@ -153,8 +159,54 @@ func (l *stChannelLog) path() string { return path.Join("channels", string(l.ChannelUUID), string(l.UUID[:4]), fmt.Sprintf("%s.json", l.UUID)) } +// channel log to be written to DynamoDB +type dyChannelLog struct { + UUID ChannelLogUUID `dynamodbav:"UUID"` + Type ChannelLogType `dynamodbav:"Type"` + DataGZ []byte `dynamodbav:"DataGZ,omitempty"` + ElapsedMS int `dynamodbav:"ElapsedMS"` + CreatedOn time.Time `dynamodbav:"CreatedOn,unixtime"` + ExpiresOn time.Time `dynamodbav:"ExpiresOn,unixtime"` +} + // InsertChannelLogs writes the given channel logs to the db func InsertChannelLogs(ctx context.Context, rt *runtime.Runtime, logs []*ChannelLog) error { + // write in batches to DynamoDB + for batch := range slices.Chunk(logs, 25) { + writeReqs := make([]dytypes.WriteRequest, len(batch)) + + for i, l := range batch { + // save http logs and errors as gzipped JSON + data := jsonx.MustMarshal(map[string]any{"http_logs": l.httpLogs, "errors": l.errors}) + buf := &bytes.Buffer{} + w := gzip.NewWriter(buf) + w.Write(data) + w.Close() + + dl := &dyChannelLog{ + UUID: l.UUID(), + Type: l.type_, + DataGZ: buf.Bytes(), + ElapsedMS: int(l.elapsed / time.Millisecond), + CreatedOn: l.createdOn, + ExpiresOn: l.createdOn.Add(14 * 24 * time.Hour), + } + + item, err := attributevalue.MarshalMap(dl) + if err != nil { + return fmt.Errorf("error marshalling channel log: %w", err) + } + writeReqs[i] = dytypes.WriteRequest{PutRequest: &dytypes.PutRequest{Item: item}} + } + + _, err := rt.Dynamo.Client.BatchWriteItem(ctx, &dynamodb.BatchWriteItemInput{ + RequestItems: map[string][]dytypes.WriteRequest{rt.Dynamo.TableName("ChannelLogs"): writeReqs}, + }) + if err != nil { + return fmt.Errorf("error writing channel logs: %w", err) + } + } + attached := make([]*stChannelLog, 0, len(logs)) unattached := make([]*dbChannelLog, 0, len(logs)) @@ -193,7 +245,7 @@ func InsertChannelLogs(ctx context.Context, rt *runtime.Runtime, logs []*Channel Key: l.path(), ContentType: "application/json", Body: jsonx.MustMarshal(l), - ACL: types.ObjectCannedACLPrivate, + ACL: s3types.ObjectCannedACLPrivate, } } if err := rt.S3.BatchPut(ctx, uploads, 32); err != nil { diff --git a/core/models/channel_logs_test.go b/core/models/channel_logs_test.go index 72bf3a3d6..b6cb604ae 100644 --- a/core/models/channel_logs_test.go +++ b/core/models/channel_logs_test.go @@ -5,11 +5,15 @@ import ( "net/http" "testing" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/nyaruka/gocommon/dbutil/assertdb" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/testsuite" "github.com/nyaruka/mailroom/testsuite/testdata" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -55,4 +59,13 @@ func TestChannelLogsOutgoing(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT count(*) FROM channels_channellog WHERE log_type = 'ivr_start' AND http_logs -> 0 ->> 'url' = 'http://ivr.com/start' AND is_error = FALSE AND channel_id = $1`, channel.ID()).Returns(1) assertdb.Query(t, rt.DB, `SELECT count(*) FROM channels_channellog WHERE log_type = 'ivr_hangup' AND http_logs -> 0 ->> 'url' = 'http://ivr.com/hangup' AND is_error = TRUE AND channel_id = $1`, channel.ID()).Returns(1) assertdb.Query(t, rt.DB, `SELECT count(*) FROM channels_channellog WHERE http_logs::text LIKE '%sesame%'`).Returns(0) + + resp, err := rt.Dynamo.Client.GetItem(ctx, &dynamodb.GetItemInput{ + TableName: aws.String(rt.Dynamo.TableName("ChannelLogs")), + Key: map[string]types.AttributeValue{ + "UUID": &types.AttributeValueMemberS{Value: string(clog1.UUID())}, + }, + }) + require.NoError(t, err) + assert.Equal(t, string(clog1.UUID()), resp.Item["UUID"].(*types.AttributeValueMemberS).Value) } diff --git a/go.mod b/go.mod index 45ee04d20..1e5381629 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,9 @@ require ( firebase.google.com/go/v4 v4.14.1 github.com/Masterminds/semver v1.5.0 github.com/appleboy/go-fcm v1.2.1 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.5 + github.com/aws/aws-sdk-go-v2 v1.30.5 + github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.3 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.9 github.com/aws/aws-sdk-go-v2/service/s3 v1.60.0 github.com/buger/jsonparser v1.1.1 github.com/elastic/go-elasticsearch/v8 v8.14.0 @@ -47,18 +49,18 @@ require ( github.com/MicahParks/keyfunc v1.9.0 // indirect github.com/Shopify/gomail v0.0.0-20220729171026-0784ece65e69 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect - github.com/aws/aws-sdk-go-v2 v1.30.4 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect github.com/aws/aws-sdk-go-v2/config v1.27.28 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.28 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.17 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.18 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect diff --git a/go.sum b/go.sum index 6a81a3872..f1b204258 100644 --- a/go.sum +++ b/go.sum @@ -30,32 +30,36 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/appleboy/go-fcm v1.2.1 h1:NhpACabtRuAplYg6bTNfSr3LBwsSuutP55HsphzLU/g= github.com/appleboy/go-fcm v1.2.1/go.mod h1:5FzMN+9J2sxnkoys9h3y48GQH8HnI637Q/ro/uP2Qsk= -github.com/aws/aws-sdk-go-v2 v1.30.4 h1:frhcagrVNrzmT95RJImMHgabt99vkXGslubDaDagTk8= -github.com/aws/aws-sdk-go-v2 v1.30.4/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= +github.com/aws/aws-sdk-go-v2 v1.30.5 h1:mWSRTwQAb0aLE17dSzztCVJWI9+cRMgqebndjwDyK0g= +github.com/aws/aws-sdk-go-v2 v1.30.5/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 h1:70PVAiL15/aBMh5LThwgXdSQorVr91L127ttckI9QQU= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4/go.mod h1:/MQxMqci8tlqDH+pjmoLu1i0tbWCUP1hhyMRuFxpQCw= github.com/aws/aws-sdk-go-v2/config v1.27.28 h1:OTxWGW/91C61QlneCtnD62NLb4W616/NM1jA8LhJqbg= github.com/aws/aws-sdk-go-v2/config v1.27.28/go.mod h1:uzVRVtJSU5EFv6Fu82AoVFKozJi2ZCY6WRCXj06rbvs= github.com/aws/aws-sdk-go-v2/credentials v1.17.28 h1:m8+AHY/ND8CMHJnPoH7PJIRakWGa4gbfbxuY9TGTUXM= github.com/aws/aws-sdk-go-v2/credentials v1.17.28/go.mod h1:6TF7dSc78ehD1SL6KpRIPKMA1GyyWflIkjqg+qmf4+c= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.3 h1:/BPXKQ6n1cDWPmc5FWF6fCSaUtK+dWkWd0x9dI4dgaI= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.3/go.mod h1:qabLXChRlJREypX5RN/Z47GU+RaMsjotNCZfZ85oD0M= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 h1:yjwoSyDZF8Jth+mUk5lSPJCkMC0lMy6FaCD51jm6ayE= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12/go.mod h1:fuR57fAgMk7ot3WcNQfb6rSEn+SUffl7ri+aa8uKysI= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 h1:TNyt/+X43KJ9IJJMjKfa3bNTiZbUP7DeCxfbTROESwY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16/go.mod h1:2DwJF39FlNAUiX5pAc0UNeiz16lK2t7IaFcm0LFHEgc= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 h1:jYfy8UPmd+6kJW5YhY0L1/KftReOGxI/4NtVSTh9O/I= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16/go.mod h1:7ZfEPZxkW42Afq4uQB8H2E2e6ebh6mXTueEpYzjCzcs= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 h1:pI7Bzt0BJtYA0N/JEC6B8fJ4RBrEMi1LBrkMdFYNSnQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17/go.mod h1:Dh5zzJYMtxfIjYW+/evjQ8uj2OyR/ve2KROHGHlSFqE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 h1:Mqr/V5gvrhA2gvgnF42Zh5iMiQNcOYthFYwCyrnuWlc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17/go.mod h1:aLJpZlCmjE+V+KtN1q1uyZkfnUWpQGpbsn89XPKyzfU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16 h1:mimdLQkIX1zr8GIPY1ZtALdBQGxcASiBd2MOp8m/dMc= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16/go.mod h1:YHk6owoSwrIsok+cAH9PENCOGoH5PU2EllX4vLtSrsY= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.5 h1:Cm77yt+/CV7A6DglkENsWA3H1hq8+4ItJnFKrhxHkvg= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.5/go.mod h1:s2fYaueBuCnwv1XQn6T8TfShxJWusv5tWPMcL+GY6+g= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.9 h1:jbqgtdKfAXebx2/l2UhDEe/jmmCIhaCO3HFK71M7VzM= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.9/go.mod h1:N3YdUYxyxhiuAelUgCpSVBuBI1klobJxZrDtL+olu10= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.7 h1:VTBHXWkSeFgT3sfYB4U92qMgzHl0nz9H1tYNHHutLg0= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.7/go.mod h1:F/ybU7YfgFcktSp+biKgiHjyscGhlZxOz4QFFQqHXGw= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18 h1:GckUnpm4EJOAio1c8o25a+b3lVfwVzC9gnSBqiiNmZM= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18/go.mod h1:Br6+bxfG33Dk3ynmkhsW2Z/t9D4+lRqdLDNCKi85w0U= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.17 h1:HDJGz1jlV7RokVgTPfx1UHBHANC0N5Uk++xgyYgz5E0= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.17/go.mod h1:5szDu6TWdRDytfDxUQVv2OYfpTQMKApVFyqpm+TcA98= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.18 h1:GACdEPdpBE59I7pbfvu0/Mw1wzstlP3QtPHklUxybFE= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.18/go.mod h1:K+xV06+Wni4TSaOOJ1Y35e5tYOCUBYbebLKmJQQa8yY= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 h1:tJ5RnkHCiSH0jyd6gROjlJtNwov0eGYNz8s8nFcR0jQ= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18/go.mod h1:++NHzT+nAF7ZPrHPsA+ENvsXkOO8wEu+C6RXltAG4/c= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 h1:jg16PhLPUiHIj8zYIW6bqzeQSuHVEiWnGA0Brz5Xv2I= diff --git a/testsuite/testsuite.go b/testsuite/testsuite.go index f3425bbb5..3856ed270 100644 --- a/testsuite/testsuite.go +++ b/testsuite/testsuite.go @@ -10,6 +10,7 @@ import ( "os/exec" "path" + "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/elastic/go-elasticsearch/v8" "github.com/gomodule/redigo/redis" @@ -247,6 +248,8 @@ func resetDynamo(ctx context.Context, rt *runtime.Runtime) { jsonx.MustUnmarshal(tablesJSON, &inputs) for _, input := range inputs { + input.TableName = aws.String(rt.Dynamo.TableName(*input.TableName)) + // delete table if it exists if _, err := rt.Dynamo.Client.DescribeTable(ctx, &dynamodb.DescribeTableInput{TableName: input.TableName}); err == nil { _, err := rt.Dynamo.Client.DeleteTable(ctx, &dynamodb.DeleteTableInput{TableName: input.TableName}) From 42499fb31622ca9f94669c6d5dd37d632cfa7c91 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 13 Sep 2024 14:12:22 -0500 Subject: [PATCH 050/216] Move some channel log stuff into clogs utility package we could potentially share with courier --- core/models/channel_logs.go | 62 +++++--------------------- core/models/msgs.go | 3 +- core/msgio/courier.go | 7 +-- core/tasks/handler/ctasks/msg_event.go | 7 +-- utils/clogs/base.go | 26 +++++++++++ utils/clogs/dynamo.go | 41 +++++++++++++++++ web/ivr/ivr_test.go | 3 +- 7 files changed, 91 insertions(+), 58 deletions(-) create mode 100644 utils/clogs/base.go create mode 100644 utils/clogs/dynamo.go diff --git a/core/models/channel_logs.go b/core/models/channel_logs.go index 5385cef66..e5fce3b5e 100644 --- a/core/models/channel_logs.go +++ b/core/models/channel_logs.go @@ -1,8 +1,6 @@ package models import ( - "bytes" - "compress/gzip" "context" "encoding/json" "fmt" @@ -19,17 +17,14 @@ import ( "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/stringsx" - "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/mailroom/runtime" + "github.com/nyaruka/mailroom/utils/clogs" ) // ChannelLogID is our type for a channel log id type ChannelLogID int64 -// ChannelLogUUID is our type for a channel log UUID -type ChannelLogUUID uuids.UUID - type ChannelLogType string const ( @@ -40,22 +35,13 @@ const ( ChannelLogTypeIVRHangup = "ivr_hangup" ) -type ChannelError struct { - Message string `json:"message"` - Code string `json:"code"` -} - -func NewChannelError(message, code string) ChannelError { - return ChannelError{Message: message, Code: code} -} - // ChannelLog stores the HTTP traces and errors generated by an interaction with a channel. type ChannelLog struct { - uuid ChannelLogUUID + uuid clogs.LogUUID type_ ChannelLogType channel *Channel httpLogs []*httpx.Log - errors []ChannelError + errors []*clogs.LogError createdOn time.Time elapsed time.Duration @@ -76,11 +62,11 @@ func NewChannelLogForIncoming(t ChannelLogType, ch *Channel, r *httpx.Recorder, func newChannelLog(t ChannelLogType, ch *Channel, r *httpx.Recorder, redactVals []string) *ChannelLog { return &ChannelLog{ - uuid: ChannelLogUUID(uuids.NewV4()), + uuid: clogs.NewLogUUID(), type_: t, channel: ch, httpLogs: []*httpx.Log{}, - errors: []ChannelError{}, + errors: []*clogs.LogError{}, createdOn: dates.Now(), recorder: r, @@ -88,14 +74,14 @@ func newChannelLog(t ChannelLogType, ch *Channel, r *httpx.Recorder, redactVals } } -func (l *ChannelLog) UUID() ChannelLogUUID { return l.uuid } +func (l *ChannelLog) UUID() clogs.LogUUID { return l.uuid } func (l *ChannelLog) HTTP(t *httpx.Trace) { l.httpLogs = append(l.httpLogs, l.traceToLog(t)) } func (l *ChannelLog) Error(err error) { - l.errors = append(l.errors, NewChannelError(err.Error(), "")) + l.errors = append(l.errors, clogs.NewLogError("", "", err.Error())) } func (l *ChannelLog) End() { @@ -134,7 +120,7 @@ INSERT INTO channels_channellog( uuid, channel_id, log_type, http_logs, erro // channel log to be inserted into the database type dbChannelLog struct { ID ChannelLogID `db:"id"` - UUID ChannelLogUUID `db:"uuid"` + UUID clogs.LogUUID `db:"uuid"` ChannelID ChannelID `db:"channel_id"` Type ChannelLogType `db:"log_type"` HTTPLogs json.RawMessage `db:"http_logs"` @@ -146,10 +132,10 @@ type dbChannelLog struct { // channel log to be written to logs storage type stChannelLog struct { - UUID ChannelLogUUID `json:"uuid"` + UUID clogs.LogUUID `json:"uuid"` Type ChannelLogType `json:"type"` HTTPLogs []*httpx.Log `json:"http_logs"` - Errors []ChannelError `json:"errors"` + Errors []*clogs.LogError `json:"errors"` ElapsedMS int `json:"elapsed_ms"` CreatedOn time.Time `json:"created_on"` ChannelUUID assets.ChannelUUID `json:"-"` @@ -159,16 +145,6 @@ func (l *stChannelLog) path() string { return path.Join("channels", string(l.ChannelUUID), string(l.UUID[:4]), fmt.Sprintf("%s.json", l.UUID)) } -// channel log to be written to DynamoDB -type dyChannelLog struct { - UUID ChannelLogUUID `dynamodbav:"UUID"` - Type ChannelLogType `dynamodbav:"Type"` - DataGZ []byte `dynamodbav:"DataGZ,omitempty"` - ElapsedMS int `dynamodbav:"ElapsedMS"` - CreatedOn time.Time `dynamodbav:"CreatedOn,unixtime"` - ExpiresOn time.Time `dynamodbav:"ExpiresOn,unixtime"` -} - // InsertChannelLogs writes the given channel logs to the db func InsertChannelLogs(ctx context.Context, rt *runtime.Runtime, logs []*ChannelLog) error { // write in batches to DynamoDB @@ -176,21 +152,7 @@ func InsertChannelLogs(ctx context.Context, rt *runtime.Runtime, logs []*Channel writeReqs := make([]dytypes.WriteRequest, len(batch)) for i, l := range batch { - // save http logs and errors as gzipped JSON - data := jsonx.MustMarshal(map[string]any{"http_logs": l.httpLogs, "errors": l.errors}) - buf := &bytes.Buffer{} - w := gzip.NewWriter(buf) - w.Write(data) - w.Close() - - dl := &dyChannelLog{ - UUID: l.UUID(), - Type: l.type_, - DataGZ: buf.Bytes(), - ElapsedMS: int(l.elapsed / time.Millisecond), - CreatedOn: l.createdOn, - ExpiresOn: l.createdOn.Add(14 * 24 * time.Hour), - } + dl := clogs.NewDynamoChannelLog(l.UUID(), string(l.type_), l.httpLogs, l.errors, l.elapsed, l.createdOn) item, err := attributevalue.MarshalMap(dl) if err != nil { @@ -225,7 +187,7 @@ func InsertChannelLogs(ctx context.Context, rt *runtime.Runtime, logs []*Channel } else { // otherwise write to database so it's retrievable unattached = append(unattached, &dbChannelLog{ - UUID: ChannelLogUUID(uuids.NewV4()), + UUID: l.uuid, ChannelID: l.channel.ID(), Type: l.type_, HTTPLogs: jsonx.MustMarshal(l.httpLogs), diff --git a/core/models/msgs.go b/core/models/msgs.go index 4f8b8e8d3..6f7697634 100644 --- a/core/models/msgs.go +++ b/core/models/msgs.go @@ -25,6 +25,7 @@ import ( "github.com/nyaruka/goflow/utils" "github.com/nyaruka/mailroom/core/goflow" "github.com/nyaruka/mailroom/runtime" + "github.com/nyaruka/mailroom/utils/clogs" "github.com/nyaruka/null/v3" ) @@ -630,7 +631,7 @@ msgs_msg(uuid, text, attachments, quick_replies, locale, templating, high_priori RETURNING id, modified_on` // MarkMessageHandled updates a message after handling -func MarkMessageHandled(ctx context.Context, tx DBorTx, msgID MsgID, status MsgStatus, visibility MsgVisibility, flowID FlowID, ticketID TicketID, attachments []utils.Attachment, logUUIDs []ChannelLogUUID) error { +func MarkMessageHandled(ctx context.Context, tx DBorTx, msgID MsgID, status MsgStatus, visibility MsgVisibility, flowID FlowID, ticketID TicketID, attachments []utils.Attachment, logUUIDs []clogs.LogUUID) error { _, err := tx.ExecContext(ctx, `UPDATE msgs_msg SET status = $2, visibility = $3, flow_id = $4, ticket_id = $5, attachments = $6, log_uuids = array_cat(log_uuids, $7) WHERE id = $1`, msgID, status, visibility, flowID, ticketID, pq.Array(attachments), pq.Array(logUUIDs), diff --git a/core/msgio/courier.go b/core/msgio/courier.go index 4227b0f90..34a3bb0b0 100644 --- a/core/msgio/courier.go +++ b/core/msgio/courier.go @@ -21,6 +21,7 @@ import ( "github.com/nyaruka/goflow/utils" "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/runtime" + "github.com/nyaruka/mailroom/utils/clogs" ) var courierHttpClient = &http.Client{ @@ -311,11 +312,11 @@ type fetchAttachmentResponse struct { URL string `json:"url"` Size int `json:"size"` } `json:"attachment"` - LogUUID string `json:"log_uuid"` + LogUUID clogs.LogUUID `json:"log_uuid"` } // FetchAttachment calls courier to fetch the given attachment -func FetchAttachment(ctx context.Context, rt *runtime.Runtime, ch *models.Channel, attURL string, msgID models.MsgID) (utils.Attachment, models.ChannelLogUUID, error) { +func FetchAttachment(ctx context.Context, rt *runtime.Runtime, ch *models.Channel, attURL string, msgID models.MsgID) (utils.Attachment, clogs.LogUUID, error) { payload := jsonx.MustMarshal(&fetchAttachmentRequest{ ChannelType: ch.Type(), ChannelUUID: ch.UUID(), @@ -337,5 +338,5 @@ func FetchAttachment(ctx context.Context, rt *runtime.Runtime, ch *models.Channe return "", "", fmt.Errorf("error unmarshaling courier response: %w", err) } - return utils.Attachment(fmt.Sprintf("%s:%s", fa.Attachment.ContentType, fa.Attachment.URL)), models.ChannelLogUUID(fa.LogUUID), nil + return utils.Attachment(fmt.Sprintf("%s:%s", fa.Attachment.ContentType, fa.Attachment.URL)), fa.LogUUID, nil } diff --git a/core/tasks/handler/ctasks/msg_event.go b/core/tasks/handler/ctasks/msg_event.go index 0ab4604db..a60a019e3 100644 --- a/core/tasks/handler/ctasks/msg_event.go +++ b/core/tasks/handler/ctasks/msg_event.go @@ -17,6 +17,7 @@ import ( "github.com/nyaruka/mailroom/core/runner" "github.com/nyaruka/mailroom/core/tasks/handler" "github.com/nyaruka/mailroom/runtime" + "github.com/nyaruka/mailroom/utils/clogs" "github.com/nyaruka/null/v3" ) @@ -51,7 +52,7 @@ func (t *MsgEventTask) Perform(ctx context.Context, rt *runtime.Runtime, oa *mod // fetch the attachments on the message (i.e. ask courier to fetch them) attachments := make([]utils.Attachment, 0, len(t.Attachments)) - logUUIDs := make([]models.ChannelLogUUID, 0, len(t.Attachments)) + logUUIDs := make([]clogs.LogUUID, 0, len(t.Attachments)) // no channel, no attachments if channel != nil { @@ -224,7 +225,7 @@ func (t *MsgEventTask) Perform(ctx context.Context, rt *runtime.Runtime, oa *mod } // handles a message as an inbox message, i.e. no flow -func handleAsInbox(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets, contact *flows.Contact, msg *flows.MsgIn, attachments []utils.Attachment, logUUIDs []models.ChannelLogUUID, ticket *models.Ticket) error { +func handleAsInbox(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets, contact *flows.Contact, msg *flows.MsgIn, attachments []utils.Attachment, logUUIDs []clogs.LogUUID, ticket *models.Ticket) error { // usually last_seen_on is updated by handling the msg_received event in the engine sprint, but since this is an inbox // message we manually create that event and handle it msgEvent := events.NewMsgReceived(msg) @@ -240,7 +241,7 @@ func handleAsInbox(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAsset } // utility to mark as message as handled and update any open contact tickets -func markMsgHandled(ctx context.Context, db models.DBorTx, msg *flows.MsgIn, flow *models.Flow, attachments []utils.Attachment, ticket *models.Ticket, logUUIDs []models.ChannelLogUUID) error { +func markMsgHandled(ctx context.Context, db models.DBorTx, msg *flows.MsgIn, flow *models.Flow, attachments []utils.Attachment, ticket *models.Ticket, logUUIDs []clogs.LogUUID) error { flowID := models.NilFlowID if flow != nil { flowID = flow.ID() diff --git a/utils/clogs/base.go b/utils/clogs/base.go new file mode 100644 index 000000000..96112daad --- /dev/null +++ b/utils/clogs/base.go @@ -0,0 +1,26 @@ +package clogs + +import ( + "fmt" + + "github.com/nyaruka/gocommon/uuids" +) + +// LogUUID is the type of a channel log UUID (should be v7) +type LogUUID uuids.UUID + +// NewLogUUID creates a new channel log UUID +func NewLogUUID() LogUUID { + return LogUUID(uuids.NewV7()) +} + +// Error is an error that occurred during a channel interaction +type LogError struct { + Code string `json:"code"` + ExtCode string `json:"ext_code,omitempty"` + Message string `json:"message"` +} + +func NewLogError(code, extCode, message string, args ...any) *LogError { + return &LogError{Code: code, ExtCode: extCode, Message: fmt.Sprintf(message, args...)} +} diff --git a/utils/clogs/dynamo.go b/utils/clogs/dynamo.go new file mode 100644 index 000000000..e6e13cb31 --- /dev/null +++ b/utils/clogs/dynamo.go @@ -0,0 +1,41 @@ +package clogs + +import ( + "bytes" + "compress/gzip" + "time" + + "github.com/nyaruka/gocommon/httpx" + "github.com/nyaruka/gocommon/jsonx" +) + +const ( + dynamoTTL = 14 * 24 * time.Hour +) + +// DynamoChannelLog channel log to be written to DynamoDB +type DynamoChannelLog struct { + UUID LogUUID `dynamodbav:"UUID"` + Type string `dynamodbav:"Type"` + DataGZ []byte `dynamodbav:"DataGZ,omitempty"` + ElapsedMS int `dynamodbav:"ElapsedMS"` + CreatedOn time.Time `dynamodbav:"CreatedOn,unixtime"` + ExpiresOn time.Time `dynamodbav:"ExpiresOn,unixtime"` +} + +func NewDynamoChannelLog(uuid LogUUID, logType string, httpLogs []*httpx.Log, errors []*LogError, elapsed time.Duration, createdOn time.Time) *DynamoChannelLog { + data := jsonx.MustMarshal(map[string]any{"http_logs": httpLogs, "errors": errors}) + buf := &bytes.Buffer{} + w := gzip.NewWriter(buf) + w.Write(data) + w.Close() + + return &DynamoChannelLog{ + UUID: uuid, + Type: logType, + DataGZ: buf.Bytes(), + ElapsedMS: int(elapsed / time.Millisecond), + CreatedOn: createdOn, + ExpiresOn: createdOn.Add(dynamoTTL), + } +} diff --git a/web/ivr/ivr_test.go b/web/ivr/ivr_test.go index e2917dcfa..c1491e24b 100644 --- a/web/ivr/ivr_test.go +++ b/web/ivr/ivr_test.go @@ -25,6 +25,7 @@ import ( "github.com/nyaruka/mailroom/services/ivr/vonage" "github.com/nyaruka/mailroom/testsuite" "github.com/nyaruka/mailroom/testsuite/testdata" + "github.com/nyaruka/mailroom/utils/clogs" "github.com/nyaruka/mailroom/utils/queues" "github.com/nyaruka/mailroom/web" "github.com/stretchr/testify/assert" @@ -640,7 +641,7 @@ func TestVonageIVR(t *testing.T) { } func getCallLogs(t *testing.T, rt *runtime.Runtime, channelUUID assets.ChannelUUID) [][]byte { - var logUUIDs []models.ChannelLogUUID + var logUUIDs []clogs.LogUUID err := rt.DB.Select(&logUUIDs, `SELECT unnest(log_uuids) FROM ivr_call ORDER BY id`) require.NoError(t, err) From df7983c73601cde8dac1bb29953d15e65f6f9240 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 13 Sep 2024 15:27:14 -0500 Subject: [PATCH 051/216] Move more functionality into clogs package --- core/ivr/ivr.go | 5 +- core/models/calls.go | 2 +- core/models/channel_logs.go | 101 +++++++++---------------------- core/models/channel_logs_test.go | 8 +-- utils/clogs/base.go | 61 +++++++++++++++++++ utils/clogs/dynamo.go | 23 ++++--- web/ivr/ivr.go | 3 +- 7 files changed, 112 insertions(+), 91 deletions(-) diff --git a/core/ivr/ivr.go b/core/ivr/ivr.go index 53f9e9e9b..3a770f708 100644 --- a/core/ivr/ivr.go +++ b/core/ivr/ivr.go @@ -27,6 +27,7 @@ import ( "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/core/runner" "github.com/nyaruka/mailroom/runtime" + "github.com/nyaruka/mailroom/utils/clogs" "github.com/nyaruka/null/v3" ) @@ -128,7 +129,7 @@ func HangupCall(ctx context.Context, rt *runtime.Runtime, call *models.Call) (*m clog.HTTP(trace) } if err != nil { - clog.Error(err) + clog.Error(clogs.NewLogError("", "", err.Error())) } if err := call.AttachLog(ctx, rt.DB, clog); err != nil { @@ -260,7 +261,7 @@ func RequestStartForCall(ctx context.Context, rt *runtime.Runtime, channel *mode clog.HTTP(trace) } if err != nil { - clog.Error(err) + clog.Error(clogs.NewLogError("", "", err.Error())) // set our status as errored err := call.UpdateStatus(ctx, rt.DB, models.CallStatusFailed, 0, time.Now()) diff --git a/core/models/calls.go b/core/models/calls.go index 68a63ee53..d1430c070 100644 --- a/core/models/calls.go +++ b/core/models/calls.go @@ -419,7 +419,7 @@ func BulkUpdateCallStatuses(ctx context.Context, db DBorTx, callIDs []CallID, st } func (c *Call) AttachLog(ctx context.Context, db DBorTx, clog *ChannelLog) error { - _, err := db.ExecContext(ctx, `UPDATE ivr_call SET log_uuids = array_append(log_uuids, $2) WHERE id = $1`, c.c.ID, clog.UUID()) + _, err := db.ExecContext(ctx, `UPDATE ivr_call SET log_uuids = array_append(log_uuids, $2) WHERE id = $1`, c.c.ID, clog.UUID) if err != nil { return fmt.Errorf("error attaching log to call: %w", err) } diff --git a/core/models/channel_logs.go b/core/models/channel_logs.go index e5fce3b5e..39582ed84 100644 --- a/core/models/channel_logs.go +++ b/core/models/channel_logs.go @@ -13,10 +13,8 @@ import ( dytypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/nyaruka/gocommon/aws/s3x" - "github.com/nyaruka/gocommon/dates" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/jsonx" - "github.com/nyaruka/gocommon/stringsx" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/mailroom/utils/clogs" @@ -25,85 +23,46 @@ import ( // ChannelLogID is our type for a channel log id type ChannelLogID int64 -type ChannelLogType string - const ( - ChannelLogTypeIVRStart = "ivr_start" - ChannelLogTypeIVRIncoming = "ivr_incoming" - ChannelLogTypeIVRCallback = "ivr_callback" - ChannelLogTypeIVRStatus = "ivr_status" - ChannelLogTypeIVRHangup = "ivr_hangup" + ChannelLogTypeIVRStart clogs.LogType = "ivr_start" + ChannelLogTypeIVRIncoming clogs.LogType = "ivr_incoming" + ChannelLogTypeIVRCallback clogs.LogType = "ivr_callback" + ChannelLogTypeIVRStatus clogs.LogType = "ivr_status" + ChannelLogTypeIVRHangup clogs.LogType = "ivr_hangup" ) // ChannelLog stores the HTTP traces and errors generated by an interaction with a channel. type ChannelLog struct { - uuid clogs.LogUUID - type_ ChannelLogType - channel *Channel - httpLogs []*httpx.Log - errors []*clogs.LogError - createdOn time.Time - elapsed time.Duration - - recorder *httpx.Recorder - redactor stringsx.Redactor + *clogs.Log + + channel *Channel attached bool } // NewChannelLog creates a new channel log with the given type and channel -func NewChannelLog(t ChannelLogType, ch *Channel, redactVals []string) *ChannelLog { +func NewChannelLog(t clogs.LogType, ch *Channel, redactVals []string) *ChannelLog { return newChannelLog(t, ch, nil, redactVals) } // NewChannelLogForIncoming creates a new channel log for an incoming request -func NewChannelLogForIncoming(t ChannelLogType, ch *Channel, r *httpx.Recorder, redactVals []string) *ChannelLog { +func NewChannelLogForIncoming(t clogs.LogType, ch *Channel, r *httpx.Recorder, redactVals []string) *ChannelLog { return newChannelLog(t, ch, r, redactVals) } -func newChannelLog(t ChannelLogType, ch *Channel, r *httpx.Recorder, redactVals []string) *ChannelLog { +func newChannelLog(t clogs.LogType, ch *Channel, r *httpx.Recorder, redactVals []string) *ChannelLog { return &ChannelLog{ - uuid: clogs.NewLogUUID(), - type_: t, - channel: ch, - httpLogs: []*httpx.Log{}, - errors: []*clogs.LogError{}, - createdOn: dates.Now(), - - recorder: r, - redactor: stringsx.NewRedactor("**********", redactVals...), + Log: clogs.NewLog(t, r, redactVals), + channel: ch, } } -func (l *ChannelLog) UUID() clogs.LogUUID { return l.uuid } - -func (l *ChannelLog) HTTP(t *httpx.Trace) { - l.httpLogs = append(l.httpLogs, l.traceToLog(t)) -} - -func (l *ChannelLog) Error(err error) { - l.errors = append(l.errors, clogs.NewLogError("", "", err.Error())) -} - -func (l *ChannelLog) End() { - if l.recorder != nil { - // prepend so it's the first HTTP request in the log - l.httpLogs = append([]*httpx.Log{l.traceToLog(l.recorder.Trace)}, l.httpLogs...) - } - - l.elapsed = time.Since(l.createdOn) -} - -func (l *ChannelLog) traceToLog(t *httpx.Trace) *httpx.Log { - return httpx.NewLog(t, 2048, 50000, l.redactor) -} - // if we have an error or a non 2XX/3XX http response then log is considered an error func (l *ChannelLog) isError() bool { - if len(l.errors) > 0 { + if len(l.Errors) > 0 { return true } - for _, l := range l.httpLogs { + for _, l := range l.HttpLogs { if l.StatusCode < 200 || l.StatusCode >= 400 { return true } @@ -122,7 +81,7 @@ type dbChannelLog struct { ID ChannelLogID `db:"id"` UUID clogs.LogUUID `db:"uuid"` ChannelID ChannelID `db:"channel_id"` - Type ChannelLogType `db:"log_type"` + Type clogs.LogType `db:"log_type"` HTTPLogs json.RawMessage `db:"http_logs"` Errors json.RawMessage `db:"errors"` IsError bool `db:"is_error"` @@ -133,7 +92,7 @@ type dbChannelLog struct { // channel log to be written to logs storage type stChannelLog struct { UUID clogs.LogUUID `json:"uuid"` - Type ChannelLogType `json:"type"` + Type clogs.LogType `json:"type"` HTTPLogs []*httpx.Log `json:"http_logs"` Errors []*clogs.LogError `json:"errors"` ElapsedMS int `json:"elapsed_ms"` @@ -152,7 +111,7 @@ func InsertChannelLogs(ctx context.Context, rt *runtime.Runtime, logs []*Channel writeReqs := make([]dytypes.WriteRequest, len(batch)) for i, l := range batch { - dl := clogs.NewDynamoChannelLog(l.UUID(), string(l.type_), l.httpLogs, l.errors, l.elapsed, l.createdOn) + dl := clogs.NewDynamoLog(l.Log) item, err := attributevalue.MarshalMap(dl) if err != nil { @@ -176,25 +135,25 @@ func InsertChannelLogs(ctx context.Context, rt *runtime.Runtime, logs []*Channel if l.attached { // if log is attached to a call or message, only write to storage attached = append(attached, &stChannelLog{ - UUID: l.uuid, - Type: l.type_, - HTTPLogs: l.httpLogs, - Errors: l.errors, - ElapsedMS: int(l.elapsed / time.Millisecond), - CreatedOn: l.createdOn, + UUID: l.UUID, + Type: l.Type, + HTTPLogs: l.HttpLogs, + Errors: l.Errors, + ElapsedMS: int(l.Elapsed / time.Millisecond), + CreatedOn: l.CreatedOn, ChannelUUID: l.channel.UUID(), }) } else { // otherwise write to database so it's retrievable unattached = append(unattached, &dbChannelLog{ - UUID: l.uuid, + UUID: l.UUID, ChannelID: l.channel.ID(), - Type: l.type_, - HTTPLogs: jsonx.MustMarshal(l.httpLogs), - Errors: jsonx.MustMarshal(l.errors), + Type: l.Type, + HTTPLogs: jsonx.MustMarshal(l.HttpLogs), + Errors: jsonx.MustMarshal(l.Errors), IsError: l.isError(), - CreatedOn: l.createdOn, - ElapsedMS: int(l.elapsed / time.Millisecond), + CreatedOn: l.CreatedOn, + ElapsedMS: int(l.Elapsed / time.Millisecond), }) } } diff --git a/core/models/channel_logs_test.go b/core/models/channel_logs_test.go index b6cb604ae..e351d75f1 100644 --- a/core/models/channel_logs_test.go +++ b/core/models/channel_logs_test.go @@ -1,7 +1,6 @@ package models_test import ( - "errors" "net/http" "testing" @@ -13,6 +12,7 @@ import ( "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/testsuite" "github.com/nyaruka/mailroom/testsuite/testdata" + "github.com/nyaruka/mailroom/utils/clogs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -49,7 +49,7 @@ func TestChannelLogsOutgoing(t *testing.T) { require.NoError(t, err) clog2.HTTP(trace2) - clog2.Error(errors.New("oops")) + clog2.Error(clogs.NewLogError("", "", "oops")) clog2.End() err = models.InsertChannelLogs(ctx, rt, []*models.ChannelLog{clog1, clog2}) @@ -63,9 +63,9 @@ func TestChannelLogsOutgoing(t *testing.T) { resp, err := rt.Dynamo.Client.GetItem(ctx, &dynamodb.GetItemInput{ TableName: aws.String(rt.Dynamo.TableName("ChannelLogs")), Key: map[string]types.AttributeValue{ - "UUID": &types.AttributeValueMemberS{Value: string(clog1.UUID())}, + "UUID": &types.AttributeValueMemberS{Value: string(clog1.UUID)}, }, }) require.NoError(t, err) - assert.Equal(t, string(clog1.UUID()), resp.Item["UUID"].(*types.AttributeValueMemberS).Value) + assert.Equal(t, string(clog1.UUID), resp.Item["UUID"].(*types.AttributeValueMemberS).Value) } diff --git a/utils/clogs/base.go b/utils/clogs/base.go index 96112daad..5c9e92285 100644 --- a/utils/clogs/base.go +++ b/utils/clogs/base.go @@ -2,7 +2,10 @@ package clogs import ( "fmt" + "time" + "github.com/nyaruka/gocommon/httpx" + "github.com/nyaruka/gocommon/stringsx" "github.com/nyaruka/gocommon/uuids" ) @@ -14,6 +17,8 @@ func NewLogUUID() LogUUID { return LogUUID(uuids.NewV7()) } +type LogType string + // Error is an error that occurred during a channel interaction type LogError struct { Code string `json:"code"` @@ -21,6 +26,62 @@ type LogError struct { Message string `json:"message"` } +// NewLogError creates a new log error func NewLogError(code, extCode, message string, args ...any) *LogError { return &LogError{Code: code, ExtCode: extCode, Message: fmt.Sprintf(message, args...)} } + +// Redact applies the given redactor to this error +func (e *LogError) Redact(r stringsx.Redactor) *LogError { + return &LogError{Code: e.Code, ExtCode: e.ExtCode, Message: r(e.Message)} +} + +// Log is the basic channel log structure +type Log struct { + UUID LogUUID + Type LogType + HttpLogs []*httpx.Log + Errors []*LogError + CreatedOn time.Time + Elapsed time.Duration + + recorder *httpx.Recorder + redactor stringsx.Redactor +} + +func NewLog(t LogType, r *httpx.Recorder, redactVals []string) *Log { + return &Log{ + UUID: NewLogUUID(), + Type: t, + HttpLogs: []*httpx.Log{}, + Errors: []*LogError{}, + CreatedOn: time.Now(), + + recorder: r, + redactor: stringsx.NewRedactor("**********", redactVals...), + } +} + +// HTTP adds the given HTTP trace to this log +func (l *Log) HTTP(t *httpx.Trace) { + l.HttpLogs = append(l.HttpLogs, l.traceToLog(t)) +} + +// Error adds the given error to this log +func (l *Log) Error(e *LogError) { + l.Errors = append(l.Errors, e.Redact(l.redactor)) +} + +// End finalizes this log +func (l *Log) End() { + if l.recorder != nil { + // prepend so it's the first HTTP request in the log + l.HttpLogs = append([]*httpx.Log{l.traceToLog(l.recorder.Trace)}, l.HttpLogs...) + } + + l.Elapsed = time.Since(l.CreatedOn) +} + +func (l *Log) traceToLog(t *httpx.Trace) *httpx.Log { + return httpx.NewLog(t, 2048, 50000, l.redactor) +} diff --git a/utils/clogs/dynamo.go b/utils/clogs/dynamo.go index e6e13cb31..1a88ca017 100644 --- a/utils/clogs/dynamo.go +++ b/utils/clogs/dynamo.go @@ -5,7 +5,6 @@ import ( "compress/gzip" "time" - "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/jsonx" ) @@ -13,29 +12,29 @@ const ( dynamoTTL = 14 * 24 * time.Hour ) -// DynamoChannelLog channel log to be written to DynamoDB -type DynamoChannelLog struct { +// DynamoLog channel log to be written to DynamoDB +type DynamoLog struct { UUID LogUUID `dynamodbav:"UUID"` - Type string `dynamodbav:"Type"` + Type LogType `dynamodbav:"Type"` DataGZ []byte `dynamodbav:"DataGZ,omitempty"` ElapsedMS int `dynamodbav:"ElapsedMS"` CreatedOn time.Time `dynamodbav:"CreatedOn,unixtime"` ExpiresOn time.Time `dynamodbav:"ExpiresOn,unixtime"` } -func NewDynamoChannelLog(uuid LogUUID, logType string, httpLogs []*httpx.Log, errors []*LogError, elapsed time.Duration, createdOn time.Time) *DynamoChannelLog { - data := jsonx.MustMarshal(map[string]any{"http_logs": httpLogs, "errors": errors}) +func NewDynamoLog(l *Log) *DynamoLog { + data := jsonx.MustMarshal(map[string]any{"http_logs": l.HttpLogs, "errors": l.Errors}) buf := &bytes.Buffer{} w := gzip.NewWriter(buf) w.Write(data) w.Close() - return &DynamoChannelLog{ - UUID: uuid, - Type: logType, + return &DynamoLog{ + UUID: l.UUID, + Type: l.Type, DataGZ: buf.Bytes(), - ElapsedMS: int(elapsed / time.Millisecond), - CreatedOn: createdOn, - ExpiresOn: createdOn.Add(dynamoTTL), + ElapsedMS: int(l.Elapsed / time.Millisecond), + CreatedOn: l.CreatedOn, + ExpiresOn: l.CreatedOn.Add(dynamoTTL), } } diff --git a/web/ivr/ivr.go b/web/ivr/ivr.go index 552421fd2..540a6e62e 100644 --- a/web/ivr/ivr.go +++ b/web/ivr/ivr.go @@ -17,6 +17,7 @@ import ( "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/core/tasks/handler/ctasks" "github.com/nyaruka/mailroom/runtime" + "github.com/nyaruka/mailroom/utils/clogs" "github.com/nyaruka/mailroom/web" ) @@ -28,7 +29,7 @@ func init() { type ivrHandlerFn func(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets, ch *models.Channel, svc ivr.Service, r *http.Request, w http.ResponseWriter) (*models.Call, error) -func newIVRHandler(handler ivrHandlerFn, logType models.ChannelLogType) web.Handler { +func newIVRHandler(handler ivrHandlerFn, logType clogs.LogType) web.Handler { return func(ctx context.Context, rt *runtime.Runtime, r *http.Request, w http.ResponseWriter) error { channelUUID := assets.ChannelUUID(r.PathValue("uuid")) From 4ccf4abd4c2fa91f0ad08e24071778563e2ef05a Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 13 Sep 2024 15:40:59 -0500 Subject: [PATCH 052/216] Update CHANGELOG.md for v9.3.17 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27684519e..e4787ffb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +v9.3.17 (2024-09-13) +------------------------- + * Move core channel log stuff into clogs utility package we could potentially share with courier + * Start writing channel logs to DynamoDB + * Fix go version in DockerFile + v9.3.16 (2024-09-12) ------------------------- * Remove old task queues From b469b95fac6b626124187220a561bcf709fe5b80 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 13 Sep 2024 17:56:58 -0500 Subject: [PATCH 053/216] Rework clogs package to provide get/put DynamoDB operations --- core/models/channel_logs.go | 31 +++------- core/models/channel_logs_test.go | 14 ++--- utils/clogs/dynamo.go | 102 ++++++++++++++++++++++++++++--- utils/clogs/dynamo_test.go | 37 +++++++++++ 4 files changed, 143 insertions(+), 41 deletions(-) create mode 100644 utils/clogs/dynamo_test.go diff --git a/core/models/channel_logs.go b/core/models/channel_logs.go index 39582ed84..30f7f1e72 100644 --- a/core/models/channel_logs.go +++ b/core/models/channel_logs.go @@ -5,12 +5,8 @@ import ( "encoding/json" "fmt" "path" - "slices" "time" - "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" - "github.com/aws/aws-sdk-go-v2/service/dynamodb" - dytypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/nyaruka/gocommon/aws/s3x" "github.com/nyaruka/gocommon/httpx" @@ -106,26 +102,13 @@ func (l *stChannelLog) path() string { // InsertChannelLogs writes the given channel logs to the db func InsertChannelLogs(ctx context.Context, rt *runtime.Runtime, logs []*ChannelLog) error { - // write in batches to DynamoDB - for batch := range slices.Chunk(logs, 25) { - writeReqs := make([]dytypes.WriteRequest, len(batch)) - - for i, l := range batch { - dl := clogs.NewDynamoLog(l.Log) - - item, err := attributevalue.MarshalMap(dl) - if err != nil { - return fmt.Errorf("error marshalling channel log: %w", err) - } - writeReqs[i] = dytypes.WriteRequest{PutRequest: &dytypes.PutRequest{Item: item}} - } - - _, err := rt.Dynamo.Client.BatchWriteItem(ctx, &dynamodb.BatchWriteItemInput{ - RequestItems: map[string][]dytypes.WriteRequest{rt.Dynamo.TableName("ChannelLogs"): writeReqs}, - }) - if err != nil { - return fmt.Errorf("error writing channel logs: %w", err) - } + // write all logs to DynamoDB + cls := make([]*clogs.Log, len(logs)) + for i, l := range logs { + cls[i] = l.Log + } + if err := clogs.BulkPut(ctx, rt.Dynamo, cls); err != nil { + return fmt.Errorf("error writing channel logs: %w", err) } attached := make([]*stChannelLog, 0, len(logs)) diff --git a/core/models/channel_logs_test.go b/core/models/channel_logs_test.go index e351d75f1..0dcbea4ee 100644 --- a/core/models/channel_logs_test.go +++ b/core/models/channel_logs_test.go @@ -4,9 +4,6 @@ import ( "net/http" "testing" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/dynamodb" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/nyaruka/gocommon/dbutil/assertdb" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/mailroom/core/models" @@ -60,12 +57,9 @@ func TestChannelLogsOutgoing(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT count(*) FROM channels_channellog WHERE log_type = 'ivr_hangup' AND http_logs -> 0 ->> 'url' = 'http://ivr.com/hangup' AND is_error = TRUE AND channel_id = $1`, channel.ID()).Returns(1) assertdb.Query(t, rt.DB, `SELECT count(*) FROM channels_channellog WHERE http_logs::text LIKE '%sesame%'`).Returns(0) - resp, err := rt.Dynamo.Client.GetItem(ctx, &dynamodb.GetItemInput{ - TableName: aws.String(rt.Dynamo.TableName("ChannelLogs")), - Key: map[string]types.AttributeValue{ - "UUID": &types.AttributeValueMemberS{Value: string(clog1.UUID)}, - }, - }) + // read log back from DynamoDB + log, err := clogs.Get(ctx, rt.Dynamo, clog1.UUID) require.NoError(t, err) - assert.Equal(t, string(clog1.UUID), resp.Item["UUID"].(*types.AttributeValueMemberS).Value) + assert.Equal(t, clog1.UUID, log.UUID) + assert.Equal(t, models.ChannelLogTypeIVRStart, log.Type) } diff --git a/utils/clogs/dynamo.go b/utils/clogs/dynamo.go index 1a88ca017..85168b107 100644 --- a/utils/clogs/dynamo.go +++ b/utils/clogs/dynamo.go @@ -3,17 +3,29 @@ package clogs import ( "bytes" "compress/gzip" + "context" + "encoding/json" + "fmt" + "io" + "slices" "time" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/nyaruka/gocommon/aws/dynamo" + "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/jsonx" ) const ( - dynamoTTL = 14 * 24 * time.Hour + dynamoTableBase = "ChannelLogs" + dynamoTTL = 14 * 24 * time.Hour ) -// DynamoLog channel log to be written to DynamoDB -type DynamoLog struct { +// log struct to be written to DynamoDB +type dynamoLog struct { UUID LogUUID `dynamodbav:"UUID"` Type LogType `dynamodbav:"Type"` DataGZ []byte `dynamodbav:"DataGZ,omitempty"` @@ -22,14 +34,19 @@ type DynamoLog struct { ExpiresOn time.Time `dynamodbav:"ExpiresOn,unixtime"` } -func NewDynamoLog(l *Log) *DynamoLog { - data := jsonx.MustMarshal(map[string]any{"http_logs": l.HttpLogs, "errors": l.Errors}) +type dynamoLogData struct { + HttpLogs []*httpx.Log `json:"http_logs"` + Errors []*LogError `json:"errors"` +} + +func newDynamoLog(l *Log) *dynamoLog { + data := &dynamoLogData{HttpLogs: l.HttpLogs, Errors: l.Errors} buf := &bytes.Buffer{} w := gzip.NewWriter(buf) - w.Write(data) + w.Write(jsonx.MustMarshal(data)) w.Close() - return &DynamoLog{ + return &dynamoLog{ UUID: l.UUID, Type: l.Type, DataGZ: buf.Bytes(), @@ -38,3 +55,74 @@ func NewDynamoLog(l *Log) *DynamoLog { ExpiresOn: l.CreatedOn.Add(dynamoTTL), } } + +func (d *dynamoLog) unpack() (*Log, error) { + r, err := gzip.NewReader(bytes.NewReader(d.DataGZ)) + if err != nil { + return nil, err + } + j, err := io.ReadAll(r) + if err != nil { + return nil, err + } + + var data dynamoLogData + if err := json.Unmarshal(j, &data); err != nil { + return nil, err + } + + return &Log{ + UUID: d.UUID, + Type: d.Type, + HttpLogs: data.HttpLogs, + Errors: data.Errors, + CreatedOn: d.CreatedOn, + Elapsed: time.Duration(d.ElapsedMS) * time.Millisecond, + }, nil +} + +// Get retrieves a log from DynamoDB by its UUID +func Get(ctx context.Context, ds *dynamo.Service, uuid LogUUID) (*Log, error) { + resp, err := ds.Client.GetItem(ctx, &dynamodb.GetItemInput{ + TableName: aws.String(ds.TableName(dynamoTableBase)), + Key: map[string]types.AttributeValue{ + "UUID": &types.AttributeValueMemberS{Value: string(uuid)}, + }, + }) + if err != nil { + return nil, fmt.Errorf("error getting log from db: %w", err) + } + + var d dynamoLog + if err := attributevalue.UnmarshalMap(resp.Item, &d); err != nil { + return nil, fmt.Errorf("error unmarshaling log: %w", err) + } + + return d.unpack() +} + +// BulkPut writes multiple logs to DynamoDB in batches of 25 +func BulkPut(ctx context.Context, ds *dynamo.Service, logs []*Log) error { + for batch := range slices.Chunk(logs, 25) { + writeReqs := make([]types.WriteRequest, len(batch)) + + for i, l := range batch { + dl := newDynamoLog(l) + + item, err := attributevalue.MarshalMap(dl) + if err != nil { + return fmt.Errorf("error marshalling log: %w", err) + } + writeReqs[i] = types.WriteRequest{PutRequest: &types.PutRequest{Item: item}} + } + + _, err := ds.Client.BatchWriteItem(ctx, &dynamodb.BatchWriteItemInput{ + RequestItems: map[string][]types.WriteRequest{ds.TableName(dynamoTableBase): writeReqs}, + }) + if err != nil { + return fmt.Errorf("error writing logs to db: %w", err) + } + } + + return nil +} diff --git a/utils/clogs/dynamo_test.go b/utils/clogs/dynamo_test.go new file mode 100644 index 000000000..fc918359a --- /dev/null +++ b/utils/clogs/dynamo_test.go @@ -0,0 +1,37 @@ +package clogs_test + +import ( + "context" + "testing" + "time" + + "github.com/nyaruka/gocommon/aws/dynamo" + "github.com/nyaruka/mailroom/utils/clogs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDynamo(t *testing.T) { + ctx := context.Background() + ds, err := dynamo.NewService("root", "tembatemba", "us-east-1", "http://localhost:6000", "Test") + require.NoError(t, err) + + l1 := clogs.NewLog("test_type1", nil, nil) + l1.Error(clogs.NewLogError("code1", "ext", "message")) + + l2 := clogs.NewLog("test_type2", nil, nil) + l2.Error(clogs.NewLogError("code2", "ext", "message")) + + // write both logs to db + err = clogs.BulkPut(ctx, ds, []*clogs.Log{l1, l2}) + assert.NoError(t, err) + + // read log 1 back from db + l3, err := clogs.Get(ctx, ds, l1.UUID) + assert.NoError(t, err) + assert.Equal(t, l1.UUID, l3.UUID) + assert.Equal(t, clogs.LogType("test_type1"), l3.Type) + assert.Equal(t, []*clogs.LogError{clogs.NewLogError("code1", "ext", "message")}, l3.Errors) + assert.Equal(t, l1.Elapsed, l3.Elapsed) + assert.Equal(t, l1.CreatedOn.Truncate(time.Second), l3.CreatedOn) +} From 12ef3acae2397381b1ca2edc0ac79244cf3fa8c1 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 16 Sep 2024 08:59:31 -0500 Subject: [PATCH 054/216] Update CHANGELOG.md for v9.3.18 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4787ffb1..ee62ca727 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.18 (2024-09-16) +------------------------- + * Rework clogs package to provide get/put DynamoDB operations + v9.3.17 (2024-09-13) ------------------------- * Move core channel log stuff into clogs utility package we could potentially share with courier From 1c8cccd90ed4ee5f56a246281cc950af871a1861 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 16 Sep 2024 11:50:59 -0500 Subject: [PATCH 055/216] Fix error handling on contact batch import --- core/tasks/contacts/import_contact_batch.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tasks/contacts/import_contact_batch.go b/core/tasks/contacts/import_contact_batch.go index 921148315..e82be9132 100644 --- a/core/tasks/contacts/import_contact_batch.go +++ b/core/tasks/contacts/import_contact_batch.go @@ -74,7 +74,7 @@ func (t *ImportContactBatchTask) Perform(ctx context.Context, rt *runtime.Runtim } if batchErr != nil { - return fmt.Errorf("unable to import contact import batch %d: %w", t.ContactImportBatchID, err) + return fmt.Errorf("unable to import contact import batch %d: %w", t.ContactImportBatchID, batchErr) } return nil From 4f74883d48221d80e1d56ebcb61a98ddeb937c48 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 16 Sep 2024 14:08:54 -0500 Subject: [PATCH 056/216] Rework clogs util package based on latest gocommon --- core/models/channel_logs.go | 2 +- core/models/channel_logs_test.go | 4 +- go.mod | 16 ++-- go.sum | 32 ++++---- mailroom.go | 2 +- utils/clogs/batch.go | 36 +++++++++ utils/clogs/{base.go => clog.go} | 59 ++++++++++++++ utils/clogs/clog_test.go | 69 +++++++++++++++++ utils/clogs/dynamo.go | 128 ------------------------------- utils/clogs/dynamo_test.go | 37 --------- 10 files changed, 193 insertions(+), 192 deletions(-) create mode 100644 utils/clogs/batch.go rename utils/clogs/{base.go => clog.go} (55%) create mode 100644 utils/clogs/clog_test.go delete mode 100644 utils/clogs/dynamo.go delete mode 100644 utils/clogs/dynamo_test.go diff --git a/core/models/channel_logs.go b/core/models/channel_logs.go index 30f7f1e72..a5c3d4678 100644 --- a/core/models/channel_logs.go +++ b/core/models/channel_logs.go @@ -107,7 +107,7 @@ func InsertChannelLogs(ctx context.Context, rt *runtime.Runtime, logs []*Channel for i, l := range logs { cls[i] = l.Log } - if err := clogs.BulkPut(ctx, rt.Dynamo, cls); err != nil { + if err := clogs.BatchPut(ctx, rt.Dynamo, "ChannelLogs", cls); err != nil { return fmt.Errorf("error writing channel logs: %w", err) } diff --git a/core/models/channel_logs_test.go b/core/models/channel_logs_test.go index 0dcbea4ee..7496acbca 100644 --- a/core/models/channel_logs_test.go +++ b/core/models/channel_logs_test.go @@ -4,6 +4,7 @@ import ( "net/http" "testing" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/nyaruka/gocommon/dbutil/assertdb" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/mailroom/core/models" @@ -58,7 +59,8 @@ func TestChannelLogsOutgoing(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT count(*) FROM channels_channellog WHERE http_logs::text LIKE '%sesame%'`).Returns(0) // read log back from DynamoDB - log, err := clogs.Get(ctx, rt.Dynamo, clog1.UUID) + log := &clogs.Log{} + err = rt.Dynamo.GetItem(ctx, "ChannelLogs", map[string]types.AttributeValue{"UUID": &types.AttributeValueMemberS{Value: string(clog1.UUID)}}, log) require.NoError(t, err) assert.Equal(t, clog1.UUID, log.UUID) assert.Equal(t, models.ChannelLogTypeIVRStart, log.Type) diff --git a/go.mod b/go.mod index 1e5381629..958ceb1e4 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/elastic/go-elasticsearch/v8 v8.14.0 github.com/getsentry/sentry-go v0.28.1 github.com/go-chi/chi/v5 v5.1.0 - github.com/go-playground/validator/v10 v10.22.0 + github.com/go-playground/validator/v10 v10.22.1 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang/protobuf v1.5.4 github.com/gomodule/redigo v1.9.2 @@ -22,8 +22,8 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 - github.com/nyaruka/gocommon v1.58.0 - github.com/nyaruka/goflow v0.222.1 + github.com/nyaruka/gocommon v1.59.0 + github.com/nyaruka/goflow v0.222.2 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 github.com/nyaruka/rp-indexer/v9 v9.2.0 @@ -101,13 +101,13 @@ require ( go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/crypto v0.26.0 // indirect - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect - golang.org/x/net v0.28.0 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.22.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.6.0 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect google.golang.org/genproto v0.0.0-20240820151423-278611b39280 // indirect diff --git a/go.sum b/go.sum index f1b204258..6a8aa6648 100644 --- a/go.sum +++ b/go.sum @@ -115,8 +115,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= -github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= +github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= @@ -194,10 +194,10 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= -github.com/nyaruka/gocommon v1.58.0 h1:q8/YCWQOEiFHVnYW+f5N+HjI0R4gjV93bOpVWqImr8k= -github.com/nyaruka/gocommon v1.58.0/go.mod h1:u0n6zC7AxmrUZxzY4VtZU1d26QJ8nhqRRb5IFOw/Lig= -github.com/nyaruka/goflow v0.222.1 h1:QrqHStCp1dNf5JmT2oxGry2sTv+UV2uGOkZ3DxytGqY= -github.com/nyaruka/goflow v0.222.1/go.mod h1:NK8xsonXqdipHPDgze4f4L9BHWUGYwDCj5Xg9UVM6Ec= +github.com/nyaruka/gocommon v1.59.0 h1:XC//IVSOWawBXEZqDiYhqxrIHWo2QPPeCIkAm4n4sY0= +github.com/nyaruka/gocommon v1.59.0/go.mod h1:Upj2DG1iL55YcfF7rve8CRrKGjMaEn0jWUIWbQQgTFQ= +github.com/nyaruka/goflow v0.222.2 h1:rF8rbWE7vwGtq07WhnSWyH1mWTF/soat6f1Yu8h420Q= +github.com/nyaruka/goflow v0.222.2/go.mod h1:iwdjLwomV3thGZeWhybtmDhujbooIkpTn1vUbso5ReY= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= @@ -260,11 +260,11 @@ go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -278,8 +278,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -297,16 +297,16 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/mailroom.go b/mailroom.go index 0cd5567f9..a1551bcb3 100644 --- a/mailroom.go +++ b/mailroom.go @@ -100,7 +100,7 @@ func (mr *Mailroom) Start() error { if err != nil { return err } - if err := mr.rt.Dynamo.Test(mr.ctx, "ChannelLogs"); err != nil { + if err := mr.rt.Dynamo.Test(mr.ctx); err != nil { log.Error("dynamodb not reachable", "error", err) } else { log.Info("dynamodb ok") diff --git a/utils/clogs/batch.go b/utils/clogs/batch.go new file mode 100644 index 000000000..7d174b886 --- /dev/null +++ b/utils/clogs/batch.go @@ -0,0 +1,36 @@ +package clogs + +import ( + "context" + "fmt" + "slices" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/nyaruka/gocommon/aws/dynamo" +) + +// BatchPut writes multiple logs to DynamoDB in batches of 25. This should probably be a generic function in the +// gocommon/dynamo package but need to think more about errors. +func BatchPut(ctx context.Context, ds *dynamo.Service, table string, logs []*Log) error { + for batch := range slices.Chunk(logs, 25) { + writeReqs := make([]types.WriteRequest, len(batch)) + + for i, l := range batch { + d, err := l.MarshalDynamo() + if err != nil { + return fmt.Errorf("error marshalling log: %w", err) + } + writeReqs[i] = types.WriteRequest{PutRequest: &types.PutRequest{Item: d}} + } + + _, err := ds.Client.BatchWriteItem(ctx, &dynamodb.BatchWriteItemInput{ + RequestItems: map[string][]types.WriteRequest{ds.TableName(table): writeReqs}, + }) + if err != nil { + return fmt.Errorf("error writing logs to db: %w", err) + } + } + + return nil +} diff --git a/utils/clogs/base.go b/utils/clogs/clog.go similarity index 55% rename from utils/clogs/base.go rename to utils/clogs/clog.go index 5c9e92285..0c14df292 100644 --- a/utils/clogs/base.go +++ b/utils/clogs/clog.go @@ -4,11 +4,18 @@ import ( "fmt" "time" + "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/nyaruka/gocommon/aws/dynamo" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/stringsx" "github.com/nyaruka/gocommon/uuids" ) +const ( + dynamoTTL = 14 * 24 * time.Hour +) + // LogUUID is the type of a channel log UUID (should be v7) type LogUUID uuids.UUID @@ -85,3 +92,55 @@ func (l *Log) End() { func (l *Log) traceToLog(t *httpx.Trace) *httpx.Log { return httpx.NewLog(t, 2048, 50000, l.redactor) } + +// log struct to be written to DynamoDB +type dynamoLog struct { + UUID LogUUID `dynamodbav:"UUID"` + Type LogType `dynamodbav:"Type"` + DataGZ []byte `dynamodbav:"DataGZ,omitempty"` + ElapsedMS int `dynamodbav:"ElapsedMS"` + CreatedOn time.Time `dynamodbav:"CreatedOn,unixtime"` + ExpiresOn time.Time `dynamodbav:"ExpiresOn,unixtime"` +} + +type dynamoLogData struct { + HttpLogs []*httpx.Log `json:"http_logs"` + Errors []*LogError `json:"errors"` +} + +func (l *Log) MarshalDynamo() (map[string]types.AttributeValue, error) { + data, err := dynamo.MarshalJSONGZ(&dynamoLogData{HttpLogs: l.HttpLogs, Errors: l.Errors}) + if err != nil { + return nil, fmt.Errorf("error marshaling log data: %w", err) + } + + return attributevalue.MarshalMap(&dynamoLog{ + UUID: l.UUID, + Type: l.Type, + DataGZ: data, + ElapsedMS: int(l.Elapsed / time.Millisecond), + CreatedOn: l.CreatedOn, + ExpiresOn: l.CreatedOn.Add(dynamoTTL), + }) +} + +func (l *Log) UnmarshalDynamo(m map[string]types.AttributeValue) error { + d := &dynamoLog{} + + if err := attributevalue.UnmarshalMap(m, d); err != nil { + return fmt.Errorf("error unmarshaling log: %w", err) + } + + data := &dynamoLogData{} + if err := dynamo.UnmarshalJSONGZ(d.DataGZ, data); err != nil { + return fmt.Errorf("error unmarshaling log data: %w", err) + } + + l.UUID = d.UUID + l.Type = d.Type + l.HttpLogs = data.HttpLogs + l.Errors = data.Errors + l.Elapsed = time.Duration(d.ElapsedMS) * time.Millisecond + l.CreatedOn = d.CreatedOn + return nil +} diff --git a/utils/clogs/clog_test.go b/utils/clogs/clog_test.go new file mode 100644 index 000000000..2df4a929c --- /dev/null +++ b/utils/clogs/clog_test.go @@ -0,0 +1,69 @@ +package clogs_test + +import ( + "context" + "net/http" + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" + "github.com/nyaruka/gocommon/aws/dynamo" + "github.com/nyaruka/gocommon/httpx" + "github.com/nyaruka/mailroom/utils/clogs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLogs(t *testing.T) { + ctx := context.Background() + + defer httpx.SetRequestor(httpx.DefaultRequestor) + httpx.SetRequestor(httpx.NewMockRequestor(map[string][]*httpx.MockResponse{ + "http://ivr.com/start": {httpx.NewMockResponse(200, nil, []byte("OK"))}, + "http://ivr.com/hangup": {httpx.NewMockResponse(400, nil, []byte("Oops"))}, + })) + + clog1 := clogs.NewLog("type1", nil, []string{"sesame"}) + clog2 := clogs.NewLog("type1", nil, []string{"sesame"}) + + req1, _ := httpx.NewRequest("GET", "http://ivr.com/start", nil, map[string]string{"Authorization": "Token sesame"}) + trace1, err := httpx.DoTrace(http.DefaultClient, req1, nil, nil, -1) + require.NoError(t, err) + + clog1.HTTP(trace1) + clog1.End() + + req2, _ := httpx.NewRequest("GET", "http://ivr.com/hangup", nil, nil) + trace2, err := httpx.DoTrace(http.DefaultClient, req2, nil, nil, -1) + require.NoError(t, err) + + clog2.HTTP(trace2) + clog2.Error(clogs.NewLogError("", "", "oops")) + clog2.End() + + assert.NotEqual(t, clog1.UUID, clog2.UUID) + assert.NotEqual(t, time.Duration(0), clog1.Elapsed) + + ds, err := dynamo.NewService("root", "tembatemba", "us-east-1", "http://localhost:6000", "Test") + require.NoError(t, err) + + l1 := clogs.NewLog("test_type1", nil, nil) + l1.Error(clogs.NewLogError("code1", "ext", "message")) + + l2 := clogs.NewLog("test_type2", nil, nil) + l2.Error(clogs.NewLogError("code2", "ext", "message")) + + // write both logs to db + err = clogs.BatchPut(ctx, ds, "ChannelLogs", []*clogs.Log{l1, l2}) + assert.NoError(t, err) + + // read log 1 back from db + l3 := &clogs.Log{} + err = ds.GetItem(ctx, "ChannelLogs", map[string]types.AttributeValue{"UUID": &types.AttributeValueMemberS{Value: string(l1.UUID)}}, l3) + assert.NoError(t, err) + assert.Equal(t, l1.UUID, l3.UUID) + assert.Equal(t, clogs.LogType("test_type1"), l3.Type) + assert.Equal(t, []*clogs.LogError{clogs.NewLogError("code1", "ext", "message")}, l3.Errors) + assert.Equal(t, l1.Elapsed, l3.Elapsed) + assert.Equal(t, l1.CreatedOn.Truncate(time.Second), l3.CreatedOn) +} diff --git a/utils/clogs/dynamo.go b/utils/clogs/dynamo.go deleted file mode 100644 index 85168b107..000000000 --- a/utils/clogs/dynamo.go +++ /dev/null @@ -1,128 +0,0 @@ -package clogs - -import ( - "bytes" - "compress/gzip" - "context" - "encoding/json" - "fmt" - "io" - "slices" - "time" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" - "github.com/aws/aws-sdk-go-v2/service/dynamodb" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/nyaruka/gocommon/aws/dynamo" - "github.com/nyaruka/gocommon/httpx" - "github.com/nyaruka/gocommon/jsonx" -) - -const ( - dynamoTableBase = "ChannelLogs" - dynamoTTL = 14 * 24 * time.Hour -) - -// log struct to be written to DynamoDB -type dynamoLog struct { - UUID LogUUID `dynamodbav:"UUID"` - Type LogType `dynamodbav:"Type"` - DataGZ []byte `dynamodbav:"DataGZ,omitempty"` - ElapsedMS int `dynamodbav:"ElapsedMS"` - CreatedOn time.Time `dynamodbav:"CreatedOn,unixtime"` - ExpiresOn time.Time `dynamodbav:"ExpiresOn,unixtime"` -} - -type dynamoLogData struct { - HttpLogs []*httpx.Log `json:"http_logs"` - Errors []*LogError `json:"errors"` -} - -func newDynamoLog(l *Log) *dynamoLog { - data := &dynamoLogData{HttpLogs: l.HttpLogs, Errors: l.Errors} - buf := &bytes.Buffer{} - w := gzip.NewWriter(buf) - w.Write(jsonx.MustMarshal(data)) - w.Close() - - return &dynamoLog{ - UUID: l.UUID, - Type: l.Type, - DataGZ: buf.Bytes(), - ElapsedMS: int(l.Elapsed / time.Millisecond), - CreatedOn: l.CreatedOn, - ExpiresOn: l.CreatedOn.Add(dynamoTTL), - } -} - -func (d *dynamoLog) unpack() (*Log, error) { - r, err := gzip.NewReader(bytes.NewReader(d.DataGZ)) - if err != nil { - return nil, err - } - j, err := io.ReadAll(r) - if err != nil { - return nil, err - } - - var data dynamoLogData - if err := json.Unmarshal(j, &data); err != nil { - return nil, err - } - - return &Log{ - UUID: d.UUID, - Type: d.Type, - HttpLogs: data.HttpLogs, - Errors: data.Errors, - CreatedOn: d.CreatedOn, - Elapsed: time.Duration(d.ElapsedMS) * time.Millisecond, - }, nil -} - -// Get retrieves a log from DynamoDB by its UUID -func Get(ctx context.Context, ds *dynamo.Service, uuid LogUUID) (*Log, error) { - resp, err := ds.Client.GetItem(ctx, &dynamodb.GetItemInput{ - TableName: aws.String(ds.TableName(dynamoTableBase)), - Key: map[string]types.AttributeValue{ - "UUID": &types.AttributeValueMemberS{Value: string(uuid)}, - }, - }) - if err != nil { - return nil, fmt.Errorf("error getting log from db: %w", err) - } - - var d dynamoLog - if err := attributevalue.UnmarshalMap(resp.Item, &d); err != nil { - return nil, fmt.Errorf("error unmarshaling log: %w", err) - } - - return d.unpack() -} - -// BulkPut writes multiple logs to DynamoDB in batches of 25 -func BulkPut(ctx context.Context, ds *dynamo.Service, logs []*Log) error { - for batch := range slices.Chunk(logs, 25) { - writeReqs := make([]types.WriteRequest, len(batch)) - - for i, l := range batch { - dl := newDynamoLog(l) - - item, err := attributevalue.MarshalMap(dl) - if err != nil { - return fmt.Errorf("error marshalling log: %w", err) - } - writeReqs[i] = types.WriteRequest{PutRequest: &types.PutRequest{Item: item}} - } - - _, err := ds.Client.BatchWriteItem(ctx, &dynamodb.BatchWriteItemInput{ - RequestItems: map[string][]types.WriteRequest{ds.TableName(dynamoTableBase): writeReqs}, - }) - if err != nil { - return fmt.Errorf("error writing logs to db: %w", err) - } - } - - return nil -} diff --git a/utils/clogs/dynamo_test.go b/utils/clogs/dynamo_test.go deleted file mode 100644 index fc918359a..000000000 --- a/utils/clogs/dynamo_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package clogs_test - -import ( - "context" - "testing" - "time" - - "github.com/nyaruka/gocommon/aws/dynamo" - "github.com/nyaruka/mailroom/utils/clogs" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestDynamo(t *testing.T) { - ctx := context.Background() - ds, err := dynamo.NewService("root", "tembatemba", "us-east-1", "http://localhost:6000", "Test") - require.NoError(t, err) - - l1 := clogs.NewLog("test_type1", nil, nil) - l1.Error(clogs.NewLogError("code1", "ext", "message")) - - l2 := clogs.NewLog("test_type2", nil, nil) - l2.Error(clogs.NewLogError("code2", "ext", "message")) - - // write both logs to db - err = clogs.BulkPut(ctx, ds, []*clogs.Log{l1, l2}) - assert.NoError(t, err) - - // read log 1 back from db - l3, err := clogs.Get(ctx, ds, l1.UUID) - assert.NoError(t, err) - assert.Equal(t, l1.UUID, l3.UUID) - assert.Equal(t, clogs.LogType("test_type1"), l3.Type) - assert.Equal(t, []*clogs.LogError{clogs.NewLogError("code1", "ext", "message")}, l3.Errors) - assert.Equal(t, l1.Elapsed, l3.Elapsed) - assert.Equal(t, l1.CreatedOn.Truncate(time.Second), l3.CreatedOn) -} From 590c01a6088f034485990d2b4a46390b90d47ae7 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 16 Sep 2024 14:41:47 -0500 Subject: [PATCH 057/216] Update CHANGELOG.md for v9.3.19 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee62ca727..8b5b0d8c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.19 (2024-09-16) +------------------------- + * Rework clogs util package based on latest gocommon + * Fix error handling on contact batch import + v9.3.18 (2024-09-16) ------------------------- * Rework clogs package to provide get/put DynamoDB operations From 97dfdd8a23d207884895e5b7287a0bc96d4c8baf Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 16 Sep 2024 17:12:41 -0500 Subject: [PATCH 058/216] Tweak error messages and add temp workaround for invalid msg locales --- core/models/events.go | 2 +- core/models/msgs.go | 7 +++++++ core/models/sessions.go | 2 +- core/runner/runner.go | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/core/models/events.go b/core/models/events.go index 973b8012d..a280b0987 100644 --- a/core/models/events.go +++ b/core/models/events.go @@ -153,7 +153,7 @@ func ApplyEventPreCommitHooks(ctx context.Context, rt *runtime.Runtime, tx *sqlx for hook, args := range preHooks { err := hook.Apply(ctx, rt, tx, oa, args) if err != nil { - return fmt.Errorf("error applying pre commit hook: %T: %w", hook, err) + return fmt.Errorf("error applying events pre commit hook: %T: %w", hook, err) } } diff --git a/core/models/msgs.go b/core/models/msgs.go index 6f7697634..391d59928 100644 --- a/core/models/msgs.go +++ b/core/models/msgs.go @@ -391,6 +391,13 @@ func newOutgoingTextMsg(rt *runtime.Runtime, org *Org, channel *Channel, contact m.CreatedOn = createdOn m.Metadata = null.Map[any](buildMsgMetadata(out)) + // TODO: temporary fix for invalid locales + if len(m.Locale) > 6 { + m.Locale = "eng" + + slog.Error("invalid locale, defaulting to eng-US", "locale", m.Locale) + } + if out.Templating() != nil { m.Templating = &Templating{MsgTemplating: out.Templating()} } diff --git a/core/models/sessions.go b/core/models/sessions.go index c47340733..227f2ead8 100644 --- a/core/models/sessions.go +++ b/core/models/sessions.go @@ -716,7 +716,7 @@ func InsertSessions(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, oa *O // gather all our pre commit events, group them by hook err = ApplyEventPreCommitHooks(ctx, rt, tx, oa, scenes) if err != nil { - return nil, fmt.Errorf("error applying pre commit hook: %T: %w", hook, err) + return nil, fmt.Errorf("error applying session pre commit hook: %T: %w", hook, err) } // return our session diff --git a/core/runner/runner.go b/core/runner/runner.go index 1e0ef6c58..8e10e69a0 100644 --- a/core/runner/runner.go +++ b/core/runner/runner.go @@ -410,7 +410,7 @@ func StartFlowForContacts( err = tx.Commit() if err != nil { tx.Rollback() - log.Error("error comitting session to db", "error", err, "contact_uuid", session.Contact().UUID()) + log.Error("error committing session to db", "error", err, "contact_uuid", session.Contact().UUID()) continue } From f4bf4bcbd40d3db826f81cc00aeaf29a5707394d Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 16 Sep 2024 17:26:39 -0500 Subject: [PATCH 059/216] Update CHANGELOG.md for v9.3.20 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b5b0d8c1..6acdf11d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.20 (2024-09-16) +------------------------- + * Tweak error messages and add temp workaround for invalid msg locales + v9.3.19 (2024-09-16) ------------------------- * Rework clogs util package based on latest gocommon From ebebfd34b7cf4f73bb402eb8d1afc94dceff7da6 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 16 Sep 2024 18:06:52 -0500 Subject: [PATCH 060/216] Tweak reporting of invalid msg locales --- core/models/msgs.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/models/msgs.go b/core/models/msgs.go index 391d59928..effa82ce8 100644 --- a/core/models/msgs.go +++ b/core/models/msgs.go @@ -393,9 +393,9 @@ func newOutgoingTextMsg(rt *runtime.Runtime, org *Org, channel *Channel, contact // TODO: temporary fix for invalid locales if len(m.Locale) > 6 { - m.Locale = "eng" - slog.Error("invalid locale, defaulting to eng-US", "locale", m.Locale) + + m.Locale = "eng" } if out.Templating() != nil { From 45cc0f11137141ca0e0697102ae9f1b632d45a8c Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 17 Sep 2024 12:24:44 -0500 Subject: [PATCH 061/216] Update deps including goflow --- go.mod | 46 ++++++++++++++--------------- go.sum | 92 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 69 insertions(+), 69 deletions(-) diff --git a/go.mod b/go.mod index 958ceb1e4..e867e5c7a 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,10 @@ require ( github.com/aws/aws-sdk-go-v2 v1.30.5 github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.3 github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.9 - github.com/aws/aws-sdk-go-v2/service/s3 v1.60.0 + github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 github.com/buger/jsonparser v1.1.1 - github.com/elastic/go-elasticsearch/v8 v8.14.0 - github.com/getsentry/sentry-go v0.28.1 + github.com/elastic/go-elasticsearch/v8 v8.15.0 + github.com/getsentry/sentry-go v0.29.0 github.com/go-chi/chi/v5 v5.1.0 github.com/go-playground/validator/v10 v10.22.1 github.com/golang-jwt/jwt v3.2.2+incompatible @@ -23,23 +23,23 @@ require ( github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 github.com/nyaruka/gocommon v1.59.0 - github.com/nyaruka/goflow v0.222.2 + github.com/nyaruka/goflow v0.222.3 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 github.com/nyaruka/rp-indexer/v9 v9.2.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_model v0.6.1 - github.com/prometheus/common v0.55.0 - github.com/samber/slog-multi v1.2.0 + github.com/prometheus/common v0.59.1 + github.com/samber/slog-multi v1.2.1 github.com/samber/slog-sentry v1.2.2 github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.9.0 - google.golang.org/api v0.193.0 + google.golang.org/api v0.197.0 ) require ( cloud.google.com/go v0.115.1 // indirect - cloud.google.com/go/auth v0.9.0 // indirect + cloud.google.com/go/auth v0.9.3 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect cloud.google.com/go/compute/metadata v0.5.0 // indirect cloud.google.com/go/firestore v1.16.0 // indirect @@ -56,13 +56,13 @@ require ( github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 // indirect github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.7 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 // indirect github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.18 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.30.4 // indirect @@ -81,7 +81,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/s2a-go v0.1.8 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -96,24 +96,24 @@ require ( github.com/samber/lo v1.47.0 // indirect github.com/sergi/go-diff v1.3.1 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/net v0.29.0 // indirect - golang.org/x/oauth2 v0.22.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.6.0 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect - google.golang.org/genproto v0.0.0-20240820151423-278611b39280 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240820151423-278611b39280 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240820151423-278611b39280 // indirect - google.golang.org/grpc v1.65.0 // indirect + google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.66.1 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 6a8aa6648..2c272d628 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ= cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc= -cloud.google.com/go/auth v0.9.0 h1:cYhKl1JUhynmxjXfrk4qdPc6Amw7i+GC9VLflgT0p5M= -cloud.google.com/go/auth v0.9.0/go.mod h1:2HsApZBr9zGZhC9QAXsYVYaWk8kNUt37uny+XVKi7wM= +cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U= +cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk= cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= @@ -48,24 +48,24 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 h1:Mqr/V5gvrhA2gvgnF4 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17/go.mod h1:aLJpZlCmjE+V+KtN1q1uyZkfnUWpQGpbsn89XPKyzfU= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16 h1:mimdLQkIX1zr8GIPY1ZtALdBQGxcASiBd2MOp8m/dMc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16/go.mod h1:YHk6owoSwrIsok+cAH9PENCOGoH5PU2EllX4vLtSrsY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 h1:Roo69qTpfu8OlJ2Tb7pAYVuF0CpuUMB0IYWwYP/4DZM= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17/go.mod h1:NcWPxQzGM1USQggaTVwz6VpqMZPX1CvDJLDh6jnOCa4= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.9 h1:jbqgtdKfAXebx2/l2UhDEe/jmmCIhaCO3HFK71M7VzM= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.9/go.mod h1:N3YdUYxyxhiuAelUgCpSVBuBI1klobJxZrDtL+olu10= github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.7 h1:VTBHXWkSeFgT3sfYB4U92qMgzHl0nz9H1tYNHHutLg0= github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.7/go.mod h1:F/ybU7YfgFcktSp+biKgiHjyscGhlZxOz4QFFQqHXGw= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18 h1:GckUnpm4EJOAio1c8o25a+b3lVfwVzC9gnSBqiiNmZM= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18/go.mod h1:Br6+bxfG33Dk3ynmkhsW2Z/t9D4+lRqdLDNCKi85w0U= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 h1:FLMkfEiRjhgeDTCjjLoc3URo/TBkgeQbocA78lfkzSI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19/go.mod h1:Vx+GucNSsdhaxs3aZIKfSUjKVGsxN25nX2SRcdhuw08= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.18 h1:GACdEPdpBE59I7pbfvu0/Mw1wzstlP3QtPHklUxybFE= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.18/go.mod h1:K+xV06+Wni4TSaOOJ1Y35e5tYOCUBYbebLKmJQQa8yY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 h1:tJ5RnkHCiSH0jyd6gROjlJtNwov0eGYNz8s8nFcR0jQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18/go.mod h1:++NHzT+nAF7ZPrHPsA+ENvsXkOO8wEu+C6RXltAG4/c= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 h1:jg16PhLPUiHIj8zYIW6bqzeQSuHVEiWnGA0Brz5Xv2I= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16/go.mod h1:Uyk1zE1VVdsHSU7096h/rwnXDzOzYQVl+FNPhPw7ShY= -github.com/aws/aws-sdk-go-v2/service/s3 v1.60.0 h1:2QXGJvG19QwqXUvgcdoCOZPyLuvZf8LiXPCN4P53TdI= -github.com/aws/aws-sdk-go-v2/service/s3 v1.60.0/go.mod h1:BSPI0EfnYUuNHPS0uqIo5VrRwzie+Fp+YhQOUs16sKI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 h1:rfprUlsdzgl7ZL2KlXiUAoJnI/VxfHCvDFr2QDFj6u4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19/go.mod h1:SCWkEdRq8/7EK60NcvvQ6NXKuTcchAD4ROAsC37VEZE= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 h1:u+EfGmksnJc/x5tq3A+OD7LrMbSSR/5TrKLvkdy/fhY= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17/go.mod h1:VaMx6302JHax2vHJWgRo+5n9zvbacs3bLU/23DNQrTY= +github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 h1:Kp6PWAlXwP1UvIflkIP6MFZYBNDCa4mFCGtxrpICVOg= +github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2/go.mod h1:5FmD/Dqq57gP+XwaUnd5WFPipAuzrf0HmupX27Gvjvc= github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 h1:zCsFCKvbj25i7p1u94imVoO447I/sFv8qq+lGJhRN0c= github.com/aws/aws-sdk-go-v2/service/sso v1.22.5/go.mod h1:ZeDX1SnKsVlejeuz41GiajjZpRSWR7/42q/EyA/QEiM= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 h1:SKvPgvdvmiTWoi0GAJ7AsJfOz3ngVkD/ERbs5pUnHNI= @@ -86,8 +86,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/elastic/elastic-transport-go/v8 v8.6.0 h1:Y2S/FBjx1LlCv5m6pWAF2kDJAHoSjSRSJCApolgfthA= github.com/elastic/elastic-transport-go/v8 v8.6.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk= -github.com/elastic/go-elasticsearch/v8 v8.14.0 h1:1ywU8WFReLLcxE1WJqii3hTtbPUE2hc38ZK/j4mMFow= -github.com/elastic/go-elasticsearch/v8 v8.14.0/go.mod h1:WRvnlGkSuZyp83M2U8El/LGXpCjYLrvlkSgkAH4O5I4= +github.com/elastic/go-elasticsearch/v8 v8.15.0 h1:IZyJhe7t7WI3NEFdcHnf6IJXqpRf+8S8QWLtZYYyBYk= +github.com/elastic/go-elasticsearch/v8 v8.15.0/go.mod h1:HCON3zj4btpqs2N1jjsAy4a/fiAul+YBP00mBH4xik8= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -98,8 +98,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= -github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k= -github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg= +github.com/getsentry/sentry-go v0.29.0 h1:YtWluuCFg9OfcqnaujpY918N/AhCCwarIDWOYSBAjCA= +github.com/getsentry/sentry-go v0.29.0/go.mod h1:jhPesDAL0Q0W2+2YEuVOvdWmVtdsr1+jtBrlDEVWwLY= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -159,8 +159,8 @@ github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= @@ -196,8 +196,8 @@ github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= github.com/nyaruka/gocommon v1.59.0 h1:XC//IVSOWawBXEZqDiYhqxrIHWo2QPPeCIkAm4n4sY0= github.com/nyaruka/gocommon v1.59.0/go.mod h1:Upj2DG1iL55YcfF7rve8CRrKGjMaEn0jWUIWbQQgTFQ= -github.com/nyaruka/goflow v0.222.2 h1:rF8rbWE7vwGtq07WhnSWyH1mWTF/soat6f1Yu8h420Q= -github.com/nyaruka/goflow v0.222.2/go.mod h1:iwdjLwomV3thGZeWhybtmDhujbooIkpTn1vUbso5ReY= +github.com/nyaruka/goflow v0.222.3 h1:Uqts5SvSl83eZegJEfbOKg7DC3R1tjiSATzUmAV40No= +github.com/nyaruka/goflow v0.222.3/go.mod h1:iwdjLwomV3thGZeWhybtmDhujbooIkpTn1vUbso5ReY= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= @@ -221,12 +221,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= +github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= -github.com/samber/slog-multi v1.2.0 h1:JIebVdmeGkCMd5/ticlmU+aDYl4tdAZBmp5uLaSzr0k= -github.com/samber/slog-multi v1.2.0/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo= +github.com/samber/slog-multi v1.2.1 h1:MRVc6JxvGiZ+ubyANneZkMREAFAykoW0CACJZagT7so= +github.com/samber/slog-multi v1.2.1/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo= github.com/samber/slog-sentry v1.2.2 h1:S0glIVITlGCCfSvIOte2Sh63HMHJpYN3hDr+97hILIk= github.com/samber/slog-sentry v1.2.2/go.mod h1:bHm8jm1dks0p+xc/lH2i4TIFwnPcMTvZeHgCBj5+uhA= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= @@ -245,18 +245,18 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -281,8 +281,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -318,8 +318,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.193.0 h1:eOGDoJFsLU+HpCBaDJex2fWiYujAw9KbXgpOAMePoUs= -google.golang.org/api v0.193.0/go.mod h1:Po3YMV1XZx+mTku3cfJrlIYR03wiGrCOsdpC67hjZvw= +google.golang.org/api v0.197.0 h1:x6CwqQLsFiA5JKAiGyGBjc2bNtHtLddhJCE2IKuhhcQ= +google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw= @@ -327,19 +327,19 @@ google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240820151423-278611b39280 h1:oKt8r1ZvaPqBe3oeGTdyx1iNjuBS+VJcc9QdU1CD3d8= -google.golang.org/genproto v0.0.0-20240820151423-278611b39280/go.mod h1:wxEc5TmU9JSLs1rSqG4z1YzeSNigp/9yIojIPuZVvKQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240820151423-278611b39280 h1:YDFM9oOjiFhaMAVgbDxfxW+66nRrsvzQzJ51wp3OxC0= -google.golang.org/genproto/googleapis/api v0.0.0-20240820151423-278611b39280/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240820151423-278611b39280 h1:XQMA2e105XNlEZ8NRF0HqnUOZzP14sUSsgL09kpdNnU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240820151423-278611b39280/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU= +google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4= +google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed h1:3RgNmBoI9MZhsj3QxC+AP/qQhNwpCLOvYDYYsFrhFt0= +google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM= +google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From ed438433cae6fab61680ad8468cc4b3b93b8e910 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 17 Sep 2024 13:02:39 -0500 Subject: [PATCH 062/216] Update CHANGELOG.md for v9.3.21 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6acdf11d2..94395590f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.21 (2024-09-17) +------------------------- + * Update deps including goflow + v9.3.20 (2024-09-16) ------------------------- * Tweak error messages and add temp workaround for invalid msg locales From 6c9a7b3275314e92142d817d8929233feb14ab2d Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 17 Sep 2024 17:45:09 -0500 Subject: [PATCH 063/216] Rework flow start batch processing so that we check the start status in case it's interrupted --- core/ivr/ivr.go | 4 +- core/models/starts.go | 76 +++++++++------------ core/models/starts_test.go | 23 ++++--- core/runner/runner.go | 44 ++++-------- core/runner/runner_test.go | 14 ++-- core/tasks/handler/handle_contact_event.go | 2 +- core/tasks/ivr/start_ivr_flow_batch.go | 38 +++++------ core/tasks/starts/start_flow.go | 2 +- core/tasks/starts/start_flow_batch.go | 21 +++++- testsuite/testdata/flows.go | 4 +- testsuite/testfiles/postgres.dump | Bin 1763441 -> 1764871 bytes 11 files changed, 109 insertions(+), 119 deletions(-) diff --git a/core/ivr/ivr.go b/core/ivr/ivr.go index 3a770f708..25970b579 100644 --- a/core/ivr/ivr.go +++ b/core/ivr/ivr.go @@ -303,7 +303,7 @@ func StartIVRFlow( } // get the flow for our start - start, err := models.GetFlowStartAttributes(ctx, rt.DB, startID) + start, err := models.GetFlowStartByID(ctx, rt.DB, startID) if err != nil { return fmt.Errorf("unable to load start: %d: %w", startID, err) } @@ -608,7 +608,7 @@ func HandleIVRStatus(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAss } // on errors we need to look up the flow to know how long to wait before retrying - start, err := models.GetFlowStartAttributes(ctx, rt.DB, call.StartID()) + start, err := models.GetFlowStartByID(ctx, rt.DB, call.StartID()) if err != nil { return fmt.Errorf("unable to load start: %d: %w", call.StartID(), err) } diff --git a/core/models/starts.go b/core/models/starts.go index b47c45a1e..79eb19bb6 100644 --- a/core/models/starts.go +++ b/core/models/starts.go @@ -42,10 +42,11 @@ type StartStatus string // start status constants const ( - StartStatusPending = StartStatus("P") - StartStatusStarting = StartStatus("S") - StartStatusComplete = StartStatus("C") - StartStatusFailed = StartStatus("F") + StartStatusPending = StartStatus("P") + StartStatusStarting = StartStatus("S") + StartStatusComplete = StartStatus("C") + StartStatusFailed = StartStatus("F") + StartStatusInterrupted = StartStatus("I") ) // Exclusions are preset exclusion conditions @@ -76,12 +77,13 @@ func (e Exclusions) Value() (driver.Value, error) { return json.Marshal(e) } // FlowStart represents the top level flow start in our system type FlowStart struct { - ID StartID `json:"start_id" db:"id"` - UUID uuids.UUID `json:"-" db:"uuid"` - StartType StartType `json:"start_type" db:"start_type"` - OrgID OrgID `json:"org_id" db:"org_id"` - CreatedByID UserID `json:"created_by_id" db:"created_by_id"` - FlowID FlowID `json:"flow_id" db:"flow_id"` + ID StartID `json:"start_id" db:"id"` + UUID uuids.UUID `json:"-" db:"uuid"` + OrgID OrgID `json:"org_id" db:"org_id"` + Status StartStatus `json:"-" db:"status"` + StartType StartType `json:"start_type" db:"start_type"` + CreatedByID UserID `json:"created_by_id" db:"created_by_id"` + FlowID FlowID `json:"flow_id" db:"flow_id"` URNs []urns.URN `json:"urns,omitempty"` ContactIDs []ContactID `json:"contact_ids,omitempty"` @@ -183,14 +185,18 @@ func MarkStartFailed(ctx context.Context, db DBorTx, startID StartID) error { return nil } -// GetFlowStartAttributes gets the basic attributes for the passed in start id, this includes ONLY its id, uuid, flow_id and params -func GetFlowStartAttributes(ctx context.Context, db DBorTx, startID StartID) (*FlowStart, error) { - start := &FlowStart{} - err := db.GetContext(ctx, start, `SELECT id, uuid, flow_id, params, parent_summary, session_history FROM flows_flowstart WHERE id = $1`, startID) - if err != nil { - return nil, fmt.Errorf("unable to load start attributes for id: %d: %w", startID, err) +const sqlGetFlowStartByID = ` +SELECT id, uuid, org_id, status, start_type, created_by_id, flow_id, params, parent_summary, session_history + FROM flows_flowstart + WHERE id = $1` + +// GetFlowStartByID gets a start by it's ID - NOTE this does not load all attributes of the start +func GetFlowStartByID(ctx context.Context, db DBorTx, startID StartID) (*FlowStart, error) { + s := &FlowStart{} + if err := db.GetContext(ctx, s, sqlGetFlowStartByID, startID); err != nil { + return nil, fmt.Errorf("error loading flow start #%d: %w", startID, err) } - return start, nil + return s, nil } type startContact struct { @@ -257,39 +263,21 @@ const sqlInsertStartGroup = ` INSERT INTO flows_flowstart_groups(flowstart_id, contactgroup_id) VALUES(:flowstart_id, :contactgroup_id)` // CreateBatch creates a batch for this start using the passed in contact ids -func (s *FlowStart) CreateBatch(contactIDs []ContactID, flowType FlowType, last bool, totalContacts int) *FlowStartBatch { +func (s *FlowStart) CreateBatch(contactIDs []ContactID, last bool, totalContacts int) *FlowStartBatch { return &FlowStartBatch{ - StartID: s.ID, - StartType: s.StartType, - OrgID: s.OrgID, - FlowID: s.FlowID, - FlowType: flowType, - ContactIDs: contactIDs, - ParentSummary: s.ParentSummary, - SessionHistory: s.SessionHistory, - Params: s.Params, - CreatedByID: s.CreatedByID, - IsLast: last, - TotalContacts: totalContacts, + StartID: s.ID, + ContactIDs: contactIDs, + IsLast: last, + TotalContacts: totalContacts, } } // FlowStartBatch represents a single flow batch that needs to be started type FlowStartBatch struct { - StartID StartID `json:"start_id"` - StartType StartType `json:"start_type"` - OrgID OrgID `json:"org_id"` - CreatedByID UserID `json:"created_by_id"` - FlowID FlowID `json:"flow_id"` - FlowType FlowType `json:"flow_type"` - ContactIDs []ContactID `json:"contact_ids"` - - Params null.JSON `json:"params,omitempty"` - ParentSummary null.JSON `json:"parent_summary,omitempty"` - SessionHistory null.JSON `json:"session_history,omitempty"` - - IsLast bool `json:"is_last,omitempty"` - TotalContacts int `json:"total_contacts"` + StartID StartID `json:"start_id"` + ContactIDs []ContactID `json:"contact_ids"` + IsLast bool `json:"is_last,omitempty"` + TotalContacts int `json:"total_contacts"` } // ReadSessionHistory reads a session history from the given JSON diff --git a/core/models/starts_test.go b/core/models/starts_test.go index 11e8c4a90..5b57cbf2a 100644 --- a/core/models/starts_test.go +++ b/core/models/starts_test.go @@ -24,7 +24,7 @@ func TestStarts(t *testing.T) { defer testsuite.Reset(testsuite.ResetData) - startID := testdata.InsertFlowStart(rt, testdata.Org1, testdata.SingleMessage, []*testdata.Contact{testdata.Cathy, testdata.Bob}) + startID := testdata.InsertFlowStart(rt, testdata.Org1, testdata.Admin, testdata.SingleMessage, []*testdata.Contact{testdata.Cathy, testdata.Bob}) startJSON := []byte(fmt.Sprintf(`{ "start_id": %d, @@ -69,20 +69,13 @@ func TestStarts(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowstart WHERE id = $1 AND status = 'S' AND contact_count = 2`, startID).Returns(1) assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowstart_contacts WHERE flowstart_id = $1`, startID).Returns(2) - batch := start.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, models.FlowTypeMessaging, false, 3) + batch := start.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, false, 3) assert.Equal(t, startID, batch.StartID) - assert.Equal(t, models.StartTypeManual, batch.StartType) - assert.Equal(t, testdata.SingleMessage.ID, batch.FlowID) assert.Equal(t, []models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, batch.ContactIDs) - assert.Equal(t, testdata.Admin.ID, batch.CreatedByID) assert.False(t, batch.IsLast) assert.Equal(t, 3, batch.TotalContacts) - assert.Equal(t, null.JSON(`{"uuid": "b65b1a22-db6d-4f5a-9b3d-7302368a82e6"}`), batch.ParentSummary) - assert.Equal(t, null.JSON(`{"parent_uuid": "532a3899-492f-4ffe-aed7-e75ad524efab", "ancestors": 3, "ancestors_since_input": 1}`), batch.SessionHistory) - assert.Equal(t, null.JSON(`{"foo": "bar"}`), batch.Params) - - history, err := models.ReadSessionHistory(batch.SessionHistory) + history, err := models.ReadSessionHistory(start.SessionHistory) assert.NoError(t, err) assert.Equal(t, flows.SessionUUID("532a3899-492f-4ffe-aed7-e75ad524efab"), history.ParentUUID) @@ -93,6 +86,16 @@ func TestStarts(t *testing.T) { require.NoError(t, err) assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowstart WHERE id = $1 AND status = 'C'`, startID).Returns(1) + + // try fetching a start from the database (won't load all fields) + start, err = models.GetFlowStartByID(ctx, rt.DB, start.ID) + assert.NoError(t, err) + assert.Equal(t, startID, start.ID) + assert.Equal(t, testdata.Org1.ID, start.OrgID) + assert.Equal(t, models.StartStatusComplete, start.Status) + assert.Equal(t, models.StartTypeManual, start.StartType) + assert.Equal(t, testdata.Admin.ID, start.CreatedByID) + assert.Equal(t, testdata.SingleMessage.ID, start.FlowID) } func TestStartsBuilding(t *testing.T) { diff --git a/core/runner/runner.go b/core/runner/runner.go index 8e10e69a0..58654cea8 100644 --- a/core/runner/runner.go +++ b/core/runner/runner.go @@ -123,49 +123,37 @@ func ResumeFlow(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets, } // StartFlowBatch starts the flow for the passed in org, contacts and flow -func StartFlowBatch(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets, batch *models.FlowStartBatch) ([]*models.Session, error) { - start := time.Now() - - // if this is our last start, no matter what try to set the start as complete as a last step - if batch.IsLast { - defer func() { - err := models.MarkStartComplete(ctx, rt.DB, batch.StartID) - if err != nil { - slog.Error("error marking start as complete", "error", err, "start_id", batch.StartID) - } - }() - } - +func StartFlowBatch(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets, start *models.FlowStart, batch *models.FlowStartBatch) ([]*models.Session, error) { // try to load our flow - flow, err := oa.FlowByID(batch.FlowID) + flow, err := oa.FlowByID(start.FlowID) if err == models.ErrNotFound { - slog.Info("skipping flow start, flow no longer active or archived", "flow_id", batch.FlowID) + slog.Info("skipping flow start, flow no longer active or archived", "flow_id", start.FlowID) return nil, nil } if err != nil { - return nil, fmt.Errorf("error loading campaign flow: %d: %w", batch.FlowID, err) + return nil, fmt.Errorf("error loading flow: %d: %w", start.FlowID, err) } // get the user that created this flow start if there was one var flowUser *flows.User - if batch.CreatedByID != models.NilUserID { - user := oa.UserByID(batch.CreatedByID) + if start.CreatedByID != models.NilUserID { + user := oa.UserByID(start.CreatedByID) if user != nil { flowUser = oa.SessionAssets().Users().Get(user.Email()) } } var params *types.XObject - if !batch.Params.IsNull() { - params, err = types.ReadXObject(batch.Params) + if !start.Params.IsNull() { + params, err = types.ReadXObject(start.Params) if err != nil { return nil, fmt.Errorf("unable to read JSON from flow start params: %w", err) } } var history *flows.SessionHistory - if !batch.SessionHistory.IsNull() { - history, err = models.ReadSessionHistory(batch.SessionHistory) + if !start.SessionHistory.IsNull() { + history, err = models.ReadSessionHistory(start.SessionHistory) if err != nil { return nil, fmt.Errorf("unable to read JSON from flow start history: %w", err) } @@ -176,8 +164,8 @@ func StartFlowBatch(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAsse // this will build our trigger for each contact started triggerBuilder := func(contact *flows.Contact) flows.Trigger { - if !batch.ParentSummary.IsNull() { - tb := triggers.NewBuilder(oa.Env(), flow.Reference(), contact).FlowAction(history, json.RawMessage(batch.ParentSummary)) + if !start.ParentSummary.IsNull() { + tb := triggers.NewBuilder(oa.Env(), flow.Reference(), contact).FlowAction(history, json.RawMessage(start.ParentSummary)) if batchStart { tb = tb.AsBatch() } @@ -185,13 +173,13 @@ func StartFlowBatch(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAsse } tb := triggers.NewBuilder(oa.Env(), flow.Reference(), contact).Manual() - if !batch.Params.IsNull() { + if !start.Params.IsNull() { tb = tb.WithParams(params) } if batchStart { tb = tb.AsBatch() } - return tb.WithUser(flowUser).WithOrigin(startTypeToOrigin[batch.StartType]).Build() + return tb.WithUser(flowUser).WithOrigin(startTypeToOrigin[start.StartType]).Build() } // before committing our runs we want to set the start they are associated with @@ -216,10 +204,6 @@ func StartFlowBatch(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAsse return nil, fmt.Errorf("error starting flow batch: %w", err) } - // log both our total and average - analytics.Gauge("mr.flow_batch_start_elapsed", float64(time.Since(start))/float64(time.Second)) - analytics.Gauge("mr.flow_batch_start_count", float64(len(sessions))) - return sessions, nil } diff --git a/core/runner/runner_test.go b/core/runner/runner_test.go index 8a4176986..d9f2ae1e3 100644 --- a/core/runner/runner_test.go +++ b/core/runner/runner_test.go @@ -37,11 +37,11 @@ func TestStartFlowBatch(t *testing.T) { err := models.InsertFlowStarts(ctx, rt.DB, []*models.FlowStart{start1}) require.NoError(t, err) - batch1 := start1.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, models.FlowTypeBackground, false, 4) - batch2 := start1.CreateBatch([]models.ContactID{testdata.George.ID, testdata.Alexandria.ID}, models.FlowTypeBackground, true, 4) + batch1 := start1.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, false, 4) + batch2 := start1.CreateBatch([]models.ContactID{testdata.George.ID, testdata.Alexandria.ID}, true, 4) // start the first batch... - sessions, err := runner.StartFlowBatch(ctx, rt, oa, batch1) + sessions, err := runner.StartFlowBatch(ctx, rt, oa, start1, batch1) require.NoError(t, err) assert.Len(t, sessions, 2) @@ -60,20 +60,20 @@ func TestStartFlowBatch(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, start1.ID).Returns("P") // start the second batch... - sessions, err = runner.StartFlowBatch(ctx, rt, oa, batch2) + sessions, err = runner.StartFlowBatch(ctx, rt, oa, start1, batch2) require.NoError(t, err) assert.Len(t, sessions, 2) assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, start1.ID).Returns("C") // create a start object with params - testdata.InsertFlowStart(rt, testdata.Org1, testdata.IncomingExtraFlow, nil) + testdata.InsertFlowStart(rt, testdata.Org1, testdata.Admin, testdata.IncomingExtraFlow, nil) start2 := models.NewFlowStart(models.OrgID(1), models.StartTypeManual, testdata.IncomingExtraFlow.ID). WithContactIDs([]models.ContactID{testdata.Cathy.ID}). WithParams([]byte(`{"name":"Fred", "age":33}`)) - batch3 := start2.CreateBatch([]models.ContactID{testdata.Cathy.ID}, models.FlowTypeMessaging, true, 1) + batch3 := start2.CreateBatch([]models.ContactID{testdata.Cathy.ID}, true, 1) - sessions, err = runner.StartFlowBatch(ctx, rt, oa, batch3) + sessions, err = runner.StartFlowBatch(ctx, rt, oa, start1, batch3) require.NoError(t, err) assert.Len(t, sessions, 1) diff --git a/core/tasks/handler/handle_contact_event.go b/core/tasks/handler/handle_contact_event.go index ccdff96bd..313014f61 100644 --- a/core/tasks/handler/handle_contact_event.go +++ b/core/tasks/handler/handle_contact_event.go @@ -181,7 +181,7 @@ func TriggerIVRFlow(ctx context.Context, rt *runtime.Runtime, orgID models.OrgID } // create our batch of all our contacts - task := &ivr.StartIVRFlowBatchTask{FlowStartBatch: start.CreateBatch(contactIDs, models.FlowTypeVoice, true, len(contactIDs))} + task := &ivr.StartIVRFlowBatchTask{FlowStartBatch: start.CreateBatch(contactIDs, true, len(contactIDs))} // queue this to our ivr starter, it will take care of creating the calls then calling back in rc := rt.RP.Get() diff --git a/core/tasks/ivr/start_ivr_flow_batch.go b/core/tasks/ivr/start_ivr_flow_batch.go index da7a93c9b..916ec3236 100644 --- a/core/tasks/ivr/start_ivr_flow_batch.go +++ b/core/tasks/ivr/start_ivr_flow_batch.go @@ -37,46 +37,42 @@ func (t *StartIVRFlowBatchTask) WithAssets() models.Refresh { } func (t *StartIVRFlowBatchTask) Perform(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets) error { - return handleFlowStartBatch(ctx, rt, oa, t.FlowStartBatch) -} + // fetch the start that this batch is part of + start, err := models.GetFlowStartByID(ctx, rt.DB, t.StartID) + if err != nil { + return fmt.Errorf("error loading flow start for batch: %w", err) + } + + // if this start was interrupted, we're done + if start.Status == models.StartStatusInterrupted { + return nil + } -// starts a batch of contacts in an IVR flow -func handleFlowStartBatch(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets, batch *models.FlowStartBatch) error { // ok, we can initiate calls for the remaining contacts - contacts, err := models.LoadContacts(ctx, rt.ReadonlyDB, oa, batch.ContactIDs) + contacts, err := models.LoadContacts(ctx, rt.ReadonlyDB, oa, t.ContactIDs) if err != nil { return fmt.Errorf("error loading contacts: %w", err) } // for each contacts, request a call start for _, contact := range contacts { - start := time.Now() - ctx, cancel := context.WithTimeout(ctx, time.Minute) - session, err := ivr.RequestCall(ctx, rt, oa, batch, contact) + session, err := ivr.RequestCall(ctx, rt, oa, t.FlowStartBatch, contact) cancel() if err != nil { - slog.Error(fmt.Sprintf("error starting ivr flow for contact: %d and flow: %d", contact.ID(), batch.FlowID), "error", err) + slog.Error(fmt.Sprintf("error starting ivr flow for contact: %d and flow: %d", contact.ID(), start.FlowID), "error", err) continue } if session == nil { - - slog.Info("call start skipped, no suitable channel", "elapsed", time.Since(start), "contact_id", contact.ID(), "start_id", batch.StartID) + slog.Debug("call start skipped, no suitable channel", "contact_id", contact.ID(), "start_id", start.ID) continue } - slog.Info("requested call for contact", - "elapsed", time.Since(start), - "contact_id", contact.ID(), - "status", session.Status(), - "start_id", batch.StartID, - "external_id", session.ExternalID(), - ) + slog.Debug("requested call for contact", "contact_id", contact.ID(), "status", session.Status(), "start_id", start.ID, "external_id", session.ExternalID()) } // if this is a last batch, mark our start as started - if batch.IsLast { - err := models.MarkStartComplete(ctx, rt.DB, batch.StartID) - if err != nil { + if t.IsLast { + if err := models.MarkStartComplete(ctx, rt.DB, start.ID); err != nil { return fmt.Errorf("error trying to set batch as complete: %w", err) } } diff --git a/core/tasks/starts/start_flow.go b/core/tasks/starts/start_flow.go index 6097f8e54..cd09ebd25 100644 --- a/core/tasks/starts/start_flow.go +++ b/core/tasks/starts/start_flow.go @@ -127,7 +127,7 @@ func createFlowStartBatches(ctx context.Context, rt *runtime.Runtime, oa *models for i, idBatch := range idBatches { isLast := (i == len(idBatches)-1) - batch := start.CreateBatch(idBatch, flow.FlowType(), isLast, len(contactIDs)) + batch := start.CreateBatch(idBatch, isLast, len(contactIDs)) // task is different if we are an IVR flow var batchTask tasks.Task diff --git a/core/tasks/starts/start_flow_batch.go b/core/tasks/starts/start_flow_batch.go index 8f8d37740..940d77e93 100644 --- a/core/tasks/starts/start_flow_batch.go +++ b/core/tasks/starts/start_flow_batch.go @@ -36,10 +36,29 @@ func (t *StartFlowBatchTask) WithAssets() models.Refresh { } func (t *StartFlowBatchTask) Perform(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets) error { + // fetch the start that this batch is part of + start, err := models.GetFlowStartByID(ctx, rt.DB, t.StartID) + if err != nil { + return fmt.Errorf("error loading flow start for batch: %w", err) + } + + // if this start was interrupted, we're done + if start.Status == models.StartStatusInterrupted { + return nil + } + // start these contacts in our flow - _, err := runner.StartFlowBatch(ctx, rt, oa, t.FlowStartBatch) + _, err = runner.StartFlowBatch(ctx, rt, oa, start, t.FlowStartBatch) if err != nil { return fmt.Errorf("error starting flow batch: %w", err) } + + // if this is our last batch, mark start as done + if t.IsLast { + if err := models.MarkStartComplete(ctx, rt.DB, start.ID); err != nil { + return fmt.Errorf("error marking start #%d as complete: %w", start.ID, err) + } + } + return nil } diff --git a/testsuite/testdata/flows.go b/testsuite/testdata/flows.go index 45d8f1aa4..e3bc27fd0 100644 --- a/testsuite/testdata/flows.go +++ b/testsuite/testdata/flows.go @@ -78,11 +78,11 @@ func ImportFlows(rt *runtime.Runtime, org *Org, path string) []*Flow { } // InsertFlowStart inserts a flow start -func InsertFlowStart(rt *runtime.Runtime, org *Org, flow *Flow, contacts []*Contact) models.StartID { +func InsertFlowStart(rt *runtime.Runtime, org *Org, user *User, flow *Flow, contacts []*Contact) models.StartID { var id models.StartID must(rt.DB.Get(&id, `INSERT INTO flows_flowstart(uuid, org_id, flow_id, start_type, exclusions, created_on, modified_on, contact_count, status, created_by_id) - VALUES($1, $2, $3, 'M', '{}', NOW(), NOW(), 2, 'P', 1) RETURNING id`, uuids.NewV4(), org.ID, flow.ID, + VALUES($1, $2, $3, 'M', '{}', NOW(), NOW(), 2, 'P', $4) RETURNING id`, uuids.NewV4(), org.ID, flow.ID, user.ID, )) for _, c := range contacts { diff --git a/testsuite/testfiles/postgres.dump b/testsuite/testfiles/postgres.dump index 4d43736dab624a46bdc4292a44ae520d315e9ae1..4b44f96dcf5869000a00d5754223cc9b9ce3d7d9 100644 GIT binary patch delta 79874 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 KI%xY2p!R=Qzvr?5 delta 78063 zcmaHU2bdH^^Y=|&;w3J@afuvn)6DJ?4?&T9s0b3|P*4GZ6EPwJMnDC0g)$()fRT7= zD-um$Aelu8=78vl2@`z3o|&FmeE;|PJOb09y1Tl%x>DEP{^{hO_e~B~_U$vMQfi#0 z1|&&J!+(wVA8q*`P52*%_s0$VUunr+BXT#K6_x90_=rbhp4K(_ENlu%=(T-xX&bF*6uW z7Mr;>V>`|Eiz0T>$zYzCKl&7z^9>)u`fklrzCP%ERPsiSrIqrLLw!q=fA-FLwok!!>0ywt4-+HQ!$y z^7b=a)AEL217a27n$lZGsbWH5GcBLP+&@3xhWY&bb^))>awc28s(DR=dzPm_*!aLR zK7@E$35T#_RY}cVE7qpWq0hsUbTMkAhCQ*)MX(u+nA9sIH?O(t>APg-kf1D@Rzxck zYBMTYFYC$6KFg+q_e**7XSR}GGk#Wag|xeyTJN6q52(|p zT|OZ>cTZE=w^M3P7aWyx>5Xn`!=$=lK=Pq=?v|N}c(~^B4PQyrdAAQO9;%sIbGAgw zdZ-!6Ase#YIVIxpwA^GSE2ug7?5|RCXpIjfR+G*yk!Z{r=DRd^m!>Sxnj3Q zjXNj}YA$@U*z1}wYi@sQw!7`ocra3vv8R)$jR(WDhRMxoRGFNWtUZ;N+`A{E=Apf% zqCqSXrMq8{b88Om8z?wzJeWvpnq2&D^W^T=GU%t?QhsvZF0E$HJF4gv55{X2zI&6D zOzsfq2?foXPVe_~TbR*cv}Vo+Hw(&(1|w+&bVWkRscHG~CGJ3GG!#zDBk2~sY0dfr zU0mLS(h#WBF3mVp?Ewsjsq-0fLCqtFm-%HOs@N=-)J*)WO27=7aa<4k`^ecf-+peW zg7VE6M!NetwFy;h!IuAZuf)-jUsLf-P(TaD!nC)K+>CP9Na;0?eml*t2rb!~&UmZ%HXLY5NYjMa`do^_FPg0$Ha6i?sU5 ztP}1jv%;p8mPH$0mUC+6{PU2MeDN=_(P7i1buY`sd~wMhALrA80U)g+#mS#y;dw{J_TCRhS^?8!cLQSLs#bM>q zghgZDkXurhJEg3pPe^%eWuDaC@0dty=5(C1R&BtH0%@Q){s}8im2b*zlM_G7V$@8! z&|OkUX}gNSfrKC>EpA5~-~;F}dSFJ_TDyS!_m$bdO|RJZL9xZs+e-7r(qmQ(hehfj<+4rfq^@GtG0S47 z+Dmi1W}uDCrF-QjY(+E@O*gQrvBZ;uzZ>mmc-L<57~TYnzC&Mm?qF;yV@$k#vz< zW<#a=A<$;kc~S$a`dH1RX9h^6)U}uNj)Q3{2C1|KExt)9rnMuaru4=rsmy7){A_77 z|5p|r?j}7*`z`{X9P*J`pH}pi1Y3v&V>opsXG*2)aBs=P$)S zM=KVJU>*A#7$pqrNO&DWVcPz_+}`d$hX-N~!4KpthB8T z7UhlfH`$7&71Fq0)M9%+Y{JMCELjnd59&(T`ccw#?nq`R5uz27qiyOIY%XsMmG2?JB z%1SQrc!n8<)R9fq6LHsY7SGm(vFH@3%xf1mS=Cgjtt!BWqtxhgxhZ>QI>^Y;49sxE(Hf}qDk-0} znIU_so)7N-e?O zF}?GE_l#R5jUAjN{R_bv*H`j}H*zUsh+9D=bEFI1X-4C5ntherj6E_(Y9$7b#$%wB zxmV$OJp3g{lk--HJnqr~+n`{krm@IPDSZ+NoU|QHv}SDf%~H7sD#3XuiVNw*`nU*w zxRCx&w=d^hkW@MS1&F@MF=@?n3>X#LR)te`^7Tv;T zkN_r!4W94a3uf2~(V%NVq+IS}M;7=q3R^*3l0&YQ^9VP0Q#SBUsnP{yMdC5G`Y!1j zmmcwLu{MjPE5uNtsL39_TS~Yh4)on9`|4hd>KuAA9tlAr&75sCWs{al5h2ILBmB(p zVp@0<^2?j|xZocvR{lIhK~!vxI0FikNKd zYH5f>J0JD9Q3y(Fa~iiA6!G=rpnzmaeg>VfS}+DH6iu)TpOo@sTKjlPVVu_1V&Ax+ zPpA5UEbLitUyEse`n2>11)r2!IxDn7W{lzH9U;+GPfM;D0fm;MgMIe1oSW?Ny{&>3 zG5|0n%D7U?pf8{ChqEH=*L6}-rVG}l6o%QO8>KU_nswmnw{MiPb8X9z6^ez^(t+$L zS6+JMSp1P3_gPbFxWt`gvUapeBcGLivkezC_BjC&ma;gz;W5BWb z=8RsJp7zcS_|*D8 zoGkYFQR#9~YXzz5e$dbKW1xPwhZSUvYo%K}`he=O>}P3+x8o7q+_F-fyx?Rz*8NvV zkzy;&Al5W)Io6c?O&TOLy+||x+W{}8Z3-BA;Dm=JD`Ljk6@N+_JlK$Uda6o$hngVu z<+0iS_}2zbHGA)0Dd;vg!91v!rW9~>i5mYPT(e6J7!ESOE2a;{tvP@2+R6Q%Y`D&=}KXSJ-+o}-#h*WyN^YDJU{ z4k!(&Q3KhPO-)$h*uDnxv)&{VG1?nYTG8?>P&6*sJRjb^=Fla7Ya|Qqg3e(oeGC%B zS?Tg~V)Z6&l@i(?P;zNbI%Ku(^|2%EGvz7X5OGK+S#pNBop8|sT}S!`a+BjXHKmmk z{V^M`{2U<66)((~MdKa^0!HM@y*w=81V555PZi`A3)12zK+d72a?-srV=-8f3P>6r zC}cl0m&;vvC=St_hDvMNULQKim|}UP2PDL{m&g-RK0BAmkGU(1#loCPgv#V5*kn~_ zf3}c=ejUGR?YcP~RfAQwlBaofL4Mylb?J)1Ns1fXXih6RgB7=tOT~EL#r@jK=Zc#* z4wZ_VhiOQAu$nhoIBG{8^(+T^7q*ofvQzEQ*}c`{p(v~(fCN(gbXf5r|=S z@y?F2cRJ%?+OZab<@ueldJh7udo;E{#}HZAyaVO(7_kR18Z#J;BT~U#d6<&z&EbCp z)9j0jn}lg$SPl}-Ne1l+%jFHYMR@GQ@f}O9x{?-G$^GR>h$e*PVs}?z3Z|PP@;5Xh zB9}Nb08zRoUs@ZLkJ7}bUn6Y0^pPpQzzw-NtuW=L?nMntE^V^pH@MlhRFvVqgSED` zZI{()K}@bsU&iGOr114f!TYprv0sSKm`Q88$Opw|!esY$#Zh;^VN6YC?8&98 zuYuK|wwwHrvjEtQTmvb6l7P71{bD&|X?KiU8CEjrRCjrL!F~T1`YhN;>5d-qIw?$D zdwM8>sWds~gM4;FPaFmpMZRT}y;gy3b|z#-9V6&pZY$r`$@=z|ZxpRuL*}2Nm1m-4 zKb$GI5kmw$!{g)OzOvspp6vchKD}Nc7qRJQgTK+&^944!EiqYsqL5|um+RSf!wfpm zPtI@#O0gNz$N}=J+#aY?@c2)@w`4hRUBhQ-dMybSvH^*J9tcpCfM+_5U%((Yf+CZ)(=* z_O`NNaz__k+^j}0t2L$QLm-jG=gTj-q?2$AX$5TRa9MF7rCPa`j*$C{0eq_#$s^^h z?v!G}h($+GD6ZBhJw8f4EWS)yG+KVeB@S+r8a+l{DX`<)q^Rg(EcetLoY{&AasvwP z2XpIunVe3ykCm%kv{{aA%B8JT1C-tSta0)Nx20=us&%*Bw+Zoa-V&vr;l~*r{hQe~ z9%ta{@$yvyoMY2DDmr@R8TxaA{IX~Sp#fHw7F2vO5FMNVV767I{4%*QopGr=RrL04 zDTgnW_j;wmKH^Nq?r)ny&_A85iSiWD(zku=nJDjcM(5@Y2J=RtI3?T|@!DixCh%gD|Fes2TP5#9lH4Hn!$!R$K|CdR?87Q#; zdm5aEJw4JuYfNunA-kq$sQ;c-;QHyfKm|-#0FGZNN9fB6hZ-CM0QH$6zeEj2$Y;{O z1)4#lt`bbwbk+XD?o^|m^3Knk&HS2j8$vqmzFNN79R=z-*Ru;b=iDtucGT=IoljeT z?s}asZE$^g%Js6a2&d@BbaJMA${juKsle1?mVC0{f&ZJ&hGVSjjgYOlf~iwQPi(~I z*@6Y7XvWSG20R5+l+1@r1*JBd5_9C6|1YhWj?9s_6fFC{M}*N2nrk6ldM(HkjKv37 zkJhh-s$6iBJPE3+PRY3fwOnaAnk>`8W9$5M!Z{;-bG<-pI90ogTm0uZ6a` z`4%}K(LJ}wB`!Wgu?YL|R{6L@uilE$Yi}1!n`@0!f1bRR-b00Gjcf7qdGfDx1np?o zyp#&A5ANkkpFy9_2jY%^#KfNAq(bv6q})4X*Pw5usCAw0lzR*26FFUFqvdybw=A?W zx^x0m!3;=mP4X#U@9BIWv;o0up+Vp8iThRrj2}u(KKuRw zIVOe;hE3|QO#a3pRwyT)J`;J!n=IE^2s%p(cGGfMxG^>Szi-u!o+0&DZYU9TP-*C3S$<7gx*@9Bo<^4#%H)wPLac@<5TjRF5V!r zLD+8X$WXnXmY;W!^ItKWil32pyIpX9|Mrahf!olRcNo#+TkI|anQ1bNeGO1Mkik!*L{ae;l`t1dI0DI(=JXypu z1i`4IJSd|n6`!f+(Drq*$_8aC=Lk{=!V+~xN`tB#C70DUQO=VJo7(|a{G6!#tXM%y z2B@u2SZ1eF8?x4S7nL6NN$d<|(eN!%QT?&le~cI?)zX-B)d#;e)e+ za2mDdF&AuQQ>B#?VNn>WV{j`NOR(e3l&huskwlt;iA5p_s=iRqD~#0L1=iH&3WQ28 zBBRKxvoWrqvuS;m-ij6%E3H}kVr7Oz6PqiUG`+c!R}`#f+n4vHl>P&|@g8J(0ZqFKzot@lNgJXYITX)21jy4ZzBcu=rf zFJwc4*m>u;MdM~9?E+ENl6ppzX4HDK5~7vWI?TNIWE;aumOCI+nh4tyQ4&&7x4N(% zg0|F>#!c5-uzseJA(g_Aw=&{++HA(wuTdO-^~g0o`RP zEosk{dVtDe${M?K(KFe=wf*0J=sp^Zr?n@Do&*`nyXY0;TMC<^1S-_9ot9c(R$=P)DveJS)+$5jPIa3juj~RqV2E_<&p_$8I zFIe1HQCUe}v=_Y*4f|U3*uog4E@U`JH~}}4HdKty)}$&u%lXzY}B(@!*j3~?omp>bJ5Ebm2>km2&5E4_!j-3Z?;p{h*-U#b;zT#O?5f;VvBHBe*MWb-Up1}iM zx=?ik$za__DpNhU@D~T|uoJd-l((IhiP)HncyTLwXsl8|B^N4f=$eTjmIY@Tg|zrW zrHmT9skfzyD!$JbDg(tSk8*L~9la@AJXTrf&CrYxW(Xk_u{nf+^4Rs`lym(_B+|Ng zu`fD(|CZi_7H2`wU3{^k(t~G$!*#e=In!&8*bX>{czn>DkM$z9X1p@ktBaXw-P}5a zywG>=ffHA+Qv>XbOBCVQk451K>&#o_v7MJHqutIHT&ZbiI-NUz16mt7QCaSp7UDGR zaW$9hit@=gZm!h9(Ho}y$8=biF83!Ai`lSv07}tRrAEvTvJVa}k4qsG=deJv2Pqn} z%(S+4Fv_GWltDsvg7tU%bY+wn&H|lvq!m|z**3pQX~Mw&yL(M7w(KgULM$g9wbCxI zLrrM*Z7}y9tJUi*-K(m!{Tijz-cEMGH42I57LH_}|83ro^}J4*E*iz*=p0LPyGX6* z$ge={zHX9A%V#PrS@BHeCDF=)-LAjiij~Y#?heEI$@hW?6Q5w34VgfB9$R&&REJ0|>9dngGor8cu6Kvs4N{%4>82tG? zcmPWrzC~3dtfPRsKZ@==-epOx(H0z3sz+0ARlamK(}FW4ZHyDO0+byWvw?*eM~DZr za7>oikl2UwlwqPlA{4`-5WJG0t#@E4w7_3V40RQ}uBS5tdjZuAjXtz|uF})7bWFWV zSt6zYeQ2Z|mBPvwD&L6_VxY|mCs+l=#VMes4FWDoe$k6*Z@N*BDnCacjtv>{F1@Je zCglujK3K}6O?L}kWF?|plHh?YbS%p#WPy7ipt$45!iluDUR@J*FbOu~VmJYZP3aP) zfrsH}knhq^sf3EI1BF!=U;-8AO7$HKVWmS|u!zx#L8jSTY!op4e#kTeA~c204=9-) zM2N6w@c@=?6g*D~utm!}$p$15WEsm-M#oX8petG%CLLL+ASA>i4@9iEEDnbxOs( z&ce+wCnR;^P8coT?3AdH228^v^FgA!oH8*uS@_N7Fc1*wW8 zNh@H}H+uKFWyV8k<#vb()jki@Uq09fu)=2*VZMd6DTd|p7!SD#zqY`8wrn#* zB}lRlNJVed*$2C3Igu&oGe#=fU1v+kFX!=jAZCb1nJnI>d>|F=t+RP3f$PL;PH3J- z-@gx9+q(nmSkdyjI))J#*^;h!RcXN@uPDV*;mW#OCfKxBLAXVa*4YGCQD-NfW${$R zLj_)kDCaVW2sD^7+E#6pvY9)T&VI28`Gyx$#T%fPL%Sdaxg-~Yn1P_pnFxdG{HCHv zMUCnZqhL)4Ll!_(OF3=7A6LuuZ$mD<>n)64m{E71Agg^Fnq6T*-A&?b?j9UM_wYyH z92>@CTOxF1A9nlleae&WL4eWEWX11z2R0T7C)oOTl~XS9Bg_RNFeiQ<07K<>V^QC} z551=F%sNK5LJ>IoKLo1mFczw~P)pUBW8rW-trJyzh)a3-$4WYT>m#MqZF+3nX-xr% zQ->Ym5cN-OR#T)rOTtvjszZAY^djjMBZ5QU2D z*6jEZMRkK;^v?yj7|aT3-RaO~3|cuGM#g=g3w`(c&mpuqHwdogFgx;v(nl(sTK7l^ z*6S;!zU0Ko+_vC97_imteJ^9$$*(bOJMv}iP5*7g<5_tl_Q5xNw!Br*oH~cZA%Q~l z5+ZC5?K1LM{5u7XM>{}f+V^6GFo|;6vE*Jb(Z|16#A^o>bAgx#z|fQrAaC#c0b+2| z-*78U{=pN2VOTUFui345tP2I-!+o&wAq{TeV-VOJ^uSP&U^9OLhjJ(%22rj;&-zJe zMJpcFbe8_J(q5tq2YYi0CoDLKSN#G3^UCAONhfk;Sf#LhSYbp8toTJ~X&YcO>Ahb? z4M4}(&%b&%8@>~`Xx4v){TuuTE~i#~a9&CnuB3`uMWf2!l+I4YProa!GYIxsgm*9f z0cYxyKa^ZC=>$CHP3_XGKf&6>QXnHbrRV&GQg^`#ggVXq+ZQ|_vp{yU<6n;bqdes` z3q{zflZptLgNYYn&yoiMZJz6{dh$*Lv^7^!mkI$JF(f65Dg^A92~oC)jsybD*?d(M z@k=qBQZUcVT0{YDO-D?~x0>oRPbhNjse5jq9lO|2dkB7Hfn2&fkt@$M!qbx@_0&9e zETCTQHn1X4$9twU*jF4dXjua_i#=Xn^+TK>J+=2+u|W+}hKMC#d9Y(h(v zPgkGzW&+NipisKnj-_X+bEG2YX(l(J{KtY~0b@lpBu{P5-pW$5r6R44!vGU?4EH($ zVLGt3P1JI!C{R~Rt{$DmTSnNEIqL0Fp3lhMfJhiJ-^l@F6n2F%IGMX9=l+xk? zRb_AIt9er4r*$`vKHpUBD;54-R|^aNk<1bVL-U9lHmjN1TPpgk?#5wA2#9}a#{Mc) zAtC%+S4$HoF78Fj7%W+&j*|+H*4;eFFpJSr(Pwowi3bsT^I4f%%$_dgi~geSmSJ|d zOvNJr=P7C|#OHz#nGmaPsaAQ#Aiv>uG1W?XCXH=_uoA1aPd2#3z>E;nQbzmQ!9bnC z17gbCs#kgtFr~3}gU8Xp758D==Y}&L3d0aWBj^|=%xK#APJ|4Ki`mLf>U$m_817nA zs4CEkJyxy?6AB0?m=>~&Y4_wnLzW*>hlpbfcXxGI9iQ@fFrwZd0CLl9N9x%bwzTEb z0y%84sd~zN98ZWtejR1xp(yiUp zR=QJ4>5K97bA1Jj1$fXI$oMY^+K|+Gh9e>S88W}6XMgoO z`-N&zdEKZ9rnTZdmR<;x+1mru6LyC}tF8{AV37Sh5Hx4MO{MwGmBx)zs3!z^>o{0# z>QK3|-^Y z2-SOG6vA_j9wXIGQfZeuu>gxqDWA;VQEDlz9i{&5cp9SpHR0uo!!7o~Xf-T&Ml{Yc zFI0O2m+|96$z1c`-}JplWSw`nDU?UJLu<>Wjg+i(fTD^H&(-tTOP8uGB$|Aw-vBJFt<#`tqMFIt;tz=qU6#@y z3`O6D&~Gv>g9Vcy0MVWwDIliPZMFS!w3068t;TlyPb=62@M;O(3^sa-3ZG+} zDgHQ7{!G*Mbfn(_q`fv(ZRQdg!fs>i`)O*#|4y(rSE!5p?>KCy>Lhp9kg9-I%~Eq= zI8_Vik{Y;!7cGL(r}iquW*1+j=Fu}VRPi`03`Mk!{SJ8P)gZn-SE(Yh60vREV#A*Z zv-E2qfw*iXjEIvG=jHICyY+N--L+~%cP1D)#1>uWU9<3)B=9)Oer4QuCZ=Fdrt?x~ zG8u%jqs<2l9GMESV*)*lmEA+;!@@8Pxy{9;#m5NuzD($;PZNwhD#XFj22q&mDe?&}s z9?_ey!?%L>imrH7*UqlWSPjZDZ&$rQ!Vo0J_I6bfcjYXZ$H{y7H>VK;WW@-0Ecf>; zehU5np3ix#_wblbWa)w%hY_UT*=~9G8GxI72j{ka%Uc$TqiKa8IDuc8}JluyBB=Q#T)mWx1cMMkpAKR$znxI)s`MW#70y< zt~#%tsncfMJ+nv^!N!0eh5^BLh~QatbN1bRI1}!QOrW!sSEtio4?ygwe8Agk_?MGe5 zl_@iUa?;AF>--+hM{<>al1(@x$3Ci-(*>{Tne4>FkeI|+R)V4CAsj z%{al^AH~r|**~HuJlvS2@KIVO^koN2to8}D(nAoef8cc7n}lM6);$Gwcg<5kzQAB4 z<~WP8sp=ryO&>k&Nu7Yng;BqA?HXXW_ZsgspaI;Gb_0ed*j@xo6kbYu&^)SI52SZk z?~RApPZJtCVCGTf7r^2C4ImP4O^8U}h%@YchY82g~hQ`WiTV)ebd-UHvMQZ)Zjp51_CE z=oh{YNA+9pX{9u0KNQ2j>)hDa~u&o4tB;b*h?;-cFpvwrqcS4v`%&} zs@kbGq^EbO-+4VD>%ZZv>~V0r=Ctw+u<6KKYCl0varjmv+|Aa4PQ9&Wv#oEd!n0|` zVZ#kNbs0z0daUansF*GpBH{#!IrqG=b$eCe!L{Phm?Ms>ZR~M$8NT1A4lcB88wk8#L71cL7vGnD2=3SZL!T9J6AYeI@Ymqqegukr`Xhf^ z5_l%`^T+BGF;#@iL2H7$tc(tQ0=iuOi6^;P3D~xv7UP*WP7}7b?4T-~x2TJQ*6h0K z!#IU&4*4f7f%~|#;}%Ol0xjJ|I(L3oeU>sbm`s`Dy(<3#L!bN{lBSEn1hCu2u4Cn2 zs@~K41b-Ry74A{d3>fW9vp<$gmo7zwMs*=P7~g!IG81e>xn0ltf2#^Nyp;g5mwyMX zLCg$Hf5-P8f435FfQ6i=+0%Z&;yVA3f)w0Ord`LD9rLwrsDGFV9&4A1lb{6&16N=Ykvj%l9shCE{YipP^|+A3>#EJN0Wug;1`;;fgV zb@wI@EoPpoi6|EEmmvE{({_6Gcwqgcp|$hBP4;O(GrdXRZm}1;qrXwide+zK`8AQW zc6@rP8ffj6V26YK%hD~T%KI(SmX@Xiz(}XK0AK%FQ z@*jxM_(1A#qp^ynzz#KTD#Sa)xkg~Yv3+JMw<%5N;AJ?n?>5ss0VxiqQ|vS-Db~_y zUXk`Y^(_(rtT-YCiX7nUi!}uAY<}(Z(YdJE+VO{VpJrsSHl}_l?!OT|L3*I;4%>HPtZS=l_7OQEi33(p<<1DK^W+zS-4BZ1d zXu~BsT#_YAoFYLtGnvfgU!8!Hv z)moOjF=4!`c;*~1Kbm$9-f>r-qZQMa*N9O7BFwDwv}>K}fD0x{mz|@vp&y5d>B3#g z#cZB7fDZ3d(rM5g24c?6*Mydi1{MWJA|u0`l~O%=I!9|jKMvOd?5W`z3{UBUg_?zi zG4|OA4fX(@XOXf;dUJ_HgA5?X2f02WeZ~^-fHy~RTuvS(M9WwN zQiOd^Ws=dF%rV;EHtfP)bs;VS`C<(GweB1YQ)!&-+cyBuy=8h;X+`wXSnUf3Uhlf$ zne449Ez5;BbF45T$MDS8F>_X|gu^U+k#^hxHLy-lI9Nk32Bq0~4%SwA*g+Tt+dN+D zAPtQ=yM#&F!PWo!_kZlS{p%Vl%pLd~!PS?5FATXv3rPd(1S$sm5AXqX0}ry@muhjT zPn{tD18RJopzzqD|3vL)PQoR0&EeB`D-3agvuxThNz10ile80dyH=Of4bDnyfDSzU z0+qi73sKh#p=kmMI{hJ9Bu^IYHg8)P8d+ zO0G<)uvpa$PLy`KiZ?&~&w3!)uDFU<+vzFZ$kbgcX$;pQ9l9EKsa?@~MhZleN!M!d zxZ5=a=lS!5fNslWpIwI&GwM2=7@n4sI!*LD<9W^G>$Q_k#VG8Rb2T6xlat+&KAnl( zeQBolozvur$)YcOl2O`!gXnvBmZ-o~!|b0~kZ=U%V?snrmyxEMpx|t@s|4ywYr7Z<-^(V@c3@zkbx^WX7Zv>m*^~Q~L zfkH4hlNRH$cS0c|cY9!>kY+0H)`Ybt7KJL0q+j=HO=(3^%Vyu-gL~OUXB5vJi|&OG zBfdj1cEu8$BJmvwJx`kVY(E;Jb<1#9FT4*@h$G+vd=TIfJW)h@9?;TR&I17N)kPo} z+jX5jF*4Y74{E{y1L-Bo)-Th9s*7(EYOx5yfc<*8)=k_>{E2}r$z(hXF?aI{|GtVM zxUU)C2-GxVy;o_%v=-y?8hco~*_{i5*tk-}6Or(6jchjMQBPCA8+(&2e~e@P&4H(# zSSte2^5AMM$BohW>@IPqS`pa5OP-3u|!gi_kAOzX+5% zbr>rG&-IEg@$_=mX041hc}Y9lpGTAsHj+zowrFi_WUyy9Yv=o5;R|!RPTdNyUAKa_ zx-1UQ@EkV2Lk^U&8@HiGSl5E={_VK!-A^;Yj_kmv+s{M*NlW`^2LdhG&{s5}2t%Yt zCJJ8DkUHFKwn@0hd9L%CSyoo{2A0*Y;c zx5HGn?e>?8Y?Y-A;Xo?ds|n}46#>bXIngc|4b%X=y$@34-TORPcsU%w0(+@SDciSS zJI5UY#Zj2K?BXHsYQ3Rr7OJUkP)K57w(dQyWDd&_cb#cLs>=KTWJ#CoIPJ8dd5n$v zkk|D1Q1CMpTI{ipK$|r0qm)9E9sU?trr|}W4+#Z|b^U}3-$kEzXA~ZbbS+qG{HO;ZhDl;nMA-fM zzK}BDk-<9;JJAg*`GxCpM~|n}S+G&^Bsg@cSD*=7^ee~RS-++<;8r-hLD89kJoeph z+%ogfZ~l;xxV=yo`~x*bKh$BPz$~~K#s;+V>_9eKbOI&~>U+W;$@UNO25j%25R^&} z)(Hw`NSpcW_*-kq%Kzqg`tC1(MzDqq{fBQ~^q-W%C|mij)=Ka*yaU*C(mUI+7{pmP zYmlmkp32c0(vqLKBp_X=dC~>gAO{T!G1VLnlwUnrN>G=keJtC8MNut5{LvYoYQT4Olr`!?2nOJX?Au20v+yLDI| zEw(yChc)7)B5|~jR>u?*s~q5d4|&knKs^r-|O#=DLujvV)(MC zM`t~svP$q26nHP7>Na>ri?RatP$wN~oV#Aj9d%FV6d3%HwI6Lr<@!o*c~SOMP+#n! zA!^doL5#B^j0igHuL#PB;V$|#qHpjU#^_)K&qBAG`t2#eDr0(_Q^WkYPcy*5(3swg zeH_=pT*MT^bhrk-)An7&)g6OX*=|S|hY=@meHXoiLwc~Q56KKU4GwqHb?RCK#3|i$ zTm}MW)PY&q-Cs6b<9%(xpWWGAKkCiYWOI7z_oaN6_R@DFEFnYkHyPgilV|vw41aHT z$3@(;w?0pzIg3@jo*;-Aeb7gD+PL~bPo73PP2sb;>Ur(pncR&RPhw3GC zaDd*bQ|V z2j%P=qQm@IGDx>vu{MYhgLy;sXC)~@S9}GR=CC0?1UR71Lo8qcH7ii^83G0m@`Ml5 zR-@VEp()K0X~Vo`#N!5O?fHo9;v*tw2XJCzuXczYSlSm)`)({y5WmmE3(*WD+JC5C zN@LFT0YY*10kT0O^iOO$*6YLa;$RHR?Tulc9_Dv~+EKyNHW1P_WI6GN-;Z($g1s?P zpCTzdk%_&?e}|{^OQiMl`XLIj4SRL8{xI@6*qegvK2~z-3wbnTq~8U;&I%Ga8IU-9 z5S*F^E(FIEO9U=IB6Co)LEl%IzEs z3^^FSmS}N8*0NDe|BmCruVlUS4;gppuSoS=>p9Yc+N=EKPBESp zhG8V*E?p#o#&;~OpPk%+5W$#7pDY$zvT~6QyLI{CfT18Zz+sC6?iK?h;*vkPwX;GX z#_>@w!?+QU(XiKO?7*<3KVm3GM@Y}He+mVeB66s~67Z?+Z^40eAgSMh%_T#l17Cyg ztJ4EIX)|y=-MK^nFhlSX0YHc@Uka&#_apS{xU?@`4*k1KZ;M>m{L<%88YJ-G#*y+M zj$MoOyh1)4I4S|>7n_=N8XXO!(^Sggy;S+v^r_ zq;o3_SN(ZB5|bgD$1jqMBRO?7IMU<03=zzaSPz!@jNVx0o7-5ng7>_WN2{L{gCdwS zls3=?apPM3UEE8^EWDuulDA8($5*Hg6Rh&e)KcQ`VJe=?ZuQB=p7Gdi%%Lm#qW@+=-X92|Z(0U@KbYr7*2AVY*0!ignCMK(~@Plx06LGVEN zWe*HkKP!IO-bskGE*r)I;xrdIK-3e4h^)PhZZp+p}r=^dJAH=`HVgvqu~z1KPiH*(#=Kv(;zb^MOI?9pUW0{fjieK-MmvzC40PX?CdVeVv*hlMhp0ash`X^QOXf^U2P37dE= zLoLjtv-TSS2Twm9Oo0{$d$Lbq$szrH7f+o&)(yJmE1?$JC6^AY zM>yKdBlWBS>yG%<2z*hlaB)uZw_E|yVFv?)({A=h(mSp0Sc z?Op^Gu;CxxO5!lL4Re}S{-J+ngA3^Vpn)LgPQa_`5r2A(ExZZt<|N5j{3jZ_*x)bd zMx>;BK~k6iRs0Lq#z~mf{;jVAN_QDLO_>Yv62C8Ei)OF?>j92K${dpd0SRO9`#C&n za^FeFwKkt#{4%&%iDERQlij6EFMkYvaR==f+C?x37fY(U83D-3`zwrsCGCYkjHw4f zb_juuJ~zvFFQusxNdzf#0LbxLIi)pc)VwuyS8>Db=GXJV)`pjTrXBTAL8x2GjK(af z7@4@q1emR=2POf{Y^axd28UZWiX2#r`XYoS_`cl4Sx9icf_(4177#~J#-*Ybxn-Ku zZW!fw?I@~uo{=dd&EAi?A)c-Ap9nZNWsGBnQNpe?jPTh^%LTPuTo;V*~FlPlV!ZZvz9ry?N?Zy`Bu`{K-Z}2Y1sG5W9xjA-#l@ zHaTokW8-yORn;2sq$oB8;kSEi%jWP5e^AJvW4vx{*p>|AFk;|zBr16+%Ws9`M`t?0 zzDRNNuOPGp%=JU;VxIXXpJsiDD{EH{q`9+m_Vbw4h;z202nQiG%rjimq7{Q8 z+1(~y`Y;b0Xw@F~G8$TQi{tRka`l@u9Mq3~tEBxtjx^~K%>gDL4ROwEeAby(T_O{*@CSdg+T?K8Z8$?&S1}=*YV+wYj};*B+5~(2Pevdl56YlGM<|1iRA}37IQA=k z1(8kr4X7z8$FF+vb`0&>rLf8bkb*w7m(NL}pMPaF63`{3Pt zU#HKyvytoRn!d&df>%L{N$c&@?d@;0VJrF>OCcS+sRrshhAjLVbeQ@MFkAzeWx)@3 zu5HLdS|*YQ8C^X6q!7=9lrxb45TRBb27yGk^~5EyWuUReKG!mS!NWdu%LaKD0IpT&~M2(W<1V5TXJewefLyh&&-PR1OsmN5G|hJwt^mq^g$@Y+AhuGT8P_;7q8E zK~RK~hPIVxdV>gbbz6%BpXzwNxI+On2JKJ)&7&JK>|m>=I251 z^KMxE>XBjDMZ5_tY5+MJE3r^Ta;Ml5}lu|{eL6Oub*6j?@Fq2Ki*1aTsqK1@i3 zl}krnQVLn;F$U~{+u#;};VDi-dk64yYoonEOkBB@UaG4cHgzn{D{U#!aOojh$z4~N zhK~~*6TXuu&XGN@sreeh#>N?sIrtDl=z@!V_>d%eIgQs9AY9?oiw*L34?(xBG(5E( z>$gWl-%yS{cAkblgzfLIaSSQ{4ND!aScvUw`(|A|HtZO zIaaT4uTAM2bNV)?>gy2?%K09HrTHpp=~!_3?Q*aO{fA%fnj<7=-FG1PR_1@GX)9r!5geW$gW*f36FT;e3uqz+Z0G~d?dn19qn3% z+Gf(yzYH9uNYGKmM=lWK#UKwul0=+)IrX>$bokZ+Tt*r82?{~Uv5z^9>%u$5NkTk( z5PZk(G5aoDM(bwdGJJM za2kV)j3*s07DZ>^HUEjlhKQEMq|*lR+ra*zKIAEblnj5SQO}>;`bVL&n8}nbt|vsK zHp^YQ#BfZZY+KTJM50mS16lO^qtKqtnC_{b;b4sRJqRXRLP9F$_+%wZjVi};19z+K z_&TuA!(SM-!wP35Q|~j%SiSp<1yTcO_BIL;Lk*V)2kxoxkpxL1NllnxqA(nBL}Yr1@OEqU{oZbc@n&d%mzAv{ zEnI;u8^1?~iJ1?COl@x2Uy}plsdgM@Q2jA-?4sRRDt}uG_-jc-=+Or~z$Ow>Ie^)a zmBzacg4|#RlY0xsHG-+&{fE4DhhXx9Z3@3@NJ#pYLmeIgszGkH$**wfyGVVBAC3qR z9RLBLvb!Xpfe>j`v7SfiD}87P;ppb%duw!smP^H}-P6W!txvRh3%C*IX#F1c5Envt z+B2)Udd9t}boD2&1kHcchlwN&Q3vM0Cn5PjVTfX08dV%rS~3)6N<2ozw$d(I?L$Rg zyk$=r`-HY5ls>rYZKd!2C;X;RB-cNK2`CXfU9o9t_EY}&b}%Lq&o!gL>mZx(@hM{i zYs@d|5eC<5yB-8b> z-~qRL)@j;$Lj-yUcz@D;V ze@c693b3Qe?e?|%jgQ3PaE;dy1TgJ0u@67J>vw}u;~XytU1P`u$YqmVis!Qlec&~Y z!Z|(C30%P#3U23>h(8i+mxro-*u+wdHarVq z?U#nW^jD>Xiof8M-0ih^3RsApq7aq%9jPyji{WHKUUTmKnz7!49D!sEJ1jCKvcq2( zizOLt5Fin8<&6Y_L)L!9rC3m_w-e8O?f`m#cG$YeK?L4WC!?oad zJ8?|xGk(7ju|UYLn9I8TXe`HsPeKr4>!HC&wLU-u%@oywfE_CoBF>{}5uTKhCT@gy zy6w1-t1mxpjI>W3{0toene^2!{``TmJ~ZoZrIgnH3Vc^A0%NQG!)VOz`qdD52~5}{ zS;_DGnzk|L$_}kg_+SuQ)6D_%?FsiVk}U*qSg>OQKd5Np;737b9!I)v@t3!ncp}Ow z{<4o(Ursqu{37>1!Zc~dK{RJMNh7EHW4z)!`ZMUvlSV5S191eB_vJYw$&TY+a}pXZ z0=I0wip@K+Max?<+0_@_4P=z*txQ%sk;0DGM#%e3o|Ta|?i^`C5ic#!egDbVnC z!8P67*d$ORj9GX%%DUzTaGTiLTG*kdXugM8JX)#D544gY>8|`22)wJQKLYk>pr4hK zw(o}6bz`%D9Zf?OMUbe2^;E=pqMV_;OrqM70DcRI8|7Xp^oNME!-av6u%|^3mft4@ zEK-6x6L;)09$bcsLU-QoImj;csS>Yyf~A3UnO2exO$QN6_6A+lBBe>3&2ACMbQrX- zJ$aJq?rvhGP_;QV@|2+baX>CDZjD81f#}j~wUb?f2-rZDd%Itg5349^mC`wu*30R9 zS6iGZn^6b523pbDJhdf5**Oxu(Z+9&dp_>8AM-V&{`Tzyh#5NEPR!dxc1iYQhXAy| zN=eqUgr+J4w{VbN?htT80?`<6fjS3pJwr9n8o+Gqo?|#5av?z zFhTpe1X|FLpEQFif5eaXjWz=!KCmkwdld{7;CX@t)N?|*W<@=eSr8)nI5RJg2Ucv2;4?@iLB03C693@opx%4`7tq7T{dZY(B>X?JAc$V=AF}D<0Q$cu%YV7~hZY?u zb_gWXO)*&0jC^wdQAWNZC=$0;9FUI4{{N9sG;Gj26N&vlx_?)E|FBCU1?s1NsRC%* z8|1UQmx7!>K864C3d;HO7i9jMsWw3g&;pvUK`}p| zK+u0yE~-lZuiRg3{cpJpM%Dj}wtp_8*&>5tX#qV`_)p9=$e;zO0UrJ#^}pCKPIjJu zp=5a){}BO5<=Hb(?E#>q)HI;LT4HTb^4CiZz}&whFaJW+aipM*+0pu64bX<*e`~aW zYJj}Ezau!~(ik-Tq>w_OWl-t<*IFKQ9G3Gr$oGr{Iptt(F&5xCVZ3g(a`@+KuihEM~*I^b=mR*7j6mwA5{j=ZR zXa-pSx0?sb#mfbnou~I;{T3DoAZV zW&D$-_rnS>4iZx*EYx2a{L4Wxpy*$S+uwr!EbMOV{wGcl=4cP{ zi!-L}K(pgz9%y#tbNnwEUJy4R73Y9r$@qy4ib0`w`nQU=fE{^tH|0pnL$q%TO~sC;+H`#qZzFiffMb8pVC_z)Rpo?I>T@+J4( z##M;CExh{GfH}cl%NO$PK!U-s$cz&qQb|t|3LjFEn7-s$teBD)KWqO zIq1${`70o#%E(6?5bWI^Z0zz!-SUSuZKw`$hAn3L%TJN5t%5+*b`}z}T4H`o)VAR} ziZ^IpZ(2PD!N8tg32-#QiIy)=RT0QiEE5`0h{nrXWVoGSzH{RSncZQ97?yRqCs48H zsg};x2zT*m^zUDD?@;*EqYuZ;oJ9wXuv2ELRrv%(!=qcrfo7~V2X;Q4*%xz9@17rD za_U#Ll?5A#`BFowO3-4PG_N(8&!WAnI-^;{iQ;XHGqCi>5{H_h63GoT$@&BuQM@(B z123j=KPpzP)UUtBS0EiN7x;Tt#5cHQg zHGIR2G8qmR-QO**n(Z-A84i@Blx{v-Wx4mDZue7SYob&o!d}# zYjnX4=a%3BuGMgoIYQ*3XrUoQ(s!gFWZi^AlA^WCR?8}v*^%6 z`^S*Ptb0^^4;JqUsEED0d9bzthu<)bLxzx5H6>;2${Y}XWBcqaU?^mtNFF`-#t3r?{Pr$*I_Ty2aN))x zq2D|urYa416lV3m+HA?1<@U;n9Lh@Ts6>3(Qo02|~jF$;@RH`AS?allrxz+e(APaOJs1 zIa%SYR~&f7NW+>i=j0$mE_c*=u6eUcg(i`M5dAv7%{|(_%P_i@RrPZ$JiyprBX$L- zN-gLH8kB#FvuieK3*e}vZzHX%;J{I0%{cP>Xg4s(v3EHPy+Bs8&Pd(`PnnT^Kn347 zO;&XQ8qtXn65^epBWyY)Jgm8V+x$+&z;Ee5>oT^%E`TmWO#5c?gnmTPQf$PG>rj^V zLr%YT30V5T!;%xAeDFo9=XHw65arn%S-Wz$9HJ{$(#|S7db&xLaxq0c>VXEGvt1@l z5xmk=$Hvj}z%FT8Y%3b-p#l4of2dJ*t?n^3&^ZUeX35Ep}Bz)I6rYVsM@+{t%#ajm{SSU?csMW9JnEb79Mbiwv#gMHtbVW&Qo3*Utjr#_9R+vo{uC9YZ=qCeD#3(hJ? z^aJ3+VU=lxTvVv7oMok{qI+YtNkju9VPc$xP~vlJ0vR_x{O7#Uff%9zPQa;QprbF7 zhmDO;-uLkr?=iW~<@29>qD`4S(^y`88uoCwhMurhzoZ!8Pz}D&NE)`lqx$VI)U~xb zrHOir=^D1kk+#!kEj1rS&NQtU7=Zq56_-cAI~aCkuL|BpMqv980xD!wg>pfE3}RJA zas+|X$PQ+aA@YcaMRHRJFGGa_a}W2#)6-Z(M4m3ODsT#2IU&(BhvCF1P|>PmCws$$SZ*Mbi-$`e_4s5N^}I5f_|d7t*MLSu|^OMMRdo^n|AuUPPj@HfadS zgeKNmT3TU~Zp@JS#sS1a`kVUJuI!(^vrDT67-k>W#f~!z_70=%L>KIUS&~PqcA|q^ zl5l;Ri-F}H+HkYh$PQH4dWxuh`sHbw7{#R8`{@F)YGn*{o_4{l9>sqGH@{z;jJ{=6 zg$^P-pXW=%7`VEJapv23C>6RVapoHWTA{Z2ZF`;v`dVJtb3dQY`J#27rZyFh*usuq z>Jal41Qbj%0e6fe{pNlGRf?U4eD~|MDmBIMsGkYBpCUcPFhY;DD$~R;ijTE;^hYgH zEA*MPU=>j-^w)LI2ew(s(K=7nS&V0jZbJ578GNHZ-Kd4t1_X&OVA_@$%}79HIm6xg z3%>grxHRkZWnvSCq>a|AE~%IkR(5HXr9|rlEf_EiXwH9qSkRkOH@)Z215SClvJ`s@xWj+XhHxqKv*J?0WJGawFVXKg3U&i!LV|gHY|4qCu_Xe{!-My{PSSdiLW!jY@#2cL@AkOjl31BS?#HC+114wi?T+0gaRPz!0-ikp z!Ox5b)}=X9bZaetQUfEB5T83a4A+M(l4BkUV(u;;d0jo?b1!sKxgxHis_km%3RPN> zVzwa$+-m5IR2mT>)NhgvUB(vsNTAuWT$Pt)q}i%Kw!XwP{Ir#6+psuh|G7z5nm7vB z?*~|U0?YEz!WBo0=Fxf-!@ZDC4H8|hBNj6qytgww`9o0m_0vzw3xO?C*~NxPK86F| z)-;nI!jukbUSt@57<#-6xSv{I`SO^x9u%mMOQe>DpSoW)-{1&c z0lDDuEk7Q{Tg!~XQ1}%}PlvDUh5d`)Ug@6;Y-9n)@Axdz_2Ki%x!`~Q>+N8v|AzdEwgJ+^)Y*G6>tn6ec9z)cuF zsvvb)E~jxHx*}5}N-&0Ll=_A`o~Z>7s;q zPn#w(E%^mZ$yg#2y8MBx3U5X{1MpIHu})Vvp!a(ksgYdyH<63dT)MnrcM*s zR8@1jj*CagCtE5PMRa>(h+TOtq^Jp<&ufeDUy8YYlloX&k^++na+5lhQ_!L?tUvKn z67RxU$_u8uX=TisdJQC5p#;_5qj)!Xi5hk6K5ZQ0 zC8jSz>Qidvu>c?FDj96=eMjm_EP1w9oi#Ml_lu`Xiwcz-X^ij_8aLv}V4xmH4)*OR zq@#OBy&It^1`iPhSN)QL-B+ud4U2KJ?N5+!jsnSiUG8PhyO*^C?R^=Bx`R6jf9h4Wb&uFJfXIYSFQah&mV*p1RKef{E@Z8`fNM4EdwBQ{` z+vP=f9e6ldJ(YiTlAoLi6-hJqJN`ZLrp^}r%Wl|JA?3|6Qe(A@hYOdqGVHS*FSC~0 zriKgeZ1d0ZAX-eT!A~$f7N^^Rn9>VAV#sxLzs7EYv->o@E*ylBW&D2@XXZ;NUh(yCTgo&+ENX>P-(|KGv!*)e}r6k z(@*|(EFKr5m~nCQ8t^o{Q+uegt2VdKp9B1Tu=@iz$|;H|0B3RGNpeQ|2oW(l@Yc$F zWBKiZFlHSs&-Q^q^Kyy!xX03weqZ(2e_;ou43rpGRcw6m;|? z2i`nK3)r$zG8%PnII%y-B)f>g!Av{|=OOu!ZyW83f&!PlHTir1ZH zTbbPj*2ZE+%~lYONPLW+4^}{9NejYbu+RZ^bI!ntcG`=fngITOee4nphzJH29`3K4 z(p3PqDZp`2C=l2@7T_g4sgCb={>$$>=hND^HYK0yCXVau*~9N+b>|9gBuGG`=u@Y< z!=kYh>~Z2_SZT-U)4Y*=Y=n(F!ANE+f(K~iet&`QF7MoRO+BW|Ge(ml2UD;HyzDM; zym-Y^IhTg%S%VT$kYt}lhIenfH)5X-?7Nlq*VX0PNK|;r0Vh?_H}2$2J# z){|m;49NkvtLXJzi1&#T5@s5KCAK|0KDo;{-nLX%QWkdkA-x0P6TpPcxnV&oBu0@J zmKvQiPYZ}RN?FIx762v+*;8wafh-A88%yRcUC0LXhZ(H$^so|(5LgX+@xb?=u#yOU zW{;cprew`;MoWqYx{&I~501heu3ZlqXpMoiT@ev<|r&mXRBuK7PN z@)ds@&?CptEz5Lb@Bx}7MQS3{4hl}m4is80t7Usqir+QzE!16QT&hT1jkF=T2f-3n z985aq-_}aJB`zZ2!Y)b+7kfa4MUWmxG&wl}A9@MN98)ZSgxaPSWdM``;2LN~^%i6yHDx>g zI3Dp#k><%9KuWPnm<0z}ur_Ab$>JI1G~DyHw}`E5S%uI*nI<`f&_n`Xf9l|odp$~O z@t!R`Dj0z;4-s+IQq+=c_!Z(T(JQi8_TaTa-ym^to&j z&D`|)Y*H{hOiu~B#ClsCRLiuBY zlmS~u-!`x;;og8sOxLs0Hp9|W_>u$e&p}vzeLOoFi>u1%Byjm+{u_adP^@omAVgle=vQ-oEbG zMr0?N{E8a15#N)G9m3`SZLTcuf_Hu3A*`t$8QzCHlZ#!*75eG*Bbymmk4;i{D%>H? z753fLp*6A$PLOPw9}R8J3u3ExRE3cG;v|U2dKY*Lml3SJwib+3i0)7mlDYcXCakW` zK=GAluP;f5IUSmN;Iufs*{4P*{FAtTeG%(xlTQ-wNZDn~A^TN??KaGeriE~W16NX0 z^S$5G*OB39MWck|FN$W(i-5I~D#gzvk?U4Xx7d2%?h zqFF^OSCg6AJjFi5SKF(Nr)!RkBlSB=GMIt(9`gv(9g3^DIi=bSjh3t600Uk;Oxraa z_-zf9feE9_Q_1`y?2CtWi!)S*2LhK>cE`h5xsITk+53scM!cg|T@Hd%sd~<;K|+tV zX1qD|5=DNs!9OXij!Q|b_o1OKyYUvQbm&djzhjkPUUNKeeNhceu4zo*FRgW%YUqI5 zy6F&yn$*vxeek_ECD==;uTRMkTc8K~;sixs*lQzN>LMdrx)@;|ZpR}*(Bz3n=7jbh zA@&t&YsMgMJvSU)p6cbOWTXm?)QAZisXsbuh~-Db05F8wD@)LI$)vY~x*GMFQzFN- zHcQa}?Ot;0bBb1=5E|b`anxptizd`g=tUGOrfuQfQxrcJTtOs9H%*^Parq{O3+*g_ zElrV1ql@J-h8XQ?qt*xqp^t07i_I>5U3nmBqpkn4#$`wL{nSEg_*|w1;rs5ps*tuT%qL|0o z#}+Zo+xpaLR9)HI7)ge`BO}#?R#b<#H(gf`bA5JkWyWa z_q5n!EW|~#xHC+!z4=F*;bcd(DKsHbjayF~iY{!7r#FFQMx{MLP%j^x`LVBbk26f) z*Vb?}AgMPuHQ9Fi;G1hsg0A*XCE+@bb``TStJojX;yk-gsCXfXt;;=tufd`?gD@=` zo@tS&4kzcgMM>rc&@q@2_;vXzE)rpWHm<*7P|*zs7J;!hM?YS!h4MriYOs|a(P^Fg zW}T@P=@W`OGZHj~;rB=qL>>KWGlWX|^uPWDz-Uvz|7)M8EsjAZ&GrbuQPqV|2i#WnpHuL

cIR;Dmvts$zu;C&MO-bGI6r9)LadT3rgP+%6K84P@a zlZrr)IAJ=a+aWoe-PS`Dws1BiX)P&Z^hj1xa_2UQ2s`2aF#0n!Wjo66M<)PsW!o(% zXz{1u=BGljN#l+YqY=0uLh5qJ77U=mp|tar8qpy`b7QaD80){uUCGC{uxf(v%ZCdotdA@NiFKJGsO&{fjFlm+u`m zuLV}v7oiW%!)q8orI9Kz<5prIY`(8OQ2N)L4)|mkrULMQT6+ndZ`m1iC#Lb|300t! zrJ2dnGmv5@MDm%^<5W!eX;#~HYrz^!@>IkkZfp3GYwTh+h9P!_)Us#xHq902Me}_y zGkprEtIN~6=aCg;ZdigR>Ql{}hkGV`Z6#vBFaE$~4y^W#1F7TZ^dXsoDNQvG=&j!= z5B|*8?SsC+A1?+ao-vl6v(D%VZ0^-4y>(wu~50r=Q{7?Z2lm%qIx-xSsb57Jkl_4i#WbIQ;VaNc*?(WP|V5 z1SrzBnYk~NqIhw{OB85wMZUtIM_y+7Q;*w|5t|#pb-^8TgLf;TZUlcJIas0}LnRu) zrqP;qk#d^2Z8}ual^VMOlR9WH%>0ksxqq(G?`pMVWZuMvD}xx)ru{pTG=sO{X|jB<{}4Z1*&# zdEtTArFGW$vBBNomr>zGm&MFBSTbT8=MR!0sy#|>LbsgGIzVS=Sd^(bH&am*p_)H_ zN+%Sh+Bdm2aPDK%9q~6ir7Mp+rFrKLt-}wq{Vx2coE39T7V{01atk>6a>s_~B|3l> z_vwC0`nOD%aWSTC{&FM!_h95>%Uk$s9Cx4wPZ3V?&-~)&awFyU;K`+#kCz|)+m|2i zeX0|n@B}b0a4`D6mw@^~fPr~ud(7*9E)>Ou*|LAd>VO+H`bv-3U;ZgYs>DXBw=lAJ zG`xtMHI?J>1gCx7Vy$Fzg7Ri^FoorF_&~Qk48iY#)O_1Q{{RnJ$&_fPZ@tM$j6%SD zTbFg`biJnKaqJs5aH_Jn6n2q4qi~O1b7&X<*ahG`-sC6vPVUYH86P+z0v#`PBwCNr z+4NoSk5@DB%wAnl_Yj2NVl`MlqdE_5&uwgf8_3BGzx5B9l<4qfGB4#yw&@Aux#0ca z6DB(n6k=n0yRFLQKCKHl`~;m&^=Squ_Pk4DS6ziz9hEX*$WB_c0+&yR9cUztG|nC1 zar;SVx9X7J0bNFLy;%%0JoC(*O*Dc7BoztbC{s0 z50CI=dno$DmCNaTvPR>O!Eal%$R0ag$7+^W&|>R2rxH4<}oE) zpq46+Og-8s%r}dsMkG!|Rc?0|XF1XGlqXo#t9=)pn?2WBDE_Y{=VPQ$ICBiMYSGVz+B2%E5cgtgTGs=G(9rtcp-1^5NIm*MDe&8)=o9|aHV4G&jkPs$|alg>W9ey}FA)$oh{UR9O&JTz)U#~EI@BBH}dDfUpKl<_0!FQ3a1Va~v zc=zA!JO5P(cJO}`B1u_(fE9Ic>gYQz(AVR;#cZ)6HYOUH5N$9l%a>29k_;VZ^%L{~ zU*>{8urnpt_h3I0U!W;RNKv;Yj&NP$LmA6=Q`5_)R868A1x8ObHaB_baoUIjXccK^ z(!-0eq!$XRTbZdkOrs4O1e$$JC~+tBfyQZy7)+0-*|BW$(z7Jmc7;Y?L5Vq3fSQX1 zn0d>7^%0``y6NDVFOsWag}bgytxz&t3u0hM`WO%V&ZE;OGPi zYXA~Mzp$xo@4&9OoQqwZJ%*9~UL|^Mi4X2N^>U_1Hu2kzH$e3V_6@8z$&iRR;*l}! z9~#U$+-Occ#)v}Ri8JrY5~ekL>bOhvk_BXQ50gjY1(an6m(MzyMH)g?DybnsGuQKW@a29A&m|`p6891wGVXR58&|u`p#OD8ogh zh)`8bM0sA;m2>z;ohDcK4ltQzH+hsFxz5x{YiBCIs05O;&~}oFh;LWY`eDtpILL`d zn%^l`=(OWls)pN<%XMaprr}^!UD^>wb<(S-LSss~R^29V0P9;E5?1OCUBFDmk}1K! zPrDSpY~}@*3ZEX6mpsia4S$c;>Wvb9yL!ZwRGrzotKlDd=<6edh?ImIkAA)_dgv-$ zN!aKkNF;7^d%qXI4}|U62~xf>{q&gOrz3vd1+M|#!y4s-sX7p&D(b_9sA)8$TG%Hm zT~}5gIOp+M6S&u#drx^ELNe;QniFlPlHQR3I6c9=2q-ln12(5-?CPt`8Or^5FnSl} zTxb2B;5KEDRPt_flJ&5Xk@1^tLaY-Mlt`)Wn3s!kG^3&~Mo}Y7Zox)MGkgA^C3(+J z7FK}vWff-s*C5Uk{+#b5syRyo=M9+ELe+KCF27{P9Z*lk@JWH+6#!-Z>&cDfmz8br zCwZHkiNuprNkbylI(L-`)O-D8s0JVZhNZRq^1>yKTzsBj>My?THIIn%*GzYBx>gPj zzp7JW=k`2e+Cu&mJDDFMCcPj~Da$MKub3(AKQ44q@2c)_n(3x=6`>WP+!OR^_W2nJ zSgUA+>j4P|YYu7I;%)m-)5FVlGw!lO`X?IvuH#JyxZKT2IJpEQ158QSmqZj&rypc0 z&!}Y&$FO9HD$x;!=J-kS^P$qS zg*y&8kgU|-m_Q8F=9c&x%`I24st2P*Vd44Hg9#X?z(Y|~-niRbZ!kZPv!pvA|B{Nu zG2*g=*STFylRhy`bHsiUzm>W`xUK3sdJAQywSBV&z6rZCMW1aPap z-xNZC#%n4IbQQuHuN$Ij;wyj8vWN&uas1vnM6Y_fKCYO(Vko=nI4a|!MUfXi?SiRm z{0uO<@w|4SV%u3{_`#n|LoWGPa-_oM*a_Ad54WjHaoaR!Xa<+@RDN!=sO7-&IIhD& z)TwPBwgrqbXL`ddge+Fp7>ucurvN!`kdcGxZ1u;iBUE|3K};-r_OA*Jgg6(48OFlC zNc&wTqpU@-O_cMFo8@9Fi_bCDUl`)!_i=y>7vjZld?L_U1$11w8FxRD{VK1hs|77& zFg6`<`NPTGpQk9odkE^0?A*^=_!lzfg+lJtFdO$%9kSunfO-fs`36zZ)`zh2V*82$ z1?n5|7*zUpi(kq3JG(I_+xf$MN?JC%>&hQ6%9JBMc+-CJ9*M+4iEt&yYVNyHjIske zal)X?Mbi?$2jSnC;ZhiBQVk3P7-TUaN~wB6IcH@*V@h%|MmIj3XQR-i{Zj3CJ=d}N zCZ-}%U)t`AbABQDON|Ps`c8rWddd#I6~hP_yRa$!QZ{xe!Jj6lI)F(xJ^2Hm+v0sigZR$b{|Gs#OHKpR-`*TOUW z%fctr+4ZwxN?AWK1TSe#&ZRhfA{JM@X(wx0TIyTBq`6!_zh6uH zy7Iq-8MtvM#ml!kL2PP&=6@lLN8t_^!x>4d4P&3iyZi^7We>aDaA!h8cJ z=8dQwN?+4ibV@!*_WL>mPsabuD!75rq9Cdj2iQs=vZ31bsmM z;x94eU{=(yZI3VT4^|_;+*j2m&CK-l5^YF(H$=o^+j|$jWL?l`YU-uGcz7%9R?~}% z9k7Z5FVRK)uw}ntAX58mS`knsnpuE5fWJl6VVp+dTXF78Mn-c23;hk!8mQ8-F>ZM& zQ5zx11ivJ!a0J%fNm!JWOTJu4efgxoZHk9)3 zXr!DUIT+vi-KpdR6zw(CH6Ioy9#11N4wlibpc6j2Uthxnp$}4SwmU>cRGxy~^0pBk z?Olv=rZ$s4#9mRl{S;^HHD2s?`kFj0WPNy#O4{)8J17f$<8w-?S%8K>;QP|Y^Ys+) zWB=z*6!TbJbN3QgH*(81_E5uLeCqJ^TDO}Ym+L^_$Ia_h-c{Dc$NMnwo5-8a&zG2s z_bV^QfRBSs|Bsivlc}4DjQ7AFE^&H&m>K4oO42M!)}%|W6Lew4a@W_pANN;NLkgKq zuuJ0DG*axR@-y4kPb*f$z}_DrnvXw!zC0K&ZN5itzD``cpFMm$7p**z+>FTg% zUkiABn-Y0jRwj9QBYD5vXZh)^Z|eJw9)ML_zTH;2VG51gkr|Nay}BR?{JoGaR}Nu3 zqV6EH>Tdb8C9XN!r*3~~{C@0p*7jYZZ80j3rkcs`;j)H1j=70z1Q?`5T29kNO$k23yWVF33Vk%Z zd%o(NQC$7x!FCSNyL?vd1>SvX9cT#;z2vGb{(gcvkfdQ3|8@-)nu#ja)ltYUaU7ts zB@*yD1hKB0SacZ%Ju%^WLJ_2@)iF!fdgqKiY^gRx56`#(+V2 zll8S9suX`aN(0hhmpwIrf8Omu+ydKrv;jjo<01!z*-{ox?xkM6ak^G_KOI}{cGj-Pwo&1RynBe-Cee;7=dVYnHYX@~c6b zE!oEOBmE5jC2V~`f|#EJPQ~GG6qKwsG46#^ubRRJsRhhO+~W+!(4gasXaMhE<(wCC z=*}D!HF2_X?3*}QwX&n#qy>k3e=H-nbM^q zm8N|3mJY5(ki-(4AH;VrXji*t_PT_)s^s09+nzlpjWoSYp9(_t49e$I9m`|t8OGV< zr@$nbb_U*A?zy$q!gLQG$_a+3dS{{hf>6X{W>+&1?;?eeF_y;B5S3jKILpzGwoGe` z$4=bTRoT-0!ugy6Z4O-};);3+i}*c|5^6})rY7j9Krm~#JJ1-=q4Q7QB8DL3QkcqIsEvZ1bSaogf#Byn?U7`iV5P z4WdF~?4~$Yx{uB%rGy6xMQFTh5nSH#D8GQgi6#SK(113!LN9rSMNzomgrq9{h(m%| z@p(6=y_QG|?8H<^iRVF-z!3(oL3~PDEMmZb6AgKQLJgIKQ(jThrj>OA>5z9yj{h)? zl*Iujf(l<;R4VG#I(#sP+`;UjO;G2ZWCK`jm3sq%kZ1#Peh+I?dS+!Ic&{#bC?wF+ zVr(TihXl-G!FX@zkBm{!*)&9zD3n=KO;~kVVx78GFqSofX8@|sw+FkUjNdw1Vbqe0 z&XPu=)b5#IT!qBg?g_0DtaE8UbEN2!=8xgngNYbWmZo;)WcA`Mk=O6t8Ya>C%DFDe zUvqPOgLWS9MvH?^QlTdXQmIISo4NqGurJyhRjt)*F5DPLn6bs9b)8yWXSkyoGeUc2 zNq0=aS?f}!Lh>zf(SbU-}R zm9_x+uDf4g@*CaxWgsd7kAo0SFng$)p??pC8}o<)RY#kiabaIu4xZfaZScX+gf`KA zaN+1OmzBw3DSbC7F#~;;UxwsO&XWo*5wNN1ZDN#r57TFuC_zeP?itB)t%Jb_^Q*?nzpn2H zh47U`n2AIhkt(i1iNy5s?g`xQXWQU;HR*$H$SK!0KY`bP!R#1gt3Oz96{u_my<)TZ zD}sVUkR7y?Cs|NUA3?+%cY}S3Hm&2{tu2zLb@?wq6+!^3pYfxyi#?Ex0zzD&3CTrP z+ss9dE4PU43^j^k4oQCZROUjcl`buRRUBFdLUD#o8<`^QDTt()NP9>bIig{}Sc%my zMxBW&6)#^0ES76K15UPdz70jV$aU3}+ey?8CgYjpdu?E%KqfxRt@HgTO35*yHC1m zMolYy3|7o%6)nstJQvakNtral)BCktW+InJ5WWDlmZ&1XN|?2qRpydJR&NzNSrlb9 z&LP{9{q_gK)+t~pY9Q6kU4mm^{F>?x#inOjkwGak(*u(FA}u0{IVbOFw0q=`KcyD=rCjIg;mlvQ{l*~EK9q*BlZMv~^F z__JcL;%W?kBGH(^_D>7&{6|$9Z^^Jd1EKc4QqrYi?t$Vh?L(e^Vv0HSIgT)M zB*=?5$O*|ZTCK~W8eHOp+|wx|i44w=RJ2driQmBF7=EB+)l$gPK>ssTh22k~voQ&dw0^iu_t7 zx*J}?ehZBJj56b-ZD`3apyb@7_+uVh_IL%wBSMBQ$l5qD1YSMG+tu?7UeaPtp53rM zBqofP_9j-Qy}t+w$y?ovjqHpx1{HqLy&-2`YYZoX!S1?zJk-~9@>qG zZ`?rQg^YMuSa}HIFx^^Q#u|D;K9(Kj*Rs<`v~-ovHm)ctIj9-T$PmPX&=bdJr8HIP zloZmhD=6d03H8Cbza_=NVKd>BjS>^2^x08t;?4V&St`R^(&a59WZG(f_EJnj?#vUD z2!n;gbXSZ*A{9$nU_+U|)q*6T#-dFFYXC%GL1kN_@dh7&e^ix81_*mPd(@2Qq1ByU z%7#oUzC{?z4rgZ+DMUhpV`>@~v*uk<{XpyF@XlR;)X2_4i`u zuMTj8^dP@)MG1(i`3;4E2aHu@VsYY~(|k+H7xm3Hw(Ut1!_(s@sNI!;_wM48)CV#t zm4i<)YDHmn8D2QEmLB(~Ccp8!N-oznfXVGj)6ZkiTNlx2!2|kYxoiSMN|qC#*EABG zP+yVJ&r%#ICc_juHB}eZOgKRQrrb3M#Q^=Dq}m^6qR#LT?BL#zsKJg+!McpozIGIAijSNd+M%t&+NAguuZQaD!@!6SB4G6BkEcQafupWda<05@#SotEU}yj>?H)H#A6kpCy%P&F`armPx6-U4rx+I=(0ca_rHwE`h^Pqos+VQq6Ya_hocHIqUlP zsTJM8>F$~H=~&Z72rIh--eAdn{OaRtmHPqVq+F&%A|U`+sNZ&!hdVum%=c=E23;YQ ziDeX&bI$pt-*XXmwM0_nBhTKZVlhPaida>@^gV1wQ5g$(N6zE-Rk@dibWy$RMYDgT za&Q}6k1PPi_A@OiYEAA=TleuWnhS?-j>J}7D49{?K{x>jB_M>e@PqC>ShT>oK(Ocv z%InbNpUZ^b6*l0#@D!_GSTfuT;|yLfB#qBqdREqHbeAG)tesQ%{m@>7S6ZquYH zwPx#)F7xT6Gd$;$qG#@`RNY%*rGTn&a(SN?4 zDZ6#PIs5uOD$!uF)UN^GC9n>)_La;L+K4fcLl+K#m z@uj2*dqSiJ_(Ii7R@!&yi)}FM#MyZApE`-DO{7>M$`FYp zrr^6r+;H3a2$|2wY0-kH;LyPn4XP+}Pi!`V6)>~h6oO3NEIOk$L7QBYohIam!((8* zpL$V8m6|DZU05?zgwQ#F{>xzxUjYdDo?g(RERmrf=plIryeoQMr{PG2<-x>S_@;;N z%Uia+Ulc;2zj;!rY~q83(WCjygsBy*klp5ZDDee~o#UUCx_$}1=iCw^?FFMK<4LLw z6-40Vm#lzd7@Qlv#hvJj$^X+55s3oH9T9oUrE@VoNbG(dbS4c4)Jnv%;nB&zl&F|Z zavxeXz6Fhn0?Unm3{NWXX5+r2FYFRM?% zJ93vQ_5`u^GYdwtyHZQ=#l9uI&jmlcLSgsZ8$!>vU_WAMoiw$okT`N6#{WS>AXV}Y zrb2p3#Mel7)=z9gE(&A$mT)?hcEfWiX)GP$Q+Gy>i5^`4M}4{iGS25ki`l9EF{G zgF40MtY#ES9Y#D%mSX1Bq!&4<3~&KD6J?~r7h2AJM8S9yVizHUgpN#`=5JkzD^hfA zMTzHc33VK2244{=p<`kI^-2n@Es94--na}vyj(`H2Q}gVZrBP=YnwAyqoIYF#cPTu zmiV1s)p{`u%7@cr?f?an_IbTC0;j@g_jaP#tkFo?FkhD2&E#%1Dbt?!_DYBexU5QP zh0H_g=nnBNeNm4u6d2HOJaS6y#WZ6d_fio7dRbYE?bZ(afR!*j3LP~!(LAfcHoK7R z`tjd?s^m!yFk}!s6GzaZ&-zfjn{+V%az~!w2l{p2bas4Wd-}P2;yGwPQzP{RW43fn zu-KYDmrO(pKi7_F2&|FxO2{Ap^T86=?pyGc5j+D27M)$TqW~hP4TJF{Qhma!*~)vz zNvP44Em*63Ob*Ylrr;VaKgnpv$B?_yNLLJ4>z(B4Fo=-y%PV7>KkI{}9s&uM>SV#d zTfhw!BG0FLfPC%Rpw+BOet;;d_X3JBh0YQN)_aaAWUWNE znVW~2s8~ba7>mC{ci8-%D)+mB*deS+X_l!JtlP1v9_<$Jk%ZvAUEVkm6anJI1Uq)> zf;+E7VcPi4{89bpT)sFPtupk^qbTIJ9Ad}_Z|18Fc?YZ)&+_cA=04Gc-!r%OXtgx& z^?PXY`#+YxF+8&7c{_G8v6G4IjjfHd;l|c(Y-8eVY}>Z2jcwbuHhS~?-`|J18u#fr zr{`4HbXVV9MKYSs1kzBPop1+L$+c*M;Wu*ti7MgU?U^WoG{fKR&pp3_)trl>8DP4I z3F*19nd>f0MiMu+K65Evy7ba;dKFnd6`4e#o>qWIEEhfurOtACa$7vLI2j#(N z2-7RWk5YPX!=z(-YJfR7^v{!%1zXXjPc2H7L>mGYt@OQsY3Z{e7zH4CPacNh?Nc0V zgXZvMVFn*0iS*vG0D7Z(dK1gh8y4n!^Ij(0e28<~I8owZ8L463&Yy$+G-Z+eDO!H= z<&V=-y)7rq#nrTSafp>*+fzRCCyV5?%WsBf*EyAq z%}!BgvKkJ=tZ*NDcJNH%S2dCDQPzvcPl=~J`Oo+=DzX=8 z5!n&uHXVRp0rI1R0F-ek$`e?w^%K?sN#7a_Bp1G!ZYGX)XB|@=YSbWd7N8SaYk(^| zUn8U41?dT$_n~0aM^3kLF9S^%Q|UQL-R;;L?Q^FiVi*?-qO+BSzLv-6_2OQ`1GOIF zx3$ud@i>|wu68qX@ZJbA;b&n=KrL8NuYul*QaF3FSBv~hanmWKAHwtOZzNEz zgq#Kf6*Ijjo|_yqc*5WBE4XaXs5=g4qg#zI#syu0zU>NwRkEw0z%MZ9F=?;^7Ai5; zmFe+%(3gH0hr87!S}g|#ip;G2_Oq&dT=p%Z)Kb7}AXonW(g4FJrD|LFfzu8K)({Fn z9i0mBPAB3^3s9d=X`AflulP?x&G&HeH-`ScVAnzL)}l%Itvf-f+bNGYbadCgiR!oUfvx<bD|6Aep6#GX9_?R2Dp$j>xqK%TS0r-;146SQG<+; zg_UnpIO-*}p6#ZZ&Fi@k!`4|STK?V3=A@9Q_NrVc8>$i9+HOQ}<||C;kzD;CIog2)VDd>k6)2Edsknfy0uUZ-y(N`@8eIqle z-2JuWf-RR$>{^$4*pNQ=tOW-7a~f2%;X+OZ4m&K%gIydXVV&F1a&_hNB_9Fkt><(N z`VM!u+Y(^lb^}JX*|n|Y#d0kJk<$+Dz^~jYFyKM>xj%tOnk024l?vZu5YO^ zKtI}XbfsP0j^)9uTcCQEtm~37A1{km--4_K$ukCC>1DHO9vD6)}))I{8?X z9XeV|)D7nWHI5ah|4j(X#E$h(c^vX2rW4lmjWMP8F%KnvV%6)(?F$W_?LewdV&UJ( zGqzHi41Z)&jz*di%mi-!2zqU;R6yMPMxkNw^nqg;lZzByGBH%o0oS`0-d-eYLg`K( z;oPOU+?Z0IxIaKpMO+g2Tg7)ZKioP-9#7Z-h6h(fYtO#RY0KHF#@TLQ!%|-leeO@C z0s#lx`Gw^CvW8I&xc$19u9~}D)3yL{55&V7Q&X~blUk3Qa)|JW3ux}yZ3p34#NS`z zPsq<9R_1eFP6I#tYyw0tzp2+9sbY?wheMVx=)aAk$T3kD-1%3&?sTnwR@O;M&zhpnu0>zR9FPf6;8;#Ba{KjlM)|l9;zs+!R=%P zgKHqSrYCq#-z{$%3`81=P3F^9+I(bGWv<=s6<;|l zfN8gGd@%sDNlY-OZ*sK)rB|!Qh_J=~?jb8p7(3fmZ`PGb)hZ_xp)v%KdRfx_e1rsk zP4j=zn1L@|iSmSyN`zQ@HPIgNmdT$t1YaG86fexSvO6ptfKU+6D`Ro68{jNHQfN@} ze058DPxjcYwbYqIR_E|yIibsHggHi$HT5IekqE%zt+CP7ubn*mNjv7&mcx@K>+ItR z0$3O7(p|2@U>nS;cK=*h>pT`oop(icNySs~2TrB5xYK57l2mXt0>(D|q?Ut3U5+MZ z^kn`(rN$IgfDnA$gt9L+i#zs4iFr-9S|2E}Mb95$1Yq^7R_!&DpnjRE8JI#uR~=y_ z$m0-rBQI`xIgYSfbx0~qZZ;y!kUTCT*5yZ}d7(cT3;lf8#Bas0_A}+P-Fe{u_7lCO z2kK`dViLHJZ`imIbJ;zIcbWHchkI}rdSW>Y{w>%+5ok9LmLxn$NlS9rkA)9t&8hx-@!$iOc!hQ)J>xcHH($VD(6EBf2zGEl>vYl$AO}w5o2wqN)T1tS-`4VE`pb1L6zkkfhUwO zY;)K)x3uqPf#U&w45&kqJKtbFW5iaEi?oWE2QgZ6?niw6Eec7g`P#=4h=nkNf1X1F z7mWp>%NSP`FAj$+a{7L4w4^7(U0CUN@Q97%gT1EZ8`evPu_lNw4x0Fj6vJxrYS(B( z{&})jR z9;m~=KX6kk4S#S;8W|ZV?FezXK<^xHs9gqzUSLP-#(bP!kS5S#ag8EK{C z{VwpxgZ+GaQ1AY?@CoKwp+Eco`5+)L!T$&$NdL!qj!aOnBxFSznA%i`^;C+nTB&oS zM@~qZC01UQC0#b=DM&y*N4Y;!=UWps+(Qk%0dD&GGsP;q8noBDuSK9#2rPC}G7>h6$DStE7%e=EX{jYu=%N)+hzu2PtRW{k`X0W5EDg5e}Vm!|=3&5u4^G{9d6kZ7X~9i{g56-SEY zt2pbcU`#~0O@yS4Go6$LOr`DF3elbymwWDQoxV~kaM_caQ>vR0$7yx@K{>Iw(uRJ@ zDo~EFjipX~J3dQdu3ODJ5W+=Zq#^F?^78554NFDDEKoffe*YTWG&&LFoVh9E>%R;q+R31T@$jRv zDVd2|Ed}JTk4F`Ou)b^;h|wkZqU1^HTX@ttz@_$$OMu%SHuV25AYj7($%pCxs2eN%3Re^40l34>NRai2cWEFk+9GaIp!al(;*A{T`@1lFB zAmpO@;tWXeg2B`vPHn7}AAg{W#P>VoI|uYB$3Y;SI^_v0;%QMO6RC%~F8+8q~^ z8l^?|J0ArFF=mflit!ogHyiXCu}>3-|CZ?E$+D$t8yw9P-t{L+qVaG>I+(j6NfC4% z-oNoyG$m;?)7N`;pLOq-!(RRD%%@_4mQA0Hcb^dEA1)+S>u{z&WVxyiYs!6$h%vFu zEDk+;U=}SQ%PV-GOJnaIcWQNkAVFJ@4l|}-eL3pxWcTY zXp}c|L>u4Xqx9#z)7&JU!oykL1n59JQA1Y@J z&{{O4gx2O7VG(JTg}HJ!5$3RSQ<6;abwP%{n0CuR%pMn zMTHW!dTij`KBGqoJ(XOk0ulICGeKE0XA1Ms1%M?qWuu%56omU z9r+>lzrX)%A^Wm>;kCl0IDpH8&})i#TAuFMW)IHZ4;)^gflOA9|C79~|0K^6?mw!U ze5!WSIxmV?cYp6aI?%(8$@OFJcTSizsZeZ`m2r9;+~@)5Et>hQ{`o_wGo!nGp`q`w zVRo`%^s=F;)12tcT=$M+;@9Pz)!j3vcWQK0`wN_VN_1TD3rNa6H9E=m4(-lZ(87mN zVj_E>#9cc=Ya2_FaouR1j6s5N)@VNOD`^@i8K}i=Y)Q(;o^qJaW0Yh>>r;|#CTv6+ zQyOa^Y-IRKta&Wl86+927fKH5@f)qmmC8*yEQ0U|8WFy-4fu_8Ux_t`g>!{OWz~gJ z`7h6TP?>e~s$P?%b^C?Vng&UJ)7)dpMV)|!=NfUNw^j6Nmy3k85MO@N+GI(l>96^y zHM~X)7l{i4fv8nlzI>OaOeMadFUZn$ezKm=qWB_eRo6xGqHRN|-OzR3W|gQh{6NUU zb&aUe^E_&`^HJhL&@P^Ho>aspR=pCU#jX=`^;T-)tmqe5#PV zXnZJns3UH4Ia4~TA#VKfm3);p7xNdF*Yg+GH^`TkH!bh+Up>8{XTuy2sW`69ZF84zCFjWuR_URHy6 zK0Y40r_5W4Jz5I@RrgyDURwz|ldc9-|F|Z7-UnIVCo}GCGv3L)J1jq*1?_+A>~$1f zlbX801DmRo5t(YDOu=~?7+_|4&yXlGB4uW#tVDKL7fyDvoNN_`Qy=gHy2ZnLv@2dcg$?NWb^p?RkR z9^N77-@c3v9i6@9eX{#hU{m-=rF;Yee(Wh6;#?jnyl^vl+3|yTySF|2hB|JY-j(!^ zaF&_}0UnHA6ZRY3Zd;2FASpA!VTb`L0=9^L^%0bA++rpiq~0 z$JTSmluzVM^p!qZ_YENXonQ~Rb=J%Eu>)EWXy|avx?sJ7c zZY}~qZ=k(3*w5#o=4iJFkk{)r(dY9cS~j`YzChoQ8_|w2f7JDjcl9+-v{oN1D3so- zM12_ZnAMcy`0|>U-h=AOP>?Fa+m~ZvvxV zF5rQu&2D`|Z(oJ8J9yw5Q`U9wRDKZPL8bw8otqVg-L;?l^Le{@rZ2>UPV0Hw^?#X^ zPp#*aEA8hRAxC)Ntt8WqnBW+t*LAs0pE|!LBJgxXr!V9^Q}#Iq@P)teBwv$u@1qOO z)1UN(cqKD=4G;a7$zbxjK6Bbx14M*ekh5VxxdsYPef&<=K z24CmuO`r-sYX@Hw`Q)Pt&NF}E9volzmKhvyk~;P4Y7UiWMW6`+@Qq95IcDqIS4Z@U zzWOnO*);^9?SmDX!2D`obiKnL^YygN3_D_-*_2)-u=Rm}kIb)3a$O+c!9D5gMq}up zuEQnbo&Fa(*=NP6BW%HW{noFqv#{CecPDMW?gcp_0^fw1uXo-Oc7c<&?z=$#>cVH( zvv$nSZFiCZAvYhH8^B^c?MJVN;DC?@px_J3{K8&Xze?Ff&#+U~U)bI6Fwpdr%3Huu z_J0`lSG!I5uXZ7{AE4el>o8Ewrqow40RHot`{voYPw3;J6|@8Fw)g4P?iO6ac-wOo z*t{C3o;vCm@>$yR-UfD`t!#A*KGFY&e5n8(;MLHRd-l`b>$pBMo+SU5%J{4o8@%bj z7ChYg`pjVqUKW3);c9{%3iQC;C3K-@N5;_XyERbp_0B&n^zJeHoq(;cvFO91+o#^4 z_xv2|1i!V@a&72iLA%xDu+#s+pxz~_zuUZwBe);#khvwU|LpG_34CDpc>`lO1H4x1 zJa30!eMxBvs-O=YN+|F&9AU>X3K7_i7IMwg%?ot@8b-|S-M2g5qD#PMmFMRjo*=*n zNQKr*BRg^QOBn!O8kL{7qZ8YNxU0PWs3QP{LW=(a9wZXKUTyj8vtRMoTN6pV?!77x zyt?uDdZ}uZUc>4?g}%B-w}UA7pDwap>CX79JOkXU^}%+RGI|X!H3k8Ew6&kprf>x3 zyBjHedVyG9t$hvxK4u<5+=wSY%x{#UPq108?A_NskwCYo!RWod=C8-}JAwnAw)6J6 zzTAaOGCyBNM}MfO-vNg>e9kRDjP>t;uXRE#dJ95|QwjfIPs^1*r9P(izWQ|Xsonf5q+$EtRjtJ6p9YDo-{!4pxC8)51M$B}NT$|`%^okhw+m>X15nlLoob8ct7t`u zMkZ^+c)5R7Je>D?=F5x=7U^VlM2HpMm_a5FDDFx>wMpx$dT=8NR9teJm{+~~H z{w%q;D2sZdia?8{fKHmd)juZw8Qn64rNs?`uQUFm%Ka_HaiL+7W$uB%heC^0Z*1vl zl0=QaX#<~dhxbe7UZRE(g;}E12(3lG%k3SkKNoxY4x7$#f28aV#;1tq8F2YbO~f>j zpU2-ZdG_^EA`^1jAO9Y%{PNi6$nnsBTz0pz&_4~hRE{=&P)kl82vsJPqmi~MI!-0R zaU34;4{wFLspp#{V%Pi*F5;4e$2 z+9OiJ**`CAB*s2MlwuGZ*pg?4tm4UH%x%-5+kcNDM`!LYgsa(4@_0SKuDbDj`p(a# zK$B2JC`+iTvTTd_aI_!L;yjhYev~`N(nPlVSNHX7{hyn=O`wwE8AMWbExW|Xl)7GV2`(+B5YHmoN(px)XCfIsBQ z5A!b;U}kuHM^(4#=iTB9Cxt77EUmdU9D|wT_K)A@RE&%4KODBBN+m=oLb$i@ySj)z zwMRJM&-Tt)IEte^RymYWX<(=0YD#a#*0RfJRdZ3h)`ZPV5x_x1-(mK(2~dPZcuXw7 zHtA!Q2Y*{)QgMY0x?69v`df=3QNmbKEg_ne8^C)MK=Hwbv^l=x*5+E1(>T#OL11R? z+;6V?H{_;I-f>rnRl(H-*UB%=kQDXl!Jk{({8Lc}deSj~rPlTy`3sPxmbcpnH|aa) z1bK1MstAv=HsFC?m*+wA8vtVKM6+B^xY)fMzX_Eq+}to0nk!XA-Gc3~vSX%H6+0Xs zWZirC1Z{r>bsnc!T~~t>4ZAOx^8iYR`mo(GpEf1GE%@3;CNjDmgaQ_$El7fK4E^>v`U_ml zcSEIVhphjEUZFr9JDNVe1=CjjkZzA)J9=evLzZ(*B9Mu?CCC^WwI+)z5##diY<^&n zn3$w9xzl^XQc)9@Dgg4)TKO#vsvJDn5|6-3tNg^(W5tw$AJ8iPDJ_rvsuCJ`EJp>> za9C)UT8W^7Tm#9NX8d9Za|}7Y=ZP1l{lPIOMo6AqvILy(=+o;@Ca@Q;^eEribFE9n zjRoZ8pI~Yo%8SZWQ^2a^E7rmo?L^dexnucDt;dt?@rKy4XpT2NNNRD?*dX_dk%`sG zSZlh!LDVe-!c-~i2Sz1E^nl-~0Z_S@t$PdR6q7-sV9N~LSSzYV7?c4YQOTNyqUMVK zrXN)L0);ovlg1Sy6qzBH2?sIWR@RNS)*u~dOj~e0Q%=VgEO*|6;f2d7V2Y?hnwvnS zt7u2n^0-d2LGD%NoHtgZm?+;@?GiZCD0iAFLonIZCiaAzmEkSEvW>R5PgS>xA! zMCKA7x%j(q`Lw|7Vjmybg#4tdv?FHQpBx3{Nx*&lg~dcb>%khI-iY zAdSQS?yEUar_q5;V@88@H&&S1;pR?D!!nhRE4S$cB6Kh`LiK%K zK|;$fG$Il- znPqU*30?}R?cTbGO5i$IAN??Xo#eg+VJ@Zx4es|*ikdwJ ztJZ^P3jQv*UP7N7zATx~r(?8n6?fi%@4Z z$xa#gG0hd2vqt=~v6c&-F@_>+O8#GmvM0<05nS8^>pAnJUNn*pvGL(VzkC<+`M8Re zMXm$FgZm=?vQa0ksa>%-QvEhjgILGNeYliitJs2F7xro_+P;K9kxO zeQFxSW;bG#jlB-64K3NOiZu#;xmq#(Oofj_Yxq&Jb@bkwnAlmT@xs3ddZ4V;ml63? zYEQ&jr&BUUHno-4`m1%J< zyOJ<@0B0K0ts{xwfR}aLA6O(-29q?}uwqq)_NhQd zBj6KZ2DHEBvhKW55msTl8wmPmie@?KQQaGb3Vq5f>Xh5R%>fQTnR{ut9dfx0nK|(F zs2gej02KqB{!r=R578lOv2KwgbDC*2y>N;z$sQinM`#L_V^DE}p4_8z_GuJPnmb2w z*bcZ?`#gL)N6Ae*7c#;=_szLVqIN#lTeKxNi16)_;$Vy8x68Jz&oG>q-f30Wbno36 z@}dl`7dSpMIdVv#uuyR$cic0#wXSchV3WdD-(Ac4m*v{^J>W@lllVa1u#Btc>tYVV zsxHV3)p7b8Dsij5rp!IFCROlgzJMpR{p0!!~peG5C&`u9nYawS#b|nunno=yH z`K0>oiQu=wCocfE{Ku`?9{4T|loN&Auh=v5uC{uq3I%F6gSzqI<5L}e$Cn!Cm6&8u zoRjQLg9KrrAIne=Mr+)mbN~lYRfkm08WEcmwNb0Y|oT1>`fdLYUw83OzwXunbm5@8ohtp$a$3S|0TboYhlwe9W<6UBeAI=jbhwVOu?C5<^J#%5? zjKUJx*v;YIm^R*Lw_AGgRJQHr@O|f$+8y)3p+{p!Y`yJZUpZB^tu2mqJIYB~nLVgL z6i$~%UhCjbGz05C8KX!3RM*Q(z)L{rcay2U=qo`W?(?b3-QKPd|FcnNOIs)2RacvC z^1|Cy_yN1m$HnO_jta1LV!&owwLDK;sVXEdF2s)_&)7U8)A_YYp7Ub)ab@EOa!s(U z!^>`T!kDignzK+;29eWQLVRF5-T~AbhYT&z;Ga3fm98^ZH&VCBC^*Ni&O=irY5dYzzVy5SxA;N+BTbM$v%IDX z{o&8PuGybg{=&~alX9=T7namORJf3bmhXuii;lF-S{?Gi{VB!m*oTBaXRQ((tIGS5 z60M1;c+6_qC&EK{*AU9d4pWt50``A0Odf1jE<_D=7!hGbt6takGY7`w^ye)@;Vds{ zJz!efXNVd;Pwk_9%iyBMP~8P3dC3YqvaBpMRMt}SA7d*`X4F!4DQN}ex1Oo)rYw~| zsfMedv*7ud{oEcvOGQ8==u<0VXq&Xuw9%_Bv{l*pmGG{_km?HqGB?Wt_h=A;kp00Y zGo%QQ)nmplUbsk1zy>V>K?84u12tp6LJ?+EKED#a;1GXi=Ht`;@naKIwTQu)xfk@l z=b1AHD(evU3Zk7kf*61~vPvf=IH)Z8T$U}6*``iP|o63xe zBKRAqWVs>eg)$>e2OmLt%HaY4Za7FR0fZIpawKE4EehBTmx*^78_v3{YuCF+5BxTb zYMbh*EbiUQ{jQiXm&ZGd7K!!9rZ3l05DwMU^Gd>&73VDu=GQJjdbT{>NEZZ}KZB+A zw`?&*UvW$z?Op?6bEUsgs{O=hs|a`5_IpBT$E?!T6r~!S({)^79xx z>A(-pH!uMgrX7?&<$F8+fbuBRekSM0A~#WaE5ApTwHqVwL0d&*)+}I-E9n_VMzbf@ zoW!PKZ_6Os0kmRdIAiJrkbJ4jewu+^|BbzjjV(N>ocKg0g8<4mW{oADH{Lfa4lx7S zw&6$RSLzB>bq}CPvW2)ZY?iaMXu3rhD<|}}B9qEc*vyy5vh%y(DPjo}tDM%f{%i$& zML%;|f2WhA8MbB4f;jrHHXAe!L!EE0FdZXHOSZ00pJ(qqrN;|M_=dl=paU{o3-fw8 z>W&jtJMC_zO6NxWnB)RoLN2-Uwm8Z$OX%k+cP`6d(-fo@C7#s=RZ#O{$K@$SF~r51 zbUUisehl;$asD`lru=s3E}D)=#PM_4z*)RReu8lqjB3B4fk?GDCwd5M@@FM0kJi0@ zniP~&^34q#CWOUoGVJYFFqYU19RZ_Aa#huues$H{-{4ENlCUmPi_z{Q;)h8hdQN>E zi|$ZR?DS!{OfwkrN0cJVH3b&^mXm3LbYix)nRZY_2)uLB1}q2~#bPWu#$@FFf2Ubl zSWNsg7jId?=dFlyLYM}S5&%`S)?R3^5OSrHT^jSYNA6vf1+cj{U7(4#%rG6|N~!M( z$;Plj<>a8gK>aG5ggtRN*$IY~&=Xi>fvt6ht~JQQ%v=>&HVPS%WBguQU&v|Mgo2#! z5M=)B3c9l=l<*eYE7DN%BAu=N29zS3mQBaCr52ly$p&z$OArxnQk}6+G_!eW@EoKQ zBG?nsv>b)Kd{8rl(jeMBFw7wpIz{$5m3!U`4=PHrDKHD)RHXeME04Gy5XLC=cj`xX z$>k3VBAg?)yC+s{ap?pxf0l^0Hjq!tFP<9?Z9^-dXN7h1Hywpd=-KFdVRS}CIfo?LVq>eH~Qrh0IpJ9w=HQf&b!xf!z|3LSK`-;RzV=nu8c23lgoaL`bT5mJ!he;dlPsA zcA)*>O^(l+@s;cr;7ABerIJqe3vyb z;5DoL$m&qOpdEM*7qJ%khT{CTdVte??g9}7(lRWx88Wv1uk5!P%_IHc6W~dpVGD>F ztq+ab?Y0-_4AB5WvY%aEm{;V7Lsob~DDk&i?Rg0{0^zdvw5=@uRoQX$4)LotZ#KJ% zwf_;VdJ(>`@K9*g^yHiUX4k2)cVXb!nR-^!< z#cidr5!9ur(eqdGcv=IcR+_OOPkbUp@&*q}cs8;%(!O6250e@iw+@t-zg;P;H! z`Xcu}pl%-;+B;~;uIw7`HJn0W-l?D5oNnfSmp3c-9hx{86naxPWhDV*e|}!%)h-+fO~G(_M=XJel80{W;hN9r z9YN<$xN?J}ah`-pH|+i|Rsx|ga>rs7RtNTtFy9G>qS;_7q{&|5EMB``1Yr32#a<$N zj_ivUdK<=D=IVmzAQj|gLGxFvcv0ifrMS&p(Zl1%*2+cUp{-3Q;P0<*#A0gUk2zV5 zZAu&L^LBT1rx6z9*`h~ttdOJg6#5-?eV`uyBs;bMVsqXc3WR+E1rbxBdnJqtpgC!)~!fl4H zlcaDYa}wtgVHzBfg{k3C>yE`&{6Jy|rsj=KQq8Ev^pS@ZM4iHk{LAjRc14VwKoD27 z-jl{ypm$+9Z;WE?45TV=Ql2l&McXr7mrClTdGCnW0cHX<_bT-D?VrJrS%?5IZ0N36 zBDhgu3*UvN`bqmfIEWb0kes^zt9J64<;NlA8c&l{o=JO7i zP85EJ?kMf=ICw|GdlL(ajdMIY5}NI@y7VW=cle%(CDf+g@P+qgL@Q52*PaZmA(W1= zveBE{5hI)84w8nnzw1rIHv?nfc)@1~a12}X=12lF9SU#04{s~= zzdm6!w3SJkp5f4tKsD8(!9hvgwYOYsx-^!m&)Ld8MM5eWVmI_3Di;$4zSbOFxQt$v zEGA!GFb!X!7Ss~2@Mpa#O85!g2c~KS;62ELVtZhH%dQ_$se1^|AUKM7AFaPar=+96 z4e584JZ^1Tshu4v#R6y_FTT9wzT4;by;515#c%Qe+PXXaL5vK*Ekgv$@poPQ*`I4r zTPl9wPDEYO;6%eFE8D+zzL>{AtR6#@>Ua-no|%6p>>ZGvY8u#R6e46+%)-8Nc$TFr zDoq7|kj*2{yaFM#V!rR<6xVgIR7B+KaaRrHD`X zBY`vHR5F4OAv*napcUhfnuE}5f_y8I1Dx@nfrbg~SPiA*tBf`A*o> zInKLYyUILVVjL6((Jp(#V%9&sHd%~43L=JL`0lu|Z+tbwj7W%$c`GnnBOA!Nucf_ zuArjzbnK}AdK{zi8NL63XUakyj&j;Me0@Ctyn>_#b;3e~r;Bq&W<&Q*+$x%4xDdCq z$o1w|%q;uap0Fpy?v?r56_fg>-%0N3_uNbbCE+y?T4eten zogGNzY%;hxjHOEmv8k=D`cuQTxZ4_0IC?`Jz9j$ECV1CNaecD8yR;|BrkWvE@zIo$ zMFk-grRVS1O|rO)nyNbvpm>}_=X55=Hr5tTXGPH-SH)k)r#aX)t13XKc0B1AjpelV z%Tfs$j(qXrqqMSHO<)?>oGqa+95tjh(&tH!<;{L^5Mq06^itL{b^5At#3)!9*|hei z@CrKjewelFoA^FjDGn^@cnQ{tFz06$FoM)13&R^S8emOuBGENps8mZb@7jHrP%sK8 zF2=&LCUsz@=F!$rjPxKS9rmbl7_m~3^z(Oc2EU7VhdqiviN;k3C}6UgvTfJ+ZY-l) zE(aK13Jcai*EK>kJK2r+yW_1uJIB#0LG2#(zt}8q_Lddx=LnzzPAsmw2!A|A)q_lI z%cCq$i`ghwmrso4PS&csb-f1G*o5PnZlPKJt_=vhKfClsA8mbv3^Y5XUp;r2{)?nO zXw~nb?u2b`>z+tta%+KpPpP)8{(ZC5-KAtRbCc7dr;qmG5ogOmVxIBBB+eh4gdAj;V?EGQ8VY4XVNtzE+jDNQFd2IBX8_mlr`V$h zr(LC{vm$g23b*LkmAclqzGpd+2ZMr7Obw3vXEcoia3LKi&qzveil#8i2`%`M8zu&t zEYRs+mt(X5tDUpROb@j`*?oAy1r{b#AxL)_Y}`3+Fc(FrFh7zqC928nX0<@m3$g5y zq95c;zq%Zip-JLs+A3q`X2H4%i-#qmek)lhKiK;)alO5Pi8NQjmhcQKHkrn|;~)wUC2UZ}wsY9>Tu3}u zDVDN-+5Y`m!gu&gDDV=+5ypZ=moq^axS>$Dv9*Ckwg#*X$MXY=#(zHXVJSF5V4e-g zI?Dy4o#Ogm=htZfnKD1!l3dGWuuALlOIL(10|Lt4$-1&k;44=c5-4LHP-Ig`Mf1{N zyhGfUj9lUlC6JE)Yyd%d^>^0eUfZ)_aGCWb-?HiVFTbCSj-=H))Dkwm9z`V?GYLR` zvR4SMGupY#HX?#mlVEYbI>y*s-AuzdWHHlGroOoK&`uToP*ZB~Q>4yU=Hh(tkk}Yr z!uxagPZ&YjnKC6WNWF)H6AQKkJ8rtwmC?9Spg9(^06lC($$}I_Uo20przz+cRI>_Y zRp8+{dbp9crtUORe+H}E9Ci|mxSWI7A&YQ^N_S5ZjGDGsm7WV#N(D{`0X(fM{Mw1L zNoO(|=QzW(_CmlT{Bo^<)|v{t8?==+IBeAI;w4tcMc`6jq>XECLiDuX7yW04HCj8; z|I=OuH<|P)3)llP1+H)ax!3;iW{&y;-T(3ss}nu7fKt|%;fwhKm4S#fReOoQ!>+-w&f8b`?(4`F5lNLE@Cx-i`)qJC zE#Ku?So%Uq9T2+og_2Y4sF3~OG`R*Fmw+~tL4OngeV-;C;Xz|6!Q>>}btL;CVoan@ z_QNBLJM|rn`W#GMO87Tq6Im-6g6V+f;=YxuMpnOl$US-~>*fAUhXZed#fCy6Gb668laZPnc6%q(iaYV~4jm%01^YhkOWN|Aw%`7vYR zW-2o}ACRvpKycwCOzG>J4lxX9N#nVR#qcyN)GnKBCsv7*tZOpsCCJfqw5TazQ65ot z5;j>}C%rI*Uu98jyLZ^;!7L$v!aJ z9B_h+@ciAUDSz#xljalv#-#ZbRxFg!BF5lXxWNfg&0W96ae>X%~cFD|_CEePfCT8k;KTz@K=PT9o!p(HL4B~Xs!F%A3k0GP2 zU)}5OAJ&6Mi#XAxz_sX)O9A4A8;5Yf3-Pj{yL+h8YMz%Dp}|qVzKvhl+s#irTi4Df zIM-d_U(}_4&Ixv8M^0fr)!%T7a9NME|FSyZC7_S`&8~u#XGC0x4k4XFa6vJE+9GL# zBYK!P2~u8Sn6r)l?k3jta2DN=iYE(yiWnYl{O&0@g&=~j&*0OF)ZpG_Jx{>U=RO=R zULEcI$)|2QA?eRcZevS4KhyY9up?GC^!o>Nhj#ULg|Mxz5yl#xbH=H#IzW-=Y8%zi z$qoF5lcictRM{WDri=@L?bsP#L(6gv*3%Dn)=UVQqH z|H7<&Y+#tJ+bl#3f{Wmx9Z=2~z*hIiQ&fxG-!&q>uM>?6#(-e3_^ONrWDwkJ*+iKP z+3}b52k-r#W>1I_oOmvGd0vnZhbcFjq)&H;`#F29M@pA+{g&;RY?H1d=#_Vq-^g8o zxVI9h!e^s)mU`yt`6sLEk%IGjq?zJ!j0KC~Coa)6TOoJ`;kII|v9r^WN8GaVtj@O+ zU1_h$L+C>#QePNR==P;hkP8_Hx!TQj*!xb@?Y}!{>Zi@41BOP|xj2%zH6&^&~TQ{LzTY&w-%eea-2og7S|K3EK+Xy){ zD8+ojpx}x6t;V*2wC!6mtNhBDM&Ok>nO{upe8=zkiovkvlDCxu#E@BXc8biszXRfK z-C8VMDRQLFi}G0cZ9MymPX&_V$4CR>{uJ38iU*u}8wixNCpn&3yyO}8dSYV&^~V+p z9)E{DFW?s=7~W^@Koe=)5)z`85@~T~yE-$d>|5cN&_8ZetDdIGCvPaXBIa57U(w;_ zIb>NHT@T1VZ_~eJj>c%>I?#1@9qEaM*L8GgyzNbyy!-k_xLi-M^-yPZG+~vWuxjb=rNV zcJ-7&yRxpt+2mRo-o=_YvqqBwp{DMA(5}C`b8p zJx*)KpRF@e6-=`xZOnE1$9vEwI`fqpd(`mT(=*>oE~EMo@@mXB(UKU9C-uE}Tag8L z!hMi}u{T#wl|Y~Ih+GZY$iNV>!d$|Zz`Cw{`Axyp707k#;}IdiO3(I#EtCF709GBF zWc>QfIJQ6DXr%?9Oc`kTsz;`BRr@deDd8%KE194Hl-XTOo9qc5=rzDmtpnek@^7T~ z%MsDfaM=&p7b)r)q1Mf^y1ZgeKf6m89TBp|&_S$XQzEfXv7;631~JP*6|7#z^aH7+ z_A=Al9gwv^*HZmfZ@yUA8wS$f>6sgBA+Gh^Iu^3sIKE3Q$Q3kn55f`HrGwZ@V==?r zmEQ=*gk31J%B{tiFxcO}lS>T{gI6NGElqLZGjQsN+4M3N6ZKufhy>78%qYTM7RF`%)J)w1>ATyUfhHA92i`RvT`)`b$jz2D(LN=j(9@&`=BWH2vd93I1yEOaR7a&-jy+eLVQXyl*R~3B%(n(P zEJI_>{vdmZAs2YN1m%9$=KRs$TELM6#`$^sUYe|)mdCmP_lsY(*Pi)&7D5WDcbWZK z{-LdV>Li)^ax3%tHl}*=9$GY;FG&M?2s=+e5@Pb!J0SI&5I>PnH{rlT70)J&$zK6d zhJ+L?j2N1Owh;kqy6LImG{2L-f?Xz{i4QeQ?M4jY?}+EU2!)P3jy)v(d^ zH}i^t@hR7xQ3b{{3Ik)sGGo$1FK1{kp?7QVCt?1*qnx{;=AJzu6*kJ7cbz2BvDmpx z;?TUl;M!=ZXqmFQ67Q0O`-b76>74O5>@cK26t3ddciWvB80t{`jR1AN36fyYkMIterb@g2*V0)9)wL{P z7ot@hVby)FLo!)w;1!BH-jAbQ^+IeVH3MXB zf+uk1`yOoeiuDeYtwh6=tSq*sxaKlQa_fPx8n(Xx9&(D0N07wyY@}0bICqs)C9_I$ zo1(^51=kL=IbB4SfIP*}wc$~_xd=u)zW`?;1OYSj^CJ&r2uvIDwuw|lTc~eisR4NuPTFe zv)xv}XV2t#li$Lv21$)0qHE=`W;?PFhm+iV3hs0Q>~>sm*+!(xpDBA;n7~RhNtj|X z25iHnq6C~8V&UTuX*k~$WB zIQ$Yzg-m6r9UGqw!)nDl+Iw!&((o52)|(k34wQE)-rn&fSYp20P^Dlr zS@U(d0g}gA*iOcM&zSHMuckN3;*#1S6x{BOkIb0zp}7ZqEC8uz3En|HT4|ODrT8;& zOfQ0&%jlma3I0(YT(klAn-4=fZqPAn_r!U$psnDlO@2vO@Jm&n#NdGr2aIwpGOo3* zDJFo4Zfk}Eru(5%H5gKr_(ZMxjtE5ZX}yIVj)`yHscdCr(Qu*NL!+p z`yfg}xifhu(uX1E!(rTH0TAa+@`5M%Qm(kh_zNg7QMXP)(InCo16bjxT7ydU&kh>5P=T#}2>}1|(Fk-TArcY^y$$q%> z>*l9CQCnaH-QI?*jahx=-85}dypGeO8onJg`~u!oQaZX!_^KIGc=+w3OWNF7gJ{py z?(*DSZOU9~`EJc%nb;GSGH~(`KsYM_pC%RIW$N(Z>}?Wt%5n+cq!Rm(2QDP`YKh$b zh8!QW)OCnP)w;?#%Y9Qryz*O)pt9D9WVzv>Q)~?NxlfH3$kdl(m+a#$e&jam60dES za>h;tGFk`8Qrih@;mb@3!(B%eu9X&-KcJF#+NNoQI|%3zP^`xM8X)A!n|zaOxi{GM>b@n}eehBjb+UVR&T5k%}u(~((Ln&^Uh$wGk|ldOLa8hx%Y62@T7L{ zF&={{W;`Bqaz@I#-4o(+Q%AkXYrn?`D5>u1wJlqdkMTS{Ej?dbTrIHUEP2OxJHAg_ zw3#3MDcr^=3n{8`U#wmD!M2!uHVol?ANBIgO>o>WHJ(N&o_>1~!Cckya6+VOox!O2 zgqUSx&_R9{1hn01jgdy6dsl#qj|Ef4{v6PV6dxM0FHJ;hWk0 z2=5akMx-d7!HA%@x_cD*?K(HUb;}_U+mTpkqiklFLWpI4)l!-+a5Q6W=71|#kJD)j zAoRy|in@NyD~ue)y}a90xZJCX+o8x4D`#jwU|Qx0vJOI}LV)Y#(t{w7quK$7@%aIF zDm70csZaA{n4Y6;W91>OK~^hV5Lf6HdQNxp|J)K#Ofw0Z7%Dm^&N8Hs%RRucH` zMnAU>USeW7D9BzumM$KZR58cvkbRI43-W4`meuEU;lkVHd}`)D=aBJ~#VV-w3&j}C z32RdQF|I$m z=E3J-Hq5O0_9;Z#^@!_TZESW~v16xv${}APEUt7p!!O<97DQdz)HdX_s%$0xEl1{~ zryc%vxCC@|*cQ$D0*$y%94<@My6||zoHm=zhN8?PJ>Xi8bA~b)gp5(0Y(qr~sg%jK zwh}eyIgN~x`Y2>-wF?h~8VX!^eojy5O;OBz!W0-9#8fzmQA5W*c=fKMtBnvm6M3&_ zvynDX7xb{_`rgoGRg?jQ;lw;eYmkaC;q8xNlU53;C?f z$fFve=5sE^5$m%)#`o4#kyJ79Th`>6*w@1`Y6h67T}vKOS5_B$%ATeJcXY(yupYvf zCAQ;P$MX&+ih{Tne8GRsY?HK}@NWhDh5gSRkf_&D2nEMLoF*H*SSqmsVrzJitbN z^~-DG4--dUz*=GmfiX!h(6G_YhF!?worbZ)X*@oesdL<2%GPEkYg$4u2f+-_xm8|m zU~TB|Z9nrej>4k2=bb~O|TOife=3YIWd z0TEg(0!uv6PU=Rn=rw&Tyo@i1OFMFkW!xCkc-4hMlV(gakBPHh$>Tm~O1Z@k$|zl1 z1y7kqn>BWTcb^t3%a(0!mBpp>UTwo|Fk?tKWAC#lci}$W5Jz6GcID@7@ErS_C87<% zB++D7GiZSQ65h&*P#pa@^(cEPvAwh5-4EXHvzElzqf!(Mk?s9LBX(nXu#*>2iAs*( zwRg{(6SSS?5f^?443`rmp$FB72v_3=Zyp|D{h6o{kJEnmlkx1V(G|~_3q;BC*d`N?LKB!l7TfMCjKt?ZQKN|+PT8bJq?M&ZLYldG5z@;CJ9eOd%r%^CT-ToV z4TsQ_;E*+8O9j59A`h!=IuE#0rb@+tuK`w8%a*%pUuVYAF`8q3%{vZ7vQ?li-8Nda zoP^5iN7SVs9_C-1KC&mAKPuk6*}2CT5&x4sR=YKDC|I-QSiN;nLxjogeP;xo7n~ES ztavb%#vuCSd^;h5jWN$x;eR#gxgR~vErHQ-hSxlLH4-lN0?4pmPDMQwes)O}$yK%?5wKRO) z8adMtcnyuoXaMJ|aU^S^6ZiL|mfN2ZMHAjLqcg9t`)Xah8TTCcbh|kMy!hb8r;r1{ zE=X}N8&tZXrtnSBpH5)ZeX2Nc5Ek_l$aU!S-O*IP7svs4l6nc!vc1QDxk%z~ zkhguWHdgT+;U(9!vi+R**qVliPnDWuP~?;?sUWRZfZ;nsL1=4%C_|Tm9utN@!umDq zNP`ijCGC>ag`ZhxH>#r-8x=c)kAoDjXc1;A?CP`fk#Zp+4|Y)QRD@8vY#FgNu!raz z2UZ|Vo(1y$ol}FMr_f?Br)abXm9&`i_y{5)k}3h0g$s;(n+*IFZOn3yt44F1;;L0z zQ%(`%C?5-rd>ecS9^-khVOHoG>oC|g35$`48`>pB9C=a$OBSgwpD%1T4+{tItytP9 zy?vk&c5V)5v*f7_sX!V`haX499TtK?QtS+L+;?qkrs-Mm%A0yh`g%(uuI%?&&2=ba z<&xp>Bq}Bt>_YS1T4T_$S%l-xc2tItZ6)yoL1#QUOwwttNjWCMmV<=UNbg(6^+DM} zh|+Q_wbv%DCW?F}%IDj`UZiZG2;BcHnrqZmBoJsn!{w$TXa_N^3$;i&w28cmO^0#t z*w%PTahLY+CK&5{lza_}2(7rpmWVyV+!i@f>3F%9pFb>_k?^gn;D+TvfzL*Ye!I*o z?L{3~cg`4|L)wyVB&WA~Pp~h^pog%H-JXRn(B!0Yz4&pCrSN<@V3tvGX76_J(h}KW6tp};XR zQy&6y*$h9q^(J$$p+z>7dK7sFbJa0YE@)&~?C`mgM>u{xS%3n{7NeERGyBm{H^;1Y$9F*n7oz<=n; zd0=tWDq86nZ4;5A`y@SgUS8y=%h=UVVD~0Xcaj@tmHW`=OToKkN3XV4Rd^On@D*Iz z-jSRRKTby1Ie8oJ7ZWXkw|sCv6=c;%t2Vb2riIE=rR65bXrnVCrVW%%+|pg@-OTH8 z*2T&Cdd<#OC947Wei#gT*L;zg%kkk-=CZFTK`uS#j=}gT6i3@gA@$W}`_c9uXT!d)+?4Yp6zbLGj$QO~=@+fVQz<{* zE+DWd%-X_zS048hDIO;A{PUBCiJY<=ckL9H;J3cI&vpnw>^N4pN+nMpJ1$B3AIwb)Y3c}Fz;rpi<7IMycOTn^TDps7Q;xu0T`V@3v~&T77&TWnt>v{U=J5tk^Euk%OKt#Q0G7G=0wIf-`8&WVAW zhW7R%pG~vJCCbbom8@?D72`$B1_z{^y1sKYEy`Bjn0yusCEoQmG%IoSBb6l){}KgS z3vTbMPn=Y@A@(ki8bNUq2}Lipvz)blx=5W)2=W8GW`qXnI{aSKDNbd;h8I1lC^hJm ztCyVB#tZ$H{@Ov2AuV{0ANMfuuvrH~^14;dTy{XhM#R(zQWjh~x$>IeZ!jM&$zjtu zlOHC{i7AJJbT#uRk@EJyvw7Gv&USGs-X${QI0nD#l3=tohSqQ5SbjKvs3G-^wW6vA z>x2WHGenBDR-JV)6PD_ES@yN&aBHtU%oJrj=-XJS$|^7}Nk7XX{>FVX4@EGv+txwK zyCvTa{H(PjtEStYCgB8cQ(L@?;r@El{n(xi3B~ap6jL0T!@WCpS+1)#`-0}yppL&o z95qXEI-QJSyM4QlSjj9j67yZ5d(H6RSX>UcFKF4h>3)W7ROqA!|4lGMV`u`^Idj4g zxSYY5rd;AMv2oXT2@pP_g0=hUF@8|NqaUdk?=b5&h&7y1oDN!FjDhYJlq4lg(dcAc z`jVpJ<8%(ZF+WV?xse&E%kCm8FQ#|q2w+ws9b4Y>I~rQslL}I2r^!F<-ksc)l)wWm zU-7qPv|+y&Cadx58j{GGYglWp_Vz<~6^qj2(J$oHP}t9tyCcEdTbL<7 zNv=Kc=h>yjsPZeg=VE12G5<0}JG1i81XdtZyj866x5(kwEPeyK6v+F*X2#}R9$XLl z>vw(plap5Vh`2IjXP=H|ZDaJAA*v5}S=4WOtb2%+82K&=YYV+=q1XZ+7?@`Rko5+$G!vnYN1gF^(b6%*-3fE)NA1L>Fs+8XzSog8>U-s0hR?81!b#4TZQxQG|aoA6CoqvUbpKdqMwsL+WjGT9oANMY7J;Gqd!he-ziO+*rxnsm{k z&O(#h^YP9m=bec$FQpX3^zj801mwC!FG<3;S1VwEaa@iG7ea8)(fpBWz+tAA_=9Y2 z2~iohLch1Nw#hnJK>W>c^m5V}b(QGf(KI_a-8)Sly!s{Yh7j)N9zxF3WXRc^cOM+n zWyQrK=kW=PGv$pcYiEf*8<$q(a&}0CcKBBoST~DkG;UVU6}5E|P7=V9-R+ZFL`8cZ zA%a0QUCx){8+}r-l_}TRi^l>wctlQkQKfEaIs>;+fhlKeNWhm1P)-10dICo7w9Is8 zaie_Rk9ekhHMF|+hC=_3bou;=+*L+h%R1)db@)hG2H;63XVX*y17~$RQ%VWa4X;=$u|ByUB_X zPzy=GVqk9cZXnIEe^u%&#~~{Ek>a$DFZb3|5nhlVrArF-Ixa798yTYZmJ2mE+5o=!x1wY9?Q8DU9HhR6b*NM`D0f&m2+3KkS>C^%5wK*5EA z2L&Gr0Te-73yi>0k`;ThYN;I4PaQa^r7Am3o|5oK+-A0!7P9!a5nRU z)#ky$WnKGA^;m(Kb{lMci!%2A4>|k4L*CtjnkKeQN)Dm=XZZThkho3i0$cvyI{gkQ z4KVYs!F4e$*4+{~GDj1dxk|Dr3*;x(%PFE%IMaD}K{M8KL4s&<8gHUr#j0n7%^D&8 zA_c-2$XuofMY5LyA^h*jCga2MoEoHoFJ@l(difFbYK8A9WO9r@Xiu0QRm+wKBd(@G zLJKV9;79Rn+9&VdbUz>6UAK|DT4jA@a1Syeb#?Q=VBA;5x_W`>!>b*6C?A2aW##cj zBG;H^*sA{cMmn5V@q6mAxL2XI-EuzG`&c!!Ni%d)CHQA#fMh!NLPW8VrEixY5b}C$ zIh4cX3p-O`_5z{UCA9=L|5dEin-qyzrou;DUwq-zw(@Ap1YRb5tCy8?mO3 z75MfsF9$h7PH?S~9SIgsx~a9#iJJtJD4EZuqFzk&DYCLTfvKwxMTu?3fd|UDL2#e8 z!nArL+c6c~emP$$A~IcW&hy9H3LS)q{V@HOb6-dsb1Nq~6_qtVj^rv$bC_cHvy&C6 zMd-Z>oN2=8F&wG}>w7QGWl24lCNxL3%#|nJBtepbW&sJQ<9#&RReI5pZ7X$EP7syR z^f1%))y!Lg-+-EB|I}2dKP_(+KGt!R43{_RGJexZqUYDa!FVS|9=Z?s#)Q;Xz774n zQw1}e@p`07$>O=ZD!w{4`x};qZ&4QC@GitbvZKcHnd(x%4JqIAD;IxoC@`4cG!^a& z?rkQ9jlmZlIS!Aj{MKrsiNZB{{unW1Iwr>C%wdt+e`hrkuS4Eb!|vgOgqeJbMMx0R z-(l>ge(jf0{JD55?Bs>aCiP)TnD%`m(AdVm6n?UX;>Bo^!tHal(Sy`vYF6TK>c zpw1wZFc^22M@$rzCt#j%7KT3BeMrIVo!Y*->+ynIw$~>F;o2w70RB-fh59sbCsu$2 zhV$XKx378x_NnN&n{SvDzn}=^6KS0Lelt z!4@q7`Wi=;+|%Z6_yS^?RAdzxe<~K4*ab0%XB@4rCDTkbnBlm0sSUJ<9h)|~a|G+i zo<>6}BzcP|6}TfP%DfKox_z`G+GDP{RlEuiM8m}g9ocuy1 zA+xw^8i92>5p!DBf3hTiu#O{BJ0#d*;dZRqryJ#OB#nW-vr}uExrl8nzmv)STTNw` zkm^(z5r#J$n~B{fQEob~eRKFGNTM+0RNSu+sUy+>VDrBX8yB(b=ECsX&=!}u3qX=r z&G~_T4Wr$%-@|Mf$lD6+m@|(Pg8*1c8MM2Oez+g7H^CQl?(cQtq?y`jC^TkTJqvW> zzmLnosqp!G99;1+Qr8H|buZl$H#ntC|Jfby8q>;2(q_slIR5pvr*+Z6)dKF5ibOxE z6YvS8jE|>uv))P&GIWn7zUOZjYPp@;XStks8$mCEEq%U$iqIr^Vm;26CP#{i9CCGc z^XSp?YP91w^T)>J&uH_-8U5ROYay~k+g0t#;dUDCTH(WGIZ=gIH)H+PIWuiv7!w1F zp#OWulYe}?`oAXg@WQ~2;?gM6!qejE!K3TJJ0sG Date: Wed, 18 Sep 2024 11:19:32 -0500 Subject: [PATCH 064/216] Stop writing channel logs to S3 --- core/models/channel_logs.go | 51 ++------------------------------ core/models/channel_logs_test.go | 2 +- mailroom.go | 5 ---- runtime/config.go | 2 -- testsuite/testsuite.go | 3 +- web/ivr/ivr_test.go | 25 ++++++++++------ 6 files changed, 20 insertions(+), 68 deletions(-) diff --git a/core/models/channel_logs.go b/core/models/channel_logs.go index a5c3d4678..4accfb79b 100644 --- a/core/models/channel_logs.go +++ b/core/models/channel_logs.go @@ -4,14 +4,10 @@ import ( "context" "encoding/json" "fmt" - "path" "time" - s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" - "github.com/nyaruka/gocommon/aws/s3x" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/jsonx" - "github.com/nyaruka/goflow/assets" "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/mailroom/utils/clogs" ) @@ -85,21 +81,6 @@ type dbChannelLog struct { CreatedOn time.Time `db:"created_on"` } -// channel log to be written to logs storage -type stChannelLog struct { - UUID clogs.LogUUID `json:"uuid"` - Type clogs.LogType `json:"type"` - HTTPLogs []*httpx.Log `json:"http_logs"` - Errors []*clogs.LogError `json:"errors"` - ElapsedMS int `json:"elapsed_ms"` - CreatedOn time.Time `json:"created_on"` - ChannelUUID assets.ChannelUUID `json:"-"` -} - -func (l *stChannelLog) path() string { - return path.Join("channels", string(l.ChannelUUID), string(l.UUID[:4]), fmt.Sprintf("%s.json", l.UUID)) -} - // InsertChannelLogs writes the given channel logs to the db func InsertChannelLogs(ctx context.Context, rt *runtime.Runtime, logs []*ChannelLog) error { // write all logs to DynamoDB @@ -111,23 +92,11 @@ func InsertChannelLogs(ctx context.Context, rt *runtime.Runtime, logs []*Channel return fmt.Errorf("error writing channel logs: %w", err) } - attached := make([]*stChannelLog, 0, len(logs)) unattached := make([]*dbChannelLog, 0, len(logs)) for _, l := range logs { - if l.attached { - // if log is attached to a call or message, only write to storage - attached = append(attached, &stChannelLog{ - UUID: l.UUID, - Type: l.Type, - HTTPLogs: l.HttpLogs, - Errors: l.Errors, - ElapsedMS: int(l.Elapsed / time.Millisecond), - CreatedOn: l.CreatedOn, - ChannelUUID: l.channel.UUID(), - }) - } else { - // otherwise write to database so it's retrievable + if !l.attached { + // if log isn't attached to a message or call we need to write it to the db so that it's retrievable unattached = append(unattached, &dbChannelLog{ UUID: l.UUID, ChannelID: l.channel.ID(), @@ -141,22 +110,6 @@ func InsertChannelLogs(ctx context.Context, rt *runtime.Runtime, logs []*Channel } } - if len(attached) > 0 { - uploads := make([]*s3x.Upload, len(attached)) - for i, l := range attached { - uploads[i] = &s3x.Upload{ - Bucket: rt.Config.S3LogsBucket, - Key: l.path(), - ContentType: "application/json", - Body: jsonx.MustMarshal(l), - ACL: s3types.ObjectCannedACLPrivate, - } - } - if err := rt.S3.BatchPut(ctx, uploads, 32); err != nil { - return fmt.Errorf("error writing attached channel logs to storage: %w", err) - } - } - if len(unattached) > 0 { err := BulkQuery(ctx, "insert channel log", rt.DB, sqlInsertChannelLog, unattached) if err != nil { diff --git a/core/models/channel_logs_test.go b/core/models/channel_logs_test.go index 7496acbca..9f882b829 100644 --- a/core/models/channel_logs_test.go +++ b/core/models/channel_logs_test.go @@ -18,7 +18,7 @@ import ( func TestChannelLogsOutgoing(t *testing.T) { ctx, rt := testsuite.Runtime() - defer rt.DB.MustExec(`DELETE FROM channels_channellog`) + defer testsuite.Reset(testsuite.ResetData | testsuite.ResetDynamo) defer httpx.SetRequestor(httpx.DefaultRequestor) httpx.SetRequestor(httpx.NewMockRequestor(map[string][]*httpx.MockResponse{ diff --git a/mailroom.go b/mailroom.go index a1551bcb3..ebf04620b 100644 --- a/mailroom.go +++ b/mailroom.go @@ -123,11 +123,6 @@ func (mr *Mailroom) Start() error { } else { log.Info("sessions bucket ok") } - if err := mr.rt.S3.Test(mr.ctx, c.S3LogsBucket); err != nil { - log.Error("logs bucket not accessible", "error", err) - } else { - log.Info("logs bucket ok") - } // initialize our elastic client mr.rt.ES, err = elasticsearch.NewTypedClient(elasticsearch.Config{Addresses: []string{c.Elastic}, Username: c.ElasticUsername, Password: c.ElasticPassword}) diff --git a/runtime/config.go b/runtime/config.go index aaa838c69..f1c37a6eb 100644 --- a/runtime/config.go +++ b/runtime/config.go @@ -67,7 +67,6 @@ type Config struct { S3Endpoint string `help:"S3 service endpoint, e.g. https://s3.amazonaws.com"` S3AttachmentsBucket string `help:"S3 bucket to write attachments to"` S3SessionsBucket string `help:"S3 bucket to write flow sessions to"` - S3LogsBucket string `help:"S3 bucket to write channel logs to"` S3Minio bool `help:"S3 is actually Minio or other compatible service"` CourierAuthToken string `help:"the authentication token used for requests to Courier"` @@ -128,7 +127,6 @@ func NewDefaultConfig() *Config { S3Endpoint: "https://s3.amazonaws.com", S3AttachmentsBucket: "temba-attachments", S3SessionsBucket: "temba-sessions", - S3LogsBucket: "temba-logs", InstanceID: hostname, LogLevel: slog.LevelWarn, diff --git a/testsuite/testsuite.go b/testsuite/testsuite.go index 3856ed270..04b2e7d51 100644 --- a/testsuite/testsuite.go +++ b/testsuite/testsuite.go @@ -79,7 +79,6 @@ func Runtime() (context.Context, *runtime.Runtime) { cfg.S3Endpoint = "http://localhost:9000" cfg.S3AttachmentsBucket = "test-attachments" cfg.S3SessionsBucket = "test-sessions" - cfg.S3LogsBucket = "test-logs" cfg.S3Minio = true cfg.DynamoEndpoint = "http://localhost:6000" cfg.DynamoTablePrefix = "Test" @@ -213,7 +212,6 @@ func resetRedis() { func resetStorage(ctx context.Context, rt *runtime.Runtime) { rt.S3.EmptyBucket(ctx, rt.Config.S3AttachmentsBucket) rt.S3.EmptyBucket(ctx, rt.Config.S3SessionsBucket) - rt.S3.EmptyBucket(ctx, rt.Config.S3LogsBucket) } // clears indexed data in Elastic @@ -279,6 +277,7 @@ DELETE FROM triggers_trigger WHERE id >= 30000; DELETE FROM channels_channel WHERE id >= 30000; DELETE FROM channels_channelcount; DELETE FROM channels_channelevent; +DELETE FROM channels_channellog; DELETE FROM msgs_msg; DELETE FROM flows_flowrun; DELETE FROM flows_flowpathcount; diff --git a/web/ivr/ivr_test.go b/web/ivr/ivr_test.go index c1491e24b..9ed9864ce 100644 --- a/web/ivr/ivr_test.go +++ b/web/ivr/ivr_test.go @@ -13,8 +13,9 @@ import ( "sync" "testing" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/nyaruka/gocommon/dbutil/assertdb" - "github.com/nyaruka/goflow/assets" + "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/goflow/test" _ "github.com/nyaruka/mailroom/core/handlers" "github.com/nyaruka/mailroom/core/models" @@ -330,10 +331,12 @@ func TestTwilioIVR(t *testing.T) { AND ((status = 'H' AND direction = 'I') OR (status = 'W' AND direction = 'O'))`, testdata.Bob.ID).Returns(2) // check the generated channel logs - logs := getCallLogs(t, rt, testdata.TwilioChannel.UUID) + logs := getCallLogs(t, ctx, rt) assert.Len(t, logs, 17) for _, log := range logs { - assert.NotContains(t, string(log), "sesame") // auth token redacted + for _, httpLog := range log.HttpLogs { + assert.NotContains(t, string(jsonx.MustMarshal(httpLog)), "sesame") // auth token redacted + } } } @@ -629,10 +632,12 @@ func TestVonageIVR(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT count(*) FROM ivr_call WHERE status = 'D' AND contact_id = $1`, testdata.George.ID).Returns(1) // check the generated channel logs - logs := getCallLogs(t, rt, testdata.VonageChannel.UUID) + logs := getCallLogs(t, ctx, rt) assert.Len(t, logs, 16) for _, log := range logs { - assert.NotContains(t, string(log), "BEGIN PRIVATE KEY") // private key redacted + for _, httpLog := range log.HttpLogs { + assert.NotContains(t, string(jsonx.MustMarshal(httpLog)), "BEGIN PRIVATE KEY") // private key redacted + } } // and 2 unattached logs in the database @@ -640,17 +645,19 @@ func TestVonageIVR(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT array_agg(log_type ORDER BY id) FROM channels_channellog WHERE channel_id = $1`, testdata.VonageChannel.ID).Returns([]byte(`{ivr_status,ivr_status}`)) } -func getCallLogs(t *testing.T, rt *runtime.Runtime, channelUUID assets.ChannelUUID) [][]byte { +func getCallLogs(t *testing.T, ctx context.Context, rt *runtime.Runtime) []*clogs.Log { var logUUIDs []clogs.LogUUID err := rt.DB.Select(&logUUIDs, `SELECT unnest(log_uuids) FROM ivr_call ORDER BY id`) require.NoError(t, err) - logs := make([][]byte, len(logUUIDs)) + logs := make([]*clogs.Log, len(logUUIDs)) for i, logUUID := range logUUIDs { - _, body, err := rt.S3.GetObject(context.Background(), rt.Config.S3LogsBucket, fmt.Sprintf("channels/%s/%s/%s.json", channelUUID, logUUID[0:4], logUUID)) + log := &clogs.Log{} + err = rt.Dynamo.GetItem(ctx, "ChannelLogs", map[string]types.AttributeValue{"UUID": &types.AttributeValueMemberS{Value: string(logUUID)}}, log) require.NoError(t, err) - logs[i] = body + logs[i] = log } + return logs } From 05ce30968746a1ff9c27c4d4eeac501fdbe1bdc6 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 18 Sep 2024 13:13:21 -0500 Subject: [PATCH 065/216] Change channel log TTL to be 1 week --- utils/clogs/clog.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/clogs/clog.go b/utils/clogs/clog.go index 0c14df292..60a9ff0d7 100644 --- a/utils/clogs/clog.go +++ b/utils/clogs/clog.go @@ -13,7 +13,7 @@ import ( ) const ( - dynamoTTL = 14 * 24 * time.Hour + dynamoTTL = 7 * 24 * time.Hour // 1 week ) // LogUUID is the type of a channel log UUID (should be v7) From 3e63517472db37d9dbaecc6fbbb420daa62fd1df Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 18 Sep 2024 13:50:35 -0500 Subject: [PATCH 066/216] Log unprocessed items after dynamo batch put operation --- core/models/channel_logs.go | 31 +++++++++++++++++++++++++------ utils/clogs/batch.go | 36 ------------------------------------ utils/clogs/clog_test.go | 4 +++- 3 files changed, 28 insertions(+), 43 deletions(-) delete mode 100644 utils/clogs/batch.go diff --git a/core/models/channel_logs.go b/core/models/channel_logs.go index 4accfb79b..1425f872d 100644 --- a/core/models/channel_logs.go +++ b/core/models/channel_logs.go @@ -4,8 +4,12 @@ import ( "context" "encoding/json" "fmt" + "log/slog" + "slices" "time" + "github.com/aws/aws-sdk-go-v2/service/dynamodb" + "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/mailroom/runtime" @@ -84,12 +88,27 @@ type dbChannelLog struct { // InsertChannelLogs writes the given channel logs to the db func InsertChannelLogs(ctx context.Context, rt *runtime.Runtime, logs []*ChannelLog) error { // write all logs to DynamoDB - cls := make([]*clogs.Log, len(logs)) - for i, l := range logs { - cls[i] = l.Log - } - if err := clogs.BatchPut(ctx, rt.Dynamo, "ChannelLogs", cls); err != nil { - return fmt.Errorf("error writing channel logs: %w", err) + for batch := range slices.Chunk(logs, 25) { + writeReqs := make([]types.WriteRequest, len(batch)) + + for i, l := range batch { + d, err := l.MarshalDynamo() + if err != nil { + return fmt.Errorf("error marshalling log: %w", err) + } + writeReqs[i] = types.WriteRequest{PutRequest: &types.PutRequest{Item: d}} + } + + resp, err := rt.Dynamo.Client.BatchWriteItem(ctx, &dynamodb.BatchWriteItemInput{ + RequestItems: map[string][]types.WriteRequest{rt.Dynamo.TableName("ChannelLogs"): writeReqs}, + }) + if err != nil { + return fmt.Errorf("error writing logs to dynamo: %w", err) + } + if len(resp.UnprocessedItems) > 0 { + // TODO shouldn't happend.. but need to figure out how we would retry these + slog.Error("unprocessed items writing logs to dynamo", "count", len(resp.UnprocessedItems)) + } } unattached := make([]*dbChannelLog, 0, len(logs)) diff --git a/utils/clogs/batch.go b/utils/clogs/batch.go deleted file mode 100644 index 7d174b886..000000000 --- a/utils/clogs/batch.go +++ /dev/null @@ -1,36 +0,0 @@ -package clogs - -import ( - "context" - "fmt" - "slices" - - "github.com/aws/aws-sdk-go-v2/service/dynamodb" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/nyaruka/gocommon/aws/dynamo" -) - -// BatchPut writes multiple logs to DynamoDB in batches of 25. This should probably be a generic function in the -// gocommon/dynamo package but need to think more about errors. -func BatchPut(ctx context.Context, ds *dynamo.Service, table string, logs []*Log) error { - for batch := range slices.Chunk(logs, 25) { - writeReqs := make([]types.WriteRequest, len(batch)) - - for i, l := range batch { - d, err := l.MarshalDynamo() - if err != nil { - return fmt.Errorf("error marshalling log: %w", err) - } - writeReqs[i] = types.WriteRequest{PutRequest: &types.PutRequest{Item: d}} - } - - _, err := ds.Client.BatchWriteItem(ctx, &dynamodb.BatchWriteItemInput{ - RequestItems: map[string][]types.WriteRequest{ds.TableName(table): writeReqs}, - }) - if err != nil { - return fmt.Errorf("error writing logs to db: %w", err) - } - } - - return nil -} diff --git a/utils/clogs/clog_test.go b/utils/clogs/clog_test.go index 2df4a929c..0a8cc1ee9 100644 --- a/utils/clogs/clog_test.go +++ b/utils/clogs/clog_test.go @@ -54,7 +54,9 @@ func TestLogs(t *testing.T) { l2.Error(clogs.NewLogError("code2", "ext", "message")) // write both logs to db - err = clogs.BatchPut(ctx, ds, "ChannelLogs", []*clogs.Log{l1, l2}) + err = ds.PutItem(ctx, "ChannelLogs", l1) + assert.NoError(t, err) + err = ds.PutItem(ctx, "ChannelLogs", l2) assert.NoError(t, err) // read log 1 back from db From f0d22a8921adc1e59c0379302128d52bf8821fc1 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 18 Sep 2024 14:29:20 -0500 Subject: [PATCH 067/216] Update CHANGELOG.md for v9.3.22 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94395590f..10075be2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.22 (2024-09-18) +------------------------- + * Change channel log TTL to be 1 week + * Stop writing channel logs to S3 + v9.3.21 (2024-09-17) ------------------------- * Update deps including goflow From 165f91c385552fe0baaa732731f3a649edb1fece Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 18 Sep 2024 15:58:38 -0500 Subject: [PATCH 068/216] Fix tests --- core/models/starts.go | 12 ++--- core/runner/runner_test.go | 20 +++---- core/search/groups_test.go | 3 +- core/tasks/ivr/cron_test.go | 6 ++- core/tasks/ivr/start_ivr_flow_batch_test.go | 6 ++- core/tasks/starts/start_flow_batch_test.go | 60 +++++++++++++++++++++ testsuite/testsuite.go | 1 + 7 files changed, 82 insertions(+), 26 deletions(-) create mode 100644 core/tasks/starts/start_flow_batch_test.go diff --git a/core/models/starts.go b/core/models/starts.go index 79eb19bb6..6295a73d0 100644 --- a/core/models/starts.go +++ b/core/models/starts.go @@ -158,27 +158,27 @@ func (s *FlowStart) WithParams(params json.RawMessage) *FlowStart { return s } -// MarkStartStarted sets the status for the passed in flow start to S and updates the contact count on it +// MarkStartStarted sets the status of the given start to STARTED, if it's not already set to INTERRUPTED func MarkStartStarted(ctx context.Context, db DBorTx, startID StartID, contactCount int) error { - _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'S', contact_count = $2, modified_on = NOW() WHERE id = $1", startID, contactCount) + _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'S', contact_count = $2, modified_on = NOW() WHERE id = $1 AND status != 'I'", startID, contactCount) if err != nil { return fmt.Errorf("error setting start as started: %w", err) } return nil } -// MarkStartComplete sets the status for the passed in flow start +// MarkStartComplete sets the status of the given start to COMPLETE, if it's not already set to INTERRUPTED func MarkStartComplete(ctx context.Context, db DBorTx, startID StartID) error { - _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'C', modified_on = NOW() WHERE id = $1", startID) + _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'C', modified_on = NOW() WHERE id = $1 AND status != 'I'", startID) if err != nil { return fmt.Errorf("error marking flow start as complete: %w", err) } return nil } -// MarkStartFailed sets the status for the passed in flow start to F +// MarkStartFailed sets the status of the given start to FAILED, if it's not already set to INTERRUPTED func MarkStartFailed(ctx context.Context, db DBorTx, startID StartID) error { - _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'F', modified_on = NOW() WHERE id = $1", startID) + _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'F', modified_on = NOW() WHERE id = $1 AND status != 'I'", startID) if err != nil { return fmt.Errorf("error setting flow start as failed: %w", err) } diff --git a/core/runner/runner_test.go b/core/runner/runner_test.go index d9f2ae1e3..012cd8687 100644 --- a/core/runner/runner_test.go +++ b/core/runner/runner_test.go @@ -25,9 +25,8 @@ import ( func TestStartFlowBatch(t *testing.T) { ctx, rt := testsuite.Runtime() - rt.Config.AndroidCredentialsFile = `testdata/android.json` - defer testsuite.Reset(testsuite.ResetAll) + defer testsuite.Reset(testsuite.ResetAll) // because it changes contacts oa := testdata.Org1.Load(rt) @@ -38,7 +37,6 @@ func TestStartFlowBatch(t *testing.T) { require.NoError(t, err) batch1 := start1.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, false, 4) - batch2 := start1.CreateBatch([]models.ContactID{testdata.George.ID, testdata.Alexandria.ID}, true, 4) // start the first batch... sessions, err := runner.StartFlowBatch(ctx, rt, oa, start1, batch1) @@ -57,23 +55,17 @@ func TestStartFlowBatch(t *testing.T) { AND direction = 'O' AND msg_type = 'T'`, pq.Array([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID})). Returns(2) - assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, start1.ID).Returns("P") - - // start the second batch... - sessions, err = runner.StartFlowBatch(ctx, rt, oa, start1, batch2) - require.NoError(t, err) - assert.Len(t, sessions, 2) - - assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, start1.ID).Returns("C") - // create a start object with params testdata.InsertFlowStart(rt, testdata.Org1, testdata.Admin, testdata.IncomingExtraFlow, nil) start2 := models.NewFlowStart(models.OrgID(1), models.StartTypeManual, testdata.IncomingExtraFlow.ID). WithContactIDs([]models.ContactID{testdata.Cathy.ID}). WithParams([]byte(`{"name":"Fred", "age":33}`)) - batch3 := start2.CreateBatch([]models.ContactID{testdata.Cathy.ID}, true, 1) + err = models.InsertFlowStarts(ctx, rt.DB, []*models.FlowStart{start2}) + require.NoError(t, err) + + batch2 := start2.CreateBatch([]models.ContactID{testdata.Cathy.ID}, true, 1) - sessions, err = runner.StartFlowBatch(ctx, rt, oa, start1, batch3) + sessions, err = runner.StartFlowBatch(ctx, rt, oa, start2, batch2) require.NoError(t, err) assert.Len(t, sessions, 1) diff --git a/core/search/groups_test.go b/core/search/groups_test.go index 31d73ec1c..974847a35 100644 --- a/core/search/groups_test.go +++ b/core/search/groups_test.go @@ -62,8 +62,7 @@ func TestSmartGroups(t *testing.T) { assert.NoError(t, err) count, err := search.PopulateSmartGroup(ctx, rt, oa, testdata.DoctorsGroup.ID, tc.query) - assert.NoError(t, err, "error populating smart group for: %s", tc.query) - + assert.NoError(t, err, "%d: error populating smart group") assert.Equal(t, count, len(tc.expectedContactIDs), "%d: contact count mismatch", i) // assert the current group membership diff --git a/core/tasks/ivr/cron_test.go b/core/tasks/ivr/cron_test.go index f517edb4d..1b63e6600 100644 --- a/core/tasks/ivr/cron_test.go +++ b/core/tasks/ivr/cron_test.go @@ -16,7 +16,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestRetries(t *testing.T) { +func TestRetryCallsCron(t *testing.T) { ctx, rt := testsuite.Runtime() rc := rt.RP.Get() defer rc.Close() @@ -32,8 +32,10 @@ func TestRetries(t *testing.T) { // create a flow start for cathy start := models.NewFlowStart(testdata.Org1.ID, models.StartTypeTrigger, testdata.IVRFlow.ID). WithContactIDs([]models.ContactID{testdata.Cathy.ID}) + err := models.InsertFlowStarts(ctx, rt.DB, []*models.FlowStart{start}) + require.NoError(t, err) - err := tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, queues.DefaultPriority) + err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, queues.DefaultPriority) require.NoError(t, err) service.callError = nil diff --git a/core/tasks/ivr/start_ivr_flow_batch_test.go b/core/tasks/ivr/start_ivr_flow_batch_test.go index 01363fbb4..7d7192299 100644 --- a/core/tasks/ivr/start_ivr_flow_batch_test.go +++ b/core/tasks/ivr/start_ivr_flow_batch_test.go @@ -21,7 +21,7 @@ import ( ) func TestIVR(t *testing.T) { - _, rt := testsuite.Runtime() + ctx, rt := testsuite.Runtime() rc := rt.RP.Get() defer rc.Close() @@ -36,10 +36,12 @@ func TestIVR(t *testing.T) { // create a flow start for cathy start := models.NewFlowStart(testdata.Org1.ID, models.StartTypeTrigger, testdata.IVRFlow.ID). WithContactIDs([]models.ContactID{testdata.Cathy.ID}) + err := models.InsertFlowStarts(ctx, rt.DB, []*models.FlowStart{start}) + require.NoError(t, err) service.callError = fmt.Errorf("unable to create call") - err := tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, queues.DefaultPriority) + err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, queues.DefaultPriority) require.NoError(t, err) testsuite.FlushTasks(t, rt) diff --git a/core/tasks/starts/start_flow_batch_test.go b/core/tasks/starts/start_flow_batch_test.go new file mode 100644 index 000000000..6b59c39b5 --- /dev/null +++ b/core/tasks/starts/start_flow_batch_test.go @@ -0,0 +1,60 @@ +package starts_test + +import ( + "testing" + + "github.com/lib/pq" + "github.com/nyaruka/gocommon/dbutil/assertdb" + _ "github.com/nyaruka/mailroom/core/handlers" + "github.com/nyaruka/mailroom/core/models" + "github.com/nyaruka/mailroom/core/tasks" + "github.com/nyaruka/mailroom/core/tasks/starts" + "github.com/nyaruka/mailroom/testsuite" + "github.com/nyaruka/mailroom/testsuite/testdata" + "github.com/nyaruka/mailroom/utils/queues" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStartFlowBatchTask(t *testing.T) { + ctx, rt := testsuite.Runtime() + rc := rt.RP.Get() + defer rc.Close() + + defer testsuite.Reset(testsuite.ResetData) + + // create a start object + start1 := models.NewFlowStart(models.OrgID(1), models.StartTypeManual, testdata.SingleMessage.ID). + WithContactIDs([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID, testdata.George.ID, testdata.Alexandria.ID}) + err := models.InsertFlowStarts(ctx, rt.DB, []*models.FlowStart{start1}) + require.NoError(t, err) + + batch1 := start1.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, false, 4) + batch2 := start1.CreateBatch([]models.ContactID{testdata.George.ID, testdata.Alexandria.ID}, true, 4) + + // start the first batch... + err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: batch1}, queues.DefaultPriority) + assert.NoError(t, err) + testsuite.FlushTasks(t, rt) + + assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowsession WHERE contact_id = ANY($1) + AND status = 'C' AND responded = FALSE AND org_id = 1 AND call_id IS NULL AND output IS NOT NULL`, pq.Array([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID})). + Returns(2) + + assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun WHERE contact_id = ANY($1) and flow_id = $2 AND responded = FALSE AND org_id = 1 AND status = 'C' + AND results IS NOT NULL AND path IS NOT NULL AND session_id IS NOT NULL`, pq.Array([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}), testdata.SingleMessage.ID). + Returns(2) + + assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_msg WHERE contact_id = ANY($1) AND text = 'Hey, how are you?' AND org_id = 1 AND status = 'Q' + AND direction = 'O' AND msg_type = 'T'`, pq.Array([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID})). + Returns(2) + + assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, start1.ID).Returns("P") + + // start the second and final batch... + err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: batch2}, queues.DefaultPriority) + assert.NoError(t, err) + testsuite.FlushTasks(t, rt) + + assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, start1.ID).Returns("C") +} diff --git a/testsuite/testsuite.go b/testsuite/testsuite.go index 04b2e7d51..eb22d077a 100644 --- a/testsuite/testsuite.go +++ b/testsuite/testsuite.go @@ -284,6 +284,7 @@ DELETE FROM flows_flowpathcount; DELETE FROM flows_flownodecount; DELETE FROM flows_flowrunstatuscount; DELETE FROM flows_flowcategorycount; +DELETE FROM flows_flowstartcount; DELETE FROM flows_flowstart_contacts; DELETE FROM flows_flowstart_groups; DELETE FROM flows_flowstart; From 09e158a634007c239091952f6e680139cc4561c6 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 18 Sep 2024 16:39:53 -0500 Subject: [PATCH 069/216] Still write old task format to avoid breaking task processing by non-updated nodes --- core/models/starts.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/core/models/starts.go b/core/models/starts.go index 6295a73d0..4400b27a4 100644 --- a/core/models/starts.go +++ b/core/models/starts.go @@ -269,6 +269,15 @@ func (s *FlowStart) CreateBatch(contactIDs []ContactID, last bool, totalContacts ContactIDs: contactIDs, IsLast: last, TotalContacts: totalContacts, + + // TODO remove these fields once all nodes using new task format + StartType: s.StartType, + OrgID: s.OrgID, + FlowID: s.FlowID, + ParentSummary: s.ParentSummary, + SessionHistory: s.SessionHistory, + Params: s.Params, + CreatedByID: s.CreatedByID, } } @@ -278,6 +287,16 @@ type FlowStartBatch struct { ContactIDs []ContactID `json:"contact_ids"` IsLast bool `json:"is_last,omitempty"` TotalContacts int `json:"total_contacts"` + + // TODO remove these fields once all nodes using new task format + StartType StartType `json:"start_type"` + OrgID OrgID `json:"org_id"` + CreatedByID UserID `json:"created_by_id"` + FlowID FlowID `json:"flow_id"` + FlowType FlowType `json:"flow_type"` + Params null.JSON `json:"params,omitempty"` + ParentSummary null.JSON `json:"parent_summary,omitempty"` + SessionHistory null.JSON `json:"session_history,omitempty"` } // ReadSessionHistory reads a session history from the given JSON From 3d3f8019779f3c109a1c283ba5fd839583a0b9fe Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 18 Sep 2024 16:50:24 -0500 Subject: [PATCH 070/216] Update CHANGELOG.md for v9.3.23 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10075be2b..431d7bf9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.23 (2024-09-18) +------------------------- + * Rework flow start batch processing so that we check the start status in case it's interrupted + v9.3.22 (2024-09-18) ------------------------- * Change channel log TTL to be 1 week From 14c7997359a84fa2e02ef325623665b87ebad145 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 19 Sep 2024 10:41:46 -0500 Subject: [PATCH 071/216] Remove no longer needed fields on flow start batch tasks --- core/models/starts.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/core/models/starts.go b/core/models/starts.go index 4400b27a4..6295a73d0 100644 --- a/core/models/starts.go +++ b/core/models/starts.go @@ -269,15 +269,6 @@ func (s *FlowStart) CreateBatch(contactIDs []ContactID, last bool, totalContacts ContactIDs: contactIDs, IsLast: last, TotalContacts: totalContacts, - - // TODO remove these fields once all nodes using new task format - StartType: s.StartType, - OrgID: s.OrgID, - FlowID: s.FlowID, - ParentSummary: s.ParentSummary, - SessionHistory: s.SessionHistory, - Params: s.Params, - CreatedByID: s.CreatedByID, } } @@ -287,16 +278,6 @@ type FlowStartBatch struct { ContactIDs []ContactID `json:"contact_ids"` IsLast bool `json:"is_last,omitempty"` TotalContacts int `json:"total_contacts"` - - // TODO remove these fields once all nodes using new task format - StartType StartType `json:"start_type"` - OrgID OrgID `json:"org_id"` - CreatedByID UserID `json:"created_by_id"` - FlowID FlowID `json:"flow_id"` - FlowType FlowType `json:"flow_type"` - Params null.JSON `json:"params,omitempty"` - ParentSummary null.JSON `json:"parent_summary,omitempty"` - SessionHistory null.JSON `json:"session_history,omitempty"` } // ReadSessionHistory reads a session history from the given JSON From 6e842791aa0cbb4fefeb28df80d681269d652e7e Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 19 Sep 2024 10:55:10 -0500 Subject: [PATCH 072/216] Improve test for start flow batch task --- core/tasks/starts/start_flow_batch_test.go | 31 +++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/core/tasks/starts/start_flow_batch_test.go b/core/tasks/starts/start_flow_batch_test.go index 6b59c39b5..37cf2d41d 100644 --- a/core/tasks/starts/start_flow_batch_test.go +++ b/core/tasks/starts/start_flow_batch_test.go @@ -23,7 +23,7 @@ func TestStartFlowBatchTask(t *testing.T) { defer testsuite.Reset(testsuite.ResetData) - // create a start object + // create a start start1 := models.NewFlowStart(models.OrgID(1), models.StartTypeManual, testdata.SingleMessage.ID). WithContactIDs([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID, testdata.George.ID, testdata.Alexandria.ID}) err := models.InsertFlowStarts(ctx, rt.DB, []*models.FlowStart{start1}) @@ -56,5 +56,34 @@ func TestStartFlowBatchTask(t *testing.T) { assert.NoError(t, err) testsuite.FlushTasks(t, rt) + assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun WHERE start_id = $1`, start1.ID).Returns(4) assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, start1.ID).Returns("C") + + // create a second start + start2 := models.NewFlowStart(models.OrgID(1), models.StartTypeManual, testdata.SingleMessage.ID). + WithContactIDs([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID, testdata.George.ID, testdata.Alexandria.ID}) + err = models.InsertFlowStarts(ctx, rt.DB, []*models.FlowStart{start2}) + require.NoError(t, err) + + start2Batch1 := start2.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, false, 4) + start2Batch2 := start2.CreateBatch([]models.ContactID{testdata.George.ID, testdata.Alexandria.ID}, true, 4) + + // start the first batch... + err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: start2Batch1}, queues.DefaultPriority) + assert.NoError(t, err) + testsuite.FlushTasks(t, rt) + + assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun WHERE start_id = $1`, start2.ID).Returns(2) + + // interrupt the start + rt.DB.MustExec(`UPDATE flows_flowstart SET status = 'I' WHERE id = $1`, start2.ID) + + // start the second batch... + err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: start2Batch2}, queues.DefaultPriority) + assert.NoError(t, err) + testsuite.FlushTasks(t, rt) + + // check that second batch didn't create any runs and start status is still interrupted + assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun WHERE start_id = $1`, start2.ID).Returns(2) + assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, start2.ID).Returns("I") } From 18c836aadb518fffe7f148b63310e95d463361e5 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 19 Sep 2024 12:22:08 -0500 Subject: [PATCH 073/216] Rework session_triggered event handling to make it easier to switch to non-persistent starts --- core/handlers/broadcast_created.go | 4 +- core/handlers/broadcast_created_test.go | 2 +- core/handlers/session_triggered.go | 2 +- ...art_broadcasts.go => create_broadcasts.go} | 20 +++----- .../{insert_start.go => create_starts.go} | 34 +++++++------- core/hooks/start_start.go | 47 ------------------- 6 files changed, 26 insertions(+), 83 deletions(-) rename core/hooks/{start_broadcasts.go => create_broadcasts.go} (54%) rename core/hooks/{insert_start.go => create_starts.go} (62%) delete mode 100644 core/hooks/start_start.go diff --git a/core/handlers/broadcast_created.go b/core/handlers/broadcast_created.go index 605588e4e..9004a7a36 100644 --- a/core/handlers/broadcast_created.go +++ b/core/handlers/broadcast_created.go @@ -16,14 +16,12 @@ func init() { models.RegisterEventHandler(events.TypeBroadcastCreated, handleBroadcastCreated) } -// handleBroadcastCreated is called for each broadcast created event across our scene func handleBroadcastCreated(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, oa *models.OrgAssets, scene *models.Scene, e flows.Event) error { event := e.(*events.BroadcastCreatedEvent) slog.Debug("broadcast created", "contact", scene.ContactUUID(), "session", scene.SessionID(), "translations", event.Translations[event.BaseLanguage]) - // schedule this for being started after our scene are committed - scene.AppendToEventPostCommitHook(hooks.StartBroadcastsHook, event) + scene.AppendToEventPostCommitHook(hooks.CreateBroadcastsHook, event) return nil } diff --git a/core/handlers/broadcast_created_test.go b/core/handlers/broadcast_created_test.go index 834405ff0..bb07d26e5 100644 --- a/core/handlers/broadcast_created_test.go +++ b/core/handlers/broadcast_created_test.go @@ -42,7 +42,7 @@ func TestBroadcastCreated(t *testing.T) { rc := rt.RP.Get() defer rc.Close() - task, err := tasks.HandlerQueue.Pop(rc) + task, err := tasks.BatchQueue.Pop(rc) assert.NoError(t, err) assert.NotNil(t, task) bcast := models.Broadcast{} diff --git a/core/handlers/session_triggered.go b/core/handlers/session_triggered.go index 751155b9b..c500275d2 100644 --- a/core/handlers/session_triggered.go +++ b/core/handlers/session_triggered.go @@ -21,7 +21,7 @@ func handleSessionTriggered(ctx context.Context, rt *runtime.Runtime, tx *sqlx.T slog.Debug("session triggered", "contact", scene.ContactUUID(), "session", scene.SessionID(), slog.Group("flow", "uuid", event.Flow.UUID, "name", event.Flow.Name)) - scene.AppendToEventPreCommitHook(hooks.InsertStartHook, event) + scene.AppendToEventPreCommitHook(hooks.CreateStartsHook, event) return nil } diff --git a/core/hooks/start_broadcasts.go b/core/hooks/create_broadcasts.go similarity index 54% rename from core/hooks/start_broadcasts.go rename to core/hooks/create_broadcasts.go index c9e923eba..545010340 100644 --- a/core/hooks/start_broadcasts.go +++ b/core/hooks/create_broadcasts.go @@ -13,13 +13,13 @@ import ( "github.com/nyaruka/mailroom/utils/queues" ) -// StartBroadcastsHook is our hook for starting broadcasts -var StartBroadcastsHook models.EventCommitHook = &startBroadcastsHook{} +// CreateBroadcastsHook is our hook for creating broadcasts +var CreateBroadcastsHook models.EventCommitHook = &createBroadcastsHook{} -type startBroadcastsHook struct{} +type createBroadcastsHook struct{} // Apply queues up our broadcasts for sending -func (h *startBroadcastsHook) Apply(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, oa *models.OrgAssets, scenes map[*models.Scene][]any) error { +func (h *createBroadcastsHook) Apply(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, oa *models.OrgAssets, scenes map[*models.Scene][]any) error { rc := rt.RP.Get() defer rc.Close() @@ -28,21 +28,13 @@ func (h *startBroadcastsHook) Apply(ctx context.Context, rt *runtime.Runtime, tx for _, e := range es { event := e.(*events.BroadcastCreatedEvent) + // create a non-persistent broadcast bcast, err := models.NewBroadcastFromEvent(ctx, tx, oa, event) if err != nil { return fmt.Errorf("error creating broadcast: %w", err) } - taskQ := tasks.HandlerQueue - priority := queues.DefaultPriority - - // if we are starting groups, queue to our batch queue instead, but with high priority - if len(bcast.GroupIDs) > 0 { - taskQ = tasks.BatchQueue - priority = queues.HighPriority - } - - err = tasks.Queue(rc, taskQ, oa.OrgID(), &msgs.SendBroadcastTask{Broadcast: bcast}, priority) + err = tasks.Queue(rc, tasks.BatchQueue, oa.OrgID(), &msgs.SendBroadcastTask{Broadcast: bcast}, queues.DefaultPriority) if err != nil { return fmt.Errorf("error queuing broadcast task: %w", err) } diff --git a/core/hooks/insert_start.go b/core/hooks/create_starts.go similarity index 62% rename from core/hooks/insert_start.go rename to core/hooks/create_starts.go index 9dff0ad41..bd63ebfb3 100644 --- a/core/hooks/insert_start.go +++ b/core/hooks/create_starts.go @@ -8,23 +8,24 @@ import ( "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/goflow/flows/events" "github.com/nyaruka/mailroom/core/models" + "github.com/nyaruka/mailroom/core/tasks" + "github.com/nyaruka/mailroom/core/tasks/starts" "github.com/nyaruka/mailroom/runtime" + "github.com/nyaruka/mailroom/utils/queues" ) -// InsertStartHook is our hook to fire insert our starts -var InsertStartHook models.EventCommitHook = &insertStartHook{} +// CreateStartsHook is our hook to fire our scene starts +var CreateStartsHook models.EventCommitHook = &createStartsHook{} -type insertStartHook struct{} +type createStartsHook struct{} -// Apply inserts our starts -func (h *insertStartHook) Apply(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, oa *models.OrgAssets, scenes map[*models.Scene][]any) error { +// Apply queues up our flow starts +func (h *createStartsHook) Apply(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, oa *models.OrgAssets, scenes map[*models.Scene][]any) error { rc := rt.RP.Get() defer rc.Close() - starts := make([]*models.FlowStart, 0, len(scenes)) - // for each of our scene - for s, es := range scenes { + for _, es := range scenes { for _, e := range es { event := e.(*events.SessionTriggeredEvent) @@ -66,18 +67,17 @@ func (h *insertStartHook) Apply(ctx context.Context, rt *runtime.Runtime, tx *sq WithParentSummary(event.RunSummary). WithSessionHistory(historyJSON) - starts = append(starts, start) + // TODO rework start batch handling to not require a persisted start + if err := models.InsertFlowStarts(ctx, tx, []*models.FlowStart{start}); err != nil { + return fmt.Errorf("error inserting flow starts for scene triggers: %w", err) + } - // this will add our task for our start after we commit - s.AppendToEventPostCommitHook(StartStartHook, start) + err = tasks.Queue(rc, tasks.BatchQueue, oa.OrgID(), &starts.StartFlowTask{FlowStart: start}, queues.DefaultPriority) + if err != nil { + return fmt.Errorf("error queuing flow start: %w", err) + } } } - // insert all our starts - err := models.InsertFlowStarts(ctx, tx, starts) - if err != nil { - return fmt.Errorf("error inserting flow starts for scene triggers: %w", err) - } - return nil } diff --git a/core/hooks/start_start.go b/core/hooks/start_start.go deleted file mode 100644 index 8c17a57b7..000000000 --- a/core/hooks/start_start.go +++ /dev/null @@ -1,47 +0,0 @@ -package hooks - -import ( - "context" - "fmt" - - "github.com/jmoiron/sqlx" - "github.com/nyaruka/mailroom/core/models" - "github.com/nyaruka/mailroom/core/tasks" - "github.com/nyaruka/mailroom/core/tasks/starts" - "github.com/nyaruka/mailroom/runtime" - "github.com/nyaruka/mailroom/utils/queues" -) - -// StartStartHook is our hook to fire our scene starts -var StartStartHook models.EventCommitHook = &startStartHook{} - -type startStartHook struct{} - -// Apply queues up our flow starts -func (h *startStartHook) Apply(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, oa *models.OrgAssets, scenes map[*models.Scene][]any) error { - rc := rt.RP.Get() - defer rc.Close() - - // for each of our scene - for _, es := range scenes { - for _, e := range es { - start := e.(*models.FlowStart) - - taskQ := tasks.HandlerQueue - priority := queues.DefaultPriority - - // if we are starting groups, queue to our batch queue instead, but with high priority - if len(start.GroupIDs) > 0 || start.Query != "" { - taskQ = tasks.BatchQueue - priority = queues.HighPriority - } - - err := tasks.Queue(rc, taskQ, oa.OrgID(), &starts.StartFlowTask{FlowStart: start}, priority) - if err != nil { - return fmt.Errorf("error queuing flow start: %w", err) - } - } - } - - return nil -} From 66efe467d3df1dd05144c56603e09620e8ea4a68 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 19 Sep 2024 13:36:22 -0500 Subject: [PATCH 074/216] Update goflow --- core/handlers/broadcast_created_test.go | 2 +- core/handlers/session_triggered_test.go | 9 ++++----- go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/core/handlers/broadcast_created_test.go b/core/handlers/broadcast_created_test.go index bb07d26e5..26d0493d2 100644 --- a/core/handlers/broadcast_created_test.go +++ b/core/handlers/broadcast_created_test.go @@ -27,7 +27,7 @@ func TestBroadcastCreated(t *testing.T) { { Actions: handlers.ContactActionMap{ testdata.Cathy: []flows.Action{ - actions.NewSendBroadcast(handlers.NewActionUUID(), "hello world", nil, nil, []urns.URN{urns.URN("tel:+12065551212")}, nil, nil, nil), + actions.NewSendBroadcast(handlers.NewActionUUID(), "hello world", nil, nil, nil, nil, "", []urns.URN{urns.URN("tel:+12065551212")}, nil), }, }, SQLAssertions: []handlers.SQLAssertion{ diff --git a/core/handlers/session_triggered_test.go b/core/handlers/session_triggered_test.go index 19bae9185..62bf1eb72 100644 --- a/core/handlers/session_triggered_test.go +++ b/core/handlers/session_triggered_test.go @@ -44,7 +44,7 @@ func TestSessionTriggered(t *testing.T) { { Actions: handlers.ContactActionMap{ testdata.Cathy: []flows.Action{ - actions.NewStartSession(handlers.NewActionUUID(), simpleFlow.Reference(), nil, []*flows.ContactReference{contactRef}, []*assets.GroupReference{groupRef}, nil, true), + actions.NewStartSession(handlers.NewActionUUID(), simpleFlow.Reference(), []*assets.GroupReference{groupRef}, []*flows.ContactReference{contactRef}, "", nil, nil, true), }, }, SQLAssertions: []handlers.SQLAssertion{ @@ -105,13 +105,12 @@ func TestQuerySessionTriggered(t *testing.T) { favoriteFlow, err := oa.FlowByID(testdata.Favorites.ID) assert.NoError(t, err) - sessionAction := actions.NewStartSession(handlers.NewActionUUID(), favoriteFlow.Reference(), nil, nil, nil, nil, true) - sessionAction.ContactQuery = "name ~ @contact.name" - tcs := []handlers.TestCase{ { Actions: handlers.ContactActionMap{ - testdata.Cathy: []flows.Action{sessionAction}, + testdata.Cathy: []flows.Action{ + actions.NewStartSession(handlers.NewActionUUID(), favoriteFlow.Reference(), nil, nil, "name ~ @contact.name", nil, nil, true), + }, }, SQLAssertions: []handlers.SQLAssertion{ { diff --git a/go.mod b/go.mod index e867e5c7a..b28b2a72f 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 github.com/nyaruka/gocommon v1.59.0 - github.com/nyaruka/goflow v0.222.3 + github.com/nyaruka/goflow v0.222.4 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 github.com/nyaruka/rp-indexer/v9 v9.2.0 diff --git a/go.sum b/go.sum index 2c272d628..e13ee5ad1 100644 --- a/go.sum +++ b/go.sum @@ -196,8 +196,8 @@ github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= github.com/nyaruka/gocommon v1.59.0 h1:XC//IVSOWawBXEZqDiYhqxrIHWo2QPPeCIkAm4n4sY0= github.com/nyaruka/gocommon v1.59.0/go.mod h1:Upj2DG1iL55YcfF7rve8CRrKGjMaEn0jWUIWbQQgTFQ= -github.com/nyaruka/goflow v0.222.3 h1:Uqts5SvSl83eZegJEfbOKg7DC3R1tjiSATzUmAV40No= -github.com/nyaruka/goflow v0.222.3/go.mod h1:iwdjLwomV3thGZeWhybtmDhujbooIkpTn1vUbso5ReY= +github.com/nyaruka/goflow v0.222.4 h1:BP15ujzzx2QBzn0j2erM2VUBtpEoG3gchmRCN44IzP8= +github.com/nyaruka/goflow v0.222.4/go.mod h1:iwdjLwomV3thGZeWhybtmDhujbooIkpTn1vUbso5ReY= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= From 26fa1ad6b5f73ea3c3de279ab3136ef4301ba33f Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 19 Sep 2024 14:33:29 -0500 Subject: [PATCH 075/216] Add support for non-persisted starts --- core/models/starts.go | 18 ++++++++++++++---- core/runner/runner.go | 19 +++++++++++-------- core/tasks/ivr/start_ivr_flow_batch.go | 17 ++++++++++++----- core/tasks/starts/start_flow.go | 20 +++++++++++++------- core/tasks/starts/start_flow_batch.go | 17 ++++++++++++----- core/tasks/starts/start_flow_batch_test.go | 21 +++++++++++++++++++++ core/tasks/starts/start_flow_test.go | 18 ++++++++++++++++++ 7 files changed, 101 insertions(+), 29 deletions(-) diff --git a/core/models/starts.go b/core/models/starts.go index 6295a73d0..886001a32 100644 --- a/core/models/starts.go +++ b/core/models/starts.go @@ -77,7 +77,7 @@ func (e Exclusions) Value() (driver.Value, error) { return json.Marshal(e) } // FlowStart represents the top level flow start in our system type FlowStart struct { - ID StartID `json:"start_id" db:"id"` + ID StartID `json:"start_id" db:"id"` // null for non-persisted start tasks used by flow actions UUID uuids.UUID `json:"-" db:"uuid"` OrgID OrgID `json:"org_id" db:"org_id"` Status StartStatus `json:"-" db:"status"` @@ -264,17 +264,27 @@ INSERT INTO flows_flowstart_groups(flowstart_id, contactgroup_id) VALUES(:flowst // CreateBatch creates a batch for this start using the passed in contact ids func (s *FlowStart) CreateBatch(contactIDs []ContactID, last bool, totalContacts int) *FlowStartBatch { - return &FlowStartBatch{ - StartID: s.ID, + b := &FlowStartBatch{ ContactIDs: contactIDs, IsLast: last, TotalContacts: totalContacts, } + + if s.ID != NilStartID { + b.StartID = s.ID + } else { + b.Start = s + } + + return b } // FlowStartBatch represents a single flow batch that needs to be started type FlowStartBatch struct { - StartID StartID `json:"start_id"` + // for persisted starts start_id is set, for non-persisted starts like flow actions, start is set + StartID StartID `json:"start_id,omitempty"` + Start *FlowStart `json:"start,omitempty"` + ContactIDs []ContactID `json:"contact_ids"` IsLast bool `json:"is_last,omitempty"` TotalContacts int `json:"total_contacts"` diff --git a/core/runner/runner.go b/core/runner/runner.go index 58654cea8..0d9113a12 100644 --- a/core/runner/runner.go +++ b/core/runner/runner.go @@ -182,21 +182,24 @@ func StartFlowBatch(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAsse return tb.WithUser(flowUser).WithOrigin(startTypeToOrigin[start.StartType]).Build() } - // before committing our runs we want to set the start they are associated with - updateStartID := func(ctx context.Context, tx *sqlx.Tx, rp *redis.Pool, oa *models.OrgAssets, sessions []*models.Session) error { - // for each run in our sessions, set the start id - for _, s := range sessions { - for _, r := range s.Runs() { - r.SetStartID(batch.StartID) + // if we have a persisted start, we need to set its id on runs before they are saved + var commitHook models.SessionCommitHook + if start.ID != models.NilStartID { + commitHook = func(ctx context.Context, tx *sqlx.Tx, rp *redis.Pool, oa *models.OrgAssets, sessions []*models.Session) error { + // for each run in our sessions, set the start id + for _, s := range sessions { + for _, r := range s.Runs() { + r.SetStartID(batch.StartID) + } } + return nil } - return nil } options := &StartOptions{ Interrupt: flow.FlowType().Interrupts(), TriggerBuilder: triggerBuilder, - CommitHook: updateStartID, + CommitHook: commitHook, } sessions, err := StartFlow(ctx, rt, oa, flow, batch.ContactIDs, options) diff --git a/core/tasks/ivr/start_ivr_flow_batch.go b/core/tasks/ivr/start_ivr_flow_batch.go index 916ec3236..d33311710 100644 --- a/core/tasks/ivr/start_ivr_flow_batch.go +++ b/core/tasks/ivr/start_ivr_flow_batch.go @@ -37,10 +37,17 @@ func (t *StartIVRFlowBatchTask) WithAssets() models.Refresh { } func (t *StartIVRFlowBatchTask) Perform(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets) error { - // fetch the start that this batch is part of - start, err := models.GetFlowStartByID(ctx, rt.DB, t.StartID) - if err != nil { - return fmt.Errorf("error loading flow start for batch: %w", err) + var start *models.FlowStart + var err error + + // if this batch belongs to a persisted start, fetch it + if t.StartID != models.NilStartID { + start, err = models.GetFlowStartByID(ctx, rt.DB, t.StartID) + if err != nil { + return fmt.Errorf("error loading flow start for batch: %w", err) + } + } else { + start = t.Start // otherwise use start from the task } // if this start was interrupted, we're done @@ -71,7 +78,7 @@ func (t *StartIVRFlowBatchTask) Perform(ctx context.Context, rt *runtime.Runtime } // if this is a last batch, mark our start as started - if t.IsLast { + if t.IsLast && start.ID != models.NilStartID { if err := models.MarkStartComplete(ctx, rt.DB, start.ID); err != nil { return fmt.Errorf("error trying to set batch as complete: %w", err) } diff --git a/core/tasks/starts/start_flow.go b/core/tasks/starts/start_flow.go index cd09ebd25..80b8b6fc3 100644 --- a/core/tasks/starts/start_flow.go +++ b/core/tasks/starts/start_flow.go @@ -47,7 +47,9 @@ func (t *StartFlowTask) WithAssets() models.Refresh { func (t *StartFlowTask) Perform(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets) error { if err := createFlowStartBatches(ctx, rt, oa, t.FlowStart); err != nil { - models.MarkStartFailed(ctx, rt.DB, t.FlowStart.ID) + if t.FlowStart.ID != models.NilStartID { + models.MarkStartFailed(ctx, rt.DB, t.FlowStart.ID) + } // if error is user created query error.. don't escalate error to sentry isQueryError, _ := contactql.IsQueryError(err) @@ -98,16 +100,20 @@ func createFlowStartBatches(ctx context.Context, rt *runtime.Runtime, oa *models } // mark our start as starting, last task will mark as complete - err = models.MarkStartStarted(ctx, rt.DB, start.ID, len(contactIDs)) - if err != nil { - return fmt.Errorf("error marking start as started: %w", err) + if start.ID != models.NilStartID { + err = models.MarkStartStarted(ctx, rt.DB, start.ID, len(contactIDs)) + if err != nil { + return fmt.Errorf("error marking start as started: %w", err) + } } // if there are no contacts to start, mark our start as complete, we are done if len(contactIDs) == 0 { - err = models.MarkStartComplete(ctx, rt.DB, start.ID) - if err != nil { - return fmt.Errorf("error marking start as complete: %w", err) + if start.ID != models.NilStartID { + err = models.MarkStartComplete(ctx, rt.DB, start.ID) + if err != nil { + return fmt.Errorf("error marking start as complete: %w", err) + } } return nil } diff --git a/core/tasks/starts/start_flow_batch.go b/core/tasks/starts/start_flow_batch.go index 940d77e93..912f9fe7e 100644 --- a/core/tasks/starts/start_flow_batch.go +++ b/core/tasks/starts/start_flow_batch.go @@ -36,10 +36,17 @@ func (t *StartFlowBatchTask) WithAssets() models.Refresh { } func (t *StartFlowBatchTask) Perform(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets) error { - // fetch the start that this batch is part of - start, err := models.GetFlowStartByID(ctx, rt.DB, t.StartID) - if err != nil { - return fmt.Errorf("error loading flow start for batch: %w", err) + var start *models.FlowStart + var err error + + // if this batch belongs to a persisted start, fetch it + if t.StartID != models.NilStartID { + start, err = models.GetFlowStartByID(ctx, rt.DB, t.StartID) + if err != nil { + return fmt.Errorf("error loading flow start for batch: %w", err) + } + } else { + start = t.Start // otherwise use start from the task } // if this start was interrupted, we're done @@ -54,7 +61,7 @@ func (t *StartFlowBatchTask) Perform(ctx context.Context, rt *runtime.Runtime, o } // if this is our last batch, mark start as done - if t.IsLast { + if t.IsLast && start.ID != models.NilStartID { if err := models.MarkStartComplete(ctx, rt.DB, start.ID); err != nil { return fmt.Errorf("error marking start #%d as complete: %w", start.ID, err) } diff --git a/core/tasks/starts/start_flow_batch_test.go b/core/tasks/starts/start_flow_batch_test.go index 37cf2d41d..e17831188 100644 --- a/core/tasks/starts/start_flow_batch_test.go +++ b/core/tasks/starts/start_flow_batch_test.go @@ -87,3 +87,24 @@ func TestStartFlowBatchTask(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun WHERE start_id = $1`, start2.ID).Returns(2) assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, start2.ID).Returns("I") } + +func TestStartFlowBatchTaskNonPersistedStart(t *testing.T) { + _, rt := testsuite.Runtime() + rc := rt.RP.Get() + defer rc.Close() + + defer testsuite.Reset(testsuite.ResetData) + + // create a start + start := models.NewFlowStart(models.OrgID(1), models.StartTypeManual, testdata.SingleMessage.ID). + WithContactIDs([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID, testdata.George.ID, testdata.Alexandria.ID}) + + batch := start.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, true, 2) + + // start the first batch... + err := tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: batch}, queues.DefaultPriority) + assert.NoError(t, err) + testsuite.FlushTasks(t, rt) + + assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun`).Returns(2) +} diff --git a/core/tasks/starts/start_flow_test.go b/core/tasks/starts/start_flow_test.go index 3e34c46e2..d3eb32191 100644 --- a/core/tasks/starts/start_flow_test.go +++ b/core/tasks/starts/start_flow_test.go @@ -254,3 +254,21 @@ func TestStartFlowTask(t *testing.T) { } } } + +func TestStartFlowTaskNonPersistedStart(t *testing.T) { + _, rt := testsuite.Runtime() + rc := rt.RP.Get() + defer rc.Close() + + defer testsuite.Reset(testsuite.ResetData) + + // create a start and start it... + start := models.NewFlowStart(models.OrgID(1), models.StartTypeManual, testdata.SingleMessage.ID). + WithContactIDs([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}) + + err := tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, queues.DefaultPriority) + assert.NoError(t, err) + testsuite.FlushTasks(t, rt) + + assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun`).Returns(2) +} From 00e312afa5f0e15df129eacd18a2783406c0769a Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 19 Sep 2024 15:07:23 -0500 Subject: [PATCH 076/216] Rework broadcasts to follow more similar pattern as starts --- core/models/broadcasts.go | 26 ++++++++------ core/models/broadcasts_test.go | 10 +++++- core/models/starts.go | 47 ++++++++++++++++--------- core/models/starts_test.go | 17 +++++---- core/tasks/ivr/start_ivr_flow_batch.go | 6 ++-- core/tasks/msgs/send_broadcast.go | 11 ++---- core/tasks/msgs/send_broadcast_batch.go | 19 +++++----- core/tasks/starts/start_flow.go | 18 +++------- core/tasks/starts/start_flow_batch.go | 6 ++-- 9 files changed, 87 insertions(+), 73 deletions(-) diff --git a/core/models/broadcasts.go b/core/models/broadcasts.go index 06aa51eaa..dec001d92 100644 --- a/core/models/broadcasts.go +++ b/core/models/broadcasts.go @@ -23,7 +23,7 @@ const NilBroadcastID = BroadcastID(0) // Broadcast represents a broadcast that needs to be sent type Broadcast struct { - ID BroadcastID `json:"broadcast_id,omitempty"` + ID BroadcastID `json:"broadcast_id,omitempty"` // null for non-persisted tasks used by flow actions OrgID OrgID `json:"org_id"` Translations flows.BroadcastTranslations `json:"translations"` BaseLanguage i18n.Language `json:"base_language"` @@ -116,20 +116,24 @@ func (b *Broadcast) CreateBatch(contactIDs []ContactID, isLast bool) *BroadcastB } } -// MarkBroadcastSent marks the given broadcast as sent -func MarkBroadcastSent(ctx context.Context, db DBorTx, id BroadcastID) error { - _, err := db.ExecContext(ctx, `UPDATE msgs_broadcast SET status = 'S', modified_on = now() WHERE id = $1`, id) - if err != nil { - return fmt.Errorf("error marking broadcast #%d as sent: %w", id, err) +// SetComplete sets the status of this broadcast to SENT +func (b *Broadcast) SetComplete(ctx context.Context, db DBorTx) error { + if b.ID != NilBroadcastID { + _, err := db.ExecContext(ctx, `UPDATE msgs_broadcast SET status = 'S', modified_on = now() WHERE id = $1`, b.ID) + if err != nil { + return fmt.Errorf("error marking broadcast #%d as sent: %w", b.ID, err) + } } return nil } -// MarkBroadcastFailed marks the given broadcast as failed -func MarkBroadcastFailed(ctx context.Context, db DBorTx, id BroadcastID) error { - _, err := db.ExecContext(ctx, `UPDATE msgs_broadcast SET status = 'S', modified_on = now() WHERE id = $1`, id) - if err != nil { - return fmt.Errorf("error marking broadcast #%d as failed: %w", id, err) +// SetFailed sets the status of this broadcast to FAILED, if it's not already set to INTERRUPTED +func (b *Broadcast) SetFailed(ctx context.Context, db DBorTx) error { + if b.ID != NilBroadcastID { + _, err := db.ExecContext(ctx, `UPDATE msgs_broadcast SET status = 'F', modified_on = now() WHERE id = $1`, b.ID) + if err != nil { + return fmt.Errorf("error marking broadcast #%d as failed: %w", b.ID, err) + } } return nil } diff --git a/core/models/broadcasts_test.go b/core/models/broadcasts_test.go index 415b5a28b..932e20e45 100644 --- a/core/models/broadcasts_test.go +++ b/core/models/broadcasts_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestInsertBroadcast(t *testing.T) { +func TestBroadcasts(t *testing.T) { ctx, rt := testsuite.Runtime() defer testsuite.Reset(testsuite.ResetData) @@ -51,6 +51,14 @@ func TestInsertBroadcast(t *testing.T) { }) assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_broadcast_groups WHERE broadcast_id = $1`, bcast.ID).Returns(1) assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_broadcast_contacts WHERE broadcast_id = $1`, bcast.ID).Returns(3) + + err = bcast.SetComplete(ctx, rt.DB) + assert.NoError(t, err) + assertdb.Query(t, rt.DB, `SELECT status FROM msgs_broadcast WHERE id = $1`, bcast.ID).Returns("S") + + err = bcast.SetFailed(ctx, rt.DB) + assert.NoError(t, err) + assertdb.Query(t, rt.DB, `SELECT status FROM msgs_broadcast WHERE id = $1`, bcast.ID).Returns("F") } func TestInsertChildBroadcast(t *testing.T) { diff --git a/core/models/starts.go b/core/models/starts.go index 886001a32..f2d41c472 100644 --- a/core/models/starts.go +++ b/core/models/starts.go @@ -77,7 +77,7 @@ func (e Exclusions) Value() (driver.Value, error) { return json.Marshal(e) } // FlowStart represents the top level flow start in our system type FlowStart struct { - ID StartID `json:"start_id" db:"id"` // null for non-persisted start tasks used by flow actions + ID StartID `json:"start_id" db:"id"` // null for non-persisted tasks used by flow actions UUID uuids.UUID `json:"-" db:"uuid"` OrgID OrgID `json:"org_id" db:"org_id"` Status StartStatus `json:"-" db:"status"` @@ -158,29 +158,44 @@ func (s *FlowStart) WithParams(params json.RawMessage) *FlowStart { return s } -// MarkStartStarted sets the status of the given start to STARTED, if it's not already set to INTERRUPTED -func MarkStartStarted(ctx context.Context, db DBorTx, startID StartID, contactCount int) error { - _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'S', contact_count = $2, modified_on = NOW() WHERE id = $1 AND status != 'I'", startID, contactCount) - if err != nil { - return fmt.Errorf("error setting start as started: %w", err) +// SetStarting sets the status of this start to STARTING, if it's not already set to INTERRUPTED +func (s *FlowStart) SetStarting(ctx context.Context, db DBorTx, contactCount int) error { + if s.Status != StartStatusInterrupted { + s.Status = StartStatusStarting + } + if s.ID != NilStartID { + _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'S', contact_count = $2, modified_on = NOW() WHERE id = $1 AND status != 'I'", s.ID, contactCount) + if err != nil { + return fmt.Errorf("error setting start #%d as starting: %w", s.ID, err) + } } return nil } -// MarkStartComplete sets the status of the given start to COMPLETE, if it's not already set to INTERRUPTED -func MarkStartComplete(ctx context.Context, db DBorTx, startID StartID) error { - _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'C', modified_on = NOW() WHERE id = $1 AND status != 'I'", startID) - if err != nil { - return fmt.Errorf("error marking flow start as complete: %w", err) +// SetComplete sets the status of this start to COMPLETE, if it's not already set to INTERRUPTED +func (s *FlowStart) SetComplete(ctx context.Context, db DBorTx) error { + if s.Status != StartStatusInterrupted { + s.Status = StartStatusComplete + } + if s.ID != NilStartID { + _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'C', modified_on = NOW() WHERE id = $1 AND status != 'I'", s.ID) + if err != nil { + return fmt.Errorf("error marking flow start #%d as complete: %w", s.ID, err) + } } return nil } -// MarkStartFailed sets the status of the given start to FAILED, if it's not already set to INTERRUPTED -func MarkStartFailed(ctx context.Context, db DBorTx, startID StartID) error { - _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'F', modified_on = NOW() WHERE id = $1 AND status != 'I'", startID) - if err != nil { - return fmt.Errorf("error setting flow start as failed: %w", err) +// SetFailed sets the status of this start to FAILED, if it's not already set to INTERRUPTED +func (s *FlowStart) SetFailed(ctx context.Context, db DBorTx) error { + if s.Status != StartStatusInterrupted { + s.Status = StartStatusFailed + } + if s.ID != NilStartID { + _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'F', modified_on = NOW() WHERE id = $1 AND status != 'I'", s.ID) + if err != nil { + return fmt.Errorf("error setting flow start #%d as failed: %w", s.ID, err) + } } return nil } diff --git a/core/models/starts_test.go b/core/models/starts_test.go index 5b57cbf2a..7547af52e 100644 --- a/core/models/starts_test.go +++ b/core/models/starts_test.go @@ -63,12 +63,12 @@ func TestStarts(t *testing.T) { assert.Equal(t, null.JSON(`{"parent_uuid": "532a3899-492f-4ffe-aed7-e75ad524efab", "ancestors": 3, "ancestors_since_input": 1}`), start.SessionHistory) assert.Equal(t, null.JSON(`{"foo": "bar"}`), start.Params) - err = models.MarkStartStarted(ctx, rt.DB, startID, 2) - require.NoError(t, err) - - assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowstart WHERE id = $1 AND status = 'S' AND contact_count = 2`, startID).Returns(1) assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowstart_contacts WHERE flowstart_id = $1`, startID).Returns(2) + err = start.SetStarting(ctx, rt.DB, 2) + assert.NoError(t, err) + assertdb.Query(t, rt.DB, `SELECT status, contact_count FROM flows_flowstart WHERE id = $1`, startID).Columns(map[string]any{"status": "S", "contact_count": int64(2)}) + batch := start.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, false, 3) assert.Equal(t, startID, batch.StartID) assert.Equal(t, []models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, batch.ContactIDs) @@ -82,17 +82,20 @@ func TestStarts(t *testing.T) { _, err = models.ReadSessionHistory([]byte(`{`)) assert.EqualError(t, err, "unexpected end of JSON input") - err = models.MarkStartComplete(ctx, rt.DB, startID) + err = start.SetComplete(ctx, rt.DB) require.NoError(t, err) + assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, startID).Returns("C") - assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowstart WHERE id = $1 AND status = 'C'`, startID).Returns(1) + err = start.SetFailed(ctx, rt.DB) + require.NoError(t, err) + assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, startID).Returns("F") // try fetching a start from the database (won't load all fields) start, err = models.GetFlowStartByID(ctx, rt.DB, start.ID) assert.NoError(t, err) assert.Equal(t, startID, start.ID) assert.Equal(t, testdata.Org1.ID, start.OrgID) - assert.Equal(t, models.StartStatusComplete, start.Status) + assert.Equal(t, models.StartStatusFailed, start.Status) assert.Equal(t, models.StartTypeManual, start.StartType) assert.Equal(t, testdata.Admin.ID, start.CreatedByID) assert.Equal(t, testdata.SingleMessage.ID, start.FlowID) diff --git a/core/tasks/ivr/start_ivr_flow_batch.go b/core/tasks/ivr/start_ivr_flow_batch.go index d33311710..483f2a400 100644 --- a/core/tasks/ivr/start_ivr_flow_batch.go +++ b/core/tasks/ivr/start_ivr_flow_batch.go @@ -78,9 +78,9 @@ func (t *StartIVRFlowBatchTask) Perform(ctx context.Context, rt *runtime.Runtime } // if this is a last batch, mark our start as started - if t.IsLast && start.ID != models.NilStartID { - if err := models.MarkStartComplete(ctx, rt.DB, start.ID); err != nil { - return fmt.Errorf("error trying to set batch as complete: %w", err) + if t.IsLast { + if err := start.SetComplete(ctx, rt.DB); err != nil { + return fmt.Errorf("error marking start as complete: %w", err) } } diff --git a/core/tasks/msgs/send_broadcast.go b/core/tasks/msgs/send_broadcast.go index 5aba51811..85a56a6da 100644 --- a/core/tasks/msgs/send_broadcast.go +++ b/core/tasks/msgs/send_broadcast.go @@ -47,9 +47,7 @@ func (t *SendBroadcastTask) WithAssets() models.Refresh { // Perform handles sending the broadcast by creating batches of broadcast sends for all the unique contacts func (t *SendBroadcastTask) Perform(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets) error { if err := createBroadcastBatches(ctx, rt, oa, t.Broadcast); err != nil { - if t.Broadcast.ID != models.NilBroadcastID { - models.MarkBroadcastFailed(ctx, rt.DB, t.Broadcast.ID) - } + t.Broadcast.SetFailed(ctx, rt.DB) // if error is user created query error.. don't escalate error to sentry isQueryError, _ := contactql.IsQueryError(err) @@ -86,11 +84,8 @@ func createBroadcastBatches(ctx context.Context, rt *runtime.Runtime, oa *models // if there are no contacts to send to, mark our broadcast as sent, we are done if len(contactIDs) == 0 { - if bcast.ID != models.NilBroadcastID { - err = models.MarkBroadcastSent(ctx, rt.DB, bcast.ID) - if err != nil { - return fmt.Errorf("error marking broadcast as sent: %w", err) - } + if err := bcast.SetComplete(ctx, rt.DB); err != nil { + return fmt.Errorf("error marking broadcast as sent: %w", err) } return nil } diff --git a/core/tasks/msgs/send_broadcast_batch.go b/core/tasks/msgs/send_broadcast_batch.go index 3c86a6c3c..a9c4ed4bd 100644 --- a/core/tasks/msgs/send_broadcast_batch.go +++ b/core/tasks/msgs/send_broadcast_batch.go @@ -3,7 +3,6 @@ package msgs import ( "context" "fmt" - "log/slog" "time" "github.com/nyaruka/mailroom/core/models" @@ -37,16 +36,6 @@ func (t *SendBroadcastBatchTask) WithAssets() models.Refresh { } func (t *SendBroadcastBatchTask) Perform(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets) error { - // always set our broadcast as sent if it is our last - defer func() { - if t.BroadcastBatch.IsLast && t.BroadcastBatch.BroadcastID != models.NilBroadcastID { - err := models.MarkBroadcastSent(ctx, rt.DB, t.BroadcastBatch.BroadcastID) - if err != nil { - slog.Error("error marking broadcast as sent", "error", err) - } - } - }() - // create this batch of messages msgs, err := t.BroadcastBatch.CreateMessages(ctx, rt, oa) if err != nil { @@ -54,5 +43,13 @@ func (t *SendBroadcastBatchTask) Perform(ctx context.Context, rt *runtime.Runtim } msgio.QueueMessages(ctx, rt, rt.DB, msgs) + + // if this is our last batch, mark broadcast as done + if t.IsLast { + if err := (&models.Broadcast{ID: t.BroadcastBatch.BroadcastID}).SetComplete(ctx, rt.DB); err != nil { + return fmt.Errorf("error marking broadcast as complete: %w", err) + } + } + return nil } diff --git a/core/tasks/starts/start_flow.go b/core/tasks/starts/start_flow.go index 80b8b6fc3..63606008f 100644 --- a/core/tasks/starts/start_flow.go +++ b/core/tasks/starts/start_flow.go @@ -47,9 +47,7 @@ func (t *StartFlowTask) WithAssets() models.Refresh { func (t *StartFlowTask) Perform(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets) error { if err := createFlowStartBatches(ctx, rt, oa, t.FlowStart); err != nil { - if t.FlowStart.ID != models.NilStartID { - models.MarkStartFailed(ctx, rt.DB, t.FlowStart.ID) - } + t.FlowStart.SetFailed(ctx, rt.DB) // if error is user created query error.. don't escalate error to sentry isQueryError, _ := contactql.IsQueryError(err) @@ -100,20 +98,14 @@ func createFlowStartBatches(ctx context.Context, rt *runtime.Runtime, oa *models } // mark our start as starting, last task will mark as complete - if start.ID != models.NilStartID { - err = models.MarkStartStarted(ctx, rt.DB, start.ID, len(contactIDs)) - if err != nil { - return fmt.Errorf("error marking start as started: %w", err) - } + if err := start.SetStarting(ctx, rt.DB, len(contactIDs)); err != nil { + return fmt.Errorf("error marking start as started: %w", err) } // if there are no contacts to start, mark our start as complete, we are done if len(contactIDs) == 0 { - if start.ID != models.NilStartID { - err = models.MarkStartComplete(ctx, rt.DB, start.ID) - if err != nil { - return fmt.Errorf("error marking start as complete: %w", err) - } + if err := start.SetComplete(ctx, rt.DB); err != nil { + return fmt.Errorf("error marking start as complete: %w", err) } return nil } diff --git a/core/tasks/starts/start_flow_batch.go b/core/tasks/starts/start_flow_batch.go index 912f9fe7e..5de3e29ba 100644 --- a/core/tasks/starts/start_flow_batch.go +++ b/core/tasks/starts/start_flow_batch.go @@ -61,9 +61,9 @@ func (t *StartFlowBatchTask) Perform(ctx context.Context, rt *runtime.Runtime, o } // if this is our last batch, mark start as done - if t.IsLast && start.ID != models.NilStartID { - if err := models.MarkStartComplete(ctx, rt.DB, start.ID); err != nil { - return fmt.Errorf("error marking start #%d as complete: %w", start.ID, err) + if t.IsLast { + if err := start.SetComplete(ctx, rt.DB); err != nil { + return fmt.Errorf("error marking start as complete: %w", err) } } From eb641c09284ce9e7fe6ca7b15a28743436c94025 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 19 Sep 2024 16:34:39 -0500 Subject: [PATCH 077/216] Update CHANGELOG.md for v9.3.24 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 431d7bf9a..be0c0e27e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.24 (2024-09-19) +------------------------- + * Rework broadcasts to follow more similar pattern as starts + * Add support for non-persisted starts + v9.3.23 (2024-09-18) ------------------------- * Rework flow start batch processing so that we check the start status in case it's interrupted From af0b161a40c65500b91687e61790bbc713fd344d Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 19 Sep 2024 17:08:30 -0500 Subject: [PATCH 078/216] Use status=(C)OMPLETE for sent broadcasts instead of (S)ENT --- core/models/broadcasts.go | 23 ++++++++++++++++++++--- core/models/broadcasts_test.go | 2 +- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/core/models/broadcasts.go b/core/models/broadcasts.go index dec001d92..b77d0fead 100644 --- a/core/models/broadcasts.go +++ b/core/models/broadcasts.go @@ -21,10 +21,21 @@ type BroadcastID int // NilBroadcastID is our constant for a nil broadcast id const NilBroadcastID = BroadcastID(0) +// BroadcastStatus is the type for the status of a broadcast +type BroadcastStatus string + +// start status constants +const ( + BroadcastStatusComplete = BroadcastStatus("C") + BroadcastStatusFailed = BroadcastStatus("F") + BroadcastStatusInterrupted = BroadcastStatus("I") +) + // Broadcast represents a broadcast that needs to be sent type Broadcast struct { ID BroadcastID `json:"broadcast_id,omitempty"` // null for non-persisted tasks used by flow actions OrgID OrgID `json:"org_id"` + Status BroadcastStatus `json:"status"` Translations flows.BroadcastTranslations `json:"translations"` BaseLanguage i18n.Language `json:"base_language"` Expressions bool `json:"expressions"` @@ -116,12 +127,15 @@ func (b *Broadcast) CreateBatch(contactIDs []ContactID, isLast bool) *BroadcastB } } -// SetComplete sets the status of this broadcast to SENT +// SetComplete sets the status of this broadcast to COMPLETE, if it's not already set to INTERRUPTED func (b *Broadcast) SetComplete(ctx context.Context, db DBorTx) error { + if b.Status != BroadcastStatusInterrupted { + b.Status = BroadcastStatusComplete + } if b.ID != NilBroadcastID { - _, err := db.ExecContext(ctx, `UPDATE msgs_broadcast SET status = 'S', modified_on = now() WHERE id = $1`, b.ID) + _, err := db.ExecContext(ctx, `UPDATE msgs_broadcast SET status = 'C', modified_on = now() WHERE id = $1`, b.ID) if err != nil { - return fmt.Errorf("error marking broadcast #%d as sent: %w", b.ID, err) + return fmt.Errorf("error marking broadcast #%d as complete: %w", b.ID, err) } } return nil @@ -129,6 +143,9 @@ func (b *Broadcast) SetComplete(ctx context.Context, db DBorTx) error { // SetFailed sets the status of this broadcast to FAILED, if it's not already set to INTERRUPTED func (b *Broadcast) SetFailed(ctx context.Context, db DBorTx) error { + if b.Status != BroadcastStatusInterrupted { + b.Status = BroadcastStatusFailed + } if b.ID != NilBroadcastID { _, err := db.ExecContext(ctx, `UPDATE msgs_broadcast SET status = 'F', modified_on = now() WHERE id = $1`, b.ID) if err != nil { diff --git a/core/models/broadcasts_test.go b/core/models/broadcasts_test.go index 932e20e45..34b98a9d3 100644 --- a/core/models/broadcasts_test.go +++ b/core/models/broadcasts_test.go @@ -54,7 +54,7 @@ func TestBroadcasts(t *testing.T) { err = bcast.SetComplete(ctx, rt.DB) assert.NoError(t, err) - assertdb.Query(t, rt.DB, `SELECT status FROM msgs_broadcast WHERE id = $1`, bcast.ID).Returns("S") + assertdb.Query(t, rt.DB, `SELECT status FROM msgs_broadcast WHERE id = $1`, bcast.ID).Returns("C") err = bcast.SetFailed(ctx, rt.DB) assert.NoError(t, err) From 3ea5c6766f4412c57647b7c1b3ca220d15ea0c1a Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 19 Sep 2024 16:55:38 -0500 Subject: [PATCH 079/216] Stop creating starts in the database for trigger_session flow actions --- core/handlers/session_triggered_test.go | 24 +++--------------------- core/hooks/create_starts.go | 5 ----- 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/core/handlers/session_triggered_test.go b/core/handlers/session_triggered_test.go index 62bf1eb72..256fc5eba 100644 --- a/core/handlers/session_triggered_test.go +++ b/core/handlers/session_triggered_test.go @@ -53,20 +53,9 @@ func TestSessionTriggered(t *testing.T) { Args: []any{testdata.Cathy.ID}, Count: 1, }, - { - SQL: "select count(*) from flows_flowstart where org_id = 1 AND start_type = 'F' AND flow_id = $1 AND status = 'P' AND parent_summary IS NOT NULL AND session_history IS NOT NULL;", - Args: []any{testdata.SingleMessage.ID}, - Count: 1, - }, - { - SQL: "select count(*) from flows_flowstart_contacts where id = 1 AND contact_id = $1", - Args: []any{testdata.George.ID}, - Count: 1, - }, - { - SQL: "select count(*) from flows_flowstart_groups where id = 1 AND contactgroup_id = $1", - Args: []any{testdata.TestersGroup.ID}, - Count: 1, + { // check we don't create a start in the database + SQL: "select count(*) from flows_flowstart where org_id = 1", + Count: 0, }, }, Assertions: []handlers.Assertion{ @@ -112,13 +101,6 @@ func TestQuerySessionTriggered(t *testing.T) { actions.NewStartSession(handlers.NewActionUUID(), favoriteFlow.Reference(), nil, nil, "name ~ @contact.name", nil, nil, true), }, }, - SQLAssertions: []handlers.SQLAssertion{ - { - SQL: `select count(*) from flows_flowstart where flow_id = $1 AND start_type = 'F' AND status = 'P' AND query = 'name ~ "Cathy"' AND parent_summary IS NOT NULL;`, - Args: []any{testdata.Favorites.ID}, - Count: 1, - }, - }, Assertions: []handlers.Assertion{ func(t *testing.T, rt *runtime.Runtime) error { rc := rt.RP.Get() diff --git a/core/hooks/create_starts.go b/core/hooks/create_starts.go index bd63ebfb3..398cfb2ad 100644 --- a/core/hooks/create_starts.go +++ b/core/hooks/create_starts.go @@ -67,11 +67,6 @@ func (h *createStartsHook) Apply(ctx context.Context, rt *runtime.Runtime, tx *s WithParentSummary(event.RunSummary). WithSessionHistory(historyJSON) - // TODO rework start batch handling to not require a persisted start - if err := models.InsertFlowStarts(ctx, tx, []*models.FlowStart{start}); err != nil { - return fmt.Errorf("error inserting flow starts for scene triggers: %w", err) - } - err = tasks.Queue(rc, tasks.BatchQueue, oa.OrgID(), &starts.StartFlowTask{FlowStart: start}, queues.DefaultPriority) if err != nil { return fmt.Errorf("error queuing flow start: %w", err) From 21da6e394413031f274b612e4b0d5cd656b57e4b Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 19 Sep 2024 17:41:59 -0500 Subject: [PATCH 080/216] Update CHANGELOG.md for v9.3.25 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be0c0e27e..dc4ad9e8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.25 (2024-09-19) +------------------------- + * Stop creating starts in the database for trigger_session flow actions + * Use status=(C)OMPLETE for sent broadcasts instead of (S)ENT + v9.3.24 (2024-09-19) ------------------------- * Rework broadcasts to follow more similar pattern as starts From 7f00bfe46e2390178f488949cc39f00bceffbc73 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 20 Sep 2024 10:42:35 -0500 Subject: [PATCH 081/216] Add warning for non-persistent broadcasts to more than 100 contacts --- core/tasks/msgs/send_broadcast.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/tasks/msgs/send_broadcast.go b/core/tasks/msgs/send_broadcast.go index 85a56a6da..add73145e 100644 --- a/core/tasks/msgs/send_broadcast.go +++ b/core/tasks/msgs/send_broadcast.go @@ -90,6 +90,10 @@ func createBroadcastBatches(ctx context.Context, rt *runtime.Runtime, oa *models return nil } + if bcast.ID == models.NilBroadcastID && len(contactIDs) > 100 { + slog.Error("non-persistent broadcast to more than 100 contacts", "count", len(contactIDs)) + } + // batches will be processed in the throttled queue unless we're a single contact q := tasks.ThrottledQueue if len(contactIDs) == 1 { From 946c87b128d9c8952c7e7d215aeb7dfd4fe3d477 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 20 Sep 2024 11:00:17 -0500 Subject: [PATCH 082/216] Add org_id to warning error about large non-persistent broadcasts --- core/tasks/msgs/send_broadcast.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/tasks/msgs/send_broadcast.go b/core/tasks/msgs/send_broadcast.go index add73145e..468a51b3c 100644 --- a/core/tasks/msgs/send_broadcast.go +++ b/core/tasks/msgs/send_broadcast.go @@ -90,8 +90,9 @@ func createBroadcastBatches(ctx context.Context, rt *runtime.Runtime, oa *models return nil } + // TODO remove once we decide whether to not add an actual limit if bcast.ID == models.NilBroadcastID && len(contactIDs) > 100 { - slog.Error("non-persistent broadcast to more than 100 contacts", "count", len(contactIDs)) + slog.Error("non-persistent broadcast to more than 100 contacts", "count", len(contactIDs), "org_id", bcast.OrgID) } // batches will be processed in the throttled queue unless we're a single contact From 57ed19375cad76e872bc3d864760be57bda5de08 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 20 Sep 2024 11:08:01 -0500 Subject: [PATCH 083/216] Update CHANGELOG.md for v9.3.26 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc4ad9e8f..606bf9545 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.26 (2024-09-20) +------------------------- + * Add warning for non-persistent broadcasts to more than 100 contacts + v9.3.25 (2024-09-19) ------------------------- * Stop creating starts in the database for trigger_session flow actions From 30126922a136caf9f920a571fcabb441fcb79488 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 20 Sep 2024 12:00:57 -0500 Subject: [PATCH 084/216] Cleanup complete vs completed, starting vs started status terminology --- core/models/broadcasts.go | 10 +++++----- core/models/broadcasts_test.go | 2 +- core/models/starts.go | 16 +++++++-------- core/models/starts_test.go | 4 ++-- core/tasks/ivr/start_ivr_flow_batch.go | 2 +- core/tasks/msgs/send_broadcast.go | 2 +- core/tasks/msgs/send_broadcast_batch.go | 2 +- core/tasks/starts/start_flow.go | 4 ++-- core/tasks/starts/start_flow_batch.go | 2 +- core/tasks/starts/start_flow_test.go | 26 ++++++++++++------------- 10 files changed, 35 insertions(+), 35 deletions(-) diff --git a/core/models/broadcasts.go b/core/models/broadcasts.go index b77d0fead..a6caea785 100644 --- a/core/models/broadcasts.go +++ b/core/models/broadcasts.go @@ -26,7 +26,7 @@ type BroadcastStatus string // start status constants const ( - BroadcastStatusComplete = BroadcastStatus("C") + BroadcastStatusCompleted = BroadcastStatus("C") BroadcastStatusFailed = BroadcastStatus("F") BroadcastStatusInterrupted = BroadcastStatus("I") ) @@ -127,15 +127,15 @@ func (b *Broadcast) CreateBatch(contactIDs []ContactID, isLast bool) *BroadcastB } } -// SetComplete sets the status of this broadcast to COMPLETE, if it's not already set to INTERRUPTED -func (b *Broadcast) SetComplete(ctx context.Context, db DBorTx) error { +// SetCompleted sets the status of this broadcast to COMPLETED, if it's not already set to INTERRUPTED +func (b *Broadcast) SetCompleted(ctx context.Context, db DBorTx) error { if b.Status != BroadcastStatusInterrupted { - b.Status = BroadcastStatusComplete + b.Status = BroadcastStatusCompleted } if b.ID != NilBroadcastID { _, err := db.ExecContext(ctx, `UPDATE msgs_broadcast SET status = 'C', modified_on = now() WHERE id = $1`, b.ID) if err != nil { - return fmt.Errorf("error marking broadcast #%d as complete: %w", b.ID, err) + return fmt.Errorf("error marking broadcast #%d as completed: %w", b.ID, err) } } return nil diff --git a/core/models/broadcasts_test.go b/core/models/broadcasts_test.go index 34b98a9d3..54bf9f89d 100644 --- a/core/models/broadcasts_test.go +++ b/core/models/broadcasts_test.go @@ -52,7 +52,7 @@ func TestBroadcasts(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_broadcast_groups WHERE broadcast_id = $1`, bcast.ID).Returns(1) assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_broadcast_contacts WHERE broadcast_id = $1`, bcast.ID).Returns(3) - err = bcast.SetComplete(ctx, rt.DB) + err = bcast.SetCompleted(ctx, rt.DB) assert.NoError(t, err) assertdb.Query(t, rt.DB, `SELECT status FROM msgs_broadcast WHERE id = $1`, bcast.ID).Returns("C") diff --git a/core/models/starts.go b/core/models/starts.go index f2d41c472..6deb402be 100644 --- a/core/models/starts.go +++ b/core/models/starts.go @@ -44,7 +44,7 @@ type StartStatus string const ( StartStatusPending = StartStatus("P") StartStatusStarting = StartStatus("S") - StartStatusComplete = StartStatus("C") + StartStatusCompleted = StartStatus("C") StartStatusFailed = StartStatus("F") StartStatusInterrupted = StartStatus("I") ) @@ -158,29 +158,29 @@ func (s *FlowStart) WithParams(params json.RawMessage) *FlowStart { return s } -// SetStarting sets the status of this start to STARTING, if it's not already set to INTERRUPTED -func (s *FlowStart) SetStarting(ctx context.Context, db DBorTx, contactCount int) error { +// SetStarted sets the status of this start to STARTED, if it's not already set to INTERRUPTED +func (s *FlowStart) SetStarted(ctx context.Context, db DBorTx, contactCount int) error { if s.Status != StartStatusInterrupted { s.Status = StartStatusStarting } if s.ID != NilStartID { _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'S', contact_count = $2, modified_on = NOW() WHERE id = $1 AND status != 'I'", s.ID, contactCount) if err != nil { - return fmt.Errorf("error setting start #%d as starting: %w", s.ID, err) + return fmt.Errorf("error setting start #%d as started: %w", s.ID, err) } } return nil } -// SetComplete sets the status of this start to COMPLETE, if it's not already set to INTERRUPTED -func (s *FlowStart) SetComplete(ctx context.Context, db DBorTx) error { +// SetCompleted sets the status of this start to COMPLETED, if it's not already set to INTERRUPTED +func (s *FlowStart) SetCompleted(ctx context.Context, db DBorTx) error { if s.Status != StartStatusInterrupted { - s.Status = StartStatusComplete + s.Status = StartStatusCompleted } if s.ID != NilStartID { _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'C', modified_on = NOW() WHERE id = $1 AND status != 'I'", s.ID) if err != nil { - return fmt.Errorf("error marking flow start #%d as complete: %w", s.ID, err) + return fmt.Errorf("error marking flow start #%d as completed: %w", s.ID, err) } } return nil diff --git a/core/models/starts_test.go b/core/models/starts_test.go index 7547af52e..d9ccf4270 100644 --- a/core/models/starts_test.go +++ b/core/models/starts_test.go @@ -65,7 +65,7 @@ func TestStarts(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowstart_contacts WHERE flowstart_id = $1`, startID).Returns(2) - err = start.SetStarting(ctx, rt.DB, 2) + err = start.SetStarted(ctx, rt.DB, 2) assert.NoError(t, err) assertdb.Query(t, rt.DB, `SELECT status, contact_count FROM flows_flowstart WHERE id = $1`, startID).Columns(map[string]any{"status": "S", "contact_count": int64(2)}) @@ -82,7 +82,7 @@ func TestStarts(t *testing.T) { _, err = models.ReadSessionHistory([]byte(`{`)) assert.EqualError(t, err, "unexpected end of JSON input") - err = start.SetComplete(ctx, rt.DB) + err = start.SetCompleted(ctx, rt.DB) require.NoError(t, err) assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, startID).Returns("C") diff --git a/core/tasks/ivr/start_ivr_flow_batch.go b/core/tasks/ivr/start_ivr_flow_batch.go index 483f2a400..ff9a72033 100644 --- a/core/tasks/ivr/start_ivr_flow_batch.go +++ b/core/tasks/ivr/start_ivr_flow_batch.go @@ -79,7 +79,7 @@ func (t *StartIVRFlowBatchTask) Perform(ctx context.Context, rt *runtime.Runtime // if this is a last batch, mark our start as started if t.IsLast { - if err := start.SetComplete(ctx, rt.DB); err != nil { + if err := start.SetCompleted(ctx, rt.DB); err != nil { return fmt.Errorf("error marking start as complete: %w", err) } } diff --git a/core/tasks/msgs/send_broadcast.go b/core/tasks/msgs/send_broadcast.go index 468a51b3c..b7da6eddd 100644 --- a/core/tasks/msgs/send_broadcast.go +++ b/core/tasks/msgs/send_broadcast.go @@ -84,7 +84,7 @@ func createBroadcastBatches(ctx context.Context, rt *runtime.Runtime, oa *models // if there are no contacts to send to, mark our broadcast as sent, we are done if len(contactIDs) == 0 { - if err := bcast.SetComplete(ctx, rt.DB); err != nil { + if err := bcast.SetCompleted(ctx, rt.DB); err != nil { return fmt.Errorf("error marking broadcast as sent: %w", err) } return nil diff --git a/core/tasks/msgs/send_broadcast_batch.go b/core/tasks/msgs/send_broadcast_batch.go index a9c4ed4bd..068a9241c 100644 --- a/core/tasks/msgs/send_broadcast_batch.go +++ b/core/tasks/msgs/send_broadcast_batch.go @@ -46,7 +46,7 @@ func (t *SendBroadcastBatchTask) Perform(ctx context.Context, rt *runtime.Runtim // if this is our last batch, mark broadcast as done if t.IsLast { - if err := (&models.Broadcast{ID: t.BroadcastBatch.BroadcastID}).SetComplete(ctx, rt.DB); err != nil { + if err := (&models.Broadcast{ID: t.BroadcastBatch.BroadcastID}).SetCompleted(ctx, rt.DB); err != nil { return fmt.Errorf("error marking broadcast as complete: %w", err) } } diff --git a/core/tasks/starts/start_flow.go b/core/tasks/starts/start_flow.go index 63606008f..059c75314 100644 --- a/core/tasks/starts/start_flow.go +++ b/core/tasks/starts/start_flow.go @@ -98,13 +98,13 @@ func createFlowStartBatches(ctx context.Context, rt *runtime.Runtime, oa *models } // mark our start as starting, last task will mark as complete - if err := start.SetStarting(ctx, rt.DB, len(contactIDs)); err != nil { + if err := start.SetStarted(ctx, rt.DB, len(contactIDs)); err != nil { return fmt.Errorf("error marking start as started: %w", err) } // if there are no contacts to start, mark our start as complete, we are done if len(contactIDs) == 0 { - if err := start.SetComplete(ctx, rt.DB); err != nil { + if err := start.SetCompleted(ctx, rt.DB); err != nil { return fmt.Errorf("error marking start as complete: %w", err) } return nil diff --git a/core/tasks/starts/start_flow_batch.go b/core/tasks/starts/start_flow_batch.go index 5de3e29ba..b9404e006 100644 --- a/core/tasks/starts/start_flow_batch.go +++ b/core/tasks/starts/start_flow_batch.go @@ -62,7 +62,7 @@ func (t *StartFlowBatchTask) Perform(ctx context.Context, rt *runtime.Runtime, o // if this is our last batch, mark start as done if t.IsLast { - if err := start.SetComplete(ctx, rt.DB); err != nil { + if err := start.SetCompleted(ctx, rt.DB); err != nil { return fmt.Errorf("error marking start as complete: %w", err) } } diff --git a/core/tasks/starts/start_flow_test.go b/core/tasks/starts/start_flow_test.go index d3eb32191..33837978f 100644 --- a/core/tasks/starts/start_flow_test.go +++ b/core/tasks/starts/start_flow_test.go @@ -53,7 +53,7 @@ func TestStartFlowTask(t *testing.T) { expectedContactCount: 0, expectedBatchCount: 0, expectedTotalCount: 0, - expectedStatus: models.StartStatusComplete, + expectedStatus: models.StartStatusCompleted, expectedActiveRuns: map[models.FlowID]int{testdata.Favorites.ID: 1, testdata.PickANumber.ID: 0, testdata.SingleMessage.ID: 0}, }, { // 1: single group @@ -65,7 +65,7 @@ func TestStartFlowTask(t *testing.T) { expectedContactCount: 121, expectedBatchCount: 2, expectedTotalCount: 121, - expectedStatus: models.StartStatusComplete, + expectedStatus: models.StartStatusCompleted, expectedActiveRuns: map[models.FlowID]int{testdata.Favorites.ID: 122, testdata.PickANumber.ID: 0, testdata.SingleMessage.ID: 0}, }, { // 2: group and contact (but all already active) @@ -78,7 +78,7 @@ func TestStartFlowTask(t *testing.T) { expectedContactCount: 121, expectedBatchCount: 0, expectedTotalCount: 0, - expectedStatus: models.StartStatusComplete, + expectedStatus: models.StartStatusCompleted, expectedActiveRuns: map[models.FlowID]int{testdata.Favorites.ID: 122, testdata.PickANumber.ID: 0, testdata.SingleMessage.ID: 0}, }, { // 3: don't exclude started previously @@ -90,7 +90,7 @@ func TestStartFlowTask(t *testing.T) { expectedContactCount: 1, expectedBatchCount: 1, expectedTotalCount: 1, - expectedStatus: models.StartStatusComplete, + expectedStatus: models.StartStatusCompleted, expectedActiveRuns: map[models.FlowID]int{testdata.Favorites.ID: 122, testdata.PickANumber.ID: 0, testdata.SingleMessage.ID: 0}, }, { // 4: previous group and one new contact @@ -102,7 +102,7 @@ func TestStartFlowTask(t *testing.T) { expectedContactCount: 122, expectedBatchCount: 1, expectedTotalCount: 1, - expectedStatus: models.StartStatusComplete, + expectedStatus: models.StartStatusCompleted, expectedActiveRuns: map[models.FlowID]int{testdata.Favorites.ID: 123, testdata.PickANumber.ID: 0, testdata.SingleMessage.ID: 0}, }, { // 5: single contact, no restart @@ -113,7 +113,7 @@ func TestStartFlowTask(t *testing.T) { expectedContactCount: 1, expectedBatchCount: 0, expectedTotalCount: 0, - expectedStatus: models.StartStatusComplete, + expectedStatus: models.StartStatusCompleted, expectedActiveRuns: map[models.FlowID]int{testdata.Favorites.ID: 123, testdata.PickANumber.ID: 0, testdata.SingleMessage.ID: 0}, }, { // 6: single contact, include active, but no restart @@ -125,7 +125,7 @@ func TestStartFlowTask(t *testing.T) { expectedContactCount: 1, expectedBatchCount: 0, expectedTotalCount: 0, - expectedStatus: models.StartStatusComplete, + expectedStatus: models.StartStatusCompleted, expectedActiveRuns: map[models.FlowID]int{testdata.Favorites.ID: 123, testdata.PickANumber.ID: 0, testdata.SingleMessage.ID: 0}, }, { // 7: single contact, include active and restart @@ -137,7 +137,7 @@ func TestStartFlowTask(t *testing.T) { expectedContactCount: 1, expectedBatchCount: 1, expectedTotalCount: 1, - expectedStatus: models.StartStatusComplete, + expectedStatus: models.StartStatusCompleted, expectedActiveRuns: map[models.FlowID]int{testdata.Favorites.ID: 123, testdata.PickANumber.ID: 0, testdata.SingleMessage.ID: 0}, }, { // 8: query start @@ -149,7 +149,7 @@ func TestStartFlowTask(t *testing.T) { expectedContactCount: 1, expectedBatchCount: 1, expectedTotalCount: 1, - expectedStatus: models.StartStatusComplete, + expectedStatus: models.StartStatusCompleted, expectedActiveRuns: map[models.FlowID]int{testdata.Favorites.ID: 123, testdata.PickANumber.ID: 0, testdata.SingleMessage.ID: 0}, }, { // 9: query start with invalid query @@ -171,7 +171,7 @@ func TestStartFlowTask(t *testing.T) { expectedContactCount: 1, expectedBatchCount: 1, expectedTotalCount: 1, - expectedStatus: models.StartStatusComplete, + expectedStatus: models.StartStatusCompleted, expectedActiveRuns: map[models.FlowID]int{testdata.Favorites.ID: 124, testdata.PickANumber.ID: 0, testdata.SingleMessage.ID: 0}, }, { // 11: other messaging flow @@ -183,7 +183,7 @@ func TestStartFlowTask(t *testing.T) { expectedContactCount: 1, expectedBatchCount: 1, expectedTotalCount: 1, - expectedStatus: models.StartStatusComplete, + expectedStatus: models.StartStatusCompleted, expectedActiveRuns: map[models.FlowID]int{testdata.Favorites.ID: 123, testdata.PickANumber.ID: 1, testdata.SingleMessage.ID: 0}, }, { // 12: background flow @@ -195,7 +195,7 @@ func TestStartFlowTask(t *testing.T) { expectedContactCount: 1, expectedBatchCount: 1, expectedTotalCount: 1, - expectedStatus: models.StartStatusComplete, + expectedStatus: models.StartStatusCompleted, expectedActiveRuns: map[models.FlowID]int{testdata.Favorites.ID: 123, testdata.PickANumber.ID: 1, testdata.SingleMessage.ID: 0}, }, { // 13: exclude group @@ -208,7 +208,7 @@ func TestStartFlowTask(t *testing.T) { expectedContactCount: 1, expectedBatchCount: 1, expectedTotalCount: 1, - expectedStatus: models.StartStatusComplete, + expectedStatus: models.StartStatusCompleted, expectedActiveRuns: map[models.FlowID]int{testdata.Favorites.ID: 124, testdata.PickANumber.ID: 0, testdata.SingleMessage.ID: 0}, }, } From cc336196dbee017ae21849894854be080c7bbb6d Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 20 Sep 2024 12:01:43 -0500 Subject: [PATCH 085/216] Fix --- core/models/starts.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/models/starts.go b/core/models/starts.go index 6deb402be..bfab3ead4 100644 --- a/core/models/starts.go +++ b/core/models/starts.go @@ -43,7 +43,7 @@ type StartStatus string // start status constants const ( StartStatusPending = StartStatus("P") - StartStatusStarting = StartStatus("S") + StartStatusStarted = StartStatus("S") StartStatusCompleted = StartStatus("C") StartStatusFailed = StartStatus("F") StartStatusInterrupted = StartStatus("I") @@ -161,7 +161,7 @@ func (s *FlowStart) WithParams(params json.RawMessage) *FlowStart { // SetStarted sets the status of this start to STARTED, if it's not already set to INTERRUPTED func (s *FlowStart) SetStarted(ctx context.Context, db DBorTx, contactCount int) error { if s.Status != StartStatusInterrupted { - s.Status = StartStatusStarting + s.Status = StartStatusStarted } if s.ID != NilStartID { _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'S', contact_count = $2, modified_on = NOW() WHERE id = $1 AND status != 'I'", s.ID, contactCount) From 6f4a796a4419a1dca8feaf78057f81344eef5672 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 20 Sep 2024 14:27:01 -0500 Subject: [PATCH 086/216] Add .broadcast field to broadcast batch tasks --- core/models/broadcasts.go | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/core/models/broadcasts.go b/core/models/broadcasts.go index a6caea785..7f41387dd 100644 --- a/core/models/broadcasts.go +++ b/core/models/broadcasts.go @@ -112,8 +112,11 @@ func NewBroadcastFromEvent(ctx context.Context, tx DBorTx, oa *OrgAssets, event } func (b *Broadcast) CreateBatch(contactIDs []ContactID, isLast bool) *BroadcastBatch { - return &BroadcastBatch{ - BroadcastID: b.ID, + bb := &BroadcastBatch{ + ContactIDs: contactIDs, + IsLast: isLast, + + // TODO remove OrgID: b.OrgID, Translations: b.Translations, BaseLanguage: b.BaseLanguage, @@ -122,9 +125,15 @@ func (b *Broadcast) CreateBatch(contactIDs []ContactID, isLast bool) *BroadcastB TemplateID: b.TemplateID, TemplateVariables: b.TemplateVariables, CreatedByID: b.CreatedByID, - ContactIDs: contactIDs, - IsLast: isLast, } + + if b.ID != NilBroadcastID { + bb.BroadcastID = b.ID + } else { + bb.Broadcast = b + } + + return bb } // SetCompleted sets the status of this broadcast to COMPLETED, if it's not already set to INTERRUPTED @@ -251,7 +260,14 @@ const sqlInsertBroadcastGroups = `INSERT INTO msgs_broadcast_groups(broadcast_id // BroadcastBatch represents a batch of contacts that need messages sent for type BroadcastBatch struct { - BroadcastID BroadcastID `json:"broadcast_id,omitempty"` + // for persisted starts broadcast_id is set, for non-persisted broadcasts like flow actions, broadcast is set + BroadcastID BroadcastID `json:"broadcast_id,omitempty"` + Broadcast *Broadcast `json:"broadcast,omitempty"` + + ContactIDs []ContactID `json:"contact_ids"` + IsLast bool `json:"is_last"` + + // TODO remove OrgID OrgID `json:"org_id"` Translations flows.BroadcastTranslations `json:"translations"` BaseLanguage i18n.Language `json:"base_language"` @@ -259,9 +275,7 @@ type BroadcastBatch struct { OptInID OptInID `json:"optin_id,omitempty"` TemplateID TemplateID `json:"template_id,omitempty"` TemplateVariables []string `json:"template_variables,omitempty"` - ContactIDs []ContactID `json:"contact_ids"` CreatedByID UserID `json:"created_by_id"` - IsLast bool `json:"is_last"` } func (b *BroadcastBatch) CreateMessages(ctx context.Context, rt *runtime.Runtime, oa *OrgAssets) ([]*Msg, error) { From 915712ff8d25242cec6115feda5297d359149573 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 20 Sep 2024 15:02:29 -0500 Subject: [PATCH 087/216] Use broadcast field on batch tasks --- core/models/broadcasts.go | 22 ++++++++++++++++++---- core/models/broadcasts_test.go | 21 +++++++++++---------- core/models/msgs.go | 4 ++-- core/msgio/courier_test.go | 2 +- core/tasks/msgs/send_broadcast_batch.go | 20 +++++++++++++++++++- 5 files changed, 51 insertions(+), 18 deletions(-) diff --git a/core/models/broadcasts.go b/core/models/broadcasts.go index 7f41387dd..af0195489 100644 --- a/core/models/broadcasts.go +++ b/core/models/broadcasts.go @@ -258,6 +258,20 @@ RETURNING id` const sqlInsertBroadcastContacts = `INSERT INTO msgs_broadcast_contacts(broadcast_id, contact_id) VALUES(:broadcast_id, :contact_id)` const sqlInsertBroadcastGroups = `INSERT INTO msgs_broadcast_groups(broadcast_id, contactgroup_id) VALUES(:broadcast_id, :contactgroup_id)` +const sqlGetBroadcastByID = ` +SELECT id, org_id, status, translations, base_language, expressions, optin_id, template_id, template_variables, created_by_id + FROM msgs_broadcast + WHERE id = $1` + +// GetBroadcastByID gets a broadcast by it's ID - NOTE this does not load all attributes of the broadcast +func GetBroadcastByID(ctx context.Context, db DBorTx, bcastID BroadcastID) (*Broadcast, error) { + b := &Broadcast{} + if err := db.GetContext(ctx, b, sqlGetBroadcastByID, bcastID); err != nil { + return nil, fmt.Errorf("error loading broadcast #%d: %w", bcastID, err) + } + return b, nil +} + // BroadcastBatch represents a batch of contacts that need messages sent for type BroadcastBatch struct { // for persisted starts broadcast_id is set, for non-persisted broadcasts like flow actions, broadcast is set @@ -315,10 +329,10 @@ func (b *BroadcastBatch) createMessage(rt *runtime.Runtime, oa *OrgAssets, c *Co return nil, fmt.Errorf("error creating flow contact for broadcast message: %w", err) } - content, locale := b.Translations.ForContact(oa.Env(), contact, b.BaseLanguage) + content, locale := b.Broadcast.Translations.ForContact(oa.Env(), contact, b.Broadcast.BaseLanguage) var expressionsContext *types.XObject - if b.Expressions { + if b.Broadcast.Expressions { expressionsContext = types.NewXObject(map[string]types.XValue{ "contact": flows.Context(oa.Env(), contact), "fields": flows.Context(oa.Env(), contact.Fields()), @@ -333,9 +347,9 @@ func (b *BroadcastBatch) createMessage(rt *runtime.Runtime, oa *OrgAssets, c *Co } // create our outgoing message - out, ch := CreateMsgOut(rt, oa, contact, content, b.TemplateID, b.TemplateVariables, locale, expressionsContext) + out, ch := CreateMsgOut(rt, oa, contact, content, b.Broadcast.TemplateID, b.Broadcast.TemplateVariables, locale, expressionsContext) - msg, err := NewOutgoingBroadcastMsg(rt, oa.Org(), ch, contact, out, b) + msg, err := NewOutgoingBroadcastMsg(rt, oa.Org(), ch, contact, out, b.Broadcast) if err != nil { return nil, fmt.Errorf("error creating outgoing message: %w", err) } diff --git a/core/models/broadcasts_test.go b/core/models/broadcasts_test.go index 54bf9f89d..bb63ba185 100644 --- a/core/models/broadcasts_test.go +++ b/core/models/broadcasts_test.go @@ -126,10 +126,7 @@ func TestNonPersistentBroadcasts(t *testing.T) { batch := bcast.CreateBatch([]models.ContactID{testdata.Alexandria.ID, testdata.Bob.ID}, false) assert.Equal(t, models.NilBroadcastID, batch.BroadcastID) - assert.Equal(t, testdata.Org1.ID, batch.OrgID) - assert.Equal(t, i18n.Language("eng"), batch.BaseLanguage) - assert.Equal(t, translations, batch.Translations) - assert.Equal(t, optIn.ID, batch.OptInID) + assert.NotNil(t, testdata.Org1.ID, batch.Broadcast) assert.Equal(t, []models.ContactID{testdata.Alexandria.ID, testdata.Bob.ID}, batch.ContactIDs) oa, err := models.GetOrgAssets(ctx, rt, testdata.Org1.ID) @@ -153,9 +150,6 @@ func TestBroadcastBatchCreateMessage(t *testing.T) { oa, err := models.GetOrgAssetsWithRefresh(ctx, rt, testdata.Org1.ID, models.RefreshOptIns) require.NoError(t, err) - // we need a broadcast id to insert messages but the content here is ignored - bcastID := testdata.InsertBroadcast(rt, testdata.Org1, "eng", map[i18n.Language]string{"eng": "Test"}, nil, models.NilScheduleID, nil, nil) - tcs := []struct { contactLanguage i18n.Language contactURN urns.URN @@ -249,8 +243,7 @@ func TestBroadcastBatchCreateMessage(t *testing.T) { contact := testdata.InsertContact(rt, testdata.Org1, flows.ContactUUID(uuids.NewV4()), "Felix", tc.contactLanguage, models.ContactStatusActive) testdata.InsertContactURN(rt, testdata.Org1, contact, tc.contactURN, 1000, nil) - batch := &models.BroadcastBatch{ - BroadcastID: bcastID, + bcast := &models.Broadcast{ OrgID: testdata.Org1.ID, Translations: tc.translations, BaseLanguage: tc.baseLanguage, @@ -258,7 +251,15 @@ func TestBroadcastBatchCreateMessage(t *testing.T) { OptInID: tc.optInID, TemplateID: tc.templateID, TemplateVariables: tc.templateVariables, - ContactIDs: []models.ContactID{contact.ID}, + } + err = models.InsertBroadcast(ctx, rt.DB, bcast) + require.NoError(t, err) + + batch := &models.BroadcastBatch{ + BroadcastID: bcast.ID, + Broadcast: bcast, + ContactIDs: []models.ContactID{contact.ID}, + IsLast: true, } msgs, err := batch.CreateMessages(ctx, rt, oa) diff --git a/core/models/msgs.go b/core/models/msgs.go index effa82ce8..f1295b9a1 100644 --- a/core/models/msgs.go +++ b/core/models/msgs.go @@ -355,8 +355,8 @@ func NewOutgoingFlowMsg(rt *runtime.Runtime, org *Org, channel *Channel, session } // NewOutgoingBroadcastMsg creates an outgoing message which is part of a broadcast -func NewOutgoingBroadcastMsg(rt *runtime.Runtime, org *Org, channel *Channel, contact *flows.Contact, out *flows.MsgOut, bb *BroadcastBatch) (*Msg, error) { - return newOutgoingTextMsg(rt, org, channel, contact, out, nil, nil, bb.BroadcastID, NilTicketID, bb.OptInID, bb.CreatedByID, dates.Now()) +func NewOutgoingBroadcastMsg(rt *runtime.Runtime, org *Org, channel *Channel, contact *flows.Contact, out *flows.MsgOut, b *Broadcast) (*Msg, error) { + return newOutgoingTextMsg(rt, org, channel, contact, out, nil, nil, b.ID, NilTicketID, b.OptInID, b.CreatedByID, dates.Now()) } // NewOutgoingTicketMsg creates an outgoing message from a ticket diff --git a/core/msgio/courier_test.go b/core/msgio/courier_test.go index 6fe794458..16549343d 100644 --- a/core/msgio/courier_test.go +++ b/core/msgio/courier_test.go @@ -153,7 +153,7 @@ func TestNewCourierMsg(t *testing.T) { // try a broadcast message which won't have session and flow fields set and won't be high priority bcastID := testdata.InsertBroadcast(rt, testdata.Org1, `eng`, map[i18n.Language]string{`eng`: "Blast"}, nil, models.NilScheduleID, []*testdata.Contact{testFred}, nil) bcastMsg1 := flows.NewMsgOut(fredURN, assets.NewChannelReference(testdata.TwilioChannel.UUID, "Test Channel"), &flows.MsgContent{Text: "Blast"}, nil, flows.NilMsgTopic, i18n.NilLocale, flows.NilUnsendableReason) - msg3, err := models.NewOutgoingBroadcastMsg(rt, oa.Org(), twilio, fred, bcastMsg1, &models.BroadcastBatch{BroadcastID: bcastID, OptInID: optInID, CreatedByID: testdata.Admin.ID}) + msg3, err := models.NewOutgoingBroadcastMsg(rt, oa.Org(), twilio, fred, bcastMsg1, &models.Broadcast{ID: bcastID, OptInID: optInID, CreatedByID: testdata.Admin.ID}) require.NoError(t, err) err = models.InsertMessages(ctx, rt.DB, []*models.Msg{msg3}) diff --git a/core/tasks/msgs/send_broadcast_batch.go b/core/tasks/msgs/send_broadcast_batch.go index 068a9241c..23059f24e 100644 --- a/core/tasks/msgs/send_broadcast_batch.go +++ b/core/tasks/msgs/send_broadcast_batch.go @@ -36,6 +36,24 @@ func (t *SendBroadcastBatchTask) WithAssets() models.Refresh { } func (t *SendBroadcastBatchTask) Perform(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets) error { + var bcast *models.Broadcast + var err error + + // if this batch belongs to a persisted broadcast, fetch it + if t.BroadcastID != models.NilBroadcastID { + bcast, err = models.GetBroadcastByID(ctx, rt.DB, t.BroadcastID) + if err != nil { + return fmt.Errorf("error loading flow start for batch: %w", err) + } + } else { + bcast = t.Broadcast // otherwise use broadcast from the task + } + + // if this broadcast was interrupted, we're done + if bcast.Status == models.BroadcastStatusInterrupted { + return nil + } + // create this batch of messages msgs, err := t.BroadcastBatch.CreateMessages(ctx, rt, oa) if err != nil { @@ -46,7 +64,7 @@ func (t *SendBroadcastBatchTask) Perform(ctx context.Context, rt *runtime.Runtim // if this is our last batch, mark broadcast as done if t.IsLast { - if err := (&models.Broadcast{ID: t.BroadcastBatch.BroadcastID}).SetCompleted(ctx, rt.DB); err != nil { + if err := bcast.SetCompleted(ctx, rt.DB); err != nil { return fmt.Errorf("error marking broadcast as complete: %w", err) } } From a6faf6b0e30be8c5dce5853b077f67a3effd00d7 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 20 Sep 2024 15:45:12 -0500 Subject: [PATCH 088/216] Update CHANGELOG.md for v9.3.27 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 606bf9545..0370f1477 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.27 (2024-09-20) +------------------------- + * Add .broadcast field to broadcast batch tasks + v9.3.26 (2024-09-20) ------------------------- * Add warning for non-persistent broadcasts to more than 100 contacts From d9d8b464bf2bff0c3ea0197e69fd0a9db547c988 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 20 Sep 2024 15:50:56 -0500 Subject: [PATCH 089/216] Add more info to error log when writing ivr channel log fails --- web/ivr/ivr.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/ivr/ivr.go b/web/ivr/ivr.go index 540a6e62e..871ef8a45 100644 --- a/web/ivr/ivr.go +++ b/web/ivr/ivr.go @@ -84,7 +84,7 @@ func newIVRHandler(handler ivrHandlerFn, logType clogs.LogType) web.Handler { clog.End() if err := models.InsertChannelLogs(ctx, rt, []*models.ChannelLog{clog}); err != nil { - slog.Error("error writing ivr channel log", "error", err, "http_request", r) + slog.Error("error writing ivr channel log", "error", err, "elapsed", clog.Elapsed, "channel", ch.UUID()) } return rerr From 2998ef9b94498f6afb6df0f2a5ff7498cb9f2f30 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 20 Sep 2024 16:01:54 -0500 Subject: [PATCH 090/216] Update CHANGELOG.md for v9.3.28 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0370f1477..47f1641dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.28 (2024-09-20) +------------------------- + * Add more info to error log when writing ivr channel log fails + v9.3.27 (2024-09-20) ------------------------- * Add .broadcast field to broadcast batch tasks From 715e3ef14c0e8ff4a140f78f636b677c6cd1cb26 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Sun, 22 Sep 2024 09:58:08 -0500 Subject: [PATCH 091/216] Fix trigger_session actions with IVR flows --- core/handlers/session_triggered_test.go | 30 ++++++++++++++----------- core/hooks/create_starts.go | 7 ++++++ testsuite/testdata/contacts.go | 4 ++++ 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/core/handlers/session_triggered_test.go b/core/handlers/session_triggered_test.go index 256fc5eba..25c2a9f69 100644 --- a/core/handlers/session_triggered_test.go +++ b/core/handlers/session_triggered_test.go @@ -23,16 +23,6 @@ func TestSessionTriggered(t *testing.T) { defer testsuite.Reset(testsuite.ResetAll) - oa, err := models.GetOrgAssets(ctx, rt, testdata.Org1.ID) - assert.NoError(t, err) - - simpleFlow, err := oa.FlowByID(testdata.SingleMessage.ID) - assert.NoError(t, err) - - contactRef := &flows.ContactReference{ - UUID: testdata.George.UUID, - } - groupRef := &assets.GroupReference{ UUID: testdata.TestersGroup.UUID, } @@ -44,7 +34,7 @@ func TestSessionTriggered(t *testing.T) { { Actions: handlers.ContactActionMap{ testdata.Cathy: []flows.Action{ - actions.NewStartSession(handlers.NewActionUUID(), simpleFlow.Reference(), []*assets.GroupReference{groupRef}, []*flows.ContactReference{contactRef}, "", nil, nil, true), + actions.NewStartSession(handlers.NewActionUUID(), testdata.SingleMessage.Reference(), []*assets.GroupReference{groupRef}, []*flows.ContactReference{testdata.George.Reference()}, "", nil, nil, true), }, }, SQLAssertions: []handlers.SQLAssertion{ @@ -72,12 +62,26 @@ func TestSessionTriggered(t *testing.T) { assert.True(t, start.CreateContact) assert.Equal(t, []models.ContactID{testdata.George.ID}, start.ContactIDs) assert.Equal(t, []models.GroupID{testdata.TestersGroup.ID}, start.GroupIDs) - assert.Equal(t, simpleFlow.ID(), start.FlowID) - assert.JSONEq(t, `{"parent_uuid":"39a9f95e-3641-4d19-95e0-ed866f27c829", "ancestors":1, "ancestors_since_input":1}`, string(start.SessionHistory)) + assert.Equal(t, testdata.SingleMessage.ID, start.FlowID) + assert.JSONEq(t, `{"parent_uuid":"36284611-ea19-4f1f-8611-9bc48e206654", "ancestors":1, "ancestors_since_input":1}`, string(start.SessionHistory)) return nil }, }, }, + { + Actions: handlers.ContactActionMap{ + testdata.Bob: []flows.Action{ + actions.NewStartSession(handlers.NewActionUUID(), testdata.IVRFlow.Reference(), nil, []*flows.ContactReference{testdata.Alexandria.Reference()}, "", nil, nil, true), + }, + }, + SQLAssertions: []handlers.SQLAssertion{ + { // check that we do have a start in the database because it's an IVR flow + SQL: "select count(*) from flows_flowstart where org_id = 1 AND flow_id = $1", + Args: []any{testdata.IVRFlow.ID}, + Count: 1, + }, + }, + }, } handlers.RunTestCases(t, ctx, rt, tcs) diff --git a/core/hooks/create_starts.go b/core/hooks/create_starts.go index 398cfb2ad..957ac96fe 100644 --- a/core/hooks/create_starts.go +++ b/core/hooks/create_starts.go @@ -67,6 +67,13 @@ func (h *createStartsHook) Apply(ctx context.Context, rt *runtime.Runtime, tx *s WithParentSummary(event.RunSummary). WithSessionHistory(historyJSON) + // TODO find another way to pass start info to new calls + if flow.FlowType() == models.FlowTypeVoice { + if err := models.InsertFlowStarts(ctx, tx, []*models.FlowStart{start}); err != nil { + return fmt.Errorf("error inserting flow start: %w", err) + } + } + err = tasks.Queue(rc, tasks.BatchQueue, oa.OrgID(), &starts.StartFlowTask{FlowStart: start}, queues.DefaultPriority) if err != nil { return fmt.Errorf("error queuing flow start: %w", err) diff --git a/testsuite/testdata/contacts.go b/testsuite/testdata/contacts.go index a169ed1ab..07cc197f1 100644 --- a/testsuite/testdata/contacts.go +++ b/testsuite/testdata/contacts.go @@ -20,6 +20,10 @@ type Contact struct { URNID models.URNID } +func (c *Contact) Reference() *flows.ContactReference { + return &flows.ContactReference{UUID: c.UUID, Name: ""} +} + func (c *Contact) Load(rt *runtime.Runtime, oa *models.OrgAssets) (*models.Contact, *flows.Contact, []*models.ContactURN) { ctx := context.Background() From 81d44ee7e87500f8d7d90b25794fa48987f9ba76 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Sun, 22 Sep 2024 10:17:28 -0500 Subject: [PATCH 092/216] Update CHANGELOG.md for v9.3.29 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47f1641dd..8d5fb09be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.29 (2024-09-22) +------------------------- + * Fix trigger_session actions with IVR flows + v9.3.28 (2024-09-20) ------------------------- * Add more info to error log when writing ivr channel log fails From b1c2b5008c4dc1ad96c831c6bb8e839f4de4b0ed Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 23 Sep 2024 09:35:17 -0500 Subject: [PATCH 093/216] Update CHANGELOG.md for v9.3.30 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d5fb09be..58c00005a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.30 (2024-09-23) +------------------------- + * Use broadcast field on batch tasks + v9.3.29 (2024-09-22) ------------------------- * Fix trigger_session actions with IVR flows From ac2941027c962723fe93c0573f077dc413603d03 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 23 Sep 2024 12:11:06 -0500 Subject: [PATCH 094/216] Add test for models.JSONB --- core/models/utils_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/core/models/utils_test.go b/core/models/utils_test.go index d121e94ad..85b8ee113 100644 --- a/core/models/utils_test.go +++ b/core/models/utils_test.go @@ -59,3 +59,34 @@ func TestBulkQueryBatches(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT count(*) FROM foo WHERE name = 'G' AND age = 36`).Returns(1) assertdb.Query(t, rt.DB, `SELECT count(*) FROM foo `).Returns(7) } + +func TestJSONB(t *testing.T) { + ctx, rt := testsuite.Runtime() + + defer rt.DB.MustExec(`DROP TABLE foo;`) + + rt.DB.MustExec(`CREATE TABLE foo (id serial NOT NULL PRIMARY KEY, value JSONB NULL)`) + + type fooValue struct { + Name string `json:"name"` + } + + type foo struct { + ID int `db:"id"` + Value models.JSONB[fooValue] `db:"value"` + } + + foo1 := &foo{Value: models.JSONB[fooValue]{fooValue{Name: "A"}}} + + err := models.BulkQuery(ctx, "inserting foo", rt.DB, `INSERT INTO foo (value) VALUES(:value) RETURNING id`, []*foo{foo1}) + assert.NoError(t, err) + assertdb.Query(t, rt.DB, `SELECT count(*) FROM foo WHERE value->>'name' = 'A'`).Returns(1) + + sqlSelect := `SELECT id, value FROM foo WHERE id = $1` + + foo2 := &foo{} + err = rt.DB.GetContext(ctx, foo2, sqlSelect, foo1.ID) + assert.NoError(t, err) + assert.NotNil(t, foo2.Value) + assert.Equal(t, "A", foo2.Value.V.Name) +} From 01f551f1f927b8dd2cc49a1dea39bf68d445f892 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 23 Sep 2024 13:26:51 -0500 Subject: [PATCH 095/216] Fix loading broadcasts from batch tasks --- core/handlers/broadcast_created_test.go | 1 + core/models/broadcasts.go | 43 +++++++++++++++++-------- core/models/broadcasts_test.go | 4 +-- core/tasks/msgs/send_broadcast_batch.go | 2 +- core/tasks/msgs/send_broadcast_test.go | 8 +++-- core/tasks/schedules/cron_test.go | 4 +-- 6 files changed, 41 insertions(+), 21 deletions(-) diff --git a/core/handlers/broadcast_created_test.go b/core/handlers/broadcast_created_test.go index 26d0493d2..0beace4be 100644 --- a/core/handlers/broadcast_created_test.go +++ b/core/handlers/broadcast_created_test.go @@ -51,6 +51,7 @@ func TestBroadcastCreated(t *testing.T) { assert.Nil(t, bcast.ContactIDs) assert.Nil(t, bcast.GroupIDs) assert.Equal(t, 1, len(bcast.URNs)) + assert.False(t, bcast.Expressions) // engine already evaluated expressions return nil }, }, diff --git a/core/models/broadcasts.go b/core/models/broadcasts.go index af0195489..0833502a3 100644 --- a/core/models/broadcasts.go +++ b/core/models/broadcasts.go @@ -26,6 +26,8 @@ type BroadcastStatus string // start status constants const ( + BroadcastStatusPending = BroadcastStatus("P") + BroadcastStatusStarted = BroadcastStatus("S") BroadcastStatusCompleted = BroadcastStatus("C") BroadcastStatusFailed = BroadcastStatus("F") BroadcastStatusInterrupted = BroadcastStatus("I") @@ -56,6 +58,7 @@ type Broadcast struct { type dbBroadcast struct { ID BroadcastID `db:"id"` OrgID OrgID `db:"org_id"` + Status BroadcastStatus `db:"status"` Translations JSONB[flows.BroadcastTranslations] `db:"translations"` BaseLanguage i18n.Language `db:"base_language"` OptInID OptInID `db:"optin_id"` @@ -78,6 +81,7 @@ func NewBroadcast(orgID OrgID, translations flows.BroadcastTranslations, return &Broadcast{ OrgID: orgID, + Status: BroadcastStatusPending, Translations: translations, BaseLanguage: baseLanguage, Expressions: expressions, @@ -108,7 +112,7 @@ func NewBroadcastFromEvent(ctx context.Context, tx DBorTx, oa *OrgAssets, event } } - return NewBroadcast(oa.OrgID(), event.Translations, event.BaseLanguage, true, NilOptInID, groupIDs, contactIDs, event.URNs, event.ContactQuery, NoExclusions, NilUserID), nil + return NewBroadcast(oa.OrgID(), event.Translations, event.BaseLanguage, false, NilOptInID, groupIDs, contactIDs, event.URNs, event.ContactQuery, NoExclusions, NilUserID), nil } func (b *Broadcast) CreateBatch(contactIDs []ContactID, isLast bool) *BroadcastBatch { @@ -169,6 +173,7 @@ func InsertBroadcast(ctx context.Context, db DBorTx, bcast *Broadcast) error { dbb := &dbBroadcast{ ID: bcast.ID, OrgID: bcast.OrgID, + Status: bcast.Status, Translations: JSONB[flows.BroadcastTranslations]{bcast.Translations}, BaseLanguage: bcast.BaseLanguage, OptInID: bcast.OptInID, @@ -221,6 +226,7 @@ func InsertBroadcast(ctx context.Context, db DBorTx, bcast *Broadcast) error { func InsertChildBroadcast(ctx context.Context, db DBorTx, parent *Broadcast) (*Broadcast, error) { child := &Broadcast{ OrgID: parent.OrgID, + Status: BroadcastStatusPending, Translations: parent.Translations, BaseLanguage: parent.BaseLanguage, Expressions: parent.Expressions, @@ -251,25 +257,36 @@ type broadcastGroup struct { const sqlInsertBroadcast = ` INSERT INTO - msgs_broadcast( org_id, parent_id, created_on, modified_on, status, translations, base_language, template_id, template_variables, urns, query, node_uuid, exclusions, optin_id, schedule_id, is_active) - VALUES(:org_id, :parent_id, NOW() , NOW(), 'Q', :translations, :base_language, :template_id, :template_variables, :urns, :query, :node_uuid, :exclusions, :optin_id, :schedule_id, TRUE) + msgs_broadcast( org_id, parent_id, created_on, modified_on, status, translations, base_language, template_id, template_variables, urns, query, node_uuid, exclusions, optin_id, schedule_id, is_active) + VALUES(:org_id, :parent_id, NOW() , NOW(), :status, :translations, :base_language, :template_id, :template_variables, :urns, :query, :node_uuid, :exclusions, :optin_id, :schedule_id, TRUE) RETURNING id` const sqlInsertBroadcastContacts = `INSERT INTO msgs_broadcast_contacts(broadcast_id, contact_id) VALUES(:broadcast_id, :contact_id)` const sqlInsertBroadcastGroups = `INSERT INTO msgs_broadcast_groups(broadcast_id, contactgroup_id) VALUES(:broadcast_id, :contactgroup_id)` const sqlGetBroadcastByID = ` -SELECT id, org_id, status, translations, base_language, expressions, optin_id, template_id, template_variables, created_by_id +SELECT id, org_id, status, translations, base_language, optin_id, template_id, template_variables, created_by_id FROM msgs_broadcast WHERE id = $1` // GetBroadcastByID gets a broadcast by it's ID - NOTE this does not load all attributes of the broadcast func GetBroadcastByID(ctx context.Context, db DBorTx, bcastID BroadcastID) (*Broadcast, error) { - b := &Broadcast{} + b := &dbBroadcast{} if err := db.GetContext(ctx, b, sqlGetBroadcastByID, bcastID); err != nil { return nil, fmt.Errorf("error loading broadcast #%d: %w", bcastID, err) } - return b, nil + return &Broadcast{ + ID: b.ID, + OrgID: b.OrgID, + Status: b.Status, + Translations: b.Translations.V, + BaseLanguage: b.BaseLanguage, + Expressions: true, + OptInID: b.OptInID, + TemplateID: b.TemplateID, + TemplateVariables: b.TemplateVariables, + CreatedByID: b.CreatedByID, + }, nil } // BroadcastBatch represents a batch of contacts that need messages sent for @@ -292,7 +309,7 @@ type BroadcastBatch struct { CreatedByID UserID `json:"created_by_id"` } -func (b *BroadcastBatch) CreateMessages(ctx context.Context, rt *runtime.Runtime, oa *OrgAssets) ([]*Msg, error) { +func (b *BroadcastBatch) CreateMessages(ctx context.Context, rt *runtime.Runtime, oa *OrgAssets, bcast *Broadcast) ([]*Msg, error) { // load all our contacts contacts, err := LoadContacts(ctx, rt.DB, oa, b.ContactIDs) if err != nil { @@ -304,7 +321,7 @@ func (b *BroadcastBatch) CreateMessages(ctx context.Context, rt *runtime.Runtime // run through all our contacts to create our messages for _, c := range contacts { - msg, err := b.createMessage(rt, oa, c) + msg, err := bcast.createMessage(rt, oa, c) if err != nil { return nil, fmt.Errorf("error creating broadcast message: %w", err) } @@ -323,16 +340,16 @@ func (b *BroadcastBatch) CreateMessages(ctx context.Context, rt *runtime.Runtime } // creates an outgoing message for the given contact - can return nil if resultant message has no content and thus is a noop -func (b *BroadcastBatch) createMessage(rt *runtime.Runtime, oa *OrgAssets, c *Contact) (*Msg, error) { +func (b *Broadcast) createMessage(rt *runtime.Runtime, oa *OrgAssets, c *Contact) (*Msg, error) { contact, err := c.FlowContact(oa) if err != nil { return nil, fmt.Errorf("error creating flow contact for broadcast message: %w", err) } - content, locale := b.Broadcast.Translations.ForContact(oa.Env(), contact, b.Broadcast.BaseLanguage) + content, locale := b.Translations.ForContact(oa.Env(), contact, b.BaseLanguage) var expressionsContext *types.XObject - if b.Broadcast.Expressions { + if b.Expressions { expressionsContext = types.NewXObject(map[string]types.XValue{ "contact": flows.Context(oa.Env(), contact), "fields": flows.Context(oa.Env(), contact.Fields()), @@ -347,9 +364,9 @@ func (b *BroadcastBatch) createMessage(rt *runtime.Runtime, oa *OrgAssets, c *Co } // create our outgoing message - out, ch := CreateMsgOut(rt, oa, contact, content, b.Broadcast.TemplateID, b.Broadcast.TemplateVariables, locale, expressionsContext) + out, ch := CreateMsgOut(rt, oa, contact, content, b.TemplateID, b.TemplateVariables, locale, expressionsContext) - msg, err := NewOutgoingBroadcastMsg(rt, oa.Org(), ch, contact, out, b.Broadcast) + msg, err := NewOutgoingBroadcastMsg(rt, oa.Org(), ch, contact, out, b) if err != nil { return nil, fmt.Errorf("error creating outgoing message: %w", err) } diff --git a/core/models/broadcasts_test.go b/core/models/broadcasts_test.go index bb63ba185..b2876312e 100644 --- a/core/models/broadcasts_test.go +++ b/core/models/broadcasts_test.go @@ -132,7 +132,7 @@ func TestNonPersistentBroadcasts(t *testing.T) { oa, err := models.GetOrgAssets(ctx, rt, testdata.Org1.ID) require.NoError(t, err) - msgs, err := batch.CreateMessages(ctx, rt, oa) + msgs, err := batch.CreateMessages(ctx, rt, oa, bcast) require.NoError(t, err) assert.Equal(t, 2, len(msgs)) @@ -262,7 +262,7 @@ func TestBroadcastBatchCreateMessage(t *testing.T) { IsLast: true, } - msgs, err := batch.CreateMessages(ctx, rt, oa) + msgs, err := batch.CreateMessages(ctx, rt, oa, bcast) if tc.expectedError != "" { assert.EqualError(t, err, tc.expectedError, "error mismatch in test case %d", i) } else { diff --git a/core/tasks/msgs/send_broadcast_batch.go b/core/tasks/msgs/send_broadcast_batch.go index 23059f24e..621d79aee 100644 --- a/core/tasks/msgs/send_broadcast_batch.go +++ b/core/tasks/msgs/send_broadcast_batch.go @@ -55,7 +55,7 @@ func (t *SendBroadcastBatchTask) Perform(ctx context.Context, rt *runtime.Runtim } // create this batch of messages - msgs, err := t.BroadcastBatch.CreateMessages(ctx, rt, oa) + msgs, err := t.BroadcastBatch.CreateMessages(ctx, rt, oa, bcast) if err != nil { return fmt.Errorf("error creating broadcast messages: %w", err) } diff --git a/core/tasks/msgs/send_broadcast_test.go b/core/tasks/msgs/send_broadcast_test.go index 991baa277..3c42920f7 100644 --- a/core/tasks/msgs/send_broadcast_test.go +++ b/core/tasks/msgs/send_broadcast_test.go @@ -21,7 +21,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestSendBroadcastTask(t *testing.T) { +func TestBroadcastsFromEvents(t *testing.T) { ctx, rt := testsuite.Runtime() defer testsuite.Reset(testsuite.ResetAll) @@ -181,7 +181,7 @@ func TestSendBroadcastTask(t *testing.T) { } } -func TestBroadcastTask(t *testing.T) { +func TestSendBroadcastTask(t *testing.T) { ctx, rt := testsuite.Runtime() rc := rt.RP.Get() defer rc.Close() @@ -283,10 +283,12 @@ func TestBroadcastTask(t *testing.T) { } bcast := models.NewBroadcast(oa.OrgID(), tc.translations, tc.baseLanguage, tc.expressions, optInID, tc.groupIDs, tc.contactIDs, tc.URNs, tc.query, tc.exclusions, tc.createdByID) + err := models.InsertBroadcast(ctx, rt.DB, bcast) + assert.NoError(t, err) task := &msgs.SendBroadcastTask{Broadcast: bcast} - err := tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, task, queues.DefaultPriority) + err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, task, queues.DefaultPriority) assert.NoError(t, err) taskCounts := testsuite.FlushTasks(t, rt) diff --git a/core/tasks/schedules/cron_test.go b/core/tasks/schedules/cron_test.go index b01121a4c..a6b22ac43 100644 --- a/core/tasks/schedules/cron_test.go +++ b/core/tasks/schedules/cron_test.go @@ -56,14 +56,14 @@ func TestCheckSchedules(t *testing.T) { AND parent_id = $2 AND translations -> 'eng' ->> 'text' = 'Hi' AND translations -> 'spa' ->> 'text' = 'Hola' - AND status = 'Q' + AND status = 'P' AND base_language = 'eng'`, testdata.Org1.ID, b1).Returns(1) assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_broadcast WHERE org_id = $1 AND parent_id = $2 AND translations -> 'eng' ->> 'text' = 'Bye' AND translations -> 'spa' ->> 'text' = 'Chau' - AND status = 'Q' + AND status = 'P' AND base_language = 'eng'`, testdata.Org1.ID, b2).Returns(1) // with the right count of contacts and groups From dd8420eb2d9f9a9c8fb9a61f3dccf36102969e95 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 23 Sep 2024 14:17:32 -0500 Subject: [PATCH 096/216] Update test database and tweak bcast code --- core/models/broadcasts.go | 46 ++++++++++++------------ core/models/broadcasts_test.go | 4 +-- core/tasks/msgs/send_broadcast_batch.go | 2 +- testsuite/testfiles/postgres.dump | Bin 1764871 -> 1764613 bytes 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/core/models/broadcasts.go b/core/models/broadcasts.go index 0833502a3..6262e8c48 100644 --- a/core/models/broadcasts.go +++ b/core/models/broadcasts.go @@ -115,6 +115,26 @@ func NewBroadcastFromEvent(ctx context.Context, tx DBorTx, oa *OrgAssets, event return NewBroadcast(oa.OrgID(), event.Translations, event.BaseLanguage, false, NilOptInID, groupIDs, contactIDs, event.URNs, event.ContactQuery, NoExclusions, NilUserID), nil } +// BroadcastBatch represents a batch of contacts that need messages sent for +type BroadcastBatch struct { + // for persisted starts broadcast_id is set, for non-persisted broadcasts like flow actions, broadcast is set + BroadcastID BroadcastID `json:"broadcast_id,omitempty"` + Broadcast *Broadcast `json:"broadcast,omitempty"` + + ContactIDs []ContactID `json:"contact_ids"` + IsLast bool `json:"is_last"` + + // TODO remove + OrgID OrgID `json:"org_id"` + Translations flows.BroadcastTranslations `json:"translations"` + BaseLanguage i18n.Language `json:"base_language"` + Expressions bool `json:"expressions"` + OptInID OptInID `json:"optin_id,omitempty"` + TemplateID TemplateID `json:"template_id,omitempty"` + TemplateVariables []string `json:"template_variables,omitempty"` + CreatedByID UserID `json:"created_by_id"` +} + func (b *Broadcast) CreateBatch(contactIDs []ContactID, isLast bool) *BroadcastBatch { bb := &BroadcastBatch{ ContactIDs: contactIDs, @@ -289,29 +309,9 @@ func GetBroadcastByID(ctx context.Context, db DBorTx, bcastID BroadcastID) (*Bro }, nil } -// BroadcastBatch represents a batch of contacts that need messages sent for -type BroadcastBatch struct { - // for persisted starts broadcast_id is set, for non-persisted broadcasts like flow actions, broadcast is set - BroadcastID BroadcastID `json:"broadcast_id,omitempty"` - Broadcast *Broadcast `json:"broadcast,omitempty"` - - ContactIDs []ContactID `json:"contact_ids"` - IsLast bool `json:"is_last"` - - // TODO remove - OrgID OrgID `json:"org_id"` - Translations flows.BroadcastTranslations `json:"translations"` - BaseLanguage i18n.Language `json:"base_language"` - Expressions bool `json:"expressions"` - OptInID OptInID `json:"optin_id,omitempty"` - TemplateID TemplateID `json:"template_id,omitempty"` - TemplateVariables []string `json:"template_variables,omitempty"` - CreatedByID UserID `json:"created_by_id"` -} - -func (b *BroadcastBatch) CreateMessages(ctx context.Context, rt *runtime.Runtime, oa *OrgAssets, bcast *Broadcast) ([]*Msg, error) { +func (b *Broadcast) CreateMessages(ctx context.Context, rt *runtime.Runtime, oa *OrgAssets, batch *BroadcastBatch) ([]*Msg, error) { // load all our contacts - contacts, err := LoadContacts(ctx, rt.DB, oa, b.ContactIDs) + contacts, err := LoadContacts(ctx, rt.DB, oa, batch.ContactIDs) if err != nil { return nil, fmt.Errorf("error loading contacts for broadcast: %w", err) } @@ -321,7 +321,7 @@ func (b *BroadcastBatch) CreateMessages(ctx context.Context, rt *runtime.Runtime // run through all our contacts to create our messages for _, c := range contacts { - msg, err := bcast.createMessage(rt, oa, c) + msg, err := b.createMessage(rt, oa, c) if err != nil { return nil, fmt.Errorf("error creating broadcast message: %w", err) } diff --git a/core/models/broadcasts_test.go b/core/models/broadcasts_test.go index b2876312e..8970a1d84 100644 --- a/core/models/broadcasts_test.go +++ b/core/models/broadcasts_test.go @@ -132,7 +132,7 @@ func TestNonPersistentBroadcasts(t *testing.T) { oa, err := models.GetOrgAssets(ctx, rt, testdata.Org1.ID) require.NoError(t, err) - msgs, err := batch.CreateMessages(ctx, rt, oa, bcast) + msgs, err := bcast.CreateMessages(ctx, rt, oa, batch) require.NoError(t, err) assert.Equal(t, 2, len(msgs)) @@ -262,7 +262,7 @@ func TestBroadcastBatchCreateMessage(t *testing.T) { IsLast: true, } - msgs, err := batch.CreateMessages(ctx, rt, oa, bcast) + msgs, err := bcast.CreateMessages(ctx, rt, oa, batch) if tc.expectedError != "" { assert.EqualError(t, err, tc.expectedError, "error mismatch in test case %d", i) } else { diff --git a/core/tasks/msgs/send_broadcast_batch.go b/core/tasks/msgs/send_broadcast_batch.go index 621d79aee..f6fa379f8 100644 --- a/core/tasks/msgs/send_broadcast_batch.go +++ b/core/tasks/msgs/send_broadcast_batch.go @@ -55,7 +55,7 @@ func (t *SendBroadcastBatchTask) Perform(ctx context.Context, rt *runtime.Runtim } // create this batch of messages - msgs, err := t.BroadcastBatch.CreateMessages(ctx, rt, oa, bcast) + msgs, err := bcast.CreateMessages(ctx, rt, oa, t.BroadcastBatch) if err != nil { return fmt.Errorf("error creating broadcast messages: %w", err) } diff --git a/testsuite/testfiles/postgres.dump b/testsuite/testfiles/postgres.dump index 4b44f96dcf5869000a00d5754223cc9b9ce3d7d9..59a1f8b64f655a87683c237b27af5fc12cb93223 100644 GIT binary patch delta 67928 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 LHwg6uL`eYve&5%W delta 68055 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 Date: Mon, 23 Sep 2024 14:31:18 -0500 Subject: [PATCH 097/216] Mark broadcasts as started once recipients are known and update contact_count --- core/models/broadcasts.go | 14 ++++++++++++++ core/tasks/msgs/send_broadcast.go | 5 +++++ core/tasks/starts/start_flow.go | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/core/models/broadcasts.go b/core/models/broadcasts.go index 6262e8c48..837fbc278 100644 --- a/core/models/broadcasts.go +++ b/core/models/broadcasts.go @@ -160,6 +160,20 @@ func (b *Broadcast) CreateBatch(contactIDs []ContactID, isLast bool) *BroadcastB return bb } +// SetStarted sets the status of this broadcast to STARTED, if it's not already set to INTERRUPTED +func (b *Broadcast) SetStarted(ctx context.Context, db DBorTx, contactCount int) error { + if b.Status != BroadcastStatusInterrupted { + b.Status = BroadcastStatusStarted + } + if b.ID != NilBroadcastID { + _, err := db.ExecContext(ctx, "UPDATE msgs_broadcast SET status = 'S', contact_count = $2, modified_on = NOW() WHERE id = $1 AND status != 'I'", b.ID, contactCount) + if err != nil { + return fmt.Errorf("error setting broadcast #%d as started: %w", b.ID, err) + } + } + return nil +} + // SetCompleted sets the status of this broadcast to COMPLETED, if it's not already set to INTERRUPTED func (b *Broadcast) SetCompleted(ctx context.Context, db DBorTx) error { if b.Status != BroadcastStatusInterrupted { diff --git a/core/tasks/msgs/send_broadcast.go b/core/tasks/msgs/send_broadcast.go index b7da6eddd..454a72fbd 100644 --- a/core/tasks/msgs/send_broadcast.go +++ b/core/tasks/msgs/send_broadcast.go @@ -82,6 +82,11 @@ func createBroadcastBatches(ctx context.Context, rt *runtime.Runtime, oa *models contactIDs = append(contactIDs, nodeContactIDs...) } + // mark our broadcast as started, last task will mark as complete + if err := bcast.SetStarted(ctx, rt.DB, len(contactIDs)); err != nil { + return fmt.Errorf("error marking broadcast as started: %w", err) + } + // if there are no contacts to send to, mark our broadcast as sent, we are done if len(contactIDs) == 0 { if err := bcast.SetCompleted(ctx, rt.DB); err != nil { diff --git a/core/tasks/starts/start_flow.go b/core/tasks/starts/start_flow.go index 059c75314..9213741a7 100644 --- a/core/tasks/starts/start_flow.go +++ b/core/tasks/starts/start_flow.go @@ -97,7 +97,7 @@ func createFlowStartBatches(ctx context.Context, rt *runtime.Runtime, oa *models } } - // mark our start as starting, last task will mark as complete + // mark our start as started, last task will mark as complete if err := start.SetStarted(ctx, rt.DB, len(contactIDs)); err != nil { return fmt.Errorf("error marking start as started: %w", err) } From 5e9be8e182e15d36623e80b2f5ecf0da72c3a01b Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 23 Sep 2024 14:43:37 -0500 Subject: [PATCH 098/216] Update CHANGELOG.md for v9.3.31 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58c00005a..489bfaa50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.31 (2024-09-23) +------------------------- + * Mark broadcasts as started once recipients are known and update contact_count + * Fix loading broadcasts from batch tasks + v9.3.30 (2024-09-23) ------------------------- * Use broadcast field on batch tasks From af7749b3c63fd73a26e7d8724891370283a4dec5 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 23 Sep 2024 15:20:41 -0500 Subject: [PATCH 099/216] Fix broadcast endpoint creating broadcasts with empty status --- web/msg/broadcast.go | 1 + web/msg/testdata/broadcast.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/web/msg/broadcast.go b/web/msg/broadcast.go index 0a0a58deb..c395ebd47 100644 --- a/web/msg/broadcast.go +++ b/web/msg/broadcast.go @@ -79,6 +79,7 @@ func handleBroadcast(ctx context.Context, rt *runtime.Runtime, r *broadcastReque bcast := &models.Broadcast{ OrgID: r.OrgID, + Status: models.BroadcastStatusPending, Translations: r.Translations, BaseLanguage: r.BaseLanguage, Expressions: true, diff --git a/web/msg/testdata/broadcast.json b/web/msg/testdata/broadcast.json index c8255da58..d8353ecc7 100644 --- a/web/msg/testdata/broadcast.json +++ b/web/msg/testdata/broadcast.json @@ -79,7 +79,7 @@ }, "db_assertions": [ { - "query": "SELECT count(*) FROM msgs_broadcast WHERE translations->'eng'->>'text' = 'Hello' AND base_language = 'eng' AND urns = '{\"tel:+1234567890\"}' AND query = 'age > 20' AND optin_id = $polls_id$ AND template_id = 10000 AND template_variables = '{\"@contact\"}'", + "query": "SELECT count(*) FROM msgs_broadcast WHERE status = 'P' AND translations->'eng'->>'text' = 'Hello' AND base_language = 'eng' AND urns = '{\"tel:+1234567890\"}' AND query = 'age > 20' AND optin_id = $polls_id$ AND template_id = 10000 AND template_variables = '{\"@contact\"}'", "count": 1 }, { From 1bd7b769f6f10ca8d1918388b749b823a49759c9 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 23 Sep 2024 15:37:25 -0500 Subject: [PATCH 100/216] Update CHANGELOG.md for v9.3.32 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 489bfaa50..1afcf8d12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.32 (2024-09-23) +------------------------- + * Fix broadcast endpoint creating broadcasts with empty status + v9.3.31 (2024-09-23) ------------------------- * Mark broadcasts as started once recipients are known and update contact_count From 4353bf27240c2c5d443a66d66820dc120909b09e Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 24 Sep 2024 11:10:28 -0500 Subject: [PATCH 101/216] Remove unused bcast task fields --- core/models/broadcasts.go | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/core/models/broadcasts.go b/core/models/broadcasts.go index 837fbc278..13c26d8aa 100644 --- a/core/models/broadcasts.go +++ b/core/models/broadcasts.go @@ -123,32 +123,12 @@ type BroadcastBatch struct { ContactIDs []ContactID `json:"contact_ids"` IsLast bool `json:"is_last"` - - // TODO remove - OrgID OrgID `json:"org_id"` - Translations flows.BroadcastTranslations `json:"translations"` - BaseLanguage i18n.Language `json:"base_language"` - Expressions bool `json:"expressions"` - OptInID OptInID `json:"optin_id,omitempty"` - TemplateID TemplateID `json:"template_id,omitempty"` - TemplateVariables []string `json:"template_variables,omitempty"` - CreatedByID UserID `json:"created_by_id"` } func (b *Broadcast) CreateBatch(contactIDs []ContactID, isLast bool) *BroadcastBatch { bb := &BroadcastBatch{ ContactIDs: contactIDs, IsLast: isLast, - - // TODO remove - OrgID: b.OrgID, - Translations: b.Translations, - BaseLanguage: b.BaseLanguage, - Expressions: b.Expressions, - OptInID: b.OptInID, - TemplateID: b.TemplateID, - TemplateVariables: b.TemplateVariables, - CreatedByID: b.CreatedByID, } if b.ID != NilBroadcastID { From 544ad97b4b0d5de6962948fc0df2d8555594dbba Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 24 Sep 2024 13:29:20 -0500 Subject: [PATCH 102/216] Re-evaluate dynamic groups of contacts who have had URNs stolen from them --- core/models/contacts.go | 23 +++++++++++++++++++++-- core/models/contacts_test.go | 28 +++++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/core/models/contacts.go b/core/models/contacts.go index d9cb2b0bd..1f74ddcfb 100644 --- a/core/models/contacts.go +++ b/core/models/contacts.go @@ -1289,10 +1289,29 @@ func UpdateContactURNs(ctx context.Context, db DBorTx, oa *OrgAssets, changes [] } } - // finally mark all the orphaned contacts as modified + // finally update the contacts who had URNs stolen from them if len(orphanedIDs) > 0 { - err := UpdateContactModifiedOn(ctx, db, orphanedIDs) + orphans, err := LoadContacts(ctx, db, oa, orphanedIDs) if err != nil { + return fmt.Errorf("error loading contacts affecting by URN stealing: %w", err) + } + + // turn them into flow contacts.. + flowOrphans := make([]*flows.Contact, len(orphans)) + for i, c := range orphans { + flowOrphans[i], err = c.FlowContact(oa) + if err != nil { + return fmt.Errorf("error creating orphan flow contact: %w", err) + } + } + + // and re-calculate their dynamic groups + if err := CalculateDynamicGroups(ctx, db, oa, flowOrphans); err != nil { + return fmt.Errorf("error re-calculating dynamic groups for orphaned contacts: %w", err) + } + + // and mark them as updated + if err := UpdateContactModifiedOn(ctx, db, orphanedIDs); err != nil { return fmt.Errorf("error updating orphaned contacts: %w", err) } } diff --git a/core/models/contacts_test.go b/core/models/contacts_test.go index cb78f6166..fb4a9b1a5 100644 --- a/core/models/contacts_test.go +++ b/core/models/contacts_test.go @@ -567,6 +567,8 @@ func TestUpdateContactURNs(t *testing.T) { defer testsuite.Reset(testsuite.ResetAll) + testdata.InsertContactGroup(rt, testdata.Org1, "e3374234-8131-4f65-9c51-ce84fd7f3bb5", "No URN", `urn = ""`) + oa, err := models.GetOrgAssets(ctx, rt, testdata.Org1.ID) assert.NoError(t, err) @@ -576,9 +578,21 @@ func TestUpdateContactURNs(t *testing.T) { assertContactURNs := func(contactID models.ContactID, expected []string) { var actual []string err = rt.DB.Select(&actual, `SELECT identity FROM contacts_contacturn WHERE contact_id = $1 ORDER BY priority DESC`, contactID) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, expected, actual, "URNs mismatch for contact %d", contactID) } + assertModifiedOnUpdated := func(contactID models.ContactID, greaterThan time.Time) { + var modifiedOn time.Time + err = rt.DB.Get(&modifiedOn, `SELECT modified_on FROM contacts_contact WHERE id = $1`, contactID) + require.NoError(t, err) + assert.Greater(t, modifiedOn, greaterThan, "URNs mismatch for contact %d", contactID) + } + assertGroups := func(contactID models.ContactID, expected []string) { + var actual []string + err = rt.DB.Select(&actual, `SELECT g.name FROM contacts_contactgroup_contacts gc INNER JOIN contacts_contactgroup g ON g.id = gc.contactgroup_id WHERE gc.contact_id = $1`, contactID) + require.NoError(t, err) + assert.ElementsMatch(t, expected, actual) + } assertContactURNs(testdata.Cathy.ID, []string{"tel:+16055741111"}) assertContactURNs(testdata.Bob.ID, []string{"tel:+16055742222"}) @@ -608,12 +622,20 @@ func TestUpdateContactURNs(t *testing.T) { assertContactURNs(testdata.Cathy.ID, []string{"tel:+16055700001"}) assertdb.Query(t, rt.DB, `SELECT count(*) FROM contacts_contacturn WHERE contact_id IS NULL`).Returns(1) // now orphaned + t1 := time.Now() + // steal a URN from Bob - err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{{testdata.Cathy.ID, testdata.Org1.ID, []urns.URN{"tel:+16055700001", "tel:+16055700002"}}}) + err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{ + {testdata.Cathy.ID, testdata.Org1.ID, []urns.URN{"tel:+16055700001", "tel:+16055700002"}}, + {testdata.Alexandria.ID, testdata.Org1.ID, []urns.URN{"tel:+16055742222"}}, + }) assert.NoError(t, err) assertContactURNs(testdata.Cathy.ID, []string{"tel:+16055700001", "tel:+16055700002"}) - assertContactURNs(testdata.Bob.ID, []string{"tel:+16055742222"}) + assertContactURNs(testdata.Alexandria.ID, []string{"tel:+16055742222"}) + assertContactURNs(testdata.Bob.ID, []string(nil)) + assertModifiedOnUpdated(testdata.Bob.ID, t1) + assertGroups(testdata.Bob.ID, []string{"Active", "No URN"}) // steal the URN back from Cathy whilst simulataneously adding new URN to Cathy and not-changing anything for George err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{ From a6bd541b6846c42a18316ef03fa73cc7367e7627 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 24 Sep 2024 13:55:03 -0500 Subject: [PATCH 103/216] Update CHANGELOG.md for v9.3.33 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1afcf8d12..f8738e0fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.33 (2024-09-24) +------------------------- + * Re-evaluate dynamic groups of contacts who have had URNs stolen from them + * Remove unused broadcast task fields + v9.3.32 (2024-09-23) ------------------------- * Fix broadcast endpoint creating broadcasts with empty status From 33480ccf236ba35d6c9fbc146bb9d0a8ebd0919e Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 25 Sep 2024 11:40:24 -0500 Subject: [PATCH 104/216] Add support for de-indexing contacts from elastic --- core/models/orgs.go | 5 +++ core/search/index.go | 79 +++++++++++++++++++++++++++++++++++++++ core/search/index_test.go | 54 ++++++++++++++++++++++++++ core/search/search.go | 11 ++---- go.mod | 2 +- go.sum | 4 +- 6 files changed, 145 insertions(+), 10 deletions(-) create mode 100644 core/search/index.go create mode 100644 core/search/index_test.go diff --git a/core/models/orgs.go b/core/models/orgs.go index e5ea38c94..1eda075c7 100644 --- a/core/models/orgs.go +++ b/core/models/orgs.go @@ -10,6 +10,7 @@ import ( "mime" "net/http" "path/filepath" + "strconv" "time" "github.com/aws/aws-sdk-go-v2/service/s3/types" @@ -59,6 +60,10 @@ func airtimeServiceFactory(rt *runtime.Runtime) engine.AirtimeServiceFactory { // OrgID is our type for org ids type OrgID int +func (i OrgID) String() string { + return strconv.FormatInt(int64(i), 10) +} + // OrgUUID is our type for org UUIDs type OrgUUID uuids.UUID diff --git a/core/search/index.go b/core/search/index.go new file mode 100644 index 000000000..d54fc5d68 --- /dev/null +++ b/core/search/index.go @@ -0,0 +1,79 @@ +package search + +import ( + "bytes" + "context" + "fmt" + "time" + + "github.com/elastic/go-elasticsearch/v8/typedapi/types/enums/operationtype" + "github.com/lib/pq" + "github.com/nyaruka/gocommon/jsonx" + "github.com/nyaruka/mailroom/core/models" + "github.com/nyaruka/mailroom/runtime" +) + +// DeindexContactsByID de-indexes the contacts with the given IDs from Elastic +func DeindexContactsByID(ctx context.Context, rt *runtime.Runtime, contactIDs []models.ContactID) (int, error) { + rows, err := rt.DB.QueryContext(ctx, `SELECT id, org_id, modified_on FROM contacts_contact WHERE id = ANY($1) AND NOT is_active`, pq.Array(contactIDs)) + if err != nil { + return 0, fmt.Errorf("error querying deleted contacts to deindex: %w", err) + } + defer rows.Close() + + cmds := &bytes.Buffer{} + + for rows.Next() { + var id models.ContactID + var orgID models.OrgID + var modifiedOn time.Time + + if err := rows.Scan(&id, &orgID, &modifiedOn); err != nil { + return 0, fmt.Errorf("error scanning deleted contact to deindex: %w", err) + } + + cmds.Write(jsonx.MustMarshal(map[string]any{ + "delete": map[string]any{ + "_id": id, + "version": modifiedOn.UnixNano(), + "version_type": "external", + "routing": orgID.String(), + }}, + )) + cmds.WriteString("\n") + } + + if cmds.Len() == 0 { + return 0, nil + } + + fmt.Println(cmds.String()) + + resp, err := rt.ES.Bulk().Index(rt.Config.ElasticContactsIndex).Raw(bytes.NewReader(cmds.Bytes())).Do(ctx) + if err != nil { + return 0, fmt.Errorf("error deindexing deleted contacts from elastic: %w", err) + } + + deleted := 0 + for _, r := range resp.Items { + if r[operationtype.Delete].Status == 200 { + deleted++ + } + } + + return deleted, nil +} + +// DeindexContactsByOrg de-indexes all contacts in the given org from Elastic +func DeindexContactsByOrg(ctx context.Context, rt *runtime.Runtime, orgID models.OrgID) error { + src := map[string]any{ + "query": map[string]any{"match_all": map[string]any{}}, + } + + _, err := rt.ES.DeleteByQuery(rt.Config.ElasticContactsIndex).Routing(orgID.String()).Raw(bytes.NewReader(jsonx.MustMarshal(src))).Do(ctx) + if err != nil { + return fmt.Errorf("error deindexing contacts in org #%d from elastic: %w", orgID, err) + } + + return nil +} diff --git a/core/search/index_test.go b/core/search/index_test.go new file mode 100644 index 000000000..ace94aabb --- /dev/null +++ b/core/search/index_test.go @@ -0,0 +1,54 @@ +package search_test + +import ( + "bytes" + "context" + "testing" + + "github.com/nyaruka/gocommon/dbutil/assertdb" + "github.com/nyaruka/gocommon/elastic" + "github.com/nyaruka/gocommon/jsonx" + "github.com/nyaruka/mailroom/core/models" + "github.com/nyaruka/mailroom/core/search" + "github.com/nyaruka/mailroom/runtime" + "github.com/nyaruka/mailroom/testsuite" + "github.com/nyaruka/mailroom/testsuite/testdata" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDeindexContactsByID(t *testing.T) { + ctx, rt := testsuite.Runtime() + + testsuite.ReindexElastic(ctx) + + assertdb.Query(t, rt.DB, `SELECT count(*) FROM contacts_contact WHERE org_id = $1`, testdata.Org1.ID).Returns(124) + assertdb.Query(t, rt.DB, `SELECT count(*) FROM contacts_contact WHERE org_id = $1`, testdata.Org2.ID).Returns(121) + assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org1.ID), 124) + assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org2.ID), 121) + + // try to deindex contacts which aren't deleted + deindexed, err := search.DeindexContactsByID(ctx, rt, []models.ContactID{testdata.Bob.ID, testdata.George.ID}) + assert.NoError(t, err) + assert.Equal(t, 0, deindexed) + + assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org1.ID), 124) + assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org2.ID), 121) + + rt.DB.MustExec(`UPDATE contacts_contact SET is_active = false WHERE org_id = $1`, testdata.Org1.ID) + + deindexed, err = search.DeindexContactsByID(ctx, rt, []models.ContactID{testdata.Bob.ID, testdata.George.ID}) + assert.NoError(t, err) + assert.Equal(t, 2, deindexed) + + assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org1.ID), 122) + assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org2.ID), 121) +} + +func assertSearchCount(t *testing.T, rt *runtime.Runtime, query elastic.Query, expected int) { + src := map[string]any{"query": query} + + resp, err := rt.ES.Count().Index(rt.Config.ElasticContactsIndex).Raw(bytes.NewReader(jsonx.MustMarshal(src))).Do(context.Background()) + require.NoError(t, err) + assert.Equal(t, expected, int(resp.Count)) +} diff --git a/core/search/search.go b/core/search/search.go index 64e14498d..bab540cc6 100644 --- a/core/search/search.go +++ b/core/search/search.go @@ -86,11 +86,10 @@ func GetContactTotal(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAss } } - routing := strconv.FormatInt(int64(oa.OrgID()), 10) eq := BuildElasticQuery(oa, group, models.NilContactStatus, nil, parsed) src := map[string]any{"query": eq} - count, err := rt.ES.Count().Index(rt.Config.ElasticContactsIndex).Routing(routing).Raw(bytes.NewReader(jsonx.MustMarshal(src))).Do(ctx) + count, err := rt.ES.Count().Index(rt.Config.ElasticContactsIndex).Routing(oa.OrgID().String()).Raw(bytes.NewReader(jsonx.MustMarshal(src))).Do(ctx) if err != nil { return nil, 0, fmt.Errorf("error performing count: %w", err) } @@ -117,7 +116,6 @@ func GetContactIDsForQueryPage(ctx context.Context, rt *runtime.Runtime, oa *mod } } - routing := strconv.FormatInt(int64(oa.OrgID()), 10) eq := BuildElasticQuery(oa, group, models.NilContactStatus, excludeIDs, parsed) fieldSort, err := es.ToElasticSort(sort, oa.SessionAssets()) @@ -134,7 +132,7 @@ func GetContactIDsForQueryPage(ctx context.Context, rt *runtime.Runtime, oa *mod "track_total_hits": true, } - results, err := rt.ES.Search().Index(index).Routing(routing).Raw(bytes.NewReader(jsonx.MustMarshal(src))).Do(ctx) + results, err := rt.ES.Search().Index(index).Routing(oa.OrgID().String()).Raw(bytes.NewReader(jsonx.MustMarshal(src))).Do(ctx) if err != nil { return nil, nil, 0, fmt.Errorf("error performing query: %w", err) } @@ -166,7 +164,6 @@ func GetContactIDsForQuery(ctx context.Context, rt *runtime.Runtime, oa *models. } } - routing := strconv.FormatInt(int64(oa.OrgID()), 10) eq := BuildElasticQuery(oa, group, status, nil, parsed) sort := elastic.SortBy("id", true) ids := make([]models.ContactID, 0, 100) @@ -182,7 +179,7 @@ func GetContactIDsForQuery(ctx context.Context, rt *runtime.Runtime, oa *models. "track_total_hits": false, } - results, err := rt.ES.Search().Index(index).Routing(routing).Raw(bytes.NewReader(jsonx.MustMarshal(src))).Do(ctx) + results, err := rt.ES.Search().Index(index).Routing(oa.OrgID().String()).Raw(bytes.NewReader(jsonx.MustMarshal(src))).Do(ctx) if err != nil { return nil, fmt.Errorf("error searching ES index: %w", err) } @@ -190,7 +187,7 @@ func GetContactIDsForQuery(ctx context.Context, rt *runtime.Runtime, oa *models. } // for larger limits we need to take a point in time and iterate through multiple search requests using search_after - pit, err := rt.ES.OpenPointInTime(index).Routing(routing).KeepAlive("1m").Do(ctx) + pit, err := rt.ES.OpenPointInTime(index).Routing(oa.OrgID().String()).KeepAlive("1m").Do(ctx) if err != nil { return nil, fmt.Errorf("error creating ES point-in-time: %w", err) } diff --git a/go.mod b/go.mod index b28b2a72f..b66912564 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 - github.com/nyaruka/gocommon v1.59.0 + github.com/nyaruka/gocommon v1.59.1 github.com/nyaruka/goflow v0.222.4 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 diff --git a/go.sum b/go.sum index e13ee5ad1..f291860fc 100644 --- a/go.sum +++ b/go.sum @@ -194,8 +194,8 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= -github.com/nyaruka/gocommon v1.59.0 h1:XC//IVSOWawBXEZqDiYhqxrIHWo2QPPeCIkAm4n4sY0= -github.com/nyaruka/gocommon v1.59.0/go.mod h1:Upj2DG1iL55YcfF7rve8CRrKGjMaEn0jWUIWbQQgTFQ= +github.com/nyaruka/gocommon v1.59.1 h1:A5E1xvzvK/vMR7jYDMWnWVpisDdZbYsJqZhLmi+2BSI= +github.com/nyaruka/gocommon v1.59.1/go.mod h1:Upj2DG1iL55YcfF7rve8CRrKGjMaEn0jWUIWbQQgTFQ= github.com/nyaruka/goflow v0.222.4 h1:BP15ujzzx2QBzn0j2erM2VUBtpEoG3gchmRCN44IzP8= github.com/nyaruka/goflow v0.222.4/go.mod h1:iwdjLwomV3thGZeWhybtmDhujbooIkpTn1vUbso5ReY= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= From a7b3a83d978ac2805793c22c588e622384fb4783 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 25 Sep 2024 13:45:48 -0500 Subject: [PATCH 105/216] Re-introduce queued status for broadcasts and flow starts --- core/models/broadcasts.go | 41 ++++++++++++---------- core/models/broadcasts_test.go | 10 +++++- core/models/starts.go | 41 ++++++++++++---------- core/models/starts_test.go | 12 ++++--- core/runner/runner_test.go | 4 +-- core/tasks/handler/handle_contact_event.go | 2 +- core/tasks/msgs/send_broadcast.go | 9 ++--- core/tasks/msgs/send_broadcast_batch.go | 7 ++++ core/tasks/starts/start_flow.go | 9 ++--- core/tasks/starts/start_flow_batch.go | 7 ++++ core/tasks/starts/start_flow_batch_test.go | 16 +++++---- 11 files changed, 97 insertions(+), 61 deletions(-) diff --git a/core/models/broadcasts.go b/core/models/broadcasts.go index 13c26d8aa..fc69d643a 100644 --- a/core/models/broadcasts.go +++ b/core/models/broadcasts.go @@ -27,6 +27,7 @@ type BroadcastStatus string // start status constants const ( BroadcastStatusPending = BroadcastStatus("P") + BroadcastStatusQueued = BroadcastStatus("Q") BroadcastStatusStarted = BroadcastStatus("S") BroadcastStatusCompleted = BroadcastStatus("C") BroadcastStatusFailed = BroadcastStatus("F") @@ -122,12 +123,14 @@ type BroadcastBatch struct { Broadcast *Broadcast `json:"broadcast,omitempty"` ContactIDs []ContactID `json:"contact_ids"` + IsFirst bool `json:"is_first"` IsLast bool `json:"is_last"` } -func (b *Broadcast) CreateBatch(contactIDs []ContactID, isLast bool) *BroadcastBatch { +func (b *Broadcast) CreateBatch(contactIDs []ContactID, isFirst, isLast bool) *BroadcastBatch { bb := &BroadcastBatch{ ContactIDs: contactIDs, + IsFirst: isFirst, IsLast: isLast, } @@ -140,43 +143,43 @@ func (b *Broadcast) CreateBatch(contactIDs []ContactID, isLast bool) *BroadcastB return bb } -// SetStarted sets the status of this broadcast to STARTED, if it's not already set to INTERRUPTED -func (b *Broadcast) SetStarted(ctx context.Context, db DBorTx, contactCount int) error { +// SetStarted sets the status of this broadcast to QUEUED, if it's not already set to INTERRUPTED +func (b *Broadcast) SetQueued(ctx context.Context, db DBorTx, contactCount int) error { if b.Status != BroadcastStatusInterrupted { - b.Status = BroadcastStatusStarted + b.Status = BroadcastStatusQueued } if b.ID != NilBroadcastID { - _, err := db.ExecContext(ctx, "UPDATE msgs_broadcast SET status = 'S', contact_count = $2, modified_on = NOW() WHERE id = $1 AND status != 'I'", b.ID, contactCount) + _, err := db.ExecContext(ctx, "UPDATE msgs_broadcast SET status = 'Q', contact_count = $2, modified_on = NOW() WHERE id = $1 AND status != 'I'", b.ID, contactCount) if err != nil { - return fmt.Errorf("error setting broadcast #%d as started: %w", b.ID, err) + return fmt.Errorf("error setting broadcast #%d as queued: %w", b.ID, err) } } return nil } +// SetStarted sets the status of this broadcast to STARTED, if it's not already set to INTERRUPTED +func (b *Broadcast) SetStarted(ctx context.Context, db DBorTx) error { + return b.setStatus(ctx, db, BroadcastStatusStarted) +} + // SetCompleted sets the status of this broadcast to COMPLETED, if it's not already set to INTERRUPTED func (b *Broadcast) SetCompleted(ctx context.Context, db DBorTx) error { - if b.Status != BroadcastStatusInterrupted { - b.Status = BroadcastStatusCompleted - } - if b.ID != NilBroadcastID { - _, err := db.ExecContext(ctx, `UPDATE msgs_broadcast SET status = 'C', modified_on = now() WHERE id = $1`, b.ID) - if err != nil { - return fmt.Errorf("error marking broadcast #%d as completed: %w", b.ID, err) - } - } - return nil + return b.setStatus(ctx, db, BroadcastStatusCompleted) } // SetFailed sets the status of this broadcast to FAILED, if it's not already set to INTERRUPTED func (b *Broadcast) SetFailed(ctx context.Context, db DBorTx) error { + return b.setStatus(ctx, db, BroadcastStatusFailed) +} + +func (b *Broadcast) setStatus(ctx context.Context, db DBorTx, status BroadcastStatus) error { if b.Status != BroadcastStatusInterrupted { - b.Status = BroadcastStatusFailed + b.Status = status } if b.ID != NilBroadcastID { - _, err := db.ExecContext(ctx, `UPDATE msgs_broadcast SET status = 'F', modified_on = now() WHERE id = $1`, b.ID) + _, err := db.ExecContext(ctx, "UPDATE msgs_broadcast SET status = $2, modified_on = NOW() WHERE id = $1 AND status != 'I'", b.ID, status) if err != nil { - return fmt.Errorf("error marking broadcast #%d as failed: %w", b.ID, err) + return fmt.Errorf("error updating broadcast #%d with status=%s: %w", b.ID, status, err) } } return nil diff --git a/core/models/broadcasts_test.go b/core/models/broadcasts_test.go index 8970a1d84..fbc4a2764 100644 --- a/core/models/broadcasts_test.go +++ b/core/models/broadcasts_test.go @@ -52,6 +52,14 @@ func TestBroadcasts(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_broadcast_groups WHERE broadcast_id = $1`, bcast.ID).Returns(1) assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_broadcast_contacts WHERE broadcast_id = $1`, bcast.ID).Returns(3) + err = bcast.SetQueued(ctx, rt.DB, 5) + assert.NoError(t, err) + assertdb.Query(t, rt.DB, `SELECT status, contact_count FROM msgs_broadcast WHERE id = $1`, bcast.ID).Columns(map[string]any{"status": "Q", "contact_count": int64(5)}) + + err = bcast.SetStarted(ctx, rt.DB) + assert.NoError(t, err) + assertdb.Query(t, rt.DB, `SELECT status FROM msgs_broadcast WHERE id = $1`, bcast.ID).Returns("S") + err = bcast.SetCompleted(ctx, rt.DB) assert.NoError(t, err) assertdb.Query(t, rt.DB, `SELECT status FROM msgs_broadcast WHERE id = $1`, bcast.ID).Returns("C") @@ -123,7 +131,7 @@ func TestNonPersistentBroadcasts(t *testing.T) { assert.Equal(t, "", bcast.Query) assert.Equal(t, models.NoExclusions, bcast.Exclusions) - batch := bcast.CreateBatch([]models.ContactID{testdata.Alexandria.ID, testdata.Bob.ID}, false) + batch := bcast.CreateBatch([]models.ContactID{testdata.Alexandria.ID, testdata.Bob.ID}, true, false) assert.Equal(t, models.NilBroadcastID, batch.BroadcastID) assert.NotNil(t, testdata.Org1.ID, batch.Broadcast) diff --git a/core/models/starts.go b/core/models/starts.go index bfab3ead4..9ce7dd9e9 100644 --- a/core/models/starts.go +++ b/core/models/starts.go @@ -43,6 +43,7 @@ type StartStatus string // start status constants const ( StartStatusPending = StartStatus("P") + StartStatusQueued = StartStatus("Q") StartStatusStarted = StartStatus("S") StartStatusCompleted = StartStatus("C") StartStatusFailed = StartStatus("F") @@ -158,13 +159,13 @@ func (s *FlowStart) WithParams(params json.RawMessage) *FlowStart { return s } -// SetStarted sets the status of this start to STARTED, if it's not already set to INTERRUPTED -func (s *FlowStart) SetStarted(ctx context.Context, db DBorTx, contactCount int) error { +// SetQueued sets the status of this start to QUEUED, if it's not already set to INTERRUPTED +func (s *FlowStart) SetQueued(ctx context.Context, db DBorTx, contactCount int) error { if s.Status != StartStatusInterrupted { - s.Status = StartStatusStarted + s.Status = StartStatusQueued } if s.ID != NilStartID { - _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'S', contact_count = $2, modified_on = NOW() WHERE id = $1 AND status != 'I'", s.ID, contactCount) + _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'Q', contact_count = $2, modified_on = NOW() WHERE id = $1 AND status != 'I'", s.ID, contactCount) if err != nil { return fmt.Errorf("error setting start #%d as started: %w", s.ID, err) } @@ -172,29 +173,29 @@ func (s *FlowStart) SetStarted(ctx context.Context, db DBorTx, contactCount int) return nil } +// SetStarted sets the status of this start to STARTED, if it's not already set to INTERRUPTED +func (s *FlowStart) SetStarted(ctx context.Context, db DBorTx) error { + return s.setStatus(ctx, db, StartStatusStarted) +} + // SetCompleted sets the status of this start to COMPLETED, if it's not already set to INTERRUPTED func (s *FlowStart) SetCompleted(ctx context.Context, db DBorTx) error { - if s.Status != StartStatusInterrupted { - s.Status = StartStatusCompleted - } - if s.ID != NilStartID { - _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'C', modified_on = NOW() WHERE id = $1 AND status != 'I'", s.ID) - if err != nil { - return fmt.Errorf("error marking flow start #%d as completed: %w", s.ID, err) - } - } - return nil + return s.setStatus(ctx, db, StartStatusCompleted) } // SetFailed sets the status of this start to FAILED, if it's not already set to INTERRUPTED func (s *FlowStart) SetFailed(ctx context.Context, db DBorTx) error { + return s.setStatus(ctx, db, StartStatusFailed) +} + +func (s *FlowStart) setStatus(ctx context.Context, db DBorTx, status StartStatus) error { if s.Status != StartStatusInterrupted { - s.Status = StartStatusFailed + s.Status = status } if s.ID != NilStartID { - _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = 'F', modified_on = NOW() WHERE id = $1 AND status != 'I'", s.ID) + _, err := db.ExecContext(ctx, "UPDATE flows_flowstart SET status = $2, modified_on = NOW() WHERE id = $1 AND status != 'I'", s.ID, status) if err != nil { - return fmt.Errorf("error setting flow start #%d as failed: %w", s.ID, err) + return fmt.Errorf("error updating start #%d with status=%s: %w", s.ID, status, err) } } return nil @@ -278,10 +279,11 @@ const sqlInsertStartGroup = ` INSERT INTO flows_flowstart_groups(flowstart_id, contactgroup_id) VALUES(:flowstart_id, :contactgroup_id)` // CreateBatch creates a batch for this start using the passed in contact ids -func (s *FlowStart) CreateBatch(contactIDs []ContactID, last bool, totalContacts int) *FlowStartBatch { +func (s *FlowStart) CreateBatch(contactIDs []ContactID, isFirst, isLast bool, totalContacts int) *FlowStartBatch { b := &FlowStartBatch{ ContactIDs: contactIDs, - IsLast: last, + IsFirst: isFirst, + IsLast: isLast, TotalContacts: totalContacts, } @@ -301,6 +303,7 @@ type FlowStartBatch struct { Start *FlowStart `json:"start,omitempty"` ContactIDs []ContactID `json:"contact_ids"` + IsFirst bool `json:"is_first"` IsLast bool `json:"is_last,omitempty"` TotalContacts int `json:"total_contacts"` } diff --git a/core/models/starts_test.go b/core/models/starts_test.go index d9ccf4270..e6a8db272 100644 --- a/core/models/starts_test.go +++ b/core/models/starts_test.go @@ -65,11 +65,11 @@ func TestStarts(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowstart_contacts WHERE flowstart_id = $1`, startID).Returns(2) - err = start.SetStarted(ctx, rt.DB, 2) - assert.NoError(t, err) - assertdb.Query(t, rt.DB, `SELECT status, contact_count FROM flows_flowstart WHERE id = $1`, startID).Columns(map[string]any{"status": "S", "contact_count": int64(2)}) + err = start.SetQueued(ctx, rt.DB, 5) + require.NoError(t, err) + assertdb.Query(t, rt.DB, `SELECT status, contact_count FROM flows_flowstart WHERE id = $1`, startID).Columns(map[string]any{"status": "Q", "contact_count": int64(5)}) - batch := start.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, false, 3) + batch := start.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, true, false, 3) assert.Equal(t, startID, batch.StartID) assert.Equal(t, []models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, batch.ContactIDs) assert.False(t, batch.IsLast) @@ -82,6 +82,10 @@ func TestStarts(t *testing.T) { _, err = models.ReadSessionHistory([]byte(`{`)) assert.EqualError(t, err, "unexpected end of JSON input") + err = start.SetStarted(ctx, rt.DB) + require.NoError(t, err) + assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, startID).Returns("S") + err = start.SetCompleted(ctx, rt.DB) require.NoError(t, err) assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, startID).Returns("C") diff --git a/core/runner/runner_test.go b/core/runner/runner_test.go index 012cd8687..5e6afacb4 100644 --- a/core/runner/runner_test.go +++ b/core/runner/runner_test.go @@ -36,7 +36,7 @@ func TestStartFlowBatch(t *testing.T) { err := models.InsertFlowStarts(ctx, rt.DB, []*models.FlowStart{start1}) require.NoError(t, err) - batch1 := start1.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, false, 4) + batch1 := start1.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, true, false, 4) // start the first batch... sessions, err := runner.StartFlowBatch(ctx, rt, oa, start1, batch1) @@ -63,7 +63,7 @@ func TestStartFlowBatch(t *testing.T) { err = models.InsertFlowStarts(ctx, rt.DB, []*models.FlowStart{start2}) require.NoError(t, err) - batch2 := start2.CreateBatch([]models.ContactID{testdata.Cathy.ID}, true, 1) + batch2 := start2.CreateBatch([]models.ContactID{testdata.Cathy.ID}, false, true, 1) sessions, err = runner.StartFlowBatch(ctx, rt, oa, start2, batch2) require.NoError(t, err) diff --git a/core/tasks/handler/handle_contact_event.go b/core/tasks/handler/handle_contact_event.go index 313014f61..e46ac297e 100644 --- a/core/tasks/handler/handle_contact_event.go +++ b/core/tasks/handler/handle_contact_event.go @@ -181,7 +181,7 @@ func TriggerIVRFlow(ctx context.Context, rt *runtime.Runtime, orgID models.OrgID } // create our batch of all our contacts - task := &ivr.StartIVRFlowBatchTask{FlowStartBatch: start.CreateBatch(contactIDs, true, len(contactIDs))} + task := &ivr.StartIVRFlowBatchTask{FlowStartBatch: start.CreateBatch(contactIDs, true, true, len(contactIDs))} // queue this to our ivr starter, it will take care of creating the calls then calling back in rc := rt.RP.Get() diff --git a/core/tasks/msgs/send_broadcast.go b/core/tasks/msgs/send_broadcast.go index 454a72fbd..518af1174 100644 --- a/core/tasks/msgs/send_broadcast.go +++ b/core/tasks/msgs/send_broadcast.go @@ -82,9 +82,9 @@ func createBroadcastBatches(ctx context.Context, rt *runtime.Runtime, oa *models contactIDs = append(contactIDs, nodeContactIDs...) } - // mark our broadcast as started, last task will mark as complete - if err := bcast.SetStarted(ctx, rt.DB, len(contactIDs)); err != nil { - return fmt.Errorf("error marking broadcast as started: %w", err) + // mark our broadcast as queued + if err := bcast.SetQueued(ctx, rt.DB, len(contactIDs)); err != nil { + return fmt.Errorf("error marking broadcast as queued: %w", err) } // if there are no contacts to send to, mark our broadcast as sent, we are done @@ -112,9 +112,10 @@ func createBroadcastBatches(ctx context.Context, rt *runtime.Runtime, oa *models // create tasks for batches of contacts idBatches := slices.Collect(slices.Chunk(contactIDs, startBatchSize)) for i, idBatch := range idBatches { + isFirst := (i == 0) isLast := (i == len(idBatches)-1) - batch := bcast.CreateBatch(idBatch, isLast) + batch := bcast.CreateBatch(idBatch, isFirst, isLast) err = tasks.Queue(rc, q, bcast.OrgID, &SendBroadcastBatchTask{BroadcastBatch: batch}, queues.DefaultPriority) if err != nil { if i == 0 { diff --git a/core/tasks/msgs/send_broadcast_batch.go b/core/tasks/msgs/send_broadcast_batch.go index f6fa379f8..6bb39a1f9 100644 --- a/core/tasks/msgs/send_broadcast_batch.go +++ b/core/tasks/msgs/send_broadcast_batch.go @@ -54,6 +54,13 @@ func (t *SendBroadcastBatchTask) Perform(ctx context.Context, rt *runtime.Runtim return nil } + // if this is our first batch, mark as started + if t.IsFirst { + if err := bcast.SetStarted(ctx, rt.DB); err != nil { + return fmt.Errorf("error marking broadcast as started: %w", err) + } + } + // create this batch of messages msgs, err := bcast.CreateMessages(ctx, rt, oa, t.BroadcastBatch) if err != nil { diff --git a/core/tasks/starts/start_flow.go b/core/tasks/starts/start_flow.go index 9213741a7..f82333c0e 100644 --- a/core/tasks/starts/start_flow.go +++ b/core/tasks/starts/start_flow.go @@ -97,9 +97,9 @@ func createFlowStartBatches(ctx context.Context, rt *runtime.Runtime, oa *models } } - // mark our start as started, last task will mark as complete - if err := start.SetStarted(ctx, rt.DB, len(contactIDs)); err != nil { - return fmt.Errorf("error marking start as started: %w", err) + // mark our start as queued + if err := start.SetQueued(ctx, rt.DB, len(contactIDs)); err != nil { + return fmt.Errorf("error marking start as queued: %w", err) } // if there are no contacts to start, mark our start as complete, we are done @@ -123,9 +123,10 @@ func createFlowStartBatches(ctx context.Context, rt *runtime.Runtime, oa *models defer rc.Close() for i, idBatch := range idBatches { + isFirst := (i == 0) isLast := (i == len(idBatches)-1) - batch := start.CreateBatch(idBatch, isLast, len(contactIDs)) + batch := start.CreateBatch(idBatch, isFirst, isLast, len(contactIDs)) // task is different if we are an IVR flow var batchTask tasks.Task diff --git a/core/tasks/starts/start_flow_batch.go b/core/tasks/starts/start_flow_batch.go index b9404e006..f7e2dd16c 100644 --- a/core/tasks/starts/start_flow_batch.go +++ b/core/tasks/starts/start_flow_batch.go @@ -54,6 +54,13 @@ func (t *StartFlowBatchTask) Perform(ctx context.Context, rt *runtime.Runtime, o return nil } + // if this is our first batch, mark as started + if t.IsFirst { + if err := start.SetStarted(ctx, rt.DB); err != nil { + return fmt.Errorf("error marking start as started: %w", err) + } + } + // start these contacts in our flow _, err = runner.StartFlowBatch(ctx, rt, oa, start, t.FlowStartBatch) if err != nil { diff --git a/core/tasks/starts/start_flow_batch_test.go b/core/tasks/starts/start_flow_batch_test.go index e17831188..168ec0391 100644 --- a/core/tasks/starts/start_flow_batch_test.go +++ b/core/tasks/starts/start_flow_batch_test.go @@ -21,7 +21,7 @@ func TestStartFlowBatchTask(t *testing.T) { rc := rt.RP.Get() defer rc.Close() - defer testsuite.Reset(testsuite.ResetData) + defer testsuite.Reset(testsuite.ResetData | testsuite.ResetRedis) // create a start start1 := models.NewFlowStart(models.OrgID(1), models.StartTypeManual, testdata.SingleMessage.ID). @@ -29,8 +29,10 @@ func TestStartFlowBatchTask(t *testing.T) { err := models.InsertFlowStarts(ctx, rt.DB, []*models.FlowStart{start1}) require.NoError(t, err) - batch1 := start1.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, false, 4) - batch2 := start1.CreateBatch([]models.ContactID{testdata.George.ID, testdata.Alexandria.ID}, true, 4) + assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, start1.ID).Returns("P") + + batch1 := start1.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, true, false, 4) + batch2 := start1.CreateBatch([]models.ContactID{testdata.George.ID, testdata.Alexandria.ID}, false, true, 4) // start the first batch... err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: batch1}, queues.DefaultPriority) @@ -49,7 +51,7 @@ func TestStartFlowBatchTask(t *testing.T) { AND direction = 'O' AND msg_type = 'T'`, pq.Array([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID})). Returns(2) - assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, start1.ID).Returns("P") + assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, start1.ID).Returns("S") // start the second and final batch... err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: batch2}, queues.DefaultPriority) @@ -65,8 +67,8 @@ func TestStartFlowBatchTask(t *testing.T) { err = models.InsertFlowStarts(ctx, rt.DB, []*models.FlowStart{start2}) require.NoError(t, err) - start2Batch1 := start2.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, false, 4) - start2Batch2 := start2.CreateBatch([]models.ContactID{testdata.George.ID, testdata.Alexandria.ID}, true, 4) + start2Batch1 := start2.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, true, false, 4) + start2Batch2 := start2.CreateBatch([]models.ContactID{testdata.George.ID, testdata.Alexandria.ID}, false, true, 4) // start the first batch... err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: start2Batch1}, queues.DefaultPriority) @@ -99,7 +101,7 @@ func TestStartFlowBatchTaskNonPersistedStart(t *testing.T) { start := models.NewFlowStart(models.OrgID(1), models.StartTypeManual, testdata.SingleMessage.ID). WithContactIDs([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID, testdata.George.ID, testdata.Alexandria.ID}) - batch := start.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, true, 2) + batch := start.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, true, true, 2) // start the first batch... err := tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: batch}, queues.DefaultPriority) From bb748104c148fa8640be15ae2a920661a59a5365 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 25 Sep 2024 14:15:27 -0500 Subject: [PATCH 106/216] Update CHANGELOG.md for v9.3.34 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8738e0fa..e1d889a1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.34 (2024-09-25) +------------------------- + * Re-introduce queued status for broadcasts and flow starts + v9.3.33 (2024-09-24) ------------------------- * Re-evaluate dynamic groups of contacts who have had URNs stolen from them From 268e4f958c0d6175da3cb4c07faf46d8e4bcff0f Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 25 Sep 2024 17:20:30 -0500 Subject: [PATCH 107/216] Add functions to deindex contacts by id and org --- core/models/contacts.go | 1 + core/search/index.go | 51 ++++++++------------------------------- core/search/index_test.go | 34 ++++++++++++++++++-------- 3 files changed, 35 insertions(+), 51 deletions(-) diff --git a/core/models/contacts.go b/core/models/contacts.go index 1f74ddcfb..45a380b4d 100644 --- a/core/models/contacts.go +++ b/core/models/contacts.go @@ -1366,6 +1366,7 @@ func (i URNID) Value() (driver.Value, error) { return null.IntValue(i) } func (i *URNID) UnmarshalJSON(b []byte) error { return null.UnmarshalInt(b, i) } func (i URNID) MarshalJSON() ([]byte, error) { return null.MarshalInt(i) } +func (i ContactID) String() string { return strconv.FormatInt(int64(i), 10) } func (i *ContactID) Scan(value any) error { return null.ScanInt(value, i) } func (i ContactID) Value() (driver.Value, error) { return null.IntValue(i) } func (i *ContactID) UnmarshalJSON(b []byte) error { return null.UnmarshalInt(b, i) } diff --git a/core/search/index.go b/core/search/index.go index d54fc5d68..1055b4c74 100644 --- a/core/search/index.go +++ b/core/search/index.go @@ -4,52 +4,23 @@ import ( "bytes" "context" "fmt" - "time" "github.com/elastic/go-elasticsearch/v8/typedapi/types/enums/operationtype" - "github.com/lib/pq" + "github.com/nyaruka/gocommon/elastic" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/runtime" ) // DeindexContactsByID de-indexes the contacts with the given IDs from Elastic -func DeindexContactsByID(ctx context.Context, rt *runtime.Runtime, contactIDs []models.ContactID) (int, error) { - rows, err := rt.DB.QueryContext(ctx, `SELECT id, org_id, modified_on FROM contacts_contact WHERE id = ANY($1) AND NOT is_active`, pq.Array(contactIDs)) - if err != nil { - return 0, fmt.Errorf("error querying deleted contacts to deindex: %w", err) - } - defer rows.Close() - +func DeindexContactsByID(ctx context.Context, rt *runtime.Runtime, orgID models.OrgID, contactIDs []models.ContactID) (int, error) { cmds := &bytes.Buffer{} - - for rows.Next() { - var id models.ContactID - var orgID models.OrgID - var modifiedOn time.Time - - if err := rows.Scan(&id, &orgID, &modifiedOn); err != nil { - return 0, fmt.Errorf("error scanning deleted contact to deindex: %w", err) - } - - cmds.Write(jsonx.MustMarshal(map[string]any{ - "delete": map[string]any{ - "_id": id, - "version": modifiedOn.UnixNano(), - "version_type": "external", - "routing": orgID.String(), - }}, - )) + for _, id := range contactIDs { + cmds.Write(jsonx.MustMarshal(map[string]any{"delete": map[string]any{"_id": id.String()}})) cmds.WriteString("\n") } - if cmds.Len() == 0 { - return 0, nil - } - - fmt.Println(cmds.String()) - - resp, err := rt.ES.Bulk().Index(rt.Config.ElasticContactsIndex).Raw(bytes.NewReader(cmds.Bytes())).Do(ctx) + resp, err := rt.ES.Bulk().Index(rt.Config.ElasticContactsIndex).Routing(orgID.String()).Raw(bytes.NewReader(cmds.Bytes())).Do(ctx) if err != nil { return 0, fmt.Errorf("error deindexing deleted contacts from elastic: %w", err) } @@ -65,15 +36,13 @@ func DeindexContactsByID(ctx context.Context, rt *runtime.Runtime, contactIDs [] } // DeindexContactsByOrg de-indexes all contacts in the given org from Elastic -func DeindexContactsByOrg(ctx context.Context, rt *runtime.Runtime, orgID models.OrgID) error { - src := map[string]any{ - "query": map[string]any{"match_all": map[string]any{}}, - } +func DeindexContactsByOrg(ctx context.Context, rt *runtime.Runtime, orgID models.OrgID) (int, error) { + src := map[string]any{"query": elastic.Term("org_id", orgID)} - _, err := rt.ES.DeleteByQuery(rt.Config.ElasticContactsIndex).Routing(orgID.String()).Raw(bytes.NewReader(jsonx.MustMarshal(src))).Do(ctx) + resp, err := rt.ES.DeleteByQuery(rt.Config.ElasticContactsIndex).Routing(orgID.String()).Raw(bytes.NewReader(jsonx.MustMarshal(src))).Do(ctx) if err != nil { - return fmt.Errorf("error deindexing contacts in org #%d from elastic: %w", orgID, err) + return 0, fmt.Errorf("error deindexing contacts in org #%d from elastic: %w", orgID, err) } - return nil + return int(*resp.Deleted), nil } diff --git a/core/search/index_test.go b/core/search/index_test.go index ace94aabb..c75774fd5 100644 --- a/core/search/index_test.go +++ b/core/search/index_test.go @@ -17,32 +17,46 @@ import ( "github.com/stretchr/testify/require" ) -func TestDeindexContactsByID(t *testing.T) { +func TestDeindexContacts(t *testing.T) { ctx, rt := testsuite.Runtime() + defer testsuite.Reset(testsuite.ResetAll) + testsuite.ReindexElastic(ctx) + // ensures changes are visible in elastic + refreshElastic := func() { + _, err := rt.ES.Indices.Refresh().Index(rt.Config.ElasticContactsIndex).Do(ctx) + require.NoError(t, err) + } + assertdb.Query(t, rt.DB, `SELECT count(*) FROM contacts_contact WHERE org_id = $1`, testdata.Org1.ID).Returns(124) assertdb.Query(t, rt.DB, `SELECT count(*) FROM contacts_contact WHERE org_id = $1`, testdata.Org2.ID).Returns(121) assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org1.ID), 124) assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org2.ID), 121) - // try to deindex contacts which aren't deleted - deindexed, err := search.DeindexContactsByID(ctx, rt, []models.ContactID{testdata.Bob.ID, testdata.George.ID}) + deindexed, err := search.DeindexContactsByID(ctx, rt, testdata.Org1.ID, []models.ContactID{testdata.Bob.ID, testdata.George.ID}) assert.NoError(t, err) - assert.Equal(t, 0, deindexed) + assert.Equal(t, 2, deindexed) - assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org1.ID), 124) - assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org2.ID), 121) + refreshElastic() - rt.DB.MustExec(`UPDATE contacts_contact SET is_active = false WHERE org_id = $1`, testdata.Org1.ID) + assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org1.ID), 122) + assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org2.ID), 121) - deindexed, err = search.DeindexContactsByID(ctx, rt, []models.ContactID{testdata.Bob.ID, testdata.George.ID}) + deindexed, err = search.DeindexContactsByOrg(ctx, rt, testdata.Org1.ID) assert.NoError(t, err) - assert.Equal(t, 2, deindexed) + assert.Equal(t, 122, deindexed) - assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org1.ID), 122) + refreshElastic() + + assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org1.ID), 0) assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org2.ID), 121) + + // run again, this time nothing to deindex + deindexed, err = search.DeindexContactsByOrg(ctx, rt, testdata.Org1.ID) + assert.NoError(t, err) + assert.Equal(t, 0, deindexed) } func assertSearchCount(t *testing.T, rt *runtime.Runtime, query elastic.Query, expected int) { From 67eca64e18f83a89762fc8247fb0ab92d1cb10fe Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 25 Sep 2024 18:25:34 -0500 Subject: [PATCH 108/216] Add web endpoint to queue org for de-indexing --- core/search/index.go | 7 +- core/search/index_test.go | 16 +++- core/tasks/search/deindex_deleted_orgs.go | 73 +++++++++++++++++++ .../tasks/search/deindex_deleted_orgs_test.go | 44 +++++++++++ web/org/{metrics_test.go => base_test.go} | 19 +++++ web/org/deindex.go | 43 +++++++++++ web/org/testdata/deindex.json | 45 ++++++++++++ 7 files changed, 241 insertions(+), 6 deletions(-) create mode 100644 core/tasks/search/deindex_deleted_orgs.go create mode 100644 core/tasks/search/deindex_deleted_orgs_test.go rename web/org/{metrics_test.go => base_test.go} (84%) create mode 100644 web/org/deindex.go create mode 100644 web/org/testdata/deindex.json diff --git a/core/search/index.go b/core/search/index.go index 1055b4c74..6f596d42e 100644 --- a/core/search/index.go +++ b/core/search/index.go @@ -36,8 +36,11 @@ func DeindexContactsByID(ctx context.Context, rt *runtime.Runtime, orgID models. } // DeindexContactsByOrg de-indexes all contacts in the given org from Elastic -func DeindexContactsByOrg(ctx context.Context, rt *runtime.Runtime, orgID models.OrgID) (int, error) { - src := map[string]any{"query": elastic.Term("org_id", orgID)} +func DeindexContactsByOrg(ctx context.Context, rt *runtime.Runtime, orgID models.OrgID, limit int) (int, error) { + src := map[string]any{ + "query": elastic.Term("org_id", orgID), + "max_docs": limit, + } resp, err := rt.ES.DeleteByQuery(rt.Config.ElasticContactsIndex).Routing(orgID.String()).Raw(bytes.NewReader(jsonx.MustMarshal(src))).Do(ctx) if err != nil { diff --git a/core/search/index_test.go b/core/search/index_test.go index c75774fd5..3b3ab4328 100644 --- a/core/search/index_test.go +++ b/core/search/index_test.go @@ -44,17 +44,25 @@ func TestDeindexContacts(t *testing.T) { assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org1.ID), 122) assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org2.ID), 121) - deindexed, err = search.DeindexContactsByOrg(ctx, rt, testdata.Org1.ID) + deindexed, err = search.DeindexContactsByOrg(ctx, rt, testdata.Org1.ID, 100) assert.NoError(t, err) - assert.Equal(t, 122, deindexed) + assert.Equal(t, 100, deindexed) + + refreshElastic() + + assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org1.ID), 22) + assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org2.ID), 121) + + deindexed, err = search.DeindexContactsByOrg(ctx, rt, testdata.Org1.ID, 100) + assert.NoError(t, err) + assert.Equal(t, 22, deindexed) refreshElastic() assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org1.ID), 0) assertSearchCount(t, rt, elastic.Term("org_id", testdata.Org2.ID), 121) - // run again, this time nothing to deindex - deindexed, err = search.DeindexContactsByOrg(ctx, rt, testdata.Org1.ID) + deindexed, err = search.DeindexContactsByOrg(ctx, rt, testdata.Org1.ID, 100) assert.NoError(t, err) assert.Equal(t, 0, deindexed) } diff --git a/core/tasks/search/deindex_deleted_orgs.go b/core/tasks/search/deindex_deleted_orgs.go new file mode 100644 index 000000000..731d3bde0 --- /dev/null +++ b/core/tasks/search/deindex_deleted_orgs.go @@ -0,0 +1,73 @@ +package search + +import ( + "context" + "fmt" + "time" + + "github.com/gomodule/redigo/redis" + "github.com/nyaruka/mailroom/core/models" + "github.com/nyaruka/mailroom/core/search" + "github.com/nyaruka/mailroom/core/tasks" + "github.com/nyaruka/mailroom/runtime" +) + +const ( + deindexContactsSetKey = "deindex:contacts" + deleteBatchSize = 10000 +) + +func init() { + tasks.RegisterCron("deindex_deleted_orgs", &DeindexDeletedOrgsCron{}) +} + +type DeindexDeletedOrgsCron struct{} + +func (c *DeindexDeletedOrgsCron) Next(last time.Time) time.Time { + return tasks.CronNext(last, time.Minute*5) +} + +func (c *DeindexDeletedOrgsCron) AllInstances() bool { + return false +} + +func (c *DeindexDeletedOrgsCron) Run(ctx context.Context, rt *runtime.Runtime) (map[string]any, error) { + rc := rt.RP.Get() + defer rc.Close() + + // get org ids that still have contacts to de-index + orgIDs, err := redis.Ints(rc.Do("SMEMBERS", deindexContactsSetKey)) + if err != nil { + return nil, err + } + + contactsDeindexed := make(map[models.OrgID]int, len(orgIDs)) + + for _, orgID := range orgIDs { + deindexed, err := search.DeindexContactsByOrg(ctx, rt, models.OrgID(orgID), deleteBatchSize) + if err != nil { + return nil, err + } + contactsDeindexed[models.OrgID(orgID)] = deindexed + + if deindexed == 0 { + if _, err := rc.Do("SREM", deindexContactsSetKey, orgID); err != nil { + return nil, fmt.Errorf("error removing org #%d from deindex set: %w", orgID, err) + } + } + } + + return map[string]any{"contacts": contactsDeindexed}, nil +} + +// MarkForDeindexing marks the given org for de-indexing +func MarkForDeindexing(ctx context.Context, rt *runtime.Runtime, orgID models.OrgID) error { + rc := rt.RP.Get() + defer rc.Close() + + if _, err := rc.Do("SADD", deindexContactsSetKey, orgID); err != nil { + return fmt.Errorf("error adding org #%d to deindex set: %w", orgID, err) + } + + return nil +} diff --git a/core/tasks/search/deindex_deleted_orgs_test.go b/core/tasks/search/deindex_deleted_orgs_test.go new file mode 100644 index 000000000..a04660582 --- /dev/null +++ b/core/tasks/search/deindex_deleted_orgs_test.go @@ -0,0 +1,44 @@ +package search_test + +import ( + "testing" + + "github.com/nyaruka/mailroom/core/models" + "github.com/nyaruka/mailroom/core/tasks/search" + "github.com/nyaruka/mailroom/testsuite" + "github.com/nyaruka/mailroom/testsuite/testdata" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDeindexDeletedOrgsCron(t *testing.T) { + ctx, rt := testsuite.Runtime() + rc := rt.RP.Get() + defer rc.Close() + + defer testsuite.Reset(testsuite.ResetElastic | testsuite.ResetRedis) + + cron := &search.DeindexDeletedOrgsCron{} + + assertRun := func(expected map[string]any) { + res, err := cron.Run(ctx, rt) + assert.NoError(t, err) + assert.Equal(t, expected, res) + + _, err = rt.ES.Indices.Refresh().Index(rt.Config.ElasticContactsIndex).Do(ctx) + require.NoError(t, err) + } + + // no orgs to deindex + assertRun(map[string]any{"contacts": map[models.OrgID]int{}}) + + err := search.MarkForDeindexing(ctx, rt, testdata.Org1.ID) + require.NoError(t, err) + + assertRun(map[string]any{"contacts": map[models.OrgID]int{1: 124}}) + + // this run finds no contacts to deindex for org 1 and removes it from the set + assertRun(map[string]any{"contacts": map[models.OrgID]int{1: 0}}) + + assertRun(map[string]any{"contacts": map[models.OrgID]int{}}) +} diff --git a/web/org/metrics_test.go b/web/org/base_test.go similarity index 84% rename from web/org/metrics_test.go rename to web/org/base_test.go index e0647d845..9a3c03e1f 100644 --- a/web/org/metrics_test.go +++ b/web/org/base_test.go @@ -11,9 +11,28 @@ import ( "github.com/nyaruka/mailroom/testsuite" "github.com/nyaruka/mailroom/testsuite/testdata" "github.com/nyaruka/mailroom/web" + "github.com/nyaruka/redisx/assertredis" "github.com/stretchr/testify/assert" ) +func TestDeindex(t *testing.T) { + ctx, rt := testsuite.Runtime() + + defer func() { + rt.DB.MustExec(`UPDATE orgs_org SET is_active = true WHERE id = $1`, testdata.Org1.ID) + }() + + rt.DB.MustExec(`UPDATE orgs_org SET is_active = false WHERE id = $1`, testdata.Org1.ID) + + defer testsuite.Reset(testsuite.ResetElastic | testsuite.ResetRedis) + + testsuite.RunWebTests(t, ctx, rt, "testdata/deindex.json", nil) + + rc := rt.RP.Get() + defer rc.Close() + assertredis.SMembers(t, rc, "deindex:contacts", []string{"1"}) +} + func TestMetrics(t *testing.T) { ctx, rt := testsuite.Runtime() diff --git a/web/org/deindex.go b/web/org/deindex.go new file mode 100644 index 000000000..a989041d2 --- /dev/null +++ b/web/org/deindex.go @@ -0,0 +1,43 @@ +package org + +import ( + "context" + "fmt" + "net/http" + + "github.com/nyaruka/mailroom/core/models" + "github.com/nyaruka/mailroom/core/tasks/search" + "github.com/nyaruka/mailroom/runtime" + "github.com/nyaruka/mailroom/web" +) + +func init() { + web.RegisterRoute(http.MethodPost, "/mr/org/deindex", web.RequireAuthToken(web.JSONPayload(handleDeindex))) +} + +// Requests de-indexing of the given org from Elastic indexes. +// +// { +// "org_id": 1 +// } +type deindexRequest struct { + OrgID models.OrgID `json:"org_id" validate:"required"` +} + +// handles a request to resend the given messages +func handleDeindex(ctx context.Context, rt *runtime.Runtime, r *deindexRequest) (any, int, error) { + // check that org exists and is not active + var isActive bool + if err := rt.DB.Get(&isActive, `SELECT is_active FROM orgs_org WHERE id = $1`, r.OrgID); err != nil { + return nil, 0, fmt.Errorf("error querying org #%d: %w", r.OrgID, err) + } + if isActive { + return nil, 0, fmt.Errorf("can't deindex active org #%d", r.OrgID) + } + + if err := search.MarkForDeindexing(ctx, rt, r.OrgID); err != nil { + return nil, 0, fmt.Errorf("error marking org #%d for de-indexing: %w", r.OrgID, err) + } + + return map[string]any{}, http.StatusOK, nil +} diff --git a/web/org/testdata/deindex.json b/web/org/testdata/deindex.json new file mode 100644 index 000000000..4cad386ab --- /dev/null +++ b/web/org/testdata/deindex.json @@ -0,0 +1,45 @@ +[ + { + "label": "illegal method", + "method": "GET", + "path": "/mr/org/deindex", + "status": 405, + "response": { + "error": "illegal method: GET" + } + }, + { + "label": "invalid org_id", + "method": "POST", + "path": "/mr/org/deindex", + "body": { + "org_id": 1234 + }, + "status": 500, + "response": { + "error": "error querying org #1234: sql: no rows in result set" + } + }, + { + "label": "org is still active", + "method": "POST", + "path": "/mr/org/deindex", + "body": { + "org_id": 2 + }, + "status": 500, + "response": { + "error": "can't deindex active org #2" + } + }, + { + "label": "org is inactive", + "method": "POST", + "path": "/mr/org/deindex", + "body": { + "org_id": 1 + }, + "status": 200, + "response": {} + } +] \ No newline at end of file From 0b12ff6646231f9d88386218e9d9e29493c26fde Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 26 Sep 2024 08:18:06 -0500 Subject: [PATCH 109/216] Update CHANGELOG.md for v9.3.35 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1d889a1e..4a5dcd2a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.35 (2024-09-26) +------------------------- + * Add web endpoint to queue org for de-indexing + v9.3.34 (2024-09-25) ------------------------- * Re-introduce queued status for broadcasts and flow starts From cf35025f45b2252a53160415b69a9d73a5f049f1 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 26 Sep 2024 12:03:23 -0500 Subject: [PATCH 110/216] Add endpoint to de-index specific contacts --- web/contact/base_test.go | 14 ++++++++ web/contact/deindex.go | 47 +++++++++++++++++++++++++ web/contact/testdata/deindex.json | 57 +++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 web/contact/deindex.go create mode 100644 web/contact/testdata/deindex.json diff --git a/web/contact/base_test.go b/web/contact/base_test.go index a03d104a5..e5eeab45f 100644 --- a/web/contact/base_test.go +++ b/web/contact/base_test.go @@ -30,6 +30,20 @@ func TestCreate(t *testing.T) { testsuite.RunWebTests(t, ctx, rt, "testdata/create.json", nil) } +func TestDeindex(t *testing.T) { + ctx, rt := testsuite.Runtime() + + defer func() { + rt.DB.MustExec(`UPDATE contacts_contact SET is_active = true, modified_on = NOW() WHERE id IN ($1, $2)`, testdata.Bob.ID, testdata.George.ID) + + testsuite.Reset(testsuite.ResetElastic) + }() + + rt.DB.MustExec(`UPDATE contacts_contact SET is_active = false, modified_on = NOW() WHERE id IN ($1, $2)`, testdata.Bob.ID, testdata.George.ID) + + testsuite.RunWebTests(t, ctx, rt, "testdata/deindex.json", nil) +} + func TestExport(t *testing.T) { ctx, rt := testsuite.Runtime() diff --git a/web/contact/deindex.go b/web/contact/deindex.go new file mode 100644 index 000000000..ff95b8b9e --- /dev/null +++ b/web/contact/deindex.go @@ -0,0 +1,47 @@ +package contact + +import ( + "context" + "fmt" + "net/http" + + "github.com/lib/pq" + "github.com/nyaruka/mailroom/core/models" + "github.com/nyaruka/mailroom/core/search" + "github.com/nyaruka/mailroom/runtime" + "github.com/nyaruka/mailroom/web" +) + +func init() { + web.RegisterRoute(http.MethodPost, "/mr/contact/deindex", web.RequireAuthToken(web.JSONPayload(handleDeindex))) +} + +// Requests de-indexing of the given contacts from Elastic indexes. +// +// { +// "org_id": 1, +// "contact_ids": [12345, 23456] +// } +type deindexRequest struct { + OrgID models.OrgID `json:"org_id" validate:"required"` + ContactIDs []models.ContactID `json:"contact_ids" validate:"required"` +} + +// handles a request to resend the given messages +func handleDeindex(ctx context.Context, rt *runtime.Runtime, r *deindexRequest) (any, int, error) { + // check that org exists and is not active + var count int + if err := rt.DB.Get(&count, `SELECT count(*) FROM contacts_contact WHERE id = ANY($1) AND org_id = $2 AND NOT is_active`, pq.Array(r.ContactIDs), r.OrgID); err != nil { + return nil, 0, fmt.Errorf("error querying contacts to be de-indexed in org #%d: %w", r.OrgID, err) + } + if count != len(r.ContactIDs) { + return nil, 0, fmt.Errorf("some contacts to be de-indexed in org #%d are active or don't exist", r.OrgID) + } + + deindexed, err := search.DeindexContactsByID(ctx, rt, r.OrgID, r.ContactIDs) + if err != nil { + return nil, 0, fmt.Errorf("error de-indexing contacts in org #%d: %w", r.OrgID, err) + } + + return map[string]any{"deindexed": deindexed}, http.StatusOK, nil +} diff --git a/web/contact/testdata/deindex.json b/web/contact/testdata/deindex.json new file mode 100644 index 000000000..a4c2efe4b --- /dev/null +++ b/web/contact/testdata/deindex.json @@ -0,0 +1,57 @@ +[ + { + "label": "illegal method", + "method": "GET", + "path": "/mr/contact/deindex", + "status": 405, + "response": { + "error": "illegal method: GET" + } + }, + { + "label": "invalid contact id", + "method": "POST", + "path": "/mr/contact/deindex", + "body": { + "org_id": 1, + "contact_ids": [ + 100000000 + ] + }, + "status": 500, + "response": { + "error": "some contacts to be de-indexed in org #1 are active or don't exist" + } + }, + { + "label": "contact that is still active", + "method": "POST", + "path": "/mr/contact/deindex", + "body": { + "org_id": 1, + "contact_ids": [ + 10000 + ] + }, + "status": 500, + "response": { + "error": "some contacts to be de-indexed in org #1 are active or don't exist" + } + }, + { + "label": "contacts that are inactive", + "method": "POST", + "path": "/mr/contact/deindex", + "body": { + "org_id": 1, + "contact_ids": [ + 10001, + 10002 + ] + }, + "status": 200, + "response": { + "deindexed": 2 + } + } +] \ No newline at end of file From c7014ebc567830a2695352f4458769807a802482 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 26 Sep 2024 14:16:18 -0500 Subject: [PATCH 111/216] Update CHANGELOG.md for v9.3.36 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a5dcd2a9..b7535e608 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.36 (2024-09-26) +------------------------- + * Add endpoint to de-index specific contacts + v9.3.35 (2024-09-26) ------------------------- * Add web endpoint to queue org for de-indexing From 9cd4d6bfbf47fa1d97dc6dfa1b200524425e6feb Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 26 Sep 2024 17:01:45 -0500 Subject: [PATCH 112/216] Remove check in deindex contact for contact being inactive.. so that it can be run before deletion actually occurs --- web/contact/base_test.go | 8 +------- web/contact/deindex.go | 10 ---------- web/contact/testdata/deindex.json | 30 ------------------------------ 3 files changed, 1 insertion(+), 47 deletions(-) diff --git a/web/contact/base_test.go b/web/contact/base_test.go index e5eeab45f..f5ed8854e 100644 --- a/web/contact/base_test.go +++ b/web/contact/base_test.go @@ -33,13 +33,7 @@ func TestCreate(t *testing.T) { func TestDeindex(t *testing.T) { ctx, rt := testsuite.Runtime() - defer func() { - rt.DB.MustExec(`UPDATE contacts_contact SET is_active = true, modified_on = NOW() WHERE id IN ($1, $2)`, testdata.Bob.ID, testdata.George.ID) - - testsuite.Reset(testsuite.ResetElastic) - }() - - rt.DB.MustExec(`UPDATE contacts_contact SET is_active = false, modified_on = NOW() WHERE id IN ($1, $2)`, testdata.Bob.ID, testdata.George.ID) + defer testsuite.Reset(testsuite.ResetElastic) testsuite.RunWebTests(t, ctx, rt, "testdata/deindex.json", nil) } diff --git a/web/contact/deindex.go b/web/contact/deindex.go index ff95b8b9e..86a45aff0 100644 --- a/web/contact/deindex.go +++ b/web/contact/deindex.go @@ -5,7 +5,6 @@ import ( "fmt" "net/http" - "github.com/lib/pq" "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/core/search" "github.com/nyaruka/mailroom/runtime" @@ -29,15 +28,6 @@ type deindexRequest struct { // handles a request to resend the given messages func handleDeindex(ctx context.Context, rt *runtime.Runtime, r *deindexRequest) (any, int, error) { - // check that org exists and is not active - var count int - if err := rt.DB.Get(&count, `SELECT count(*) FROM contacts_contact WHERE id = ANY($1) AND org_id = $2 AND NOT is_active`, pq.Array(r.ContactIDs), r.OrgID); err != nil { - return nil, 0, fmt.Errorf("error querying contacts to be de-indexed in org #%d: %w", r.OrgID, err) - } - if count != len(r.ContactIDs) { - return nil, 0, fmt.Errorf("some contacts to be de-indexed in org #%d are active or don't exist", r.OrgID) - } - deindexed, err := search.DeindexContactsByID(ctx, rt, r.OrgID, r.ContactIDs) if err != nil { return nil, 0, fmt.Errorf("error de-indexing contacts in org #%d: %w", r.OrgID, err) diff --git a/web/contact/testdata/deindex.json b/web/contact/testdata/deindex.json index a4c2efe4b..6844e9328 100644 --- a/web/contact/testdata/deindex.json +++ b/web/contact/testdata/deindex.json @@ -8,36 +8,6 @@ "error": "illegal method: GET" } }, - { - "label": "invalid contact id", - "method": "POST", - "path": "/mr/contact/deindex", - "body": { - "org_id": 1, - "contact_ids": [ - 100000000 - ] - }, - "status": 500, - "response": { - "error": "some contacts to be de-indexed in org #1 are active or don't exist" - } - }, - { - "label": "contact that is still active", - "method": "POST", - "path": "/mr/contact/deindex", - "body": { - "org_id": 1, - "contact_ids": [ - 10000 - ] - }, - "status": 500, - "response": { - "error": "some contacts to be de-indexed in org #1 are active or don't exist" - } - }, { "label": "contacts that are inactive", "method": "POST", From 317c8e7d82509fb95bc9fb5f9bd7ecb165f17bd1 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 26 Sep 2024 17:17:13 -0500 Subject: [PATCH 113/216] Update CHANGELOG.md for v9.3.37 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7535e608..79a8ae015 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.37 (2024-09-26) +------------------------- + * Remove check in deindex contact for contact being inactive.. so that it can be run before deletion actually occurs + v9.3.36 (2024-09-26) ------------------------- * Add endpoint to de-index specific contacts From ad1130d90dac6cf93b35ebbfbda557418979f187 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 30 Sep 2024 11:57:51 -0500 Subject: [PATCH 114/216] Add endpoint for testing error handling and sentry integration --- testsuite/web.go | 8 ++++++++ web/server.go | 21 +++++++++++++++++++++ web/testdata/server.json | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/testsuite/web.go b/testsuite/web.go index a1f6ad758..d381a1175 100644 --- a/testsuite/web.go +++ b/testsuite/web.go @@ -140,6 +140,14 @@ func RunWebTests(t *testing.T, ctx context.Context, rt *runtime.Runtime, truthFi } else { expectedResponse = tc.Response expectedIsJSON = true + + // if response is a single string.. treat it as a text/plain response + if bytes.HasPrefix(expectedResponse, []byte(`"`)) && bytes.HasSuffix(expectedResponse, []byte(`"`)) { + var responseText string + jsonx.MustUnmarshal(expectedResponse, &responseText) + expectedResponse = []byte(responseText) + expectedIsJSON = false + } } if expectedIsJSON { diff --git a/web/server.go b/web/server.go index d3ed7371d..bdee35787 100644 --- a/web/server.go +++ b/web/server.go @@ -62,6 +62,7 @@ func NewServer(ctx context.Context, rt *runtime.Runtime, wg *sync.WaitGroup) *Se router.MethodNotAllowed(handle405) router.Get("/", s.WrapHandler(handleIndex)) router.Get("/mr/", s.WrapHandler(handleIndex)) + router.Get("/mr/test_errors", s.WrapHandler(RequireAuthToken(JSONPayload(handleTestErrors)))) // and all registered routes for _, route := range routes { @@ -139,6 +140,26 @@ func handleIndex(ctx context.Context, rt *runtime.Runtime, r *http.Request, w ht }) } +type testErrorsRequest struct { + Log string + Return string + Panic string +} + +func handleTestErrors(ctx context.Context, rt *runtime.Runtime, r *testErrorsRequest) (any, int, error) { + if r.Log != "" { + slog.Error(r.Log) + } + if r.Return != "" { + return nil, http.StatusInternalServerError, fmt.Errorf(r.Return) + } + if r.Panic != "" { + panic(r.Panic) + } + + return map[string]any{}, http.StatusOK, nil +} + func handle404(w http.ResponseWriter, r *http.Request) { WriteMarshalled(w, http.StatusNotFound, &ErrorResponse{Error: fmt.Sprintf("not found: %s", r.URL.String())}) } diff --git a/web/testdata/server.json b/web/testdata/server.json index ffc4fcfb5..159b759cc 100644 --- a/web/testdata/server.json +++ b/web/testdata/server.json @@ -47,5 +47,45 @@ "url": "/mr/", "version": "Dev" } + }, + { + "label": "test errors endpoint noop without params", + "method": "GET", + "path": "/mr/test_errors", + "body": {}, + "status": 200, + "response": {} + }, + { + "label": "test errors endpoint with logged error", + "method": "GET", + "path": "/mr/test_errors", + "body": { + "log": "this is a logged error" + }, + "status": 200, + "response": {} + }, + { + "label": "test errors endpoint with an error response", + "method": "GET", + "path": "/mr/test_errors", + "body": { + "return": "this is an error response" + }, + "status": 500, + "response": { + "error": "this is an error response" + } + }, + { + "label": "test errors endpoint with a panic", + "method": "GET", + "path": "/mr/test_errors", + "body": { + "panic": "this is a panic" + }, + "status": 500, + "response": "Internal Server Error\n" } ] \ No newline at end of file From 94d64db7787690a737bfef4dcd9da31bf805fb9f Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 30 Sep 2024 12:42:43 -0500 Subject: [PATCH 115/216] Make panic recovery code consistent --- utils/crons/cron.go | 8 +++++--- web/middleware.go | 6 ++++-- workers.go | 6 +++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/utils/crons/cron.go b/utils/crons/cron.go index 70e0e4475..8a7ff5f6d 100644 --- a/utils/crons/cron.go +++ b/utils/crons/cron.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log/slog" + "runtime/debug" "sync" "time" @@ -117,9 +118,10 @@ func fireCron(rt *runtime.Runtime, name string, cronFunc Function, timeout time. defer func() { // catch any panics and recover - panicLog := recover() - if panicLog != nil { - slog.Error(fmt.Sprintf("panic running cron: %s", panicLog), "cron", name) + if panicVal := recover(); panicVal != nil { + debug.PrintStack() + + slog.Error("panic running cron", "cron", name, "panic", panicVal) } }() diff --git a/web/middleware.go b/web/middleware.go index 48b028a93..e26e2bd18 100644 --- a/web/middleware.go +++ b/web/middleware.go @@ -38,9 +38,11 @@ func requestLogger(next http.Handler) http.Handler { func panicRecovery(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { - if rvr := recover(); rvr != nil { + if panicVal := recover(); panicVal != nil { debug.PrintStack() - slog.Error("recovered from panic in web handling", "error", fmt.Sprint(rvr)) + + slog.Error("panic in web handling", "url", r.URL.String(), "panic", panicVal) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } }() diff --git a/workers.go b/workers.go index 1dcf300fb..dd6a1dc7e 100644 --- a/workers.go +++ b/workers.go @@ -160,10 +160,10 @@ func (w *Worker) handleTask(task *queues.Task) { defer func() { // catch any panics and recover - panicLog := recover() - if panicLog != nil { + if panicVal := recover(); panicVal != nil { debug.PrintStack() - log.Error("panic handling task", "panic", panicLog, "task", string(task.Task)) + + log.Error("panic handling task", "task", string(task.Task), "panic", panicVal) } // mark our task as complete From 98699de629264818699a17242f876a5c4c67fd5b Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 30 Sep 2024 14:16:14 -0500 Subject: [PATCH 116/216] Update CHANGELOG.md for v9.3.38 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79a8ae015..20f53c295 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.38 (2024-09-30) +------------------------- + * Make panic recovery code consistent + * Add endpoint for testing error handling and sentry integration + v9.3.37 (2024-09-26) ------------------------- * Remove check in deindex contact for contact being inactive.. so that it can be run before deletion actually occurs From 14ed96f8636ed7eb00e876f1c921632f3d3eabf7 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 30 Sep 2024 14:51:43 -0500 Subject: [PATCH 117/216] Change test_errors endpoint to POST --- web/server.go | 2 +- web/testdata/server.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/server.go b/web/server.go index bdee35787..420b29ab7 100644 --- a/web/server.go +++ b/web/server.go @@ -62,7 +62,7 @@ func NewServer(ctx context.Context, rt *runtime.Runtime, wg *sync.WaitGroup) *Se router.MethodNotAllowed(handle405) router.Get("/", s.WrapHandler(handleIndex)) router.Get("/mr/", s.WrapHandler(handleIndex)) - router.Get("/mr/test_errors", s.WrapHandler(RequireAuthToken(JSONPayload(handleTestErrors)))) + router.Post("/mr/test_errors", s.WrapHandler(RequireAuthToken(JSONPayload(handleTestErrors)))) // and all registered routes for _, route := range routes { diff --git a/web/testdata/server.json b/web/testdata/server.json index 159b759cc..bd4d5eeaf 100644 --- a/web/testdata/server.json +++ b/web/testdata/server.json @@ -50,7 +50,7 @@ }, { "label": "test errors endpoint noop without params", - "method": "GET", + "method": "POST", "path": "/mr/test_errors", "body": {}, "status": 200, @@ -58,7 +58,7 @@ }, { "label": "test errors endpoint with logged error", - "method": "GET", + "method": "POST", "path": "/mr/test_errors", "body": { "log": "this is a logged error" @@ -68,7 +68,7 @@ }, { "label": "test errors endpoint with an error response", - "method": "GET", + "method": "POST", "path": "/mr/test_errors", "body": { "return": "this is an error response" @@ -80,7 +80,7 @@ }, { "label": "test errors endpoint with a panic", - "method": "GET", + "method": "POST", "path": "/mr/test_errors", "body": { "panic": "this is a panic" From ff99cb806cb75fa496141248fc61fc9a56ea4fd9 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 30 Sep 2024 15:03:30 -0500 Subject: [PATCH 118/216] Update CHANGELOG.md for v9.3.39 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20f53c295..6961a6302 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.39 (2024-09-30) +------------------------- + * Change test_errors endpoint to POST + v9.3.38 (2024-09-30) ------------------------- * Make panic recovery code consistent From a4a5665d5a511400e26e62319304ea08d42f2737 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 30 Sep 2024 15:40:42 -0500 Subject: [PATCH 119/216] Include stack explicitly in panic recovery log --- utils/crons/cron.go | 2 +- web/middleware.go | 2 +- workers.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/crons/cron.go b/utils/crons/cron.go index 8a7ff5f6d..352de8c76 100644 --- a/utils/crons/cron.go +++ b/utils/crons/cron.go @@ -121,7 +121,7 @@ func fireCron(rt *runtime.Runtime, name string, cronFunc Function, timeout time. if panicVal := recover(); panicVal != nil { debug.PrintStack() - slog.Error("panic running cron", "cron", name, "panic", panicVal) + slog.Error("panic running cron", "cron", name, "value", panicVal, "stack", debug.Stack()) } }() diff --git a/web/middleware.go b/web/middleware.go index e26e2bd18..26a14c623 100644 --- a/web/middleware.go +++ b/web/middleware.go @@ -41,7 +41,7 @@ func panicRecovery(next http.Handler) http.Handler { if panicVal := recover(); panicVal != nil { debug.PrintStack() - slog.Error("panic in web handling", "url", r.URL.String(), "panic", panicVal) + slog.Error("panic in web handling", "url", r.URL.String(), "value", panicVal, "stack", debug.Stack()) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } diff --git a/workers.go b/workers.go index dd6a1dc7e..692a9819c 100644 --- a/workers.go +++ b/workers.go @@ -163,7 +163,7 @@ func (w *Worker) handleTask(task *queues.Task) { if panicVal := recover(); panicVal != nil { debug.PrintStack() - log.Error("panic handling task", "task", string(task.Task), "panic", panicVal) + log.Error("panic handling task", "task", string(task.Task), "value", panicVal, "stack", debug.Stack()) } // mark our task as complete From d930b85bbdfe10429a72e5048ea33598936b8a00 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 30 Sep 2024 15:45:45 -0500 Subject: [PATCH 120/216] Update CHANGELOG.md for v9.3.40 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6961a6302..9cd177ea0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.40 (2024-09-30) +------------------------- + * Include stack explicitly in panic recovery log + v9.3.39 (2024-09-30) ------------------------- * Change test_errors endpoint to POST From 17c1ae422d54002857b8e54b75af8ae41a0d1a6e Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 30 Sep 2024 16:57:17 -0500 Subject: [PATCH 121/216] Call sentry directly from panic recovery --- cmd/mailroom/main.go | 2 +- utils/crons/cron.go | 7 ++++--- web/middleware.go | 3 ++- workers.go | 3 ++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/cmd/mailroom/main.go b/cmd/mailroom/main.go index 3dfd726a1..1ab05b52c 100644 --- a/cmd/mailroom/main.go +++ b/cmd/mailroom/main.go @@ -62,7 +62,7 @@ func main() { // if we have a DSN entry, try to initialize it if config.SentryDSN != "" { - err := sentry.Init(sentry.ClientOptions{Dsn: config.SentryDSN, EnableTracing: false}) + err := sentry.Init(sentry.ClientOptions{Dsn: config.SentryDSN, AttachStacktrace: true}) if err != nil { ulog.Fatalf("error initiating sentry client, error %s, dsn %s", err, config.SentryDSN) } diff --git a/utils/crons/cron.go b/utils/crons/cron.go index 352de8c76..e3fc8ea5c 100644 --- a/utils/crons/cron.go +++ b/utils/crons/cron.go @@ -8,6 +8,7 @@ import ( "sync" "time" + "github.com/getsentry/sentry-go" "github.com/gomodule/redigo/redis" "github.com/nyaruka/gocommon/analytics" "github.com/nyaruka/gocommon/jsonx" @@ -85,7 +86,7 @@ func Start(rt *runtime.Runtime, wg *sync.WaitGroup, name string, allInstances bo // ok, got the lock, run our cron function started := time.Now() - results, err := fireCron(rt, name, cronFunc, timeout) + results, err := fireCron(rt, cronFunc, timeout) if err != nil { log.Error("error while running cron", "error", err) } @@ -112,7 +113,7 @@ func Start(rt *runtime.Runtime, wg *sync.WaitGroup, name string, allInstances bo // fireCron is just a wrapper around the cron function we will call for the purposes of // catching and logging panics -func fireCron(rt *runtime.Runtime, name string, cronFunc Function, timeout time.Duration) (map[string]any, error) { +func fireCron(rt *runtime.Runtime, cronFunc Function, timeout time.Duration) (map[string]any, error) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -121,7 +122,7 @@ func fireCron(rt *runtime.Runtime, name string, cronFunc Function, timeout time. if panicVal := recover(); panicVal != nil { debug.PrintStack() - slog.Error("panic running cron", "cron", name, "value", panicVal, "stack", debug.Stack()) + sentry.CurrentHub().Recover(panicVal) } }() diff --git a/web/middleware.go b/web/middleware.go index 26a14c623..5fa1e42ad 100644 --- a/web/middleware.go +++ b/web/middleware.go @@ -8,6 +8,7 @@ import ( "strconv" "time" + "github.com/getsentry/sentry-go" "github.com/go-chi/chi/v5/middleware" ) @@ -41,7 +42,7 @@ func panicRecovery(next http.Handler) http.Handler { if panicVal := recover(); panicVal != nil { debug.PrintStack() - slog.Error("panic in web handling", "url", r.URL.String(), "value", panicVal, "stack", debug.Stack()) + sentry.CurrentHub().Recover(panicVal) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } diff --git a/workers.go b/workers.go index 692a9819c..e58ff5bf8 100644 --- a/workers.go +++ b/workers.go @@ -7,6 +7,7 @@ import ( "sync" "time" + "github.com/getsentry/sentry-go" "github.com/nyaruka/mailroom/core/tasks" "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/mailroom/utils/queues" @@ -163,7 +164,7 @@ func (w *Worker) handleTask(task *queues.Task) { if panicVal := recover(); panicVal != nil { debug.PrintStack() - log.Error("panic handling task", "task", string(task.Task), "value", panicVal, "stack", debug.Stack()) + sentry.CurrentHub().Recover(panicVal) } // mark our task as complete From a8b4b18dffdeee64deff0a30dad79cf14c651959 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 30 Sep 2024 16:47:08 -0500 Subject: [PATCH 122/216] Log instances of flows stealing URNs --- core/hooks/commit_urn_changes.go | 7 ++++++- core/models/contacts.go | 30 ++++++++++++++++-------------- core/models/contacts_test.go | 18 +++++++++++------- 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/core/hooks/commit_urn_changes.go b/core/hooks/commit_urn_changes.go index c0f9d5c7d..524982571 100644 --- a/core/hooks/commit_urn_changes.go +++ b/core/hooks/commit_urn_changes.go @@ -3,6 +3,7 @@ package hooks import ( "context" "fmt" + "log/slog" "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/runtime" @@ -23,10 +24,14 @@ func (h *commitURNChangesHook) Apply(ctx context.Context, rt *runtime.Runtime, t changes = append(changes, sessionChanges[len(sessionChanges)-1].(*models.ContactURNsChanged)) } - err := models.UpdateContactURNs(ctx, tx, oa, changes) + affected, err := models.UpdateContactURNs(ctx, tx, oa, changes) if err != nil { return fmt.Errorf("error updating contact urns: %w", err) } + if len(affected) > 0 { + slog.Error("URN changes affected other contacts", "count", len(affected), "org_id", oa.OrgID()) + } + return nil } diff --git a/core/models/contacts.go b/core/models/contacts.go index 45a380b4d..008ade283 100644 --- a/core/models/contacts.go +++ b/core/models/contacts.go @@ -247,7 +247,7 @@ func (c *Contact) UpdatePreferredURN(ctx context.Context, db DBorTx, oa *OrgAsse } // write our new state to the db - err := UpdateContactURNs(ctx, db, oa, []*ContactURNsChanged{change}) + _, err := UpdateContactURNs(ctx, db, oa, []*ContactURNsChanged{change}) if err != nil { return fmt.Errorf("error updating urns for contact: %w", err) } @@ -1200,7 +1200,7 @@ func UpdateContactLastSeenOn(ctx context.Context, db DBorTx, contactID ContactID } // UpdateContactURNs updates the contact urns in our database to match the passed in changes -func UpdateContactURNs(ctx context.Context, db DBorTx, oa *OrgAssets, changes []*ContactURNsChanged) error { +func UpdateContactURNs(ctx context.Context, db DBorTx, oa *OrgAssets, changes []*ContactURNsChanged) ([]*Contact, error) { // keep track of all our inserts inserts := make([]any, 0, len(changes)) @@ -1260,7 +1260,7 @@ func UpdateContactURNs(ctx context.Context, db DBorTx, oa *OrgAssets, changes [] // first update existing URNs err := BulkQuery(ctx, "updating contact urns", db, sqlUpdateContactURNs, updates) if err != nil { - return fmt.Errorf("error updating urns: %w", err) + return nil, fmt.Errorf("error updating urns: %w", err) } // then detach any URNs that weren't updated (the ones we're not keeping) @@ -1271,54 +1271,56 @@ func UpdateContactURNs(ctx context.Context, db DBorTx, oa *OrgAssets, changes [] pq.Array(updatedURNIDs), ) if err != nil { - return fmt.Errorf("error detaching urns: %w", err) + return nil, fmt.Errorf("error detaching urns: %w", err) } + var affected []*Contact + if len(inserts) > 0 { // find the unique ids of the contacts that may be affected by our URN inserts orphanedIDs, err := queryContactIDs(ctx, db, `SELECT contact_id FROM contacts_contacturn WHERE identity = ANY($1) AND org_id = $2 AND contact_id IS NOT NULL`, pq.Array(identities), oa.OrgID()) if err != nil { - return fmt.Errorf("error finding contacts for URNs: %w", err) + return nil, fmt.Errorf("error finding contacts for URNs: %w", err) } // then insert new urns, we do these one by one since we have to deal with conflicts for _, insert := range inserts { _, err := db.NamedExecContext(ctx, sqlInsertContactURN, insert) if err != nil { - return fmt.Errorf("error inserting new urns: %w", err) + return nil, fmt.Errorf("error inserting new urns: %w", err) } } // finally update the contacts who had URNs stolen from them if len(orphanedIDs) > 0 { - orphans, err := LoadContacts(ctx, db, oa, orphanedIDs) + affected, err = LoadContacts(ctx, db, oa, orphanedIDs) if err != nil { - return fmt.Errorf("error loading contacts affecting by URN stealing: %w", err) + return nil, fmt.Errorf("error loading contacts affecting by URN stealing: %w", err) } // turn them into flow contacts.. - flowOrphans := make([]*flows.Contact, len(orphans)) - for i, c := range orphans { + flowOrphans := make([]*flows.Contact, len(affected)) + for i, c := range affected { flowOrphans[i], err = c.FlowContact(oa) if err != nil { - return fmt.Errorf("error creating orphan flow contact: %w", err) + return nil, fmt.Errorf("error creating orphan flow contact: %w", err) } } // and re-calculate their dynamic groups if err := CalculateDynamicGroups(ctx, db, oa, flowOrphans); err != nil { - return fmt.Errorf("error re-calculating dynamic groups for orphaned contacts: %w", err) + return nil, fmt.Errorf("error re-calculating dynamic groups for orphaned contacts: %w", err) } // and mark them as updated if err := UpdateContactModifiedOn(ctx, db, orphanedIDs); err != nil { - return fmt.Errorf("error updating orphaned contacts: %w", err) + return nil, fmt.Errorf("error updating orphaned contacts: %w", err) } } } // NOTE: caller needs to update modified on for this contact - return nil + return affected, nil } // urnUpdate is our object that represents a single contact URN update diff --git a/core/models/contacts_test.go b/core/models/contacts_test.go index fb4a9b1a5..1ce27f59d 100644 --- a/core/models/contacts_test.go +++ b/core/models/contacts_test.go @@ -602,13 +602,13 @@ func TestUpdateContactURNs(t *testing.T) { bobURN := urns.URN(fmt.Sprintf("tel:+16055742222?id=%d", testdata.Bob.URNID)) // give Cathy a new higher priority URN - err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{{testdata.Cathy.ID, testdata.Org1.ID, []urns.URN{"tel:+16055700001", cathyURN}}}) + _, err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{{testdata.Cathy.ID, testdata.Org1.ID, []urns.URN{"tel:+16055700001", cathyURN}}}) assert.NoError(t, err) assertContactURNs(testdata.Cathy.ID, []string{"tel:+16055700001", "tel:+16055741111"}) // give Bob a new lower priority URN - err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{{testdata.Bob.ID, testdata.Org1.ID, []urns.URN{bobURN, "tel:+16055700002"}}}) + _, err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{{testdata.Bob.ID, testdata.Org1.ID, []urns.URN{bobURN, "tel:+16055700002"}}}) assert.NoError(t, err) assertContactURNs(testdata.Bob.ID, []string{"tel:+16055742222", "tel:+16055700002"}) @@ -616,7 +616,7 @@ func TestUpdateContactURNs(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT count(*) FROM contacts_contacturn`).Returns(numInitialURNs + 2) // but 2 new URNs // remove a URN from Cathy - err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{{testdata.Cathy.ID, testdata.Org1.ID, []urns.URN{"tel:+16055700001"}}}) + _, err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{{testdata.Cathy.ID, testdata.Org1.ID, []urns.URN{"tel:+16055700001"}}}) assert.NoError(t, err) assertContactURNs(testdata.Cathy.ID, []string{"tel:+16055700001"}) @@ -624,12 +624,14 @@ func TestUpdateContactURNs(t *testing.T) { t1 := time.Now() - // steal a URN from Bob - err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{ + // steal a URN from Bob and give to Alexandria + affected, err := models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{ {testdata.Cathy.ID, testdata.Org1.ID, []urns.URN{"tel:+16055700001", "tel:+16055700002"}}, {testdata.Alexandria.ID, testdata.Org1.ID, []urns.URN{"tel:+16055742222"}}, }) assert.NoError(t, err) + assert.Len(t, affected, 1) + assert.Equal(t, testdata.Bob.ID, affected[0].ID()) assertContactURNs(testdata.Cathy.ID, []string{"tel:+16055700001", "tel:+16055700002"}) assertContactURNs(testdata.Alexandria.ID, []string{"tel:+16055742222"}) @@ -637,13 +639,15 @@ func TestUpdateContactURNs(t *testing.T) { assertModifiedOnUpdated(testdata.Bob.ID, t1) assertGroups(testdata.Bob.ID, []string{"Active", "No URN"}) - // steal the URN back from Cathy whilst simulataneously adding new URN to Cathy and not-changing anything for George - err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{ + // steal the URN back from Alexandria whilst simulataneously adding new URN to Cathy and not-changing anything for George + affected, err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{ {testdata.Bob.ID, testdata.Org1.ID, []urns.URN{"tel:+16055742222", "tel:+16055700002"}}, {testdata.Cathy.ID, testdata.Org1.ID, []urns.URN{"tel:+16055700001", "tel:+16055700003"}}, {testdata.George.ID, testdata.Org1.ID, []urns.URN{"tel:+16055743333"}}, }) assert.NoError(t, err) + assert.Len(t, affected, 1) + assert.Equal(t, testdata.Alexandria.ID, affected[0].ID()) assertContactURNs(testdata.Cathy.ID, []string{"tel:+16055700001", "tel:+16055700003"}) assertContactURNs(testdata.Bob.ID, []string{"tel:+16055742222", "tel:+16055700002"}) From 54cb896decae600e377b0b34f1f638db4f43c9c9 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 30 Sep 2024 17:17:09 -0500 Subject: [PATCH 123/216] Update CHANGELOG.md for v9.3.41 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cd177ea0..3ef2aa61a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.41 (2024-09-30) +------------------------- + * Log instances of flows stealing URNs + * Call sentry directly from panic recovery + v9.3.40 (2024-09-30) ------------------------- * Include stack explicitly in panic recovery log From dcc41fd25c5c559af4bee0d6083523affbe49c93 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 1 Oct 2024 09:18:08 -0500 Subject: [PATCH 124/216] Update deps including goflow to get group search fix --- go.mod | 78 ++++++++++++++-------------- go.sum | 160 ++++++++++++++++++++++++++++----------------------------- 2 files changed, 119 insertions(+), 119 deletions(-) diff --git a/go.mod b/go.mod index b66912564..8f59e4494 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,10 @@ require ( firebase.google.com/go/v4 v4.14.1 github.com/Masterminds/semver v1.5.0 github.com/appleboy/go-fcm v1.2.1 - github.com/aws/aws-sdk-go-v2 v1.30.5 - github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.3 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.9 - github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 + github.com/aws/aws-sdk-go-v2 v1.31.0 + github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.8 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.35.3 + github.com/aws/aws-sdk-go-v2/service/s3 v1.63.3 github.com/buger/jsonparser v1.1.1 github.com/elastic/go-elasticsearch/v8 v8.15.0 github.com/getsentry/sentry-go v0.29.0 @@ -23,50 +23,50 @@ require ( github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 github.com/nyaruka/gocommon v1.59.1 - github.com/nyaruka/goflow v0.222.4 + github.com/nyaruka/goflow v0.222.5 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 github.com/nyaruka/rp-indexer/v9 v9.2.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_model v0.6.1 - github.com/prometheus/common v0.59.1 - github.com/samber/slog-multi v1.2.1 + github.com/prometheus/common v0.60.0 + github.com/samber/slog-multi v1.2.2 github.com/samber/slog-sentry v1.2.2 github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.9.0 - google.golang.org/api v0.197.0 + google.golang.org/api v0.199.0 ) require ( cloud.google.com/go v0.115.1 // indirect - cloud.google.com/go/auth v0.9.3 // indirect + cloud.google.com/go/auth v0.9.7 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect - cloud.google.com/go/compute/metadata v0.5.0 // indirect - cloud.google.com/go/firestore v1.16.0 // indirect - cloud.google.com/go/iam v1.2.0 // indirect - cloud.google.com/go/longrunning v0.6.0 // indirect + cloud.google.com/go/compute/metadata v0.5.2 // indirect + cloud.google.com/go/firestore v1.17.0 // indirect + cloud.google.com/go/iam v1.2.1 // indirect + cloud.google.com/go/longrunning v0.6.1 // indirect cloud.google.com/go/storage v1.43.0 // indirect github.com/MicahParks/keyfunc v1.9.0 // indirect github.com/Shopify/gomail v0.0.0-20220729171026-0784ece65e69 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect - github.com/aws/aws-sdk-go-v2/config v1.27.28 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.28 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5 // indirect + github.com/aws/aws-sdk-go-v2/config v1.27.39 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.37 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 // indirect - github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.7 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.18 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.30.4 // indirect - github.com/aws/smithy-go v1.20.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.23.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.19 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.23.3 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.31.3 // indirect + github.com/aws/smithy-go v1.21.0 // indirect github.com/blevesearch/segment v0.9.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/elastic/elastic-transport-go/v8 v8.6.0 // indirect @@ -96,11 +96,11 @@ require ( github.com/samber/lo v1.47.0 // indirect github.com/sergi/go-diff v1.3.1 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect + go.opentelemetry.io/otel v1.30.0 // indirect + go.opentelemetry.io/otel/metric v1.30.0 // indirect + go.opentelemetry.io/otel/trace v1.30.0 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/net v0.29.0 // indirect @@ -110,10 +110,10 @@ require ( golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.6.0 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect - google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/grpc v1.66.1 // indirect + google.golang.org/genproto v0.0.0-20240930140551-af27646dc61f // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect + google.golang.org/grpc v1.67.1 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index f291860fc..ba7ee3dfe 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,18 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ= cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc= -cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U= -cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk= +cloud.google.com/go/auth v0.9.7 h1:ha65jNwOfI48YmUzNfMaUDfqt5ykuYIUnSartpU1+BA= +cloud.google.com/go/auth v0.9.7/go.mod h1:Xo0n7n66eHyOWWCnitop6870Ilwo3PiZyodVkkH1xWM= cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= -cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= -cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= -cloud.google.com/go/firestore v1.16.0 h1:YwmDHcyrxVRErWcgxunzEaZxtNbc8QoFYA/JOEwDPgc= -cloud.google.com/go/firestore v1.16.0/go.mod h1:+22v/7p+WNBSQwdSwP57vz47aZiY+HrDkrOsJNhk7rg= -cloud.google.com/go/iam v1.2.0 h1:kZKMKVNk/IsSSc/udOb83K0hL/Yh/Gcqpz+oAkoIFN8= -cloud.google.com/go/iam v1.2.0/go.mod h1:zITGuWgsLZxd8OwAlX+eMFgZDXzBm7icj1PVTYG766Q= -cloud.google.com/go/longrunning v0.6.0 h1:mM1ZmaNsQsnb+5n1DNPeL0KwQd9jQRqSqSDEkBZr+aI= -cloud.google.com/go/longrunning v0.6.0/go.mod h1:uHzSZqW89h7/pasCWNYdUpwGz3PcVWhrWupreVPYLts= +cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= +cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= +cloud.google.com/go/firestore v1.17.0 h1:iEd1LBbkDZTFsLw3sTH50eyg4qe8eoG6CjocmEXO9aQ= +cloud.google.com/go/firestore v1.17.0/go.mod h1:69uPx1papBsY8ZETooc71fOhoKkD70Q1DwMrtKuOT/Y= +cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU= +cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g= +cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc= +cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= @@ -30,50 +30,50 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/appleboy/go-fcm v1.2.1 h1:NhpACabtRuAplYg6bTNfSr3LBwsSuutP55HsphzLU/g= github.com/appleboy/go-fcm v1.2.1/go.mod h1:5FzMN+9J2sxnkoys9h3y48GQH8HnI637Q/ro/uP2Qsk= -github.com/aws/aws-sdk-go-v2 v1.30.5 h1:mWSRTwQAb0aLE17dSzztCVJWI9+cRMgqebndjwDyK0g= -github.com/aws/aws-sdk-go-v2 v1.30.5/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 h1:70PVAiL15/aBMh5LThwgXdSQorVr91L127ttckI9QQU= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4/go.mod h1:/MQxMqci8tlqDH+pjmoLu1i0tbWCUP1hhyMRuFxpQCw= -github.com/aws/aws-sdk-go-v2/config v1.27.28 h1:OTxWGW/91C61QlneCtnD62NLb4W616/NM1jA8LhJqbg= -github.com/aws/aws-sdk-go-v2/config v1.27.28/go.mod h1:uzVRVtJSU5EFv6Fu82AoVFKozJi2ZCY6WRCXj06rbvs= -github.com/aws/aws-sdk-go-v2/credentials v1.17.28 h1:m8+AHY/ND8CMHJnPoH7PJIRakWGa4gbfbxuY9TGTUXM= -github.com/aws/aws-sdk-go-v2/credentials v1.17.28/go.mod h1:6TF7dSc78ehD1SL6KpRIPKMA1GyyWflIkjqg+qmf4+c= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.3 h1:/BPXKQ6n1cDWPmc5FWF6fCSaUtK+dWkWd0x9dI4dgaI= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.3/go.mod h1:qabLXChRlJREypX5RN/Z47GU+RaMsjotNCZfZ85oD0M= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12 h1:yjwoSyDZF8Jth+mUk5lSPJCkMC0lMy6FaCD51jm6ayE= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.12/go.mod h1:fuR57fAgMk7ot3WcNQfb6rSEn+SUffl7ri+aa8uKysI= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 h1:pI7Bzt0BJtYA0N/JEC6B8fJ4RBrEMi1LBrkMdFYNSnQ= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17/go.mod h1:Dh5zzJYMtxfIjYW+/evjQ8uj2OyR/ve2KROHGHlSFqE= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 h1:Mqr/V5gvrhA2gvgnF42Zh5iMiQNcOYthFYwCyrnuWlc= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17/go.mod h1:aLJpZlCmjE+V+KtN1q1uyZkfnUWpQGpbsn89XPKyzfU= +github.com/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72QmG5U= +github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5 h1:xDAuZTn4IMm8o1LnBZvmrL8JA1io4o3YWNXgohbf20g= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5/go.mod h1:wYSv6iDS621sEFLfKvpPE2ugjTuGlAG7iROg0hLOkfc= +github.com/aws/aws-sdk-go-v2/config v1.27.39 h1:FCylu78eTGzW1ynHcongXK9YHtoXD5AiiUqq3YfJYjU= +github.com/aws/aws-sdk-go-v2/config v1.27.39/go.mod h1:wczj2hbyskP4LjMKBEZwPRO1shXY+GsQleab+ZXT2ik= +github.com/aws/aws-sdk-go-v2/credentials v1.17.37 h1:G2aOH01yW8X373JK419THj5QVqu9vKEwxSEsGxihoW0= +github.com/aws/aws-sdk-go-v2/credentials v1.17.37/go.mod h1:0ecCjlb7htYCptRD45lXJ6aJDQac6D2NlKGpZqyTG6A= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.8 h1:YNkm1DPhE4wnslPKD8jLVfKPujd94R8eI175vgKvIHI= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.8/go.mod h1:Ipgx7ZeodWz/Fd1TxCQwy0rXkxk2WDxZBJUuoZLzpqw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 h1:C/d03NAmh8C4BZXhuRNboF/DqhBkBCeDiJDcaqIT5pA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14/go.mod h1:7I0Ju7p9mCIdlrfS+JCgqcYD0VXz/N4yozsox+0o078= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 h1:kYQ3H1u0ANr9KEKlGs/jTLrBFPo8P8NaH/w7A01NeeM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18/go.mod h1:r506HmK5JDUh9+Mw4CfGJGSSoqIiLCndAuqXuhbv67Y= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 h1:Z7IdFUONvTcvS7YuhtVxN99v2cCoHRXOS4mTr0B/pUc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18/go.mod h1:DkKMmksZVVyat+Y+r1dEOgJEfUeA7UngIHWeKsi0yNc= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17 h1:Roo69qTpfu8OlJ2Tb7pAYVuF0CpuUMB0IYWwYP/4DZM= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.17/go.mod h1:NcWPxQzGM1USQggaTVwz6VpqMZPX1CvDJLDh6jnOCa4= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.9 h1:jbqgtdKfAXebx2/l2UhDEe/jmmCIhaCO3HFK71M7VzM= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.34.9/go.mod h1:N3YdUYxyxhiuAelUgCpSVBuBI1klobJxZrDtL+olu10= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.7 h1:VTBHXWkSeFgT3sfYB4U92qMgzHl0nz9H1tYNHHutLg0= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.7/go.mod h1:F/ybU7YfgFcktSp+biKgiHjyscGhlZxOz4QFFQqHXGw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19 h1:FLMkfEiRjhgeDTCjjLoc3URo/TBkgeQbocA78lfkzSI= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.19/go.mod h1:Vx+GucNSsdhaxs3aZIKfSUjKVGsxN25nX2SRcdhuw08= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.18 h1:GACdEPdpBE59I7pbfvu0/Mw1wzstlP3QtPHklUxybFE= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.18/go.mod h1:K+xV06+Wni4TSaOOJ1Y35e5tYOCUBYbebLKmJQQa8yY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 h1:rfprUlsdzgl7ZL2KlXiUAoJnI/VxfHCvDFr2QDFj6u4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19/go.mod h1:SCWkEdRq8/7EK60NcvvQ6NXKuTcchAD4ROAsC37VEZE= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17 h1:u+EfGmksnJc/x5tq3A+OD7LrMbSSR/5TrKLvkdy/fhY= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.17/go.mod h1:VaMx6302JHax2vHJWgRo+5n9zvbacs3bLU/23DNQrTY= -github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2 h1:Kp6PWAlXwP1UvIflkIP6MFZYBNDCa4mFCGtxrpICVOg= -github.com/aws/aws-sdk-go-v2/service/s3 v1.61.2/go.mod h1:5FmD/Dqq57gP+XwaUnd5WFPipAuzrf0HmupX27Gvjvc= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 h1:zCsFCKvbj25i7p1u94imVoO447I/sFv8qq+lGJhRN0c= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.5/go.mod h1:ZeDX1SnKsVlejeuz41GiajjZpRSWR7/42q/EyA/QEiM= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 h1:SKvPgvdvmiTWoi0GAJ7AsJfOz3ngVkD/ERbs5pUnHNI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5/go.mod h1:20sz31hv/WsPa3HhU3hfrIet2kxM4Pe0r20eBZ20Tac= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.4 h1:iAckBT2OeEK/kBDyN/jDtpEExhjeeA/Im2q4X0rJZT8= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.4/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0= -github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= -github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18 h1:OWYvKL53l1rbsUmW7bQyJVsYU/Ii3bbAAQIIFNbM0Tk= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18/go.mod h1:CUx0G1v3wG6l01tUB+j7Y8kclA8NSqK4ef0YG79a4cg= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.35.3 h1:X4iS+RcIKHkAMQz47nDt/nHxZUCKdnfgw940yluJ29Q= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.35.3/go.mod h1:k5XW8MoMxsNZ20RJmsokakvENUwQyjv69R9GqrI4xdQ= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.23.3 h1:q+pKQ9hZfIJNyoYSwPWbj19GnEPWvLOXwHpR/HYyx4o= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.23.3/go.mod h1:NZQWaOwOszI7jnQ7s1i5kN/FUAglaaJIm2htZG7BJKw= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 h1:QFASJGfT8wMXtuP3D5CRmMjARHv9ZmzFUMJznHDOY3w= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5/go.mod h1:QdZ3OmoIjSX+8D1OPAzPxDfjXASbBMDsz9qvtyIhtik= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20 h1:rTWjG6AvWekO2B1LHeM3ktU7MqyX9rzWQ7hgzneZW7E= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20/go.mod h1:RGW2DDpVc8hu6Y6yG8G5CHVmVOAn1oV8rNKOHRJyswg= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.19 h1:dOxqOlOEa2e2heC/74+ZzcJOa27+F1aXFZpYgY/4QfA= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.19/go.mod h1:aV6U1beLFvk3qAgognjS3wnGGoDId8hlPEiBsLHXVZE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 h1:Xbwbmk44URTiHNx6PNo0ujDE6ERlsCKJD3u1zfnzAPg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20/go.mod h1:oAfOFzUB14ltPZj1rWwRc3d/6OgD76R8KlvU3EqM9Fg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18 h1:eb+tFOIl9ZsUe2259/BKPeniKuz4/02zZFH/i4Nf8Rg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18/go.mod h1:GVCC2IJNJTmdlyEsSmofEy7EfJncP7DNnXDzRjJ5Keg= +github.com/aws/aws-sdk-go-v2/service/s3 v1.63.3 h1:3zt8qqznMuAZWDTDpcwv9Xr11M/lVj2FsRR7oYBt0OA= +github.com/aws/aws-sdk-go-v2/service/s3 v1.63.3/go.mod h1:NLTqRLe3pUNu3nTEHI6XlHLKYmc8fbHUdMxAB6+s41Q= +github.com/aws/aws-sdk-go-v2/service/sso v1.23.3 h1:rs4JCczF805+FDv2tRhZ1NU0RB2H6ryAvsWPanAr72Y= +github.com/aws/aws-sdk-go-v2/service/sso v1.23.3/go.mod h1:XRlMvmad0ZNL+75C5FYdMvbbLkd6qiqz6foR1nA1PXY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.3 h1:S7EPdMVZod8BGKQQPTBK+FcX9g7bKR7c4+HxWqHP7Vg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.3/go.mod h1:FnvDM4sfa+isJ3kDXIzAB9GAwVSzFzSy97uZ3IsHo4E= +github.com/aws/aws-sdk-go-v2/service/sts v1.31.3 h1:VzudTFrDCIDakXtemR7l6Qzt2+JYsVqo2MxBPt5k8T8= +github.com/aws/aws-sdk-go-v2/service/sts v1.31.3/go.mod h1:yMWe0F+XG0DkRZK5ODZhG7BEFYhLXi2dqGsv6tX0cgI= +github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA= +github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -196,8 +196,8 @@ github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= github.com/nyaruka/gocommon v1.59.1 h1:A5E1xvzvK/vMR7jYDMWnWVpisDdZbYsJqZhLmi+2BSI= github.com/nyaruka/gocommon v1.59.1/go.mod h1:Upj2DG1iL55YcfF7rve8CRrKGjMaEn0jWUIWbQQgTFQ= -github.com/nyaruka/goflow v0.222.4 h1:BP15ujzzx2QBzn0j2erM2VUBtpEoG3gchmRCN44IzP8= -github.com/nyaruka/goflow v0.222.4/go.mod h1:iwdjLwomV3thGZeWhybtmDhujbooIkpTn1vUbso5ReY= +github.com/nyaruka/goflow v0.222.5 h1:JpkNS9yJ632XaYp1jyKyVQKMtlun1mU8O/jAXjhQJUA= +github.com/nyaruka/goflow v0.222.5/go.mod h1:iwdjLwomV3thGZeWhybtmDhujbooIkpTn1vUbso5ReY= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= @@ -221,12 +221,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= -github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= +github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= +github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= -github.com/samber/slog-multi v1.2.1 h1:MRVc6JxvGiZ+ubyANneZkMREAFAykoW0CACJZagT7so= -github.com/samber/slog-multi v1.2.1/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo= +github.com/samber/slog-multi v1.2.2 h1:tJfAyxFDk7CGiEumFTj1iXpD3Uu9rFPUKldpsTgUTGk= +github.com/samber/slog-multi v1.2.2/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo= github.com/samber/slog-sentry v1.2.2 h1:S0glIVITlGCCfSvIOte2Sh63HMHJpYN3hDr+97hILIk= github.com/samber/slog-sentry v1.2.2/go.mod h1:bHm8jm1dks0p+xc/lH2i4TIFwnPcMTvZeHgCBj5+uhA= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= @@ -245,18 +245,18 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 h1:hCq2hNMwsegUvPzI7sPOvtO9cqyy5GbWt/Ybp2xrx8Q= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0/go.mod h1:LqaApwGx/oUmzsbqxkzuBvyoPpkxk3JQWnqfVrJ3wCA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= +go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= +go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= +go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= +go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= +go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -318,8 +318,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.197.0 h1:x6CwqQLsFiA5JKAiGyGBjc2bNtHtLddhJCE2IKuhhcQ= -google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw= +google.golang.org/api v0.199.0 h1:aWUXClp+VFJmqE0JPvpZOK3LDQMyFKYIow4etYd9qxs= +google.golang.org/api v0.199.0/go.mod h1:ohG4qSztDJmZdjK/Ar6MhbAmb/Rpi4JHOqagsh90K28= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw= @@ -327,19 +327,19 @@ google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU= -google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4= -google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed h1:3RgNmBoI9MZhsj3QxC+AP/qQhNwpCLOvYDYYsFrhFt0= -google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto v0.0.0-20240930140551-af27646dc61f h1:mCJ6SGikSxVlt9scCayUl2dMq0msUgmBArqRY6umieI= +google.golang.org/genproto v0.0.0-20240930140551-af27646dc61f/go.mod h1:xtVODtPkMQRUZ4kqOTgp6JrXQrPevvfCSdk4mJtHUbM= +google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f h1:jTm13A2itBi3La6yTGqn8bVSrc3ZZ1r8ENHlIXBfnRA= +google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f/go.mod h1:CLGoBuH1VHxAUXVPP8FfPwPEVJB6lz3URE5mY2SuayE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f h1:cUMEy+8oS78BWIH9OWazBkzbr090Od9tWBNtZHkOhf0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM= -google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From c4ae2b6416b459e4b99e377fd79b72654c7b32b3 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 1 Oct 2024 09:32:25 -0500 Subject: [PATCH 125/216] Update CHANGELOG.md for v9.3.42 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ef2aa61a..72fd6b997 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.42 (2024-10-01) +------------------------- + * Update deps including goflow to get group search fix + v9.3.41 (2024-09-30) ------------------------- * Log instances of flows stealing URNs From 201242af7baffedc5dd4145cbbbfc43af3d55f36 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 1 Oct 2024 15:08:45 -0500 Subject: [PATCH 126/216] Include flow UUID when logging URN stealing --- core/handlers/contact_urns_changed.go | 8 ++++++++ core/hooks/commit_urn_changes.go | 12 ++++++++++-- core/models/contacts.go | 1 + core/models/contacts_test.go | 16 ++++++++-------- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/core/handlers/contact_urns_changed.go b/core/handlers/contact_urns_changed.go index f295e7825..adefe3322 100644 --- a/core/handlers/contact_urns_changed.go +++ b/core/handlers/contact_urns_changed.go @@ -5,6 +5,7 @@ import ( "log/slog" "github.com/jmoiron/sqlx" + "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/events" "github.com/nyaruka/mailroom/core/hooks" @@ -22,11 +23,18 @@ func handleContactURNsChanged(ctx context.Context, rt *runtime.Runtime, tx *sqlx slog.Debug("contact urns changed", "contact", scene.ContactUUID(), "session", scene.SessionID(), "urns", event.URNs) + var flow *assets.FlowReference + if scene.Session() != nil { + run, _ := scene.Session().FindStep(e.StepUUID()) + flow = run.FlowReference() + } + // create our URN changed event change := &models.ContactURNsChanged{ ContactID: scene.ContactID(), OrgID: oa.OrgID(), URNs: event.URNs, + Flow: flow, } scene.AppendToEventPreCommitHook(hooks.CommitURNChangesHook, change) diff --git a/core/hooks/commit_urn_changes.go b/core/hooks/commit_urn_changes.go index 524982571..8ea25e72b 100644 --- a/core/hooks/commit_urn_changes.go +++ b/core/hooks/commit_urn_changes.go @@ -5,6 +5,7 @@ import ( "fmt" "log/slog" + "github.com/nyaruka/goflow/assets" "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/runtime" @@ -18,10 +19,17 @@ type commitURNChangesHook struct{} // Apply adds all our URNS in a batch func (h *commitURNChangesHook) Apply(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, oa *models.OrgAssets, scenes map[*models.Scene][]any) error { + var flowUUID assets.FlowUUID + // gather all our urn changes, we only care about the last change for each scene changes := make([]*models.ContactURNsChanged, 0, len(scenes)) for _, sessionChanges := range scenes { - changes = append(changes, sessionChanges[len(sessionChanges)-1].(*models.ContactURNsChanged)) + urnChange := sessionChanges[len(sessionChanges)-1].(*models.ContactURNsChanged) + changes = append(changes, urnChange) + + if urnChange.Flow != nil { + flowUUID = urnChange.Flow.UUID + } } affected, err := models.UpdateContactURNs(ctx, tx, oa, changes) @@ -30,7 +38,7 @@ func (h *commitURNChangesHook) Apply(ctx context.Context, rt *runtime.Runtime, t } if len(affected) > 0 { - slog.Error("URN changes affected other contacts", "count", len(affected), "org_id", oa.OrgID()) + slog.Error("URN changes affected other contacts", "count", len(affected), "org_id", oa.OrgID(), "flow_uuid", flowUUID) } return nil diff --git a/core/models/contacts.go b/core/models/contacts.go index 008ade283..6159a4a68 100644 --- a/core/models/contacts.go +++ b/core/models/contacts.go @@ -1361,6 +1361,7 @@ type ContactURNsChanged struct { ContactID ContactID OrgID OrgID URNs []urns.URN + Flow *assets.FlowReference // for logging } func (i *URNID) Scan(value any) error { return null.ScanInt(value, i) } diff --git a/core/models/contacts_test.go b/core/models/contacts_test.go index 1ce27f59d..6df3a1ef3 100644 --- a/core/models/contacts_test.go +++ b/core/models/contacts_test.go @@ -602,13 +602,13 @@ func TestUpdateContactURNs(t *testing.T) { bobURN := urns.URN(fmt.Sprintf("tel:+16055742222?id=%d", testdata.Bob.URNID)) // give Cathy a new higher priority URN - _, err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{{testdata.Cathy.ID, testdata.Org1.ID, []urns.URN{"tel:+16055700001", cathyURN}}}) + _, err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{{testdata.Cathy.ID, testdata.Org1.ID, []urns.URN{"tel:+16055700001", cathyURN}, nil}}) assert.NoError(t, err) assertContactURNs(testdata.Cathy.ID, []string{"tel:+16055700001", "tel:+16055741111"}) // give Bob a new lower priority URN - _, err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{{testdata.Bob.ID, testdata.Org1.ID, []urns.URN{bobURN, "tel:+16055700002"}}}) + _, err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{{testdata.Bob.ID, testdata.Org1.ID, []urns.URN{bobURN, "tel:+16055700002"}, nil}}) assert.NoError(t, err) assertContactURNs(testdata.Bob.ID, []string{"tel:+16055742222", "tel:+16055700002"}) @@ -616,7 +616,7 @@ func TestUpdateContactURNs(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT count(*) FROM contacts_contacturn`).Returns(numInitialURNs + 2) // but 2 new URNs // remove a URN from Cathy - _, err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{{testdata.Cathy.ID, testdata.Org1.ID, []urns.URN{"tel:+16055700001"}}}) + _, err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{{testdata.Cathy.ID, testdata.Org1.ID, []urns.URN{"tel:+16055700001"}, nil}}) assert.NoError(t, err) assertContactURNs(testdata.Cathy.ID, []string{"tel:+16055700001"}) @@ -626,8 +626,8 @@ func TestUpdateContactURNs(t *testing.T) { // steal a URN from Bob and give to Alexandria affected, err := models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{ - {testdata.Cathy.ID, testdata.Org1.ID, []urns.URN{"tel:+16055700001", "tel:+16055700002"}}, - {testdata.Alexandria.ID, testdata.Org1.ID, []urns.URN{"tel:+16055742222"}}, + {testdata.Cathy.ID, testdata.Org1.ID, []urns.URN{"tel:+16055700001", "tel:+16055700002"}, nil}, + {testdata.Alexandria.ID, testdata.Org1.ID, []urns.URN{"tel:+16055742222"}, nil}, }) assert.NoError(t, err) assert.Len(t, affected, 1) @@ -641,9 +641,9 @@ func TestUpdateContactURNs(t *testing.T) { // steal the URN back from Alexandria whilst simulataneously adding new URN to Cathy and not-changing anything for George affected, err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{ - {testdata.Bob.ID, testdata.Org1.ID, []urns.URN{"tel:+16055742222", "tel:+16055700002"}}, - {testdata.Cathy.ID, testdata.Org1.ID, []urns.URN{"tel:+16055700001", "tel:+16055700003"}}, - {testdata.George.ID, testdata.Org1.ID, []urns.URN{"tel:+16055743333"}}, + {testdata.Bob.ID, testdata.Org1.ID, []urns.URN{"tel:+16055742222", "tel:+16055700002"}, nil}, + {testdata.Cathy.ID, testdata.Org1.ID, []urns.URN{"tel:+16055700001", "tel:+16055700003"}, nil}, + {testdata.George.ID, testdata.Org1.ID, []urns.URN{"tel:+16055743333"}, nil}, }) assert.NoError(t, err) assert.Len(t, affected, 1) From a6e5e46643c9645d1b91eb0b42740d23189148a3 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 1 Oct 2024 15:38:32 -0500 Subject: [PATCH 127/216] Update CHANGELOG.md for v9.3.43 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72fd6b997..53b00aa99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.43 (2024-10-01) +------------------------- + * Include flow UUID when logging URN stealing + v9.3.42 (2024-10-01) ------------------------- * Update deps including goflow to get group search fix From e539e9e716e301182acd946167e60b407483a466 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 7 Oct 2024 14:54:19 -0500 Subject: [PATCH 128/216] Don't include db-trigger maintained status groups in engine assets --- core/models/assets.go | 11 ++++++++--- core/models/contacts.go | 4 ++-- core/models/groups.go | 12 +++++++++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/core/models/assets.go b/core/models/assets.go index f20572d28..1765d5088 100644 --- a/core/models/assets.go +++ b/core/models/assets.go @@ -217,16 +217,21 @@ func NewOrgAssets(ctx context.Context, rt *runtime.Runtime, orgID OrgID, prev *O } if prev == nil || refresh&RefreshGroups > 0 { - oa.groups, err = loadAssetType(ctx, db, orgID, "groups", loadGroups) + groups, err := loadAssetType(ctx, db, orgID, "groups", loadGroups) if err != nil { return nil, fmt.Errorf("error loading group assets for org %d: %w", orgID, err) } - oa.groupsByID = make(map[GroupID]*Group) - oa.groupsByUUID = make(map[assets.GroupUUID]*Group) + oa.groups = make([]assets.Group, 0, len(groups)) + oa.groupsByID = make(map[GroupID]*Group, len(groups)) + oa.groupsByUUID = make(map[assets.GroupUUID]*Group, len(groups)) for _, g := range oa.groups { group := g.(*Group) oa.groupsByID[group.ID()] = group oa.groupsByUUID[group.UUID()] = group + + if group.Visible() { + oa.groups = append(oa.groups, g) + } } } else { oa.groups = prev.groups diff --git a/core/models/contacts.go b/core/models/contacts.go index 6159a4a68..060e60f48 100644 --- a/core/models/contacts.go +++ b/core/models/contacts.go @@ -265,8 +265,8 @@ func (c *Contact) FlowContact(oa *OrgAssets) (*flows.Contact, error) { // convert our groups to a list of references groups := make([]*assets.GroupReference, 0, len(c.groups)) for _, g := range c.groups { - // exclude the db-trigger based status groups for now - if g.Type() == GroupTypeManual || g.Type() == GroupTypeSmart { + // exclude the db-trigger based status groups + if g.Visible() { groups = append(groups, assets.NewGroupReference(g.UUID(), g.Name())) } } diff --git a/core/models/groups.go b/core/models/groups.go index cc2753e01..c054b9d4d 100644 --- a/core/models/groups.go +++ b/core/models/groups.go @@ -27,9 +27,12 @@ const ( type GroupType string const ( - GroupTypeManual = GroupType("M") - GroupTypeSmart = GroupType("Q") - GroupTypeDBStopped = GroupType("S") + GroupTypeDBActive = GroupType("A") + GroupTypeDBBlocked = GroupType("B") + GroupTypeDBStopped = GroupType("S") + GroupTypeDBArchived = GroupType("V") + GroupTypeManual = GroupType("M") + GroupTypeSmart = GroupType("Q") ) // Group is our mailroom type for contact groups @@ -60,6 +63,9 @@ func (g *Group) Status() GroupStatus { return g.Status_ } // Type returns the type of this group func (g *Group) Type() GroupType { return g.Type_ } +// Visible returns whether this group is visible to the engine (status groups are not) +func (g *Group) Visible() bool { return g.Type_ == GroupTypeManual || g.Type_ == GroupTypeSmart } + // loads the groups for the passed in org func loadGroups(ctx context.Context, db *sql.DB, orgID OrgID) ([]assets.Group, error) { rows, err := db.QueryContext(ctx, sqlSelectGroupsByOrg, orgID) From e0bf0b5b64fe8c21ba9dad9e8f05a64a780b42f1 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 7 Oct 2024 15:13:11 -0500 Subject: [PATCH 129/216] Replace status groups with status when searching in Elastic --- core/models/assets.go | 2 +- core/models/fields_test.go | 7 +++++++ core/models/groups_test.go | 28 ++++++++++++---------------- core/search/search.go | 24 ++++++++++++++++++++++-- 4 files changed, 42 insertions(+), 19 deletions(-) diff --git a/core/models/assets.go b/core/models/assets.go index 1765d5088..95a6c62bc 100644 --- a/core/models/assets.go +++ b/core/models/assets.go @@ -224,7 +224,7 @@ func NewOrgAssets(ctx context.Context, rt *runtime.Runtime, orgID OrgID, prev *O oa.groups = make([]assets.Group, 0, len(groups)) oa.groupsByID = make(map[GroupID]*Group, len(groups)) oa.groupsByUUID = make(map[assets.GroupUUID]*Group, len(groups)) - for _, g := range oa.groups { + for _, g := range groups { group := g.(*Group) oa.groupsByID[group.ID()] = group oa.groupsByUUID[group.UUID()] = group diff --git a/core/models/fields_test.go b/core/models/fields_test.go index 52154c628..afd2ed96a 100644 --- a/core/models/fields_test.go +++ b/core/models/fields_test.go @@ -17,6 +17,13 @@ func TestFields(t *testing.T) { oa, err := models.GetOrgAssetsWithRefresh(ctx, rt, testdata.Org1.ID, models.RefreshFields) require.NoError(t, err) + fields, err := oa.Fields() + require.NoError(t, err) + assert.Len(t, fields, 6) // excludes the proxy fields + assert.Equal(t, "age", fields[0].Key()) + assert.Equal(t, "Age", fields[0].Name()) + assert.Equal(t, assets.FieldTypeNumber, fields[0].Type()) + expectedFields := []struct { field testdata.Field key string diff --git a/core/models/groups_test.go b/core/models/groups_test.go index 167eb7afd..dad77daf4 100644 --- a/core/models/groups_test.go +++ b/core/models/groups_test.go @@ -3,7 +3,6 @@ package models_test import ( "testing" - "github.com/nyaruka/goflow/assets" "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/testsuite" "github.com/nyaruka/mailroom/testsuite/testdata" @@ -19,29 +18,26 @@ func TestLoadGroups(t *testing.T) { groups, err := oa.Groups() require.NoError(t, err) + assert.Len(t, groups, 3) // excludes the status groups + assert.Equal(t, testdata.DoctorsGroup.UUID, groups[0].UUID()) + assert.Equal(t, "Doctors", groups[0].Name()) tcs := []struct { - id models.GroupID - uuid assets.GroupUUID + group *testdata.Group name string query string expectedCount int }{ - {testdata.ActiveGroup.ID, testdata.ActiveGroup.UUID, "Active", "", 124}, - {testdata.ArchivedGroup.ID, testdata.ArchivedGroup.UUID, "Archived", "", 0}, - {testdata.BlockedGroup.ID, testdata.BlockedGroup.UUID, "Blocked", "", 0}, - {testdata.DoctorsGroup.ID, testdata.DoctorsGroup.UUID, "Doctors", "", 121}, - {testdata.OpenTicketsGroup.ID, testdata.OpenTicketsGroup.UUID, "Open Tickets", "tickets > 0", 0}, - {testdata.StoppedGroup.ID, testdata.StoppedGroup.UUID, "Stopped", "", 0}, - {testdata.TestersGroup.ID, testdata.TestersGroup.UUID, "Testers", "", 10}, + {testdata.ActiveGroup, "Active", "", 124}, + {testdata.BlockedGroup, "Blocked", "", 0}, + {testdata.DoctorsGroup, "Doctors", "", 121}, + {testdata.OpenTicketsGroup, "Open Tickets", "tickets > 0", 0}, } - assert.Equal(t, 7, len(groups)) - - for i, tc := range tcs { - group := groups[i].(*models.Group) - assert.Equal(t, tc.uuid, group.UUID()) - assert.Equal(t, tc.id, group.ID()) + for _, tc := range tcs { + group := oa.GroupByUUID(tc.group.UUID) + assert.Equal(t, tc.group.UUID, group.UUID()) + assert.Equal(t, tc.group.ID, group.ID()) assert.Equal(t, tc.name, group.Name()) assert.Equal(t, tc.query, group.Query()) diff --git a/core/search/search.go b/core/search/search.go index bab540cc6..1ca8b7394 100644 --- a/core/search/search.go +++ b/core/search/search.go @@ -86,7 +86,14 @@ func GetContactTotal(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAss } } - eq := BuildElasticQuery(oa, group, models.NilContactStatus, nil, parsed) + // if group is a status group, Elastic won't know about it so search by status instead + status := models.NilContactStatus + if group != nil && !group.Visible() { + status = models.ContactStatus(group.Type()) + group = nil + } + + eq := BuildElasticQuery(oa, group, status, nil, parsed) src := map[string]any{"query": eq} count, err := rt.ES.Count().Index(rt.Config.ElasticContactsIndex).Routing(oa.OrgID().String()).Raw(bytes.NewReader(jsonx.MustMarshal(src))).Do(ctx) @@ -116,7 +123,14 @@ func GetContactIDsForQueryPage(ctx context.Context, rt *runtime.Runtime, oa *mod } } - eq := BuildElasticQuery(oa, group, models.NilContactStatus, excludeIDs, parsed) + // if group is a status group, Elastic won't know about it so search by status instead + status := models.NilContactStatus + if group != nil && !group.Visible() { + status = models.ContactStatus(group.Type()) + group = nil + } + + eq := BuildElasticQuery(oa, group, status, excludeIDs, parsed) fieldSort, err := es.ToElasticSort(sort, oa.SessionAssets()) if err != nil { @@ -164,6 +178,12 @@ func GetContactIDsForQuery(ctx context.Context, rt *runtime.Runtime, oa *models. } } + // if group is a status group, Elastic won't know about it so search by status instead + if group != nil && !group.Visible() { + status = models.ContactStatus(group.Type()) + group = nil + } + eq := BuildElasticQuery(oa, group, status, nil, parsed) sort := elastic.SortBy("id", true) ids := make([]models.ContactID, 0, 100) From 668d9f0a58c692fa1ba4cd4f877bb3dc6d7cacfd Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 7 Oct 2024 15:56:40 -0500 Subject: [PATCH 130/216] Don't load contacts with status groups --- core/models/contacts.go | 28 +++++++--------------------- core/models/contacts_test.go | 4 +--- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/core/models/contacts.go b/core/models/contacts.go index 060e60f48..3159d9b30 100644 --- a/core/models/contacts.go +++ b/core/models/contacts.go @@ -165,16 +165,7 @@ func (c *Contact) Stop(ctx context.Context, db DBorTx, oa *OrgAssets) error { return fmt.Errorf("error marking contact as stopped: %w", err) } - // they are only in the stopped group - newGroups := make([]*Group, 0, 1) - for _, g := range oa.groups { - if g.(*Group).Type() == GroupTypeDBStopped { - newGroups = append(newGroups, g.(*Group)) - break - } - } - - c.groups = newGroups + c.groups = []*Group{} // currently groups always implicitly exclude non-active contacts c.status = ContactStatusStopped return nil } @@ -349,11 +340,11 @@ func LoadContacts(ctx context.Context, db Queryer, oa *OrgAssets, ids []ContactI currentFlowID: e.CurrentFlowID, } - // load our real groups + // load our real groups (exclude status groups) groups := make([]*Group, 0, len(e.GroupIDs)) for _, g := range e.GroupIDs { group := oa.GroupByID(g) - if group != nil { + if group != nil && group.Visible() { groups = append(groups, group) } } @@ -560,15 +551,10 @@ SELECT ROW_TO_JSON(r) FROM (SELECT FROM contacts_contact c LEFT JOIN ( - SELECT - contact_id, - ARRAY_AGG(contactgroup_id) AS groups - FROM - contacts_contactgroup_contacts g - WHERE - g.contact_id = ANY($1) - GROUP BY - contact_id + SELECT contact_id, ARRAY_AGG(contactgroup_id) AS groups + FROM contacts_contactgroup_contacts g + WHERE g.contact_id = ANY($1) + GROUP BY contact_id ) g ON c.id = g.contact_id LEFT JOIN ( SELECT diff --git a/core/models/contacts_test.go b/core/models/contacts_test.go index 6df3a1ef3..4c824d629 100644 --- a/core/models/contacts_test.go +++ b/core/models/contacts_test.go @@ -478,9 +478,7 @@ func TestContactStop(t *testing.T) { err := contact.Stop(ctx, rt.DB, oa) assert.NoError(t, err) assert.Equal(t, models.ContactStatusStopped, contact.Status()) - if assert.Len(t, contact.Groups(), 1) { - assert.Equal(t, "Stopped", contact.Groups()[0].Name()) - } + assert.Len(t, contact.Groups(), 0) // verify that matches the database state assertdb.Query(t, rt.DB, `SELECT status FROM contacts_contact WHERE id = $1`, testdata.Cathy.ID).Returns("S") From 76bc2126c174188a478e033a6be050f8dbe9d2b7 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 7 Oct 2024 17:39:51 -0500 Subject: [PATCH 131/216] Update CHANGELOG.md for v9.3.44 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53b00aa99..ac638bc8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.44 (2024-10-07) +------------------------- + * Replace status groups with status condition when searching in Elastic + * Don't include db-trigger maintained status groups in engine assets or contact groups + v9.3.43 (2024-10-01) ------------------------- * Include flow UUID when logging URN stealing From 1342294e2a846b178acc1f5a21d28a206b8681d7 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 16 Oct 2024 15:42:19 -0500 Subject: [PATCH 132/216] Read user team from orgmembership instead of usersettings --- core/models/users.go | 3 +-- core/models/users_test.go | 5 ++--- testsuite/testfiles/postgres.dump | Bin 1764613 -> 1772010 bytes 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/core/models/users.go b/core/models/users.go index 2b8dd7dfd..2b89a12ac 100644 --- a/core/models/users.go +++ b/core/models/users.go @@ -77,8 +77,7 @@ SELECT ROW_TO_JSON(r) FROM ( SELECT u.id, u.email, u.first_name, u.last_name, m.role_code, row_to_json(team_struct) AS team FROM orgs_orgmembership m INNER JOIN auth_user u ON u.id = m.user_id - LEFT JOIN orgs_usersettings s ON s.user_id = u.id -LEFT JOIN LATERAL (SELECT id, uuid, name FROM tickets_team WHERE tickets_team.id = s.team_id) AS team_struct ON True +LEFT JOIN LATERAL (SELECT id, uuid, name FROM tickets_team WHERE tickets_team.id = m.team_id) AS team_struct ON True WHERE m.org_id = $1 AND u.is_active = TRUE ORDER BY u.email ASC ) r;` diff --git a/core/models/users_test.go b/core/models/users_test.go index 615c5c15b..b09b0b5f4 100644 --- a/core/models/users_test.go +++ b/core/models/users_test.go @@ -20,7 +20,6 @@ func TestLoadUsers(t *testing.T) { require.NoError(t, err) partners := &models.Team{testdata.Partners.ID, testdata.Partners.UUID, "Partners"} - office := &models.Team{testdata.Office.ID, testdata.Office.UUID, "Office"} expectedUsers := []struct { id models.UserID @@ -29,9 +28,9 @@ func TestLoadUsers(t *testing.T) { role models.UserRole team *models.Team }{ - {id: testdata.Admin.ID, email: testdata.Admin.Email, name: "Andy Admin", role: models.UserRoleAdministrator, team: office}, + {id: testdata.Admin.ID, email: testdata.Admin.Email, name: "Andy Admin", role: models.UserRoleAdministrator, team: nil}, {id: testdata.Agent.ID, email: testdata.Agent.Email, name: "Ann D'Agent", role: models.UserRoleAgent, team: partners}, - {id: testdata.Editor.ID, email: testdata.Editor.Email, name: "Ed McEditor", role: models.UserRoleEditor, team: office}, + {id: testdata.Editor.ID, email: testdata.Editor.Email, name: "Ed McEditor", role: models.UserRoleEditor, team: nil}, {id: testdata.Viewer.ID, email: testdata.Viewer.Email, name: "Veronica Views", role: models.UserRoleViewer, team: nil}, } diff --git a/testsuite/testfiles/postgres.dump b/testsuite/testfiles/postgres.dump index 59a1f8b64f655a87683c237b27af5fc12cb93223..bb750d5135bad296f71ac570a62b5ec235f5102d 100644 GIT binary patch delta 85995 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 V0i3AbshpPO5i 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 Z?tfkj0rP)``#&T6pOOC0DE~3u{{YIf3T^-Z delta 84706 zcmeFa_kUHz^FN+F=iGB|LP<{ukVdb`?FA_Tl%_yHL8K!#1Sx_A1q>>RN(tVVjE!bP zqy?|CiUL7J5UGNwD8&MTQi2VT_Id8^xi^>B>-!IUACI>`;JI67XJ=<;XJ%(7zlF!l zI565-+V%RLrL1;>;b4p<;J-BSk0Jg^7yo37e;neUDdL}m@btD>^SX4gr`z!3Vs^NY zWz1Xpuy#p&y{ugp|H}#QXY+Ee$eyB$0OoT0-3cZ>JZ_&m5D0gSq(?|&%=EbZL9aW! zP+zWH&8ChUv^wFD#tsytSt#tctzrqZ=~FF}oaZ$oe8u*i13kPUPeKY!$Y$x0Kg`iO z+Z#^J%R)uSiBYs4xD zPd)2*P_zlp$#X<{=Jl|Jhu>!b4kcs(FjctBjL43LkJ`d1d8#Jhlr`a1&9fq9&9~@b zYG_rs5^@AC6|Hl^bw{U1UTED%4|i*BRk~GWZ&yzE_`vjt*L6Ye zxjyT+H?qBLqCLESoYmQvkSmb+_d2s)IH^l!r0MmqvGD4)R;@pwP}G)oFbg9!yS&Z9 zUw5|P1R{I7Zr8$J++ei~CbSh8R?W|k9P06_7Jjd*RS`<)D=JpBtQXOGZPvmIdZ>z^ zGa)1@O8ewT9DQnQ;mtj><>Cch2{%69zaV^gR$^r6%};3IEv0JApgVG<-z+Vhb+ZM* zqbA;SprXpR1HNYAANyIAURg;6oy_dWv4Ka`A_u&YJ%g8;;b#X}t$YaqiNvE#Bb!Eg zwQ%RxtO|dEOI2iq%cj+e1n+vo2(KJyArc5ry)P#`c(fx@cXSsmd}AG}JQ$gJ-)&m> zn9~9hO2`BdI@`rekJK2SWrQCdsal4dk@l08YvCX6wSK!I=0mx*aN#8Dw>wfY<%lDE z;z8@TC(>?4S0mhTiuKzYS@KL5N4Q{y8Y$#c==NBW8)@?VbB^$n&sYupk)1EUXoP>A zXEh8Y6bO{&v@;7LZ!da93$J7I~3Xb_Pd(cXnvW~yd;na_^>AD;({e>DV zJ5sUECtLb~!tWm~x4vDGSJ#hJ-$6Gex7D&D^)|jE@DBKcE;{m|mP>6~vNQpLg6p)D zNZ}`qMSH);8;soaX-(@p5E=T}DE000Q(=3pe&odF)=W^u|Za?#OFjJ|XGo2{4yTrNb(`Ms{T+gM02}n zIgt&A7b}>-U_xD**9Fv^^5Ym;>>a9!*O!n+OWrjzBjf(; zrAv&xkQA~gTz1Ybd5Z*vfJ|a|@t;YNymL)N-#`#(XHoghdQRld^G`{33VH&Ov;WSP z^bdk}p1ky>jQmez~nQWCHP0;CfC*+B;LVEcL zmck!SWNrZv6)s+p#NJfUT`mDDlaH^-9uT+$onB8u1JN&ss`_cQczSKt%c^iEG*=a+ zwOLI*Cz(AfvG-x5x^i?XyH~gIcB!nlRpCk~tgfIL!&qG`0=C-5WR@u!oBVPbD^i%a zgWQ$Df@+HH03VmlW-Blre?mP$og5mT!)&}k4l9+b@ALS8s;sE`z%cns_1L4LN66`P zCS=f>JeJKpx$HqX?NBJhSLd;(6-cafI*s_t%;L8cu+eh$eVBM%Rd%o;8zVY}oNg!Y z-I#qXh6_1eA?)-%jad%Ap&7ehV(syG`TFMURteSP5fC;uXIXS+ujb&8EO)3@Za;sc z6`O4R@X@AyTAtj1y^n$#i&`){4S!fq;*S=wWJZ&!m=wHjm!B^!W*r$lTWpm8kj=#! zGFpEsTH@n3xY-Si7CoY;(90fK><@T@oO{?8VrD_N*Oib@bEoPJX!NaMg>?beO6&<# zP}zR1HC6OwX|%J6mQ7Xnvm|mddlnVCK1eVssb zIRbR3Jeu$`h7hw3Py4FQ{5vtb#k2JcD!HEhDI57i35`Xg1~EL+=u{FGMN&exl^AWf zo~4t8(F?S?Tu-GfU6@c)f;dYsV-~h%%rRqmG@!zl&`4FFr$bG$;vOws5f@)+)garQ zFj8q>_vrEk_}T7E9yvjbz=z++1_^9(fmIers3Xg0 z{2|Rz&1G^ztFV{(<=(8ZY8w(9eRzSw+(Q+IfzjMP?5rFq6r_^FS__V~d039^art=B zEsCdu9+#KeAF-H+f71^;Mi9f}b^+whxAX!Is{19Ic_6QT)LN?f?_LTynA{(WeE44@ zO;Fv$BL9a92D0X2Scq5Ndmy_-jp+7M$xm8y(KU^*v<=8Ph^3NT68Tm6lVpF#3ye?@03VbNVRbnK&?LDB17M=7!&y6d@&^1KD*8pM&xei#TLA|@ zOaNYa2Lz(};i8nIS^>=-#?qeu!{UrZ6SO4$;$5t%8Y2*dJQ*6%v-rWg*;|4)0VI%6 zSMX+^_rP#Z4P!a6&L#JYo6df_dWLKz=3#rdmu8OfA!Co2hW zOm9h*_pl(fdy*9p%4^dt53t(>;Gok*tBz}p<#Z`&1}hZx$#iTK8>m`7_n?#qXeqeQ zm1WD|qaR`)$U{Hq6x@fhOkOgD-7N_ibOrg!sjNz#lTM$Hj!edJwxNtQ7w86@J}-2D z@-g-d8h*Fk;P%Jacva!|0~RXkahzZttIP*cCd;axRFXa5#Qqrb6wVx=j~_0+>1muC zVhMt-fHJVwoYJ!RxM$c9g^)W)tEOmm`N?NlCq*)M07p#46p*p!EH+j`3i+IT(`@#r zL_6dS@lNGYj&Qm8`gyFKJgNdN*V-4@NQs>%6vR2c=VlvNZ9WSK;T!OT1pA?^9zXmN zn<(3PT`tk?EKr#A3Tq_PvVhk`JI`v3<$5j}1_6}60LPcM5TaV_s{p8!O(Sl%rIYhd z&CW{~F{vw}!b6+>&>GQ-hSlTfiHl3IE_+!PZKnx;-#0w6;;na-8Gmma}}TOOV%J$)u7Tz;@;H zRw;=S5cnlmFr~3$pYo0Gs?C7S<>5u|f$kVH=nuF!y>A_A{s0}gpyf(Jjvt8;ufEGn zIuKgv=D#dOb7X$h0Z`_V!LyYC~dijGptepY1>4iRe9iF^f$!x#Z=cG}4K)Z_YrZ2N; z@iwgYqP;AMzqf}y!>HnRR*zoZ%OBbR(0-9A+tOMYzvSYxMjdS#4T+m`x8? zUam(wzhe#>euRyYzydz1)b)J1_CnW>RVVRQKU&h=iv>-k!lNvMkNZh6qu&cTlYbNv zj(#|yG%=Sqz+XScwlL}j3m3pcF0Y?v*+82`$Dt9>%2N==p(|FMC&VwDV7;{$NK=hH zL4M~+)|JuWv$j-v>X=k6LN1&l8MLXfBb)zuicQz(-s2X4fS=E+V#672KLNPx57|Bd z-4|r|v6hj^eSfgFLU9Fegm9S1vN?aTd*vF2Tpn7z1O`LaKT4YpgkVJEQ0lifuwtTa z=g*yIw@XF}$s>Ku68PK}*D-2vFl|_htdN^nlHQUQZf0rxw@Z+~R7aDu3AnJqe46H! zQosdTjWaa0zg^f(E8fy__+^_`C`}|6Hj+Fj_t>?%^!6y$f~sn3i8SXQTM{p`Yt1CW zE{}(=a%fu=M5q)iYFRuWTG!?~YG@55x-M9}=`^P{&f?m&w6M@X0;^0`Eu2ouP!Gr=?MvI#nc0MChd&sH~W6o=C zWbwM$+9frnkA|h{g}iN!Car*g%j@B9)zt>58F<~)BTcW%^KvyP_{W`Ux!nOb>}Xd5 zEuGKH)28b*EKjiqG(Xz2TFd9X8ff(x)oK|nhW%F0%%ajpS~6eWQ0va9q_&w#X$>^# zLvuse(;t4I_r!=180hgZ0_}Hof(aHYpJX|oxNU57cwX24dWYpS~g>IWDN*}L(ENQ{MrFl z9#<5kp99)C_I!0w&>Oz;UVTalY5zh67GPy(Y>`H7w1Y5xqD63PQN8N`@5SmOY%zGW z{0#P()l7J;XjnV#ZPv89NI9(b!ak~QhkxgE7ltaV(GKivDP0&3SDz!u&OVC%ek5|wt+9;fkdY8hfea7fX~ zPTEE~Hdf0Jgo}Bl_z#`63`RA_%Z4Zqg;yWQNo2ClImXVZQ^ft z)y6Vv^RjAbIhan+=$oyAIIj`!*#nD2fA_I~#C${)zEP{9xusTl%rQhwZor1ydy{q# zW6R})r8JE>fB5)bT3tq8b&_KR;P#<6dTYOvy}g1|eJjB?lxhJ+-*-@0!KcIF(;3W2 z@2Hx_d2klpEH-%mIkF<&jdSi6?K8TrmsNg^o8~(@6a{pxGluS(pygBHt=en!T+0mEr)5JvE$Jq96NqW9IkCh^~j4T zc^L(JDk0&w>L%!Qb~q*n=Z8r8)y-!UM8?gjNpcNpD56I5)^+cY`Y9W`J22 zPeLE*@Ypy?t4%LY(`!)COPk57X8Q0jP@^f*mFqsKXHdP zlI|$CI#jzto*Rh{FTG@Sm_HLX@P<)ZGTqT%>qo)6P`-bj#9R0`2>y)*G;1U_)UYwo zJIkKc(`eUS+C;i>kp;Qhw}GjAL@z9fmd9KX*N#ZTe>JQjyLch`I!w{7C%7E!)M^x_ z`0Z=f;5cPUM`_>F<5MkwF%JU`x>s96>u#~it33zT-KVuto&!0f*GFqZ>7#xM9y|sF zk3o~zWENA;kqSb!qksV6L+W;i)df}v?0*>gW3{!^{We)DtopU%w3>`w9b^@NOt9cH z>ALaS1)AxzN+Bj;xaZK_6SPlha#uMNEb*vyPJ{2)zNW11R(Z_uri|CIKGP>+gFSJR z1q#;out^$pnmcY(&ExE9xvQ4VQOjA2hG*JpQtJn`X_V6=+BRkzfB&F1l+kt9Au2 zE}yO0XzuqoFkr7vp-y*85Z9WkR60XjPtD8JsMR*9l5HcN!uIbq-5MIkXYSKtIy=MB zk|13-Q~QR!9u*qWsBHC&R)?`StIGmjKJ-~l)99u<6$-E^>5*C52DVf-$`HpG?0_W5 zpZg}>J#pB$@smbTVF&ny%4Tc(*|KU-%3M4`k3X(3uC@nh#$4?%)p{yg909Z48%bn$s@FHKyk+6cq!cvvf7%vIe8 z%iQ1v&BMav1Wjn@B}ljjUeHESWjE~xI{co^PVMJopwwYnMw&3BAhpE({Cy)Q$Pp_-%R0I-|_wC)w{5N%rkI4$qBN?|j7@~UQMH0(_+mDm^w zCEi4;wNNW!^x~`NR5(*nInE%OvIw?B-MJ#5K^`XJA7?ZO!3S$*!(#0lN_$2T6BZJO zg#>N%`e!Xr*BV0Pe*x-&=XI?XqX)XEPQvba_D#*f=<~@|iL!UL(5eTl0vIy0mue-9 zs;*aXt1T9~`nI-(mP02JYLg328yF{fbY+>go&Gb$Y8^972!@HB5Gb*qLJvnPqlO68 zSfO2|008Uh3p#jp}3ebd-lemhrJgm=<$gl5(eoL7Ox6h;O$dsn-gUR?v#{;wGLYTJNz zy{jz}I)4iLr@A_(@YA~yZJAK)k6ONAdz-cgX ztG0*wy{~1_z%R6~sOwsMNtj)>Y5S-l$4}>%(2mnTh^t5;Ts+pI@7JO1p6%M3bWYSZ z+@bwJUw(+MmOHhdX|-bk_ zm$4CF-|p2OW&O$>aOAll1K; z_6VEpF8l{bR(-40 zpo2eX_tKE93f^(L{tJ8!I;8EUR@+orGL;_IzN5Nd;%A2=+G(n{JzDX@TWauLgzJ|y z!npk?=O_4!YwbW2AdbF2LH1?t#80^gZXN<(*npq4O;mdq%8Gx11iB)=E*^!NoPSjN zoX+o7Kljlud+>Gi7#8EJukf|!S8XeOvKL>gk3)v9*a!V(*dfUM_E|X9a4%q|$`c@a z_-izgEHe8yu*jJGsz+^FFveb+;NN#p&vfwdiQ`%^L9Ka!#(sk)qfcUmAN&?yz^XQV zbPC-1uLf=%{)=j7(zsL5fj<0M>rGjwLD-4kp>ey@z;EmUd^JC#eL-Wt$Csl@+d!j# zz!xT6n-1MnZ&V%8qLK=k&)Jlx@=WJ@k1MzTP(UU9|2jzGmC> z2-UaaqI1O`_<6UfuVn$6`zQFZvmG6${G~OaVu$_}jrkk(*@^mo8vGBw^d$Wx1<&E@ zL=Am2)jf}|*|qgrbij_axa~_^2kibFP!^@>(#cnY+_m&O=?~FvWi5RheSSgp*hg>w zi?4^0^+Qy45nrQI^dmI(lB!Lk;vDS1vQ+(j8hROJH>T-ZsQ(pwHA}}N_GSU~0ax)e zFGK%<&Zp}sFSG?W6=dq$>E8^vpRyQ!re*1$(BS>h7|x5I|7PixbTSjQUuyUXvTvnz z+4#9!$IqR0^iOGN9RTLzbm0ku3n<7p=IE0cYeq}1*F|ul-|zPD(t7$Uj5Q)xBUuKA zke@qpb(pd(WSQ_J_@MseQT-8SE`KObPiM>-R~1ZfKmUFXH63Zzq0$1~;D_>cxDX!e z4JTgvMtY(I5b`H9nSZa@nBP{YBPih6d|f#G5ZmNT@KNtZdM2-EpqDY)db8C_s1^P| zkoRn)!*h{YsM-hMH10;JZE-Wbs43*bktTW*IyVX3k2ThxW;CTi6mAe#3ZflHJ0Bcn zMT!buXl7@d>BWrpHIq%@h;b$K6=6|1G^iE4apPO)5yn2C!WN1~9>0^9w9?_b`O2wa zczkaDWox|&qw+}>4A7?woN+p?_J_b|-BApm?rp{TWJY&BXmvv9!5OE1J;EPGVv0pA z^hB`&9;oxBZoLPiz|gp2uhWmH2>l&K1ud-pem|e)(+e0qGR$fS0{T55U>gxvMN@-% zeg2PMzmd_0!{b_b-OhyDL<^1<-!p31DoRQ(T*PHQJ)6&IBjDcM*s5?j5e!h=PH)Jo zu0sX8KE@lu)Z^hXAk{QKRLP1}G7j5cNht7Hq-_lX?Wo8f z!Ua+*8RkW^Up6!OZ`WgiS*sXiguW#>Rq$5w5;Kht>4J5@xhl4S7}@V}fm=jO7|Hsh zUBROW^Qf*50Dg7-P2F`bgNUiF_awB4tN*MAW=4+g3Ohd>VAkj&PK@$iH#4c?Cb)s# z`Ag5Fm4ozLs(jN-mQiH1;B~W^sNGEaXU9z)QDF*59jdj$%qCDLC)Tws`FiP_S?6km z5J_MmynBslaA$A5Gh-#PUhaEb5|@|i{b5WK)-k$EgYc2LMYNcU`}^wGF`9QqG9Mg< zem>`B{UO*r^MO>XeIS(3lj_~7H>X4W^rl4huo0i>r^9D(tdG^x=jA!KVkGvj+);wu zUbtkMh-j$v`Cni)A_Ig(xce0V7P~^U@Ht%|XtV{L8K^sG^uHk1mAXcKIx-$ouJ;zR zv8pH^2zjr^=I+B~zmrn?YIP|2OZdb8Ge}6ItV)ZRV){OpldA3pxZSTIlrQB=xYKin z=&*A(1gohLbUD2VLq$+nAuZUCB`Y1Kx8&aq)t582ExIG%Y4P(V!*!UPT?VP;_PNCP zOAeaFeD>`anr)9Yb|Ikc$B}xz#&$>LC!*K_2|Z-6+Wha#LMls!9$a=er0AHt^t)ks zEse(;L0-Un0;sjxDwfo8`xDy4qSs8maFl)*V|8My?L+8S;b$yB{ynsA+j5T|o z6Pzb%`Thw)%nthyL+)H4*A>oIKNw5|rcu#Fvkvb!5nRUJs+P50Cto>9|A4W#W8C6_ z7rPa$o&@5reo*Y|H>#U^-2CE0dMRUFXww2Iu0w9Oo8L28EP0|sPhmZ(E6{lRBSJjN z__OspvD8t#+#V-x$ag-1d%7pbVq;xN1e;Y(2G=c|fy8bp}Kg3$P;o%5Wq6jr%bwO>Wb7ANCZf!|K6k<(_zLK3C%D_>pevu%J%sTx z`8&_(vl;s}))-MI3GM%+OpPlFXTo3+k`|(uX6Pxr**tw6gSBm`DF`of@~h8d`czqNVH5Hu45Z4N z5GN#OKIR~_1c^>_;_%Hwx>P=aZw_Pzv^vhXCPz_t#}Tk zkr8mKn{wLySLhjx-CkXVS2A81B1cm@fGi)c zgp%;W)_9tMs~gdo8|)2vyVZI=qkC7%iNd$;#6pP*8n*@uvAqYl?xPAp)^XMF;T>#6 zr4w+MesPUZ)@qs-Y;kVDt;z>Sbliq5+Yu*75z2tmN`%GHoL=^fweLgyVP(}Y5o=XI zi@2W8N3GSf8HJMLU79}uH;o{~uV047@j4%ZXQ_;<^>X2iE~XK~?9Hh7 zBRz?KzD{>BI`n~>WYFcsBvIRxxBFOc3KeOcQXhke(4D?sXl|DtvQWp25QG+_Ib#q+ zi-0y8**9UI%&BLqMO!w4I6XG%GZ>j$tEY-9fDjJ1Xb7B*qeoB1w#c~(crGyVgirK; z#1UyNXTS-<`$Zoo{55(a-}sqO>~8r)VvL})P{J*=@F9B<&E5hOoj=LqodyNx>Z`&NA=t*;+XO+@%NqQjHH z6bHTlQ?Mmb(0*KFfnuVHYHkNxNHqg&(S#O145x73HocLmSpB8`9{lhECBX~W8N6(} zemkQF4`L*t5(xr?e13j)hYla**la_ZI3akcn-JTH0E?-+^hS)`9D{-26TV@$uy0oW zZNY%Z2;h9dc`kzUX#E_B)$!eIHR$yUNZ&^tIG1`oZ^uR8T)R(Ito};>lFt8gP5%Hk zNp=5{ebBad?-fTaqTiK#MHC;jZ5i1|m&-9oc|V)MhkT8tVTG|>9Yjn|KNZVY_6h{d zs8btP8Ie}<89*H760^!gE{tU~bcIX?>>1A3oDrvM~Q zBXX_->s#^mz&v4Vb{e+m{U z#7+!xc)Rnc@D#9{dKyxcv{TkBLteh>jG%4v_HiQwA9#QMSi(|17&AEUjS$|Pb z>chx2wSGurS(|rRcQWd+Hon&H1s!)^1sxy1BIx+*hu74Ki!NFF;Z?mRV?V{zP@(In zs`oD#sQI@V62!-b9(vVS_Z7e>;IB# z91^b?+K~*jJCbH(P-!bihLlizS+aps^OIVZ>~n__I>fbWn`Y$jUa7`VM!saL3FLnZ z*@OcWBn6LhLCEb5B?RMo56Cpq_~Z-&hT5r==l}ueZxRti{MDpY*-$= zfH}A8;>h4{u<6cI) z${Y^bpM}`Mw=;ou%*cZrgDcAlFocsO!?g2NE(3uRBAk$oh#}{J5j#w_ra9p7@EINzM#%1}1_3$Kzq%0(4H_*Jj<*F2*vk!lfLP-9lKD$* z4BP^Xh($IkwzWObTF+c3c1?8@;!|mHgzp$WoO~^%!eoccUR+*xk@@7XBMI43OjNdl*IF z@Ea5l!&S~5H;G|>uOByz54<2Ff>V1Nb*XPp<5z0Y%@EI)13q}U@`WfsRX%^Dmtn&u z?5?DMAO5-4^5HOb7zyvw&fZ1=qs2X!(TrPQ*F)H}V+kAc-ObUjUDB z8q$RR2yLyr)quY6!(iDM2fCA=9U#t(eFLl#r<)fK1gEgIwC7brooP-FrX*e$Q@0UD zZT{XMqZ!Vuhk*27%%<&T&R=+U9Z zRkkUvGN4}d_wiz1jC<5{(E4GG%8x$pnkAFN;Frl+i+~VRaP=ofo6N0zZod!(Bj2IJnA?`$?2%2{n#v6Z^ z0cApeS^^9?AtB4}Mtiz_X0#;8Ke)$ukkN&07~;y~hP)SX!tv8w`o-tYz)DZPR}j9U zrA=B6PG~`mWaW^j;3wTXTCh-I>u4o>&a(29ksOcn~cfdqA`pe1CLkZ$eY$y+8dTt`AN> zD7G1&7_IdsG*-1muYd}j9~SvOI?BPt;Q|K?{C#5k%$i`0D0Uw_9cp7Pe$=STKYK(R zayZsN9ofm_`9{$oWYMPehnfp2gr6}v}K-!KAu2I^brwZ&KEs;Jq=cZ zU(~|hA8>}MThOp)&}8LI(WGk(8(8ny!WgfRdOwTPB;{E_)*B~VgW`ENrjK+c7=kLE z!!DZq99D`U$~-n(x0+54-#AOma+&xHsYGFR-jc2UqElrN#Vs!x6&J0(&sc1l?fYsuxi`5+>rb-XA10Q({Rd9fd! zib=O1&V$zSAv{%t!Ge>+m>YXKu3osi#Y1^2cmWG};y+>o+*D}FofyPn+eFsRCv1RQ z=8LV}0&pZV!O)vRVl}qCBTL{S5Ag3_gu+9OH_KBBB_SSqSsW-8pWDRo5Ol(6B*n+3 zSAfpqR}3d(=c3{&=<*0%N?d@@iUqh(Oj`hXzR-laAcQGKMSmCJ;dv%+w@`?(8V;*Y z*cWin2~jS3pGKAhK6epBJ=It!Ne$RgW7P%825~`haWRfr2A{E7MbryY$qST%*FlQA zUlVLFZh%E4&-L~^}Y)MLOqo(RYFf?u(@B}9j!9E9So3(huHw^^; zZ;cW==z{MdATBH#@R#2bcE!rY7HW6~*-BK@rF#zna{IT122kgMqg((C}5zzlW~`A3wN4 zoTU5Th|?oEHP|Bs_duOJwc7ZF zr9`{nKFO^nheyvM`#`NV!gj!O4vX$i=n}2Psv!pA1Z41Y?+O;ph^r4_NLk?}MOiVYipEr5v<37ThG^{})O{|0&b^ZgMUl zMexqUn*?zYSA0pHmmwz%w<47?VfQB>04}{n6?Q6al)Oq?obZ{^kV-!@{*YHo^vgEM z@Tm9jahr{H){RrQ>W*Nz0-+(~Z!rq!%IC&;b@f#AfD#-5x0g5EYSa;0kXQ|IZUvB@ zM5IlkgY?qZM2GO-F? z*o96(WZcl@Qq@n`P0fE2i+rNGTL39VeijJda>POyE8|kR9|j+V52XH4q1>LWMi0lU zU!`w2bPQ~>_?STL*sOSBAj+jkH9378*ta|`mhbNv2%po3dR(3riYv231oGJJeBlXk z2zEQBrszXV%AwzY7rR*94c;3#CXm;GoG;8Wa5W8b1=ZrmUaifvq~V3_*S)f(a488 z5!qqHIg@V{Efd2 zM0p^NHCFFJtdgkb_0Ac1u~)eu9#Jf8kxVB+0#UyDym0IxTSBxq6p&(7UxjoMyyd?F z$8Y~pE9}DxLp+J=v*DLOo5dH!(YE@n_yG`XVKv!x8BLNe3kfzbIwf34c=%0MV5YHQ zvP>L#UL-karmoA*Tty#_e;76ROAd-2Ubx;wz6rXC+0uCivq5LeTY*7^eIi*HuD@=Y z&Ch5}t~BHU(#K!XZ8%^SZIhNKO8m;uU1+oAQ`{vy7t z+i(!JTpLGEPsq=AX8=2Dx+~hy!;7*(2v+LQQWWE@ycgZLlQO~RDql&Kk zLgIuwIUf}?#&u>vzO6nVn`eU(c3?yNXh^Ao%dZS7FR^oL=Y+g7SSH#Z@P7A~+z!ZJlF~hSKqJq`57Fil*3Wkl74OIniU)Lv@o2 ziuyd>wz&QhN;TOIyuOR*I; zH?7RG#Jm4}ZXETg2!ytg|e^wU^5+ zm=_Ofqh~%+MwEMOaOa=0?coC)QF{A`EUDUw_ zWoj6y4T9i8exbw$mF$IT7>Jte(@C`2^o(rf$Mqmz-dV7L^EtUWQQ}LeFXnjm8GC&? zcfIWtJq@E2WJZ;rvo2sl)+b6kOgcnuyV|Zm;0rgQU`ns>m8gU$le*bT7)y>K5J2h( zzNfpb3!_gvN@0q|0oXJmdkVF^37ca4jkZQCTzSx-(J$d$a`{UPi7o!`pZ}x5|M9{9 zZG-<41^+*gg!6B-WmB7O@_>VQ5At0%+uB2A--A=w@+k$p(wBz(SoL_M&3umY_KC5#0-@nlH9b6Yk6b@OoL`4gerSq(|A1Kix_sh&?@fz4aHG|4W1Nq zM|e*T<$bT$;`>JmUG-HlTy+y+Dv2iCc_$8bntiKv)Wf!zd6!U{PTZk1Y^1+dE|kK1 zYz6c0v0Y%@qGxb0JJXj?;GF&crjPzA)fFSIbc0_VHN2zB$qJr+Ij?xD?C+aN}*?o~=8yot)2 za&{cl)!pNSO8>=UQR+K+@dO(#0`9Gbi7T@q_X{OE?^qlW127ZbnF!;CjfrdO1pEp38@6I?z8LTB6bgsp%U zKViE-CHKq50YujEwNHx12QDaXf$_+HoFV4l=VG+P%L|?sochv(5+Hh6isk8OUK5q{4BaWYAOVnuS6A~I8ZTtEBa@*HH_DKsq%+#6B+YSh~ znzisqbrs-`{m1qjULQTDU_yCUi1#F0E`NKzFkdofE5jrR$9iL#>22+cHiS*x*+KOY z`hGn@*VLDh-LKzEwjyC+M42mu>x#uMLz%_Wh%(_h2ti&nQK{kbUbWT3ZE%SiB800D zpGwv?bRmG1FR%p>AkoRHh3(o})xP?xJ)LJSf^eZHUXJz^R-ViW*JFt-lh0f%hz0}qj57HJ!8@Lq-(V!l0 zwhWdR1s1D?69(3+@7TsO3f``a4=ROT`Q4)qv9Mc2T=9{ z+ZDawMo1!V$ixcWbjDst%T_{I1#qv7y@*gR92iwzZmX~* ziC9_dA|6>5#p{~s?;`)=U4EslBM*6`bE%%gTz$radHpK{n)k6y$+vM#ECkDu@OK&)8R zdyy3v_Y#~q^?Z2h(BUIYiDqz#GO!5oyFRw%FzR*X8n|x$>Uweh zbpP0z8$4jYZxB9^qjBK8ynQ944t%g;zG?_3pRfrG!cNIDVe9$efM}?a^1YMlNaYPb z!Od*eluD?vu%sd^f|#b)C*w%Bfl`^Q#& zF^6D45&b3MTupPN&}&<tnSLd3)!-UDzd z*WZG=@xYKvw6SV;crSeZfxWi#s@bCrrX%O@41W858Knk@0N&nmrlAd`}HV?UBrYCs zf^ge*(1zGwenRoQ8;){$-Fxt`Ej#XVrR*1*$!|L>m~Cc~3Y8Mcv>!fVLo~|WpMl?n zD^`dBaSbKUk}E%AS0DV*mWGgrh6-W17z@FzX)#sGwSRJ zc<=!3(lMNS>yFwQi_Q`P+&MqZ1TbzVn)o914|DKhMmEgg|;KU$0E!*0C{ z0+R1{YnD(i3Ir+zS0N~`UV%C^(k8jx_FbnAo^YFIm&Yp*b+o#B}e!Z1F6# z6iNAxqd@sxNodWE$bls2f@vzO9h(J7dg>(;(`;9tafWN zfZ2l?Vo79v%c`%UBZ}x5*)BH=K}5f2nkUtWhl6qQ)q}T7P7!iik1l2-1SXViVt-v0 z=`9zww%Uo~#DnAHu{vfOtFFVo=qMhrjT~h|T`YWhUEG+dSkgYR6GiMqWbuR+F4JNT zTBa0lJR{;$>YI3HfAtSlf>-r?XRaV}OHHmWEQ9FZ5XwhzLuS6{)U3l+a*Q?rH>np%}ilDMGqq{1XdFf%bG)_GjSY@ zXkuPe@vP&j+aU53X(y2^Q-rIg3(YJAE`0Nn%9?q9Y_@nK!_PD`Ay#GJ>x>5g@JLJ3 zE)Qj=lNPr&n}*BknDmGjT-U0V*+l+Y)(r`h58rM}qQ_d9HK~1(nL&q3Or$|?W!6Fq z2f16CGc^2(kO9mAU#v zc?UW=p#vlp##00-yxNtRIC;f#V!eG(|MtLi7~JrhR$fA@{P2;1CI{WxGYz*EvfYOr z<(`0kJ!I`7`nB zh&V0LSuuId9wzi!=Qevy9ehc1`<@d)4{czSPh3}b%!@hWNyN|e}j;Tcw`2;C~=-;|omEPiU&-b|$XhwEwV z{_0%;l?xBH5J$)N^fl*-c%c!ufUy>TE+HYMipYPkDMv4+f&I)vTKKBnON;Ab*-Gl- zQ5(vi_R zQvw(tf2)Zs_o}NKEY=d5_M#yUl|aSo#5PaLMGlFguIN8zV6^|Z5r_jHo{J}y)fE*< zj(#-zO8_2zAEX9q9n@i<`2bbDVKkO7WJ$H31IG}p3l#cq>U zB|6NS;t$A3grNJ<@evL&7rd17C6vf4F|=JI+*&mRl4JM;dmXBp86C1B4+x1`j@mRf zzo%hOtcJ8{J*XSF!N@#`j+^^jj^|q0rYIVXne4 zf*B@SGsIt$*)#gPpc@fR|mb=XhEJ-2ggUv>3ZpOu_y^Dn)?(MIPGPj9Ige>-mu5qB7 z1q!h_-DUEe;xXm{{MDT~_NJ2FI8jTkO@?Eq9sLalA2Zhc56kc%@rYPZH+%~*?s>o5 zE+hdSSbBNMc=Hduu8?l)ITHb`<> z6vv+=C>V+fZTG975)W?QvAd$_bO`BmkUQxC2+EWP%;k)xPJ-b4^g%O+uJc(v@K@As zh$YXdL$5xJkkQT$nM+xL#2%Yav04C@J*FcH)qfAhkoCfHyq-Sv;L%~v$>#fN7|k6F zuil-_9*VwX@gp9Fmpp2I%@VzeG9KiY-knWfU8hj-B5MBTXf_?#3DYp;ccuz>5X)vF z{>q=ih)UYvRpR98;o-OJAMMwOrrwSOL;Xd19V(w;Hc^}5YS=8o-ooAxW%KVahtS4iM|~RKLC1sZ z8Rjsm>ZF@#&gfjLLEl#z1szPdJ_ec(eG0o=NGv2PKW>`32TD#!Eo|U7`r6f#1h7<3 z^{il~`7@XnQKvC{<#u`d7S$53<>DaIc)UX3+n+VxVp^2F7x%YdBF{=Mz?6A^GHL8= zvjM+2%Uppy3+ydMLn7)xc*0@u*#Nr~N={^nofkG;y#8GCC`%FRA`BnJ=Lp02ZlE%>uXec!EU!YKR!av#~*?Zv~P7bB0BS;*@Bn72yW0I_~ikzVPv#vD4~mN%8|He zkD4((w#{%L&_Ybb=k(InZKmAr;%$F7rO}BjMKtX(NYTTu;slGzWqS<-ro@&bb%#7+ z0zs@q_qcw&r(?U`w-~aypsfSR6jma#cIYB=m^fz37eWIZvr8Utcv|6we39=DFyM#K zf^Co#?ofEd@oakhpw$TUy-`6I*^M}|8-2&-ch}%8{KvE>1r8bKbl8#SD4K>irsSG*=nG-V&Q;Sj$QP-b^ap2DIU_;cW{Qc zeH)S;C-Hy5BT>mw38!!6?AWOz&SJSFWv3L(0JyQYn2&`Wx;0kdZkmTf4Ajb0_@M&n zPT(kyqDCX|f{oh$qUZtx1_!%hxEXfL`Q&i4ItYDrdn~ zyR<5Lj92Y~T88vcZE5{1MDU@a4y%C|$BNOTduiD*iz4tl_f@MQw0=V3rwm?JVgAUH zqBQg)*!vNTzwNM8rtrul#23A5YMPL7akNIeK6t)CSwC5zG3ZU=1x5>6SPpO7oVC%@ z9b!nL=m;W)9H<^y~)!XC8NXq@hbkua?C0Gnl+FA>cAIGr-#P@%M zqgzbY?og)y9y83-9d)SX2TC9X1CY0}EzOz_ood^L=weiqfdw#H7BAmm_G3|v2?uPr z^inpZuCoB({Y7_~7mN3*G{2NbtgVLldvlTl{9f5Q=ZtKPOdlTXYoTSzbXZS*V(t`M zQA*Q>e@EK{!RgU9RI(W~?9k0#hoAY(d|pXE_&Tk<25A*MC#Q`+a^(Rgv3^~*;;fLn zUgXf?$e{&`qu?*oUNhEP98kkAM0*Dz07UP#Uzlr@4i%d-OeLv_PP}NL2p3CV`D}zE z@77r{1-1jy&efa_e{JT`>hB?7hJ9y_RX=~g^JSj*HPF>??3Av?tXw}@ zy`i|>rYsF)5OZj>`yq#6De?iY8maJjbR@Sm(%SFLB8|G%vZvBYyVL;jm(KhUCO|_z z9We|V0Kw((PsB>*m=pvn&gpPMO zdCHW*Vzd&^fh_2>OI4rs-}-iB#Baz(i#I)V;g9G{%5H~(Dd`7D2JILNdrm(4K@&JG#g4Uj!;2tc$s4^h zESe&-=&--UKKP%LMjo9dpD4dx2sQ=U718ST2$cW*A9Dvwhf7tld32qzQwM#f6r2!p z33L(9gj!PfjgSJB7ozgIa-WdbVqMj_rPndIg01bZ6q! zLz?~xh{>Bt{SPrNZa(5NWQWweraj}Z*T4>q9XbPkQ}R>f*y$(Us08@QtL8DLK?^FH z4q8wDS#5eR49~u@0afVsJU&dbzb$Eqb2uuykQPApdH z%9iKD6YUeM6DR(lz0gu^Ixj4k0phQ!`uO!V>}O#j;1XGFO^8P(g#L&k3oBT>LqyXM zuc&1|84WH*2c>NjF6iL-M^-ye!YxpSxFJB(4qhZd#rxJr%ttJlI0$9Nx^td1kS-FMhE&Fr;wVGOwNB!>I}s~P?h*3GKf z?kxN7(6lVI0xKsQy34GU1HA``H#k1-%(4H?>`}ojO{~(#C2t||or{mCXD`siaE@fr z#siBYx6^WBy9RxrPj3OIrF;iO0~;eg?m#1Z1J%xI#A9!Z*Le&p97x2bO zmK@d8j^{*c?Qmfke7r_;K^#jB$1@QsoCB}m{HuyM zK5UU8)p`0j9vXem($VaHw#bgfiXI+V3P^b+wn(c#A$IbL?863{BflC`zE?a@IFTIYLTIM%ZCRM zw^lb6`2Z97A&>pAB^o?93nf!P@7RP#M|e!E*v;n)EoFW?bQAcClN6s}rJXbZ*U?m! zWPwM@r6JW>0tv6198|9j4osniQdt4kLndq=wp7RErc87gYf{qCK7cmXS4}!zTiYwc z3o^=$5G8@wVBKpe2`Zl14!Sn2U|n9-282Mr|6x@(txUH1LGK-SZND_yeHk|c=h{UD z*|c`{*I7*gMzUzkgZKEmYKh$N9ggT=zw}>X0tc@m{nq!acDS@&($RjDrCZ#4jRA6I zt;M}?j&@b)2P<^&-S8WLN5wffj0-y1@du4#Wm4^0INIKXoL%uxw4!_b(TWjhUT3)F z{S{GMo&3}d_RVnnH+Mjr2PR79LYqo{Q~LgcZd?tV?P~w!f98Wk$HKYMZ>ib|BA%nW z_!Ge`>4*cD`u%&@m&VDyCGVl_tYy}?a9?)3$$kzOfA8CqqDmnI_?G3?w4nlbRev%V zxe>Sg&)Alzuf0l*?Sc{5MdeCdS89jlgu5AAji_ZKjrFH2^&PjEoB9Hx^&~~4&ykle zkIYmo>q4Mf8wrZ;?q|0&(IIz;OiLdkVdnC+_!E1msB?|VBq2QctlAo`ppUQbZ$FOf z*vYs{x!M7aUWi?5oOfnfD5rMp{6 zK~RB3kdhRH6_J*bE*C@^MMN4TB?JWl=??kN?t=H;@9+OSSI*rtbI#0}nfIM{-uS#F zdDS+*MZfgz*kwPGR;1vRMiHS@VGKmC<;Rr{>@@A2z{Qou_c06ZhuvM<4jr_gT(A;TaAGd#Wt8-NEoK&~QHk!V5|69mIO z>jB|Ms>qSi=QsZQHY)gv6NC`~#_0u^rjKA^iCu7jMdO?gAVbldjJ7I z)AS%y@^cqR;sQD7Uu^2yd3AAM zAwj`fTHJ$_p=;v*!$;wxAUX~?48WcUc0mBLDS&wB1DOJQ6CvKYW)-+9iYDg*Ua}H! z)=|LEUil(t9a-yKd;Ir{!RUSfj0tI_69mqLV0ZvW0iQ(lpDzB^I9ndTAy^LmMeAGu zVG@Mny*g)7XRidgm89U5nyC69B2*mo6q(-|p7I5pKKcSde4k|?q!N(nnGz73%wl2y z1>iNKco1+5mO^L;L4aXI@G~YJTssT``A=7L#?hlC1?+Fv&_JB=!H`y@fcW#i?2fcI0?xI)}$hIW~B;)2jaG3DTT;-3i*E4 ztH0_-akRRz#sKv;5WXf1oIjAgLQF+NV*b&xFYBNWU&y@4S+Ni@EG!X_c3KaLG6IPL zIN;g%vzyiiU|Vu`Knb-H$P!(6k5autDaz4_;g%6}(q?`fHTqwT__~fOthk^Y`>z{`a&aWUd z04r@28>kZhz4P2oLN956>;-I1NF-83aXJJ6Bsipt#y=K76}q$c0(n*IFVHE-36ce2 zfrI{Bf}iRECMGxrps$fh&fyc2fQh+eL2h7T3xOk8%4KvrNY(N!)bj`clnT@xKtjXC z2d8tCv;-6{sAEGd9Nikg?*gkEg^&8m0hb!#mJ50Pf4auAab6i|sYO;!E&zj+nh$x3 zNpPlO0D`bMdVnOh&T5056s%Mv^eQUUg60jFn!YDMbeSmv(}lc;0QU&MR*beF1N*9W z3HZ)mM(AB#0)Y3N^=+mY&~tGSk_oH;a{@{kCWid#FD7UyMFN@KtPHSwsZt0Z{ADPh zD-_R1HiZE9G=Oe79Ew9I0t9&!3kKe-@Q-Gwhk>`e6aqZF`~MMok*+rI1&gx|0bS)y zRJwTh#0((hhDy*NYHCnzfvF*egFNvdt>!G48c-^ai9U#d=TMCTABNvn=wpBckgR3k zNHke^Y7N+SQ3h|Y0fPYHd5|rzsTRp%Kmp1;XZZmPV&->jTsW)-0wY8M4Wi_j(I5?#02ZT|BqX()@*GC}u<#+8FvB1I61KltJG|cl~ zvH@ypIO;WVj_5|jK{Pu5J8<+nvrYn-21#^{S!Z-Z;*!XZLqXh{zlXg2tCwEBb6zeE zIwIH%p@X}^fRp?0#{YCga1kI>H#H(V(wq(KzGns`cw8X(7#g?pUVw1pX0Wv)$rKkG z_<+J!O#zYstH)V6z$_JP1;QXcSSRR1j41r^eGtzv^!Tg>IO&2K)bP|`z&Xl4{EHz2 zI$H$R%)U=RJWKxo;eu=L;90}LG!&xsu)ws0GAZ3nM~gfFMi2EWgy}~})&J_rP);b+ zV6Oy>SrT}EAZ~v8Ka3d|_)*oQ^3lYD1R|6~0{2oOqCZ1&G0$xdVsPesKqB~L=xSgO z1Y*=*2w*sUH((ZTw}GY^=&+dK&Aq@cXMG8q4FL++!J=5gMk|v4nX+3H=Wh}PJMQ_L zs1c<-kmqOg08TXM@e>nAd-bRlo^=xCI*39SPV0jtp>_}PBDA0_4ubEk2EcxKZe;|r z(m&crQMgeFnq482tjMD9hT&g^(Z_Qsc>O9i8~SHF2jn4==i$POh9QyX_94^}Jge+% zM1eTr@fDK&-&G45J99^=IO{ABX09WHTm<0nUVz2VF?PP@fSwt}S8)+jqYyLHngfE? zU)3-WGpOF9KME-`1dc-pv5+b``o^=HAX6*2DF~;3g-ZsH`F8$Vl+}lH6G$U86A(^R zDFT#I{%zpw1c{Vmu$s}M0l;g)RtQ2)kf6yF2-<1LI_k3^7tZX3;HywROXOhPB1sy+ ziUPss`~mpscfgll&jPAIvq<2!3%6_qQjg*1v-&`bqJxfU3EBn+36FW8Kiru^9@T&t z^naXcF1Mq{SPBGF%>01RV!;)bAm{8CkWYUZph%97XYU4zIlc3~(7~6p0E3TMgn-oY zC}xHHPLwi3=+jw!pdB#yTOZN1gw)Gf%t8Lg7H86THa0+!WbwD_>~IjVb9OufYI1sb z^LG$S1-=Ovez=V-iU6h3n8_Y^7YNP`gvj;-IO!>9A$Ajj2k+T}Fd>iyD2JYZdupJh8(q`$w@3n% z9V938|9_tcYUFTG;v8@VHWH$m;OP`zQ`BAqqe)igBSp zhX0oVL0I(6fMD!}+HF8BWa=3t(l-3G5zKEq6!`mr{T4NuP{b`L)abu20BREEX+SjT z-e=7K-RjB(C=l_%j1du>JKG=^)S(|rVId(ZE(D}IfXyukk8c4<0LuuWXAXI&JsqqX zD7+jOM+lKg2xY;<{}<__NP_jzSyzE4hAIl;8(wq~3LNoJZg59HW-KLzTKs?X=j;Jz z?SlXnHDrho1;T<13MQwU4QbQ4K6SbnlL{M}KB8ZS%#peq+bnxHQP&8d2j;Nqk=l)RuWD7nlhCb{G0Y8EGOVGi8 zm!aOa&y8X&0Cya?B-26%01aM(5`ZOxi3Lt^3PR_l5{OEA=qz$Ap$a%9&pH8+A9ayI zKYZ{E5MBf?VT2MPm>8j5=s^NdBWMu`BoBfINTWCiKr2TwDAY5t10Zb8nHe-ob|x90 z`@kT5P9|i|9sqg(;Himhgz*ZL716~CeS7`@(p?`dNdlg%^yTbpL11pv73dOb0ROSZ zAc0R%$})T46boocN*HJ_0pc5YF@l~GS_Slp=l%W5C6xzIBLK35h#^S0p`-tzPGL9$ zfI)|A44u6U%!f8QjtB{>jsURgVAm)YMmk)ArH|?)&{M!wzM{VhX{6Eor#@Up08Aok zr2&@=V73(lK+;XTP!0qr0IuYMUm<{r{>#CPd-VK05@2-x!nq?rWK{3}r#e?1dfiEZ z1E9)3)#1DoV1M8gfu{fG%Vs73g@)Cm$w3Mn>vhqO{KaWU0G#%DOhVuTdp5yfz8Go1 zaEt;kP$Zv{0&$6Y|ML(M+?@y!B>|lVs{Hd25kfx)^F<9Kh;BmqMWP_wxfGxukt@#X zI{J($0wDM3m7u`bakfUwalsl5)kJf6FrPqHLp}&!-3H%er2Ch-X_1a5_deJ{BUMQQ)RDe!N@e@hXdl;K=8Dj=w1s+h{HjzS>XbwxEJAZ1|aJ2ramtD=V) zm_db#`#lo=ojR2J#joQmjJs6s=Nmm7_}p;&o?8KW!s7yhaBCnzoq$oR?z>- z&&X3>EC?}!d)`Hp2Vh!&%b;FbZVQ-$lMS>9>7}3TcGf6x{MAC2e}m#(gdf?1HxU5GxSVdgcQ3GOwM38`qvZHNoPY0BA=UGpznd|5$%+SZgV9C%}J4d zshY@OCUTJ04s1F|heWieh$t}eqoi2m_ONh>qCF5Eh8z-5ogM+Q5zL+q;2FsPB@n!i z3Ve{t397eJA|QSlT^pIZfC_%<2{w2aKQKTR?obN^_$duYT>`ZRMFC4ek-8{|)L?pr z=C>e*4$v^FcQN;%!$7DztNky_5&V%9%FRPc1Z2Z9LC0l*WCW~mS6{&7QT~l4)8MQR z(gTpfdqr?0&;>|uq~vit1Gt6Ud7-=rFnc`}1_ASn~I_bc-M?e%qSpZS%5tXU{bq{5(h6eQa90WNJz{?HsiUcd?YjIAOy)bMANUsnV|AIfHVz+aFQ@6GXki#O3%;bfFjSv94!ze(W3_miJz#6&diV(j+Bm( zr<-&48W9P&5f#emLJFBNO8sA17#g{NPwzn_vDVx1;r;KR z^qBGJO9J}x9DPYfUtXXuSK#r{&}bM3GKQG|f`Q@p>dK4T34F{@<)*Tz2gCUItM`*L+h2$s(uUa{ImT|?apyYSb~>H(+{xkF+FGN(=dxxd z`xC2J2%FTFovC9oNcJb@D3k|$J_bj z#m8xuPYl+JpA+=%(SWsRJB zxBS&4c*gxP#a6c_=WYfaPNuUqdSVN9Xf@ONV)xYg5vNcq`~-jJq^`u1z(%;gAFrPn z%Sak3{tVxHP9`_$ZaQP>C@K&2X4?u;%ommClrwGK(2yaIb;jwSn()3?Z*&jullHf% z9{V1mgr;8(%mcsNnC0v={1+1nNZD^^X=W?ER0$Q2#P=i{4sN^6CrO{Q{lHSMNHK89 zje}cbyV0n%L78{R*K0$H>+$~7jQS;J0i-ZcYj3_~vi59T2{#fxG? z0pu-tIf@l^?|+hO)HJJ9^}h|Ee;Yb?$iBhDlboNur||Ir*4Z_yVwTkw zS*z5Me>3!E$jXo4d0Zjgte3@|*;JqI$oX*Vy?V!^7uWXIqgLsfW8L3>MBzL`;FcD+ zh5UE+^F)&3Mj|MgpT!q@P^+|u-Psk|M)?l>G~e_HW!pS@!<;m2NYr`UNIPlMP2~+M z+iLJG%eG|vR>Zrrglo?1Tb47>1sBsQBZ=*e_$k(@v@|BpAFsw7zG@vdG&o3D=om#@ zn6Elyv->ldI?UZoOEu4(LEgvKehiCPt2A`TT6w9Q*=ICf`mXwA;@89wop9{JwhB(F zBjQg-oK)Ir9qGHiiYB(SYdaUT;%y>fxH`PEGH+QUaK>66tk_1i$;tD@XS7}n#1FUN zAjopB8B|&{6<7@I+uP8U+)gls2ILD+#kEm+(94G^mx23;RV+{~rYW?~_p^x7bfFRH z;nY+6CYkoPmJFNkzrEKBkoG^7`z0WQOOA;#eHRc~%zrr_g+-2-P>@G%9kA$FE7 z(UY5Eq|(C*i+X22mOMXU{omV-_K)M$Oqu5U$zQ8aU@^ZT-hI%G^%(KZuzbf_>#q6e zA=kpJDQ}2B$msDZoc~SK?M5F%^Dmpf-5g)I`(7RNM+n=za_4#apmFD+8R0hH1K2Xw ztc=1u^Xm$`F*&D%#Ef{2*dfL*+c~~wf`f5Wo-La44{zWo$v>>ZRgxdEpw&~lIqy^s z{{NjBxGP<%u&6c?rLf4+KoYwxCpmC5qrMiRTvAZy?EM~3&DpgjtF=8?IW!VC9G{$k zm{N6%cAl<^B+WnN*L*#`-2~nkY&Y1y=8r<3bw*JsK~BZ)r)sy8W)=6Dz^%#kvS`Sqab0Z;W>;N6B9s zy-lU=o#>8|e`3)S5oz(q+bSELry$tR^ZqN7so8E}pyqbMm*7Z?Suwf(;~H42N*Qaf z=C<@3Oo^>=Rge9_(E0}(zp=tC$UGUsD^(WT!}}shvd9^gIOLw(^oOSHlS*$edw*A6 zZ0}IvkYktL5bBSNi@fQt>HXa{o7_^K_(OPKMoDjodcE-$Vg&bKj^H9$@^fN6vxIk9 zF|U0L?0vQraUbkW%nWZC{Ekk5B?a8BtmL_#SC?Wo8$}`;o4Z{YHhE($U1c@bSDR&T z(|21i5qdEMzreC`ckI3f5ErqE=kt4V7C2NG z5*%6fbV4}11jPJU_P!_XoRBNfFP|_fyj}LxWFEhfpe0vv172oV3NM98)lD_wZd25e z6L@h@&EMqxlx7rvwSvjlMsc-0crp^Vj{JrU@dxz(fhx(~B@tH&lNw$xKrb@+`aL_^ zQhPM}Lr3gdv)S4&KvN0wKOf%l;<(o!R(Fr1jz*T4wTJb6+!nOVli}erHZOM#j+EsU z+tRAIx*oZohZ74hnJGER(FO2#(`tHd9njc2&G{Z$uf6f8_c zjS_= z*3tS!80MY+te{V_rHsEUgIbmQO=dB1@JVo0)vIyhT~ik6ro;DP-=a}Yr`UMeEdcudT z%zC>w{iLSzw@!Zl*^{=faZfj@PNx5?_;vvL@hgn`fE#}QnvA%`2kI;$wQBdSr+oJ6 zwx(F>7faue&R!meynkcg?CTT_8(!JxdZ4K`9Ok{!CwEo9W2{cAuaP$6(y@2J!<6UE zR&tlqkaYxVU!rViL_K%0d`lLnUsVuy_GA9&=8ZtMCN&^S1_s zleFuRcZT~)Fg495Oj@vU5=WlSnqtcZxMsW@b8I1b>l5Ls|6HQBOE-iv)WFx_1#Wnr zul?P3+3!!xKXhg^mBBv{h0Xmo6yImZxN@f@tH_?bk5e2+qe#(saFm|yTiDM`<7rN| zu}-HuFmWr2tx^)9X*=f4kmzIfsExHP*EO6NP_3^E*#c73@*M}F0q@iJGpg$I zIs?QrzffO3yq?FJC&YX`^W%n5kksv9cD5@Y157CBKkhF3)t}VyGP+y# z|1k1j9DdDS2sK%FpP3VCY9V*0ry}9&y$4qe z6a*HnU@7n-hxoVj!%j+iDyDvUEfEEH8=7tXi@Wu~2A*sxgWwkDlRN1fhdex1SDR0k zsE(FwAa^RJu`=^4amwScnE2hx$M@6<^hU(Cd&^{;DZNA#^py2>41x;sFqUF4FmDyl ziw6~il~Y(tN%MD<9qc`)ET{MoM|^wvmg4OQ7?gLImrDVkGQ{Qwmmriz*`<>!Cd5+8 zKiB9E?WHFbdQqC#IUdQ~VdOYEMmH*oMFpV(Zn(U0RnFscE2N*A5vEcx89!m|+~J?a zWpp38mtW}>T%y(=dWHKb=H*y{Jwft>MAe&T1Z1hw_hzx~rMT^tb$zI*qsqUzzry6> z!v&K{7o>6BhF-jt&Q3!)l0??t`K*G!V*U57U(;pyL4ZkRw(iF;+WZy1Dpt?=#{(qE z0?*{UZY=!>YY8(scvFr0=*00MY-`}xo;QGEEIu{+WwO{sUwS`I7xy7e#@Y!%KOJZw+ypb2Q^=YU0`tJOZW(Fkg~24)tr&Tjcug5z#2ML z86N`=bcE0U488E3p>q7AprV($yVDEK16G8%Ch0e4Uf=Azmv0AVY*P>S&0=DGljnMe z>0|zc-gH@%QcQ~0{uS(4>)r>5cj_}aMk&{4kL#Q!kD$TOvUcZpb4=y44cC11j0S)>(EUvG~ z30rNHx@p(*=SgSj@3VwcK_Zv+zVpUb4(pqylXm(>syyW--VCObc~Oz7p_7~ANw+m` zz_!&p_M?A0ttgG~sY|x7TXTHs_ z;C94PKcR{q9S2T~f=aX;jvslsNsJ6mvAon^=p|PQ3}gNwXby%PZ=b2d!zY!d5zkaG zVd#Rfatv7(-0RBN7f@;Z)pYB%+sB7LDh-Ov6=~QH zW$8CB9tE>QyGsnNhOn!5m$^d_#L1`^%mmhHWl9GP#9!HPJq2HS43K_@k0r)G*2l ztkgP#i24GdwbYutwk03A)AY4P!Kvs4GRT`N#SRZ-nLWhxU0#h)Vo^sK6U`m6WV+VM zJ~WoA!l!atH8jk13$@{+g#^)-UR1b;L5h&97^lNXsyXiUbCfLtmREfG5fulhs8O3Y z-5A@V742Y}@U3z?j>Lw_-A9Lk8L;Gu5&!Nt3B1Iiv5YoFHEc%w`sNI#W%@1Hx!l#% zCd%oY*B0%UZ+}EgcsKPf^qLxk+Scg(bQG=Hha&JAa&DYIT(p78lklw_D2Q11C%J+HCxDd-pDeS*aFzRaCBbhIP4exl~cML`k6x|t;#qDNlsHJ<~ zF^6g9^MSS%tEnP2q}D`>Y2Edf;hPI8#6)zRO408xwNtuaJ^pp563$TAmOm5xX^1_# zv_RCgbtU0*KYLGUB#vwAV)kd^n=q_ZNCt0mhnR{O{gtoz!YX2EmwQUMoNyP*%Vlht z-^_ZK%U#dnz+gBE$&INTvg=5$;fY>#l3CDT*Km z;))cP-9&Q63G<)htFE|`I#d;`6Zo6Oytch>#pYd%oedF`GgsYG{muTo(x$%Smq!~u zkheVNZ~5oV2F_FZV8fuf-^(Y${4RR4O5vT)Pb34o^W(gagS;Sn<4PCsCYVA8JN!>Q zAH6RdF(Z5aBOG5s`)lhZucUa+b(n>I8?WW8YG?1@kKVY6y%TO4e(y_c-v>)7hcY&B zOKHFV$>{R`Bg7ClsIQnknnjc&G{^1fxx(+vFt^~Xi_J$VO~~>`Hoaq~?{mN7ibJRi zwkIqb@uxj@NCS_{*mpf`ZyCqF({3lLwfkbh4QGf#dSm{UvVMJUDVdkIIX`TA@Z`?J zf>Y}2m(}U${78(oLyQ&$KP4~AH7(=_L!#)hjNf%|{rJm- zHNwH4>2Wri1h)JVo(OmhlP(`+9Ee(SLZ0f1h2#6umA`PyeU0@lTxa%YYev^*mvKp3 zb0~4h=uuaDDuY|r7v@=`!Nbu$n4dTH%`Otxm_HeX%38r>+IKKt$h1CTieVvqimin; zw@`(#WYaYj*faQxwMg1IL*Smb`z0EjalHY;um;7P5>>5Kjyy=HchJ5E`5f$|?Ww*% zNw}kQk-@iat6Qd8#pA(_2BtTPYnxhLS0~i6N7j@*reQA%Yj?O?=Xxhpb^u1}7XQ9V zn6#6qSA8nKGon4RitRF4+y+@?Pi|H1#%-UvA5QU3jK=y#e{hrT$LQNH8T6^g89m#! zd8jfM@Sd@$@|}9Xrr1rFomCarIyG4H(oQF1xlFEnd@Nq1jL*yVUz{68J;vtO!_6aE zyDwr{|7KkOxft~Re#cgWkT4S;tOW8_y;#n2RoB39V5R0$MXtTX?6`-XUhFJ7fyfN^*157j+8%C{j*8IJq$ zioIikw6I#&-9=ErfMj~e!>o9j0Q8Ah<7KiJC#3lWmw>(Ro|J^ftOLVis_v zLJI{`dkJ7cEC!c|7ICHUdhf!NWTZlkSyj56`m@V9N!PPA69Qt=EdC z5}2#5(=$$4SyjhA$8*0?CG=YKz~p6}_Jhr8QJu7bMUD(33)5hsnD6YGF?3q{-s#r{ zL<+>*#7ia!x;ATH!jgF|-|Qt#W2E}EN9{7{_Rc?JVXsS`DqKIDRw9)jHuCFuPL2_# zm4}%iB zVhvG9z5cSbT*9e(_%#>#AS^^lTEX8a^kA@LT|ps60P1;VJyYAf=n7o**WJ|2;lt~L z-tV>gY=m-+C1=?`@kbs!jBi-AYaf~W`Uk1(tN#K5~OtLYL#cc)Pd7$FA6p6D1I{(6JR7JnG_V$q7P-r#5MKA6NHe#J8S~!6 zMVnnu%UMC0x_h(>?&PLC^kLY)e;nWY?I*e>$G7H8n1UAwMzkY(Tp7%KU*&x&6>>!`h0<1f98#$+svJd zpH~IPqEaiP!0+J4=A2q@&2^tfXw!GRG@Dp)S+Bg-v}~_T_lS8u_g74EN8p7A_WSuC z#&wyA)Ht-~>hE9^-Mx22QfiX+!VXK8S^e^JyNGl$MfF#4b~x$eikTzH@xSfM!|yVX z(HP8QcQV^@M6OH0` z6;{?MdMqV@d%wDAjgo3D&6Adljc$nhDeSQ0ES^46*k&pYJ8dwa`aZMfBl|H)tbE=h z<7ZXTNIr~4ksx9q-|t0s$Q$*}d(B^(TlCnJq8d*czU8~8DVt~%i@}7Sg}VHDn?Ow! zX|#i3`eivviY>t$Dm%ACIidD^(n z-!YN6>oTnJ>E4=t6f zROli|J(_gt;z$EO*gu02=3}xLaz9&!0%GA7k|eCkN~-#;e|gMpU{N)()_P#&w)Eo^ znX4n$dQwCW3(4A85vNxqImpPFXKt5(zw=>J8Ieqt4OEelq=K~uku5D#Csww1o?W;d z6ZWPr%0*n~g9*mnOlWC=>YScv8_rC}AFC{=Wy~#i_coAb1!kK3&=jGlhPHMVGBt<9!;GU z^>OkzxM7A`jlvVVsJ)R7L~cd%@|Q}pMLt|;dVXq;Tg z*t&RgU!r`3vx_)Jj+tW1$(8MPfM{~&@H3SajSejLfG37|kf9gh?IiDI2J?gO7S56t zrt&ofkFUoLmHf8pV~ikeABI}E%^7&mE#UJ3j_0#6LCat=%_svv>u+m9a z(jp>$T*X{pvMYcQNA1XYa)&V3sem4YZ1yYfr%au*_6Do>TaHz_98YcbT_DW5S<6o&?+WR(hzcC))d zAF-?>H0YXP=@Inq1MO>_l31Rtdn%WniuNBKs+hlga;u$Yn%tUmU}!B;lERs8H1Rg) zc&*S%Bol?}IQKkd?EBhT$!gEUQ9T9H@oL$--1`%+cFTsJhkc7PZOy|9&`!m)5StxV zUYG3gZ_mnbu4IjExUog|Y+jc-U-Wx(V$Y{+Py6Q<6tIeL^Mt{X?U@ITcKc$o*ItWk zo{U@0s9za_X~kXCoCvO^C8xGww((3Ioe@%Uo*12xVsWM$yLAiau(ssx`o629bLwo7 zRO3%;_oLc?OT2#L`K~Qion3)*<646&)~{?suB}D;FGXDqFsG*18hFQ}oDs9K`gt^f zwX{8Osl)jRQxA%FBHuQzF9ZgYJJsoCS1sZp34e9O(<2vaTmh<<%W z`D;twWjVb5A4^lJRvucdFWzNb6f}D*$i8Zg1D%T1fT`EIdp4OZ*nUmgw~t*7DKbEC zykNRVIuhp}UjYxx(K(_YKT&S#IS zf0jQ#ay5gcwKGvg-Ewlah2V6=vd2P-AJu1nBZTk_I>pycY$tyxtYTZ0@V9qf;l40; zQ|qb0e#Bkp?X@7D4s~nqoy=QP@hrb~9#iv^^5F$JANNQ% z<)!%UMECwjnZ6YB4(9D{<1&6`be&J@?@cC-9Y|};>T1`M0MtLynEI+oC>x@uGC=FK7llkn0TI&n?+%t4h`6~ zS*-+DFPBVvZ9cfEc8$~5=t_C#CELP}S>J>7gAZ`8!NTd3mswZ1zFRI1%vz8(I(U3z ztj#Sb_DQ(O?=5Z>9(p8xYVotT*SS;r*yR5HBB@W8>MhX>@`7o6K82UVG!zmb)b!p} znU9*)-a_iG21b9+ACF+$7hm#z256qk|<=NBs`g5EsJA^R|duQ!R4#Kg~k zsfe6l`p-lhTR`bbyN}Ma(MLQDJK;i;uGP$^#T@$Zcch}9FX}w8n7;uIj9t%n%2oHS z+<82wXE?z5kjW{)Mq|*a?)A0&0p9n1rtxna3)|<`V3P>EeuO6J!;#E~@C!Ppv@c8Y z+BtX16}JV+pU8=OCtuBW{$z&RdpK0ZM3zeH+?x-`hywEK)Uupm;WwMH35WVlyq66W7p83b>z(MuQnBM+%cjYdIP&W@{qZfn(k_K6IR>!uX*mBJ0#`l9fEO-ko=3Ym|d)gG`@#C zs~ii)4PVr-;wKk>%aUbAtClT4gXZ3D$N;yldKNn#dyKTXuen{`5aNebF*Fec@Z~CX z6g}WdvU*{Y^fFtAQkCBza*TU;|FMX{lgKFi>5s5MnW;*K-KBydYH15Cv%)9o6ES*+ z#$_s@Q|;VBp1HJs*Clic)GUg(p$m2TooWr~&Y4-i9GlXe3w~d-B>$swt^2mzM&<0apG;*CL1Us`&_qTut0Gnq5o3?=djUT zE@y9z=Qga911-?Rf^F&;Q(~732#uYZIf2DYb5%>FKf$r%4%pJ)N9_`W zmF!OxC`iAs6JceuWCr=~FB43-4t%Rj)L@rdGn0{ez`TokC|=qhLq$ zZJvaO#0n(0;pU6K`^#2lm^7W-pf8oxoc*R8OuL#Cn`v=pR!gN!`>zUKGRvXYx=>Xf zy+Ehy_N7ARUfyRPmYmSQcrQm&j@K+7b#Htw6xG*p>r zJ0WvP^snS!9F+^=VNx5t=~-h2ObTQ$^IxI`!JQul`&9_LCWOo2Uscn>_KM8Y!lt`h zZPcs_N>qCZ*0&vvW2rCO?h{{Wx6r!q`#sNZBlaU`sy$2lX6==#Z}SNr`9>dx`Q(2N zS9{(4_O%pZ5U^KY|~$_>BAD# zy3K!yEq{qHk=eA=KFUd@3$7Cf$$HisvqQR|SigxtQ<-1`VM~f4!F7GiK zt92ztn5;UT%`7T-*PZ97wl=lQ=}!huGlfq?BjZYyQ?y?A1Rf9)OWpq@!uQI%&2cdF z?ejaH^}>SkV$I{a$=_ezOwoFCy?o#mtP}UsS9O|$e$oi+el9j_$9IyK?1Tqc+*|&* zs?T&BbcD4J?WgwPnQu5cgo!$gz6}5Nv!o1Cllr^7up+0xdySSqLS1BwVCb{$wRoY7 zqP;JUTXwQZGGtC-dZQ_kMO%l$6&Wc9O(m*pxAaEJO+W82&y((%M8N~gMvjPXMBwWN zoGnQnv|Ab<>H?3)qr-&{zk^^5PNyL)0EwiEANqj$D-h>{dm(h_CKWxxI1;TQHuL^|XS9fN+cCztv| z(Um}YX^Xhx=x;Y31m2!{>tVcSzUxye-4sLMBvOq%f1i$dPq1>fymP((r{Vqz$)nMO z$&dUb`gK9VzfYcf4g0#2UyT`8pckI6{YKQ+zE)lvz8APpuFzh4%n=&+dgc{{PJ)vG zK9`=FegD;`fS{ej;s@SaFAkIRd9Ju&nr8Lg%vvz55+@q?M?CDx=fYfMN%5i?G)RC0oeT??$4H($Jw0RIuEE|WG*a? z`EQD^OD0n)#$4h3L_2^(A6gl7joVhixCOF zdk#i8DyU^J3`qRPNo+F%EJ$sy)#YPEDd2xs4`L1tra#ncRt&8XNqEr=jz~!KW zxtY)ZdF*x9d0nmQ)%|>%HTaJ|Iao(GvQK)cisyT|eq4F5b?2S-uN^tVTKRX@CW#$m zlO~C|8;h4F_WI%Q$(rIo?{aTpCi0W`^f+gm?*>DWj)WJCw>u-N!$U47gea<8FSVys z2TJYqeW+5aP8Vu-N*{aYgEQ!2QnGDF-VEz+|LvOrV{~Lxy&JJ#u+ugrM zj|^)??dPoKS=iDNJ}aX#Nl(7_WB!wrevaWXZCqHz#3y?#;?kQD{qlD&SVmSE|4vuD z4NIw2pD$hH6iO6zfSQI6nJ4W;!|^5@cSN;=6+zX*N*?v6E%dmzNbDT+T0F&wO6e@N8=O zaL02sY}s^5ODpIrv9i%ix#*&Ci^Kb@4mV+CJ9pDG-zrxXZrd4-G2C5>S}1kQz4!TP zVf)r5&HJoxX8fs-C6+GP97qRV`8u26Hh157**2hY*@=^OX>+Jpc5>8zSzOkl;h?B- zfe5c+sIJa;DzDC5@@I-;MDYBGoKn%&JpS|c5JEur1qCIJXK7`$kxe2+# z0Bc`>mTD{$nKaN65{r0GQ)8E~6)LtD<8w@v3h)I~-M?SxJ^A{!Bo@KKz&7#v?*}P8 zYUYz1h)IhB3C8xc(J7Wh<}ZCl7_!+C6~yU3c&jK*#)}5bId5Y>6sK|EOgAWGc##v) z>a6~Od0O>qb;U}K=i;=l>>JKMw~BrBhfHJM zI*Wzhl(aA=QdUdgt!r3dR?g%bI}nrEGYO%+v6QCxhzoyw(?tBUO0GoVhjPnOdsjDF zHPetETJkNrQ?WLDrc1jK%+l3$6svjCxDV{BryDD>_Q;(y@lK_Znk0Xbs3ypXkYQ!R zj_bUP-!#x^L|??xBfwdjQ;!dU;%ge*ieMVZD_lM3cIfZHfhj2^jkA zcj>IIH8Zth;XKo%q+od+WLcK@sBF*V)Rt-|HykcNLP7$=#<+>ah2rd(ASMFkEUk;a z+(uut(U&4PK|NFvcIl~qnsBzjN{CQg@!+r)%cGds;xgo0<4Q$kQM8uUb2A!oRgxZb~1G zrTgOrde{;wkEi9^vL8MFSU2W&bg76x{WD{5mo08-sE{44VH@lc%L~mTh7yN2IZVwu zv!<_2f-*nTY`@e0D8%Jk9(qNh6oY&V0frFx{7n;dq^6-l5ZwuTHhXt|XPBp%rxVvQN-==7~`Zj#` zY;a1D92=Gc{(x=oOAB$ZAmPW2tzC*KEsgpbx;bObFCX#9h}dfP;z)BW)`!;mrZe>P zosCyVW`$x9Z=f`3Z5S>0Fm_Xz?GvraJCDR~%qx7(v>pwNmc6|ge>EZR3fn?Y#M9~n zm7`j2jw{Jj&KJXYNbl=;0D?&Im{<1YD{>CtDjI`;QMZF|(hz_13(_vE=0W^>F*g((ZhLMm#NCbaVR8Cdo-dzOKz3f>Vp7 zfVup9-x7lQ$vwh!1G)!+CbGxYp{aH8f2if8TE|+a8yF~~)|Y!v-$!R0te2hs>3KN0 z{?LqUAxEyXj3h2q>Bq?2=XMz4=!IK8nK}3V{Lg@U0f$qOZ}LA)|8Y2(g~N>2I7;Q* z^2W?K_139XTP$Dn|1P(U*IB2QllR;{^lv(a!&aB6kL4cDUHlI7?3j7;YX@4hFCAg6 z`SERT3e>YEr>Ka9o&2q1o;z$qd)abgZJ}37p6v9fHP~p1>Nr2{GnXz*-IA`A@d{4y zzyv8xVd!f1G(%9IaJCbn;T68mZM_N`)ACd03cLi89qPSQuVXFOFuje1%Naat8y|Yz z({TAVCv;uj^cEk1umYQ$De1$fS;Y5?hG!BigX%xGT#UOM+$;2qo^QRgoXIXM>Lu0u zbFY9BOzK|*vmyqe+I}jYsjvoVw@SyHqzfbus)P6luI~=-hD`I4VmP{B9slT72*b6s zdu+Q$`tY?-;l^0-UE^C9tWNG?j!aQwwF@a=rbqOZ3gZ?!JcuN?F?2G9#Y2_tW*zXB z=_XCg#W)c?yPS(5i4jet4$j@a)BYxo1jR-X?fYvs)cBs0kEEMuGo?ylKiLHAmLGR- zXxS~?f7|>t^wNv>mt&fYZxw#L)QXX;hAHT5@GVd}Pm`}yXEek1@q2qkUN2ir4q8Qd z1S`2U5mwOUgDy%Fh61CbWKZz?MEzia24i7~L&j>(eoN zs(CBXWa1*xcb|VBg`+*S4 zjUCK>5nT@TUsbqN^mcKwywvrAQY*qZ74c#s+Bo0)$T8&&@75@|rRNBlq)3q`d1VCc}W zpC%VOeGwb+3RVAvL9w%l^PcmH`p>r#SlNo2W!1u8XYW3yjQOyjL=7u=zvM=mhdb?1 zK-qVZ!_Q4Z*lroal;OA>&p?za!D9}h0L1SlpR#;k!H+(8w~Yd!n>(MR(kM+Uvn!et z-GVQA=+UJIE)YEwl<*=mTvJ`DD&e&lpoveQjJ|TcZe6)>z)@@B2SxLsbcCXmS-fXe%;+QlCRsB@SRv@XuYq# z@_o#w(%wY+11qs(TVHsn<&rt2sS5ArcBu|F{Q>5lOcnN9(%bQZZZJP?9cylKZZY9P zT%tP|6do1u4>X<5>j(64d@2JBNt7%X{ z{-nP$F}tWn=n-W@p87z25!TB-wHQw};`K6z_*t2S&ff%UGl~$2!mNX?da62(L#7qHo?yN?n1hkrZfO)v+ z-PMWC*nJT?t1YXlG#-!WTP4<|NS{2nxhh0LbZ;+qfI0%G_G^UkYNWhV2ZzUz>d|d)07H`MCNUT9WT2Cki)bW%3{B2<%83Jb-4{%?MZMUZe54VMcTrh73Pb((8 zV@nQ{2^9uhK0k01^arOZbnAwaO~2Q9f}@LM284z>vsnOfaOARg{^l_Q36M=gh6B*y zyR=0u5~Dw%Ak%7=V>2^-{wCY72Jwk8n1q2Nf%SEo(P9lMLUv>}*$5+Y5`i^sOE$bA z>?5}G8QgIb+`)=FoFF1vf!~R9cab~+X*^uf=zxI1APCxlB2p)w*a#TENF3H-1*bu@ zitX!ZbaikiGI2C`Ok42ihAENTP+c3b=;0ymQ*ZlV=a;p=d#1sZp%j$dI=p^_M579> zBDx^e?b3Yvr`O?4F?c~}CHVR=_m_?IdN7C2F3wbinFZu*A@_A5RUD+K0Dj2aUc8S`f6 zesl}2T1vC{lA)dFAo-}9doVdzmU)mIg82Y=Nd8gWFdt^B`*h)VwHgzSki1{q{b>4E z{>}qGa+w=K*~7sa`qm=$#V4MK*fD@=kV=5Fgq|Hf$x-EexjAdZUJ>CCXf^gHiDoaj zv`+Xx4Hn4hQQE4eYqhoTtPcFStA#$6B=`b0DbHl^dI)>kQv10 zD5u?;%zSWQ!#M<{;&XHzig0Hzu^XBO@*^8%*p_? zhiQUqzdgf6Eb+@m(zZrXgL)bIVmwi%eFy{VVnI$#-QyjZC|mqjsDIvk^Aibn8DUhe z<1Od?hB>U*xCMum+{`M|S|Pqtv4jW^N++Dj9I;LMSKm>{p8>FCSY0t@Ch-$g*atryBeMQmq0p ztolzyo-TU|x?zc`YpF2KYcy_K&<{97!9+)hvcEj4uo+FKO=;u}O{8lwBq72C z2u+K`CFsT@pqoZ*k`bMj$@Ig28$LC5D#qy)f$jSt$#7hN>s>k@oj}p2RRea?Qxf2c zzgL#jNKnXlPf?^4K8)KJIu0UGr;UVl@2oJvD#`P%zcQ)tE0ByEbw2#x}}q-2E2 zm`s_SJICWq%Tt4g!BCHFfD37~zevX0Vw@+2SadastCph>5$}##CaGMSTb}%?LaSx_ z#n08>3wvihQNJ%i4u%rEmIL76v-zQSp|%>pP!!@&balBn7smI3J%xrtq2-VgeSRZm z;n6c8wZ&>%QmEKwakGKjMP&{l?fZoOuM2RguP1eFWVqPNHfCf5T?(zCMwMNPN6hV7Qc=R4qmXM(VaNrK^PZE(y7Z%kA z(*7nYHSdFGrAj!VpCAk1nxpH@k8>Msx5HY^Pcb=&jpB}!_a^7Hw`Fxa&t2f7j>nE( zed~#p$u||B>e@z6t+zPN4~Y@c5!Rr&8--PAE=JeeKXH=Y`+zjW_64)a{O(07>Fixy zJrPXnfSO|pZIP$|FlGVDBiPIGQ7HB3&Jos;UIaJ z@T~JJ%#A)orb8W1%lqxE>qcdr)Es=PnDJsm!hrJYH6Yan5DvivcOF~vGo^4j6z51Q z9HN;<2;FJmZf*+_>v+i5sevUE<57z(v|#_7-^MXOitPe?Wll&dq7`l}YNW|y^& zZ>W zmDaW>YKW9hD^YL4&!zh$H*eHjz`oU-JXK}<8aF7JL&`qP*f@tAm%v9Ik?ayo;S3mA z>%%8Rw?aVHF*$uNy_}R9SukE4krieNo_dt&8v#!On7!O*o7ab2$WN&G#v?H9xaL4a*|*gr_F4@hX|r{8l8p zZzurX!gyrjG627~O=+HJvgka(=DD{XeZzFiZIu?1~T)@yPk$p$; zo7c7zN#8@$tAGu_j}MB7bSR~}LQj#*QHlZlAj~Tz|JK$^%G98Y+D_HE9$`F&wXriI zK35wafp%S1Md^E8QKW3$V?GC&%r*WqXGp4?CwZs%v7YnT5MxiwIoIbW-^W8hT`+lx zFAUC+;Xi4+t>{er1{mry`cyBr79x`(a$=g{d=kG(CO@?G)iAfu+N9bx>Mj=7i2e)! zX2n_QqsQWH77me_(BC?|d^p791r(pVQ{&!Tat!hXx!cdhaC3Dw4@>Byh;#DVWDo zs8d@aB;BKLggshBes^j!VZ<4+cLz)tO*{$7>u(SA+C>jSJG_upFX5*_U2M!AjXe;* zVq$g&CjeQdLCjttp&8f%ZD6nt`9abt@qVW3KQ+;nYXxR-g68P-(7JaygsBVYm;UvB zbIUv@(~H%QCNG$w9);PORc!$W+t8=blU?x!dZTuH0~kED(bZtMNRpU{SjfZg=x)^? zJk3AxwG%_^{TlY{?UGxhI(K}`*SlJf^K=82?MFc$MmUTZoZJIjotOJ^GqfI6#WV!w z?+A;-(E)<|fy6>5tZ8A5qXmGQRyfb%^@Tr zCiTt%M*d$Kt{9ZJIE8KqR?6SK@UZmLQHANY_Ce6UP$nJ!PrpV))l<$f7@6sflpw!Dt7V?u*{ddVmVY+UAYKt3*scPg(r|f&=rDHI3 z48=k1PTnmu-ms*9tzletW^ghJ1bqiw#uoK^evYrtLu-GiRF(d^J7&BfbW#%2kLvVt z3FZm=_(ZY0>$#e3?*A321?wFD#_{xQ4wl4mZtJw*)!NVu*d*>|jw$eJ>z@KU6ymo} z@oLrg7GVa5KqYP2{v&OnHex@!?ntVayv>+?1{$$!Fn1H(1uY^T6n6+w?H~O(o(6ay z4@V08miP27qofzmhb@_;-eoK8u7>@K-1fJdBB2K@vWeK)v2>g)ZV_wR8Ig%gwb*kg zS>f6cPM;m{R&IUK9h@MiVFvgy{yMh_%=uHf z%K0ns>B4gjdcFxYI%*UepW>V77GpQarbaOZKN>Sr6hOxnj)E+;wBRV>nLw6Dm zlN4bJ!H&avK;{J_cCfb{n>;DSWW_?n1fX?Bu!*TXGg+tFdAtq<>sBw<-^I}I8u!*H zT3na>*fv^>f%I=Cmn0x&$=JcsH5KEJ_U!m;i;TnRX5kIjUDwiB?7kk$*jT^Z+MV_~ z4}pjj9C=bUSRuC-*b+ai8>LYQW zaK>&{_D+ooRg9KQ9&<8j=wWWSld~^$DI!?pFLi0RQ0 z1jG#m8>6S71q8zMI%1;$Y_+-4mnK+p7V z`w+xTK*^ewy@&@+6c)VHWORf!j^K@OYko@JHR4MfG!98Pg-(>NJb$=XD#{83QIFw| z^by}mZYfIOAy>WME{?7bnjJ^QVW3F=egqL-@T345BPe3RvqtE4KV;_-KpeHpbYJ|h zIS%nhRQFtlavpl#OEyEP#;s##?N8hwpf;&o@w_<-VyLoi&sqd@5+wxf+U2q;yil|~ zCF?Kkr?$nLvj7876Y=8-ykjZs>Q!(vcAV7~bu;CDBK{FOG5qLHOF+4ONa@eNfEEqzTQ1cY zIH-ZLNm;Sw?H@v`95n>8{r(G@G|+;khVDUwE-hvYNuOfMK7|X=UTrh=l?oHELty<| zpw%tL931gL3t-=M4GQiBVbdrx$Xpns&BIDgCXAN}ZMz6j=py7BBs-mfY(ZM9(T{eb z(A2<6;cFjlL>Y2Crpn}a+z%=n=+xGP5d2G{$2nU>moO2Iv-Jz#6;2Hv{XbJtt#!M= z5TtOy>r%-dQB9CZ+@@>l5EWRKE&?SX|IH|b9`erufQI|gyvIcnuA{Ep_7itaj^L=Y z01j=gvv)j-J04n(Uc}Su2Ml5@$n}_V=7*n{5}eXts?Aua%?`i5={`Cwf(JT6Jvx0) z@Q)|&`)`m+U`**f60rVDL*bweD3XEtKj!~b?9sfHNT8}xo*UUi8*NE97pi*0RurG% zjzQ=JkdT+2(=cmIu)B@0oK>nfELBa9Ob_s?tDE3mo8dBv9YCymn}HiTwrCM}kwapH zosF*;5iOOds7j`64iV@E4`ak~-|jzF2{aWXP8otOe~5{%v)8^eJ6frH9pb74y@JdY zy4Ykm)WnxMRNKVwsi2rg;~1xr`OR?sc2Vq6G!X++vjcm>lL*>3Y0idKx2fBGzfk5Y889a{g6c4?hc#MU=@;InEt$}oVs0*D%umRC4T3XsOAHeGZD}j&g0@g z!lCEb2}>>vaKu!axl&e3bKgRFb01UZ&5cD6=8jJAnByCh0e{nn!s@ z2Bt-7^wjf8%9U&9U|0_DT+9hDBY02MuCJ5ER?{M;+OOo5Nhd}iJON-ublv_=rUf`vQft}nz83ddX{md-DG@q(hN8R`xI z+nh@H9^dnf_5j98BA^BMd@9EL@X%It;_w#Cpfeyou@gTgEJpD}j(S{#*4xn0H)b7~ zBD0C0)f)flDfF$SQ`;0==n5u{GP4n@_G>{utTdw2Q<`p^xu*s~)gqhS&QbKIbQ z&*@Bc=FUZ6nbjcXukW1bJ_JR6aKd3Lhw>{jvC=I}f3G=X`0+kw`4kmVL6KLJnBKIt z5>i0k+22!OHI60nrY*6~z?De8_`d?x#xf>U0Y;aqonpU&DUaqj#3u6j z?pycl6^E^T-~g?Jn)&kQ?K6(r6KiRu_jB45&x=vpj~!HV7ms7sVobms2s2@RKthvD z`X~#R+k=3t{@X&HoZ}J5ME|D&L^^88dBY|4py2X^Nlb0VAoK|B-ACIw_q>srwVtp+ z+Mjsy0tdLah?7*4HA6Sm)u%0`%_XZgM?s{tQ;Z*G0e%tMCx=WrB-(u(#ki_2p-7Nw z)l~}epeH|pk^U8dBqU*Km~6HucQ-#h4eQR5y7iHyvb<6<;j6wdqo(u2PbEPZNobqF zBF@owMw34=ryDAh?9gnG%8P_7XM^COJ^Pkr@f?%EJuk?Z<7dD56*^U(xkpfNFnVL0F996D4%ex%{sx>lpG z@AwDvL(WzA4la>&-S^(Nu8gs}JgHHc}3Gn`GQ6T7(p<6nk@W50uk@c%Y z<0t+?scV)H#vgC`a4OE?G=E+#XdXZ*u z^Nbnm&p!pcizw{Bonw&r(ZuV)9*0fju$sO#BL+$`pOy}-{T1$$wl?-z(0E!(WxZyz z1?&Z;T0>jNIBbg@1Vdhc8H5jZ$>^EL@N~uH5T24>vKfK|OFD%kj-UEY!wn|Sky25$ zv?x@X@Mh|UWr`&5e0y-{HG;hf6KBaL!5^50E%7JrDUa3YG?h8TTzy%$5wGEw{Rw{) zBqgup?+8sNmVX*3K{3VRMD^sOU_(&o0F%EX;TvYevp(OSUWYfq3_!yY-Q%G#=5}l=!2&6?Ma;*+QMvDa=Ke_SS5U!VY%wqel z1PJJ=Qf=KW@!%tAS$il4T05VqNrre-dPC`K>emFFRGC0xh)|x{E3#l=h9gUt0z$A~ zYN9478Ev=~ZfhLATk*Hte*7D|4SMPNLhUPX%lf}_AHx5g`#=DlX9gWFOjC-(Vh0`3 z%ra^8t}0X4k}rFvVTq>j)18lNpvwD>pQ4v(dv}|2E{ePy&Vc^OIS9fF=*ye5wQEfRTzc`SWZo3sG+X8YLZ z@p$hh%H8{{m!BZX`!cbdiO4c>aDK+zl>K)y7^hWo8e|%VK&g|F-K0@VyUA;_NWQ*c zu9--})bvn$cl;1%t{j%nBxZ~)*ZwsJ8sCT)s#ZZQUpfHikURcd3Vp(s*bk9e*u=H<H^VP;fCtLx_)J9q`ooB{{nVM^*u>hH~(QvWTk{fmvIxZKf{?ES(Xmh@dt))rr(1R?J49DzH2i2qYRALPIG?Q1Up*l8Mj(f^@C z^gnc%{U18MIWLK!h0pGQ(6OYZB5GCcE~YSyjYf4aY?)61(WusR@WT1*wiZS^D6&os zc)hHvYBHEXB!@2qzn@=co~%72w5%uZmdvdJ->$Yo+%`Zhto1@;2LH7R^%K%>SDjNQ zqsuMmZ4u_&>9sT9t9h<{JEZ(fF7f0{PEBW7o)pX@?1D489Sq+cYQ;*zXr$BN29P&7 zs||bcnLX{^dQWJB*i^mTg40fUl#NXVNQ1Fzfxua0VD=tv7s^$coh5wRCt%iS_D$p8 z>~<)}%WBQ#uqi@-JRy5?LW?D|7R#GhcY9F}LYmS6w%NDO>~R>+2;+X=(R^lcV}iiy zfn`@hn|8_N$E8p(`_&K-}3 zW8CRT^X`%*4D5rvd_lIv7>>ID{HreCp&ru=PJ<3_kUU^dc!@EP$f)D{n0Q`J2!Ft+yGaO@RHEqrbl78o$^yJS?%! z8(&Cjz;85!8ql4TtUjV+vYyU+CA)vbrJI|}d)DCq_%qfzXsUWYa9HZb0AE!U#5VdP z1B;a)k(EixAvRz7{(%oxmB8eN!FCKqFl>g><68T!RXX&NGqit)`(fwU{eP(I`44sG zaR2Efr7XSHtxgolzM+9`Y92ej48bo~p~6^sa#8do2a6g?n~bk}(cZh+gZD2fet>t4 zZN#;0S)T3BZGf$#|DwRkV&{o{_Uu;0`pLcH8wV+|_apiP8!5HQW6A>uDf8|L<;hsU z(uaP0wrHH%V=G=uKbkD_miZC|lMM5M`BE_`X&tE?X~b)3PcOllbz3TCmStv-C`otX zx5G~_Nw?*<(*q@&Zc7ikiTcJhYE#zScI(-xvrYYSHkLxpojN@k#`-R&Ofvti6|%H;G%it`avqyGp!R1 zn}DU8RzbU$HR2Zk>x7NKXPN8P9yR}|ol4<0UOU#SgpF_hCC`(fY;LQX|JY7tkG-(n ztzOcG{iWKarLf)gO38|uuw5P~0p)sXWoupAWovyqW$RqKSI@X-UfvKWBm^rGFnjGb z_zvt<2(1J1Y!8#`1s!uAp2`6U7QKEOb9v1N1;7AElitB)xu|Y8kNJ zHpz|OWfke}1+b6hUgEme{f+3>m&cdchYB!zIqYs=;A7&Ar(Q?oPGnzF9?D0AL zR$T>X%C<8Yr7&m!`*EI&TB$0@LpX zFwJd)%<`yTM|^wk2K>z7O*mH`L;URENs*Y+>sSjaPBT_7KSIVLP+5ps0`8(*vP7{E z_9w^Qf1+9zAr^3Ii`)T!o|Y7Q?Awc&0DS;-q;HZhr1`j$QLpgRfM2?vf%sEz#V`o> zsD5gdfWl^kdjRU2YBXSOQQ{wDgna3Z=v_5{=^2sKu1odHG@|x1WRhY@G}TW4&}}ly zy7KK`*i$PI_A-)g8*r8J`fYSDiU7n{O_Vo9_esw2uLNIreTSr9z@1s#R8|wf2fR9EFNVgK{GUq$^WG7r81~-( zirpsw0xa(S_Y)6#iU)ig5{7-jk&gL?Cq24{Cmr$+j=gpcj(xaBBwhMPB)vZ&kUq|0 zi`;xM25cqi0D&9c5lN@}2&5)p^>_EockrYp&;@saJFpQ+tPv`}@b(u*#iK>+Mm zB5lCki_c(G0FerCui`!&^+~+>j@_i~?p~<*?*0QbQMV-#s83eScLUC#haf`cfJ#6y zh@uOk+`7Y)KKQcihnXB>h%5m*!s~%nApWfzh-~&B`2<4*2;xhMOi>4no170uy)hdB zRlmv+Ngr%G?}`8-^0>XztAPEmT@dMF-(z3$QyEtTr~%^b%%X_^10(N>@@LRQmU!_& zb!jDmd#w0_-oOM2plZ^0IMNR;5XA(45cX>tOQak1I|AuNo+79XP;TiZ3q+p45&@c& z-w6lFp^GftW(`L9acKbeZuLN{ng4pcsRwG7@*7aq*%I_cvY7TI|A7RolC&wj{&qo*3iGS5pd5sad#tZQ(F!2! zKaLSm-M%s)?5?-^N7$22ETADs?jChTh5mEEk8wm4D3X3a`l}D1@ey|02BN)jg36PY zf?CMK`ZE2O4Pq96D&A6pcrQ8OfPh?|53+sIFY|n7;Mgv3=w+q8;2r$)p7q4X_{02! z5D2oF&xS;U*n$5Qs>A&_Tur*~W@Im@2naBa69MW2Ggb6~?O*ypA7cYx`;O5<(*0BV zDd6|l*vl);1_!{fK4@_2Jp%Q$5G03vAdy=sNx$3gUR5|p9Ug3Zz19ShSnM0bg9>z| z|Jglxj6gl@GkoPS-ATcTeewQ1GbHnvWXK z8*zId1bXRHKrro7fN21IK!M8f;AucF2prWIA~QPhq%S7$q@ooWcf>pOr;q{EkFvcB zPa{FB!laq?py@5q8iehu?D-zh4w(8BjE|^)r3}6^8twdI%V!Q40F4$9q+tM>`=#{9 zE}xz|R{%+t=#s1_SD&d!QaNu185LodJ;v=2Y^sPSoRs>R|rxieQ9s77gcuCtXDcBSM@9Z#&m%`JIkWL_n&XD zA6I=}S?gcmf5WSP{htZa_dj2TjN9LE&~0;P@!P!lq@y~8 zLYfG1D^H*My$+nG(B}<_mOI_%kR-{2PJo?g&I!$uXrG0dMZrRv?j=BlREXqJUE`5x zamL)5R&*dO3X-w9?Y(g^BUnWhoSA|B?g~IbaPI#t4F6rn5_S=#?l-1Oe!m`hkihOU zPS~UO9tHZ2O&b9qiS*bb_cd}8%#h>Q(^+Sd9lsy3`0DxF{qc=|Sdn+)il%6QA;HoP zk0zn8nSC!xlzJ}J_agrpjsqf%8epxKPMp6noLK1RSk4d5T%}Akd2k(Mp>e= z`Bm0=6?W74SELOotSXix@zSl)i^1JK9mh~4Sb9_D<2Bi?m}3}|&UP3uL-Pc}4MoJp zEa@oDzw9N*U8yx;-i{w;au#1(2{>cXs#vf1| zfDl^QhzyTBKW?SSjclak5ybRV22!XUT4HFLnv6Rolct~$jgN?g_Lc>~WxELQ*eiH) z)}Y3{6nss|80>?ivS1;J65HBP2^v&Ex>%d8ECB>*eBo}&iOX*BkxDcw&mI7yjm8fe zZfC`R!@0tAEMI1aE0&{mVei9%;i&~e2%N;i==7?&l|D-xqT)a7m15IqeYA^ITwFtY z^c(};CpkOORR2LGJ57h$#=Q*(_T^`A;$cnHPv#f3_Z~3m26C3x^GQ;ZMCa5-G5`=_ z{3?$yw-z@2Xw?>_!>fH{%T)mBDu$sountRbTGOFzMr}Qo6Tz`81q9ZXf)`t6;(0yd z7OC8#YJwguJ7Ub4M#5@CV4gED-j(7lX+e^&&9R?wcD}_Zn#kJZOMll4R4TZ~x<&nU z6dqn!!GMT=-+b@?PclgVgE=oKP?Jw?p8lrOAs=0Sa2)NPmu6^%ya{lxkY58EvzVHg^o5a-X7hu4z@+l8N8^1L$RCU7l>hJB<|JQJ~11=(L(0 z%T>J$3tt%jZX)zom|>voNz-o_hJ(2Mf zJ>Hg-pKFxEa!RXbb=y0G8VeM@yi%xn8(KZZ&G>KMSksqQ-Xoq8v!o7P!RWEA3*Yas zA6Nm=vw%>a+ODF=t;pn5f3}21>1m8IFV*;O2{IehinjytBOC+c7%=-$q>**qPq#Dk z7CjOJKP`je@Qu{haAPmrU0k=Y>=i=?wf$*YO%F=j+g=)7(SJKdln(n?bM_7|pwaG{ zByyz}qaV1uY3(>^9eNpTI;t=nEUXk+zWnSwOdr1X`5~yc^K~| ze$|qv0-s#H@Gcf*xvvQ64Wmvl5{{zEg@1E13~W)GO= zY3Coq2xe;haCh=dzw0D*)`Jg#61xPj@|#REU-vu%=YC7Ot)fs%PKn zFV)aFrVt~cUoLDAGKl9ScZ2yy&1xNA)D+)HcT{Ti-srDs;^-rhFJ>2ttvx)4#ek>1 zOY#)V<5S>Yl~MsN{>vqhT{|NG%7vWJu_Gh!tK!s^nVeRX|E2wB{EKFV7q9EDr z08$T7FAPJ?I6T;4^Y|=g9Ts_ZZwDAu*~ilBY@Kk+t$I zbH!QPf@kJy!DB64?p&;Y+qahJTz4ZviS6XoN}*14^dj=*O6&i7=E{eIHB?ajv6g2- zoC*)sWKG6sDoRdiS|V9@f?udoETG8;W@>mXst2FYq8ft*%k4?D0U^VdYzQEWGs|Ao zL$FWa5o!FXNY58pLMqCk>gn+t#Y?43{Y+EL8iKdJ?hi|1w87<&1Oh^*K)DXimGPga_GffwkpAt@EazEUOjs9kW;?u}^Vw7i={I}r3w&dC*?T

}KyU{5H_=z*PY+4|XYj%{yr-N(-v=;u9Z_DP6C=0@ z0vg0TBq*8k@?Uw|V36acn$83=kd@ZLT>7iT|A@NI3FYyl%}}*w=kgq(o|vKP^huqr z(Q>+Aozv;SzA3w$ET7C3?9GnC*iVzp!P&XiKC@}Ab`fqq$EAf9$*2Jo`6W>@$^#j5 zNKg@Z%r=hU2W>cVof+%`xspw{{Kp#jEF2WP1**fLN636ub5g z>;x@aRU%z~;=jf#=+FRmW*|5f`BKMQmreK$IjL|aU=pqJ(9Q?%a#*rMC*-x|!2EhI zu6b+3FC6_#Dvcb1`1jE92R2j}yV+gCcPq;uIBqB-u-_TtJQT9=G|#;9-T4dl^ftAl zO9zL{y5OwXeWx|z$u7p)?1kGC@6&Dl6UW#R%Vr*^vdY!m-);c)-NU8(KBM2I2djH8 z>28$i1FRc4%;u`<(@$#A;AfKQgeN&QxWql7EiOcIl_C+o{&s!VM73BKkYQ2%nnkMl zLA%zmO4edMRj;FbF_cXmMa_Ov@fDSShMM=NIau9L#Jk z&#w&;w^-3Z4mm~ujCn4YsUi7B717MdD$2uV zn9-5&oU*jREi$6~Xs7~!7p#*hi_!%2^m^(Wx7d^wE)INpVGRas!E7qA^MkEINyju& zn3h$V*#u{Q%S2n8%Ijc@T*Iug+mzAm8$MMhHMCm?{8+mlHk<{O;Rh{s+vTcWm+T&J z4xvwJI?7W4`ynu89MrgiJ7B-x4-5<3j$xzuwJ6YP(sZ2+dZw66cLu`4G*vQ}BPVL0 zVerItA_yt%)MfIH{nCt#dbv9$pwHq{-C~z$*_|l1iBzF?luk35)U@}piLll`y~End zb0+NR5BcC#WlHq((}q1L-*l?%nT4>0XLL(H%H!t%vJjle&zw9{FSPKD&Gr#eaNWoi zic;z4oj4-1T0Gvy$9P*T_6qTSj5=olky>NQ4y(CXcacP6`%1 zOE=s9c0(!*g~zxVvOr?!Y1m)MU#;UOHV(^0w2z*zAj|1`yEqiZ=%Cb5NjC|jHjMI{ zvOr^q14k-|^&(WaFWUt9!7i4rZ>FsqU!a~Mpd4u-N z^L<@(xxKX^TQ z*n@rHQHrQPxFX@xugFCbS8$A^>CX2W0lf%)#(+<(qY#QM0~v6 zZ@`Ek9dTM^h~NCy0#~O(`aO$;5QM{MmOY)!V4X*NYRr5Yp=6?FDDpe|>`|1op2@UM zZgz2R9eMv97y^lB9|)p#5Yj2(>uTi}IkKJrtzDO6 zJqV|=;Wm>>MJ=9HLUx?(ENbjIV@F+ohK7#y*f?TskRzk1WwzDU42i$LLaWfvR>cUd ziY)4U&P)IB)cLu3&1{{}y4s})Zy=btr65%q)Nyc&YMDIE+DFhbrmyHu9$jRA7U>U5 z9o26h4MNj&178Jt=^tg5U|E(|1{BUE^&*|>$F>2lxkO8b}*7C+?fwZp8PfC$gsTpZ~B<+I0$4A&lf6Tb?{0P$BKoPdP zg)=LJWtwE(f|~BU$0Tu5cFEp)B9Ao%Y>(Z$=dY2 zRBDn(k5!4Z5{2wv4?Esk<0)>;{ZuMcGm4Mq9C?NjTIi1s^yA`~noK*VJ9&%wqumGH z^x#!!=20d{%EnAF6jS;1OZrb(j`wNpXdl*((fBhtKLD0LE6e88lk#Zav%R>V!(q3{ zC$l(Ohj|rhT(~^*6o?-EkO!6yezMUF&6Xl`^HFG+nMtlqrb(~*Qr2A8B>XjE_=s;3 zjie@`Egdl7{5^|gN>4OUhLwtPgU3Rh1{HCSHKOnDAcr#y-%bTbiU@J^TZtyNM%{XL zwZ^e-^A#|1eIn@ldq?rkBLtTz`W9UEs|o>=c#l^TtRI7>H5-jmO3QnOU45K9^4~^p zP>=Do|EM?FQtOmr=|x2t=QWxw;QS9eDu33r29J*{8s5YMol$*!ip>}897vhk-{9a z{4qdCT$Z|3qF+^*EMzEHOe3wt6rrRztl*$1EWS-f%Ycpbpnxt$b2-AZ>dJ0`_UE0Z z*Kg(Iv^w7Vr zH?KX-zjio1$vN5%DUZFHf7`8yHp;5R>7YxO&OgL(IbeuUk=>;F?A2qGbXotfU1N2pu|q2+?ve7-pAjVlWVPWtp5 zTzH(2cYqKwA1w=KF5wDm<07wJ!qNdCrHd+^#n7N@;e~YohN>t;yBe%}2#1Y*b&sv3 z=n;ImRst)|#S-s3Ayb?_VB#^@yLo3=vipe2#|n2c50|WQOGW6p169#tF?FqOwCs4Am$Wj&8P>-cb@A6~zIdo)_3y zxjXY1pdIoZ`#2kre6kn!T$+}>&`(;PCY-L$9&va*B37$hi%_flo}Z9f zWE0F4tO(&!apiZE-=F2{b^l=gY2MQzAV;HTB$B77;%LZX>OfA2b5RyjF9+jw2Xqdhp3-Fca(Qnq#A} zaPtu<>L`lCs{kwRwLE4sq}l~k=okd%r0VdW`{Cpya;7@CX> zfsdJ2h~#X_^sOFmh3^HGoxl}Jlt`g$;NC2^OJtHMY`()g~XZ73qO&x^j>%L#(z~gC4nU+uu%8as#QyxbX8 zs@8KcT%)*Fg~=yDK6QSSf;`#3-hVrSBgSL8huIDRzC!9 z+}S@^eA^4)`hw1{)`ttDbf`n}d6PG`5F1Dhr=_f%n?-v7S*402zf}$exRM0nYSiH& zYnsJ8&hihb;<6jn<8_w_D_oC}v)FEED7W}-RLD$JI%c}!#sv+Tg$cG@W*meo#!BKU z;F;WF>!v17q>MX=_Ni(H{F&tFq>SuTi8=&?yMjs6IRI0QpHRZzdqPAZ$6U3l=BtXW zEXlN6l2(=h$zMW*Yk%2|zXASQc-&YF~64(S*p~NdK%X_|8F;g{aPc z<3y5@g`S|j&cbuDAprIF_VGu&tG)-=%dh363m!>iKwDw)OeGH{hPMDQXQ84a0yP~i z6q5}+?eDXcR!D_nvEF$sO$14@#*lI%VsO=BHnGY;R6N zPoJ511CVg&QTG2@y2_|HnkCBO?(XjH4#C|WLU4ixcUUC2yE}p4?(PuWT>>l+++7~| z@_yX8eR_8Kbk)rE^i)?@&2%^y;`~g25jp@^t{OX+AW1mJHYzM@w^c&GVd)Bq{xGHM z|2o|z{}a89Zcd0vhEH3rA~2v(h*e&aQ#c757qTYo&shnn3<`3Au`k{|2O@&XbeSVq zOYwABx}TnPOf*Kov2DMnI6jU=ijfqnu0DI6&CYxs3jRw5&}z?*s5wFyeKKGuvz%)| z@!$gQl-aeih-cgeQ-fWI4hKGUHuHKY+yD2+HdtkxQzB*|zu=%1hh|}N!V65tl}ao| zXLc2M*sT`0F|0~xwv!|Vb4o{>KR`)d1vh6o6yJ;LYs;yeSAyeH4W<82!F&JCxsg8L zk(IjE28*7oPC%XtfeA+r7*3_egriPXh5T3r8%wqRC<7+`Erw0~qnUp+_aCJWV8HRG zzMaDo0rdZ05&2R6^6xjs&GlGt1n|J`3Zgy;$$k{4-!3pnTovAca*ye63XbT5)ep*z zJjvig3lAm@7ma-ht`$}xmlzuQnq4+w_whfk91gKW`rjyNV=$$_Nx{ndHpFxMohuOL zvyPTqMhzcksZW8Xuof zx(c>?cVAg;I}BfCDC}uhUK%J5(nRyf+~D#>QJ@A`IA4d-5!HdjapLa_oDNQ%nw9aH%>D?sfgb16kS5N9@t!WZd6d?+_hd-*IGXb>O3a7jIqQg+j1A1V$p z?wdLU_XoXRL5&>$v&39JwGHgY-p*V)1zNq)*BfffxJz>Tte7$uT4d;Xdvfz8XPGoUA_)lpidRFxwdg#sa5OF zOxKZ^N)D;r6Wc1SjnniuMwoaQc+|ks#gyjqUyyhWkixcmHi3yig&NG+JhM<>YZ#L?2zVu1hEd_OphZw2AtZ(M|d8)_>&=k*^ju`w?E;x-m z4f4<3+EuYl#yIa74^_*|N~E!MeO?SDXklx476&y&2etFy#Mva$_Ed7HK586lc_`*;d_j`xy zGe?44>KS5ZKV2DlAS!(EhXb6$B&(;Gxwi8lve%#3yw244#@do;;u^2%&osYjxIl;Y zr8$r-9_OtCDZS32sS1953A*p#M2*7Eqlg~nM~ko)`!xwQ+@<2vnd6hzyo?WRLGl)# zy#STgr1{GO>rO#zu0AJ$uM-ZVqhO~ic@fpE4?bo=hEf8uK>dmsIUFlK8^8)LQi>*`@eXKY(2$~pK0P~LmsCGXRAk^2=4tp@Fr}_{ z4z1ZQGf@*jdo9&s4NU)ffR7P^nGKZvpX~&wYxX*XBTDT8*nz2%>y4VuAWg;5Pu`@& zvkTk4d|9VNK+Ba)Euy7)WplZP+PUGh+2$|6_J@dObAL)ZDnz5O=1A|S%hN!TsqR;l z$ZvnRw=M(5ZU~frId}Ujd!e|w0y7ytUpOE>>_`;rQEU> zon<`+)bLwPPWCG~r%~Xmt?tFb=3q^S7?;nxCthc6E< zV~Zu9(TJV^VW^`b_&y(hI!yt+(ziq)a9e>Y63l@pyD*Z2g166S?jU-JxT;8on8hd% zZ78{20c^}Gr?83{c#$@k^f@Sk7o}8n(Wf~00r2CSjE^RqEtoGl+I=ywe|Q80+UujJ zILGDNj#1w-<0IqBS?PWISlQTxB)XVSf_4GbgJ{`EGHJEH$F}JvFe$@1mh@v&j0w#F zT2XBan=RO{46FH`re{e&hmenyO73Cm5grhu!27|7owiU-$Goi)@>KEI<(W2?;>3xA zi%mkz4s%4f-KlrqM-cJE%T{WM&SYG;ARc?K8TaJzTfPCyd3vP*SOy^KhR&TR-^lg$3~)T{!Y=f6LP;js}7m&23I|Dj&cK1~G@mWF!@e{9hn}wgMm{ zZ|^Z-Z3zm^H#LSLNX^m5CAvs-emHQI`~-cO%2RAVMAR4zKx%VjrTJHewu9}l1?$^a zY8*BeKd`&61T8hA$=-0?So72y&yqeuqv(3|y0r47 z$3!Wt;@>5>ke-k)s|HWZCZcht(b|C`5|#&RX^%ZWu+aERR4s+IM>ZlClM}ftK_+~* z_ften!v}ROyc%J#T&dCug0*|NxUpbW^WZnj`+iGc4yJbjZUWqtf4yhM;CFR%PZRmQn)< z!AXwle$;!LR42_s{yZMOM@esJx7wh9%nGxMn z_Q!_(W{cI24t#f%!%Zc-&mjT!xRR`Jk??MW@n_2gk$4X95o;6Ow*b@Dm*GkTzN-#@ z9w|H)Jdxz?c2FWKE<}3cQqarNWXu=7LnG6r)kfAPw9w(abi4Ip5n@8dqi6Gp)bhbw_Ae~=1gaR;v&D`wip~PTPJf479dVL~|2G$wKhiO2E|r~TO-I{9)(cCoZ? z3yVm^Z7I-5e&fS_mjVCr%)2muI5~6Ilu^^uVuwmnS!(nWOTw%{v&}nJ1nwEg7Zb^p zg2Y7ngZ(ymtCp_|tZn=uWk7^(ec=?;ztzc=xXmuW#-*SvI;ev^0<<|Sg`F%|DqQ3jJeN%6u4EP$89g94 zZIqC(v83pABamUo+u)LVDTbsne$wdg7LQKI-|VYANKP37F%UX{nrrM|?4X&+cu;N( zi}A@&5;2{v!4pVG#Yiz+TE4|yvb0(WXGb+t_G<>`lrMFdJA;o|M&l^8Wfk3HF2r|z zI@m+7pivwEifP*ysxXSPqO|QX`fxxd-5B$QAJFM5(g!;7kr7z>_mc%`=sdhL>+fZF zg7x%&1AS@J-QXorwh^7%JOX>ctC{kshmV{!Qms>8h*viF?&HCUks~d?KmYYWqLTNF zJ8jKf@p%2p3&UhX@Qm+_EB5Q_<%8Jsdso zyYR*N%{y=lvYVgSOWg{vTGx>~J^HS5@_dpiFx5Tru>Q2ec5C`Pv(-6V$Zu$WNwx(c zz{tYFCa0ei8w+vb^DxGw5--wKwtHH2PyAbn@bH8ZE8dsFhfa56`#plBACqF=Cz4mH zDo9O?PeqTwC^@nA^I_Vj&)XsXlFe+u!8o+#K??{Dk}HsNlum~l_VN&?@b^RXLlN5X z4g#b!jn8=$#wpIymXcUylK0yF`TKQOGQ$o6!5ovaqyOYSbYn$P&(n@AlU~R4b#l z7S&+0E?W%T^Z;8G6%S`=3F9r*Okq)!#`t&)Vqf+6`n2s{tb+}})@p`1xH3To&Y^cH z7nUkt)4*Ar>n8|1i?ZONARA&jaYD2PKIQfmduO(~@#>Y|aXk}KuV?+%V|h`GJ2`QS zVcSHzMGoHvvH{B>mw(#n5z~GHPc!UBL0~M75sAn`JEu9KSA%dPh6&J^AEMH(Y)%>z_PSZ*_yN3Z-o?4J`ApkFg+drAT=q^#u?891-}wmS_>| zOUj>-0ho+IT65YC6d%z{+n0Wg<}VO~=U*?x48hnk-Urb6_#<~S8B;`z;@wb7#T}k5 z5MOzS^nOEeTzDM!oP)pxrtjUD>GPT(g$JiuO&As3)4bGtYanamNM%!8IoA1pZbcpt zS3BSFb-rRKqWR~`3J4)|mV%Qqdv{<^!lPS{l_yPs%ym%_tFVo4PvyQyTH+9KP$G~r zcS8jvY`6hWMfWGqJ4cTq>&8%gOsM|QTG?x0#QPL(F^chZ=6WIn$SWl!W+Ro6fUs*Y zgUY!Tc?P9)sZn)5O)+^%wG}nbCisjFJI^K0+UO3Vc)!Zz$o>_li|a(+-F09g9$DAX zq4TmkW%latALVv2#om9R8+eoo_p<@IbVRqb1V1r%<*9Wx3|-ri{|vz0IHT&H%_Rv{ z0?J;gY{9Q12*q&%YJKWE7>z|}R#&)k`y`@;s+$%$tfX?{Kn<)PYQ|Y=pq-xeyLG($ z{aP%Ma_ql#u(lSv9GD`KqvsG;i|VrTK-26khjwmPiL=SGGO~j;acrIBiy-U*&&C5V zE0X1h&v;Uh{HZd2LG_6~(W|24+ZuxK9=2a#J0AZ+raIjM(5cc)!jjV#K-Y4_1A9uQ zH(|Wn`0^cbJb$>GXryOPEN!2+bx48h{$iZYK`_@~v?_#lP1cm>D$IAtJ~sQA2K$#W z$NhuAV?LAiFw$z=Hqnwev^UL-L|d^nAn7Jp+0>V(r%I^LbX1`RZFF#$T6r#MOKAPG zV)@Z%FV$ky=N8+$f`QV>=hyL96E>^OEH-mgk)lrl9En`c9E_4C@N$or)8 z6rNOq24Hq~30RifL%-VTfK$i5ib~s1Jg5?*djdZ zyLGJOI|%~z`VcE<=w5`QFiW8LJ5zDv{FSfphlE|ovudrSn9$ggk`yw7#DGe~m!&Bl zd`4~qar-`*DssCAO{;ERd*oS8$l zZe6YKz#hx8=UO+~4lcN`#oNBav=)^*{|OD4*Tv*@t($~e?&em{`|rnwGGA}tlhVBt z7^K2{qyi6)C>gqcc-!}M2z6#Iv`-+S5LPw9j{rhTP{=UYG|g%-N7f9= z#?#F%>x_1HYb<S1_Gy~lG=fAlM-N5SkOoKY z8=mq(ir`1N5-?Jo2-zK3W6GN8!e62)+uC6`{b>BwhSLy>inU<&)8~bW8xE#U zXQk7I&`n6q0|)aFq#WJ?uXd}-TeB&pRtJ=V9QMmbc%K+(8OrUplCH4HLE3zDadS-` z6m4y1ZB+W@_!E^G_S@D#ecNE?LOZ8rAQaZD68UW$h0x0xFke!iTdBXbh${t*`~B** zG*vsJfNcTRQc$DUk>g|*Tn4gtne#&Nwyk&SPb!W5cJ{?pT=nD)lvu7niVpTLc7c#I z_~eyuP&$W*AdyHn;oxl*-zK!#fDjpDQkot{94)AARLG8gdTKNy+$iXh%4SFbsLSE@ zZ4nV*J6h`Oyc?}Zq%%RuLR+k+v45s)%4Ff!MWF3xJwpi~2vQ?LU^^&h*na-=?YJFg z2TuehTcit$_?@-ZVf#E$@ko@!M7vC|`0Z*%O9@LYxh-P0HD&A=nS&*hII~v8jMSxt zZ{ih*5X!|XJAX*rv1$d))}WjVOz2z?4Q6+d(?~P&P=spShDb~6L=dJ$S-?N_f(V+@Vn5I1(+rpP=)`q|#oIXksIS3M_ce-R1|RDPjE~nQoO` z0{KVEi=C_PB&W(-2d%^$p!ptdwbHpZl|9E)GlQ9K>kO5kKxwHYBF$ib<#$t?2Xdmz zn@qGC(s3zN(W8)7h!M9VN>L}Ls7-U6jpv)<*&F;M5v0zA za2I{3#i?kNT)(>nU>5XbqUaQ<^!Q@m{5QZRen^E3s73kuj_p8wYtD16$c`v0ZP}C% zbi;(xL$eA2%T-ky(j5}q#11QsE>Wsu-mf$C8dsW&5Q9j4v4IVU!Z&NxvOiktQO_Py z`uTP=)J}%Qk5;orh;Yu(l~KK_JE*-uH>%3DQ0uwqQbo88bV1NTT9R=xzGJELUzHk$ znJU1Zu##E#-;g@n9SbF#J(_g`cNO=r7)V3v9pgKXnR}(wN|Q18HQmyLN`TyxJL-V5 z?RPR88E-WODpH-J(o#({mikq)HN%Ihp`x~ezCbg&3ey50zP|=$U^kSg|#EuCB(

Uly+jcpuK23f3DoZg>ked& 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 sM=-U^6GYbKph!i4BcMf`!@;Kie Date: Wed, 16 Oct 2024 15:57:41 -0500 Subject: [PATCH 133/216] Fix metrics endpoint to strip \ from group names --- core/models/contacts_test.go | 2 +- core/models/groups_test.go | 4 ++-- web/org/metrics.go | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/models/contacts_test.go b/core/models/contacts_test.go index 4c824d629..833e8cbb2 100644 --- a/core/models/contacts_test.go +++ b/core/models/contacts_test.go @@ -635,7 +635,7 @@ func TestUpdateContactURNs(t *testing.T) { assertContactURNs(testdata.Alexandria.ID, []string{"tel:+16055742222"}) assertContactURNs(testdata.Bob.ID, []string(nil)) assertModifiedOnUpdated(testdata.Bob.ID, t1) - assertGroups(testdata.Bob.ID, []string{"Active", "No URN"}) + assertGroups(testdata.Bob.ID, []string{"\\Active", "No URN"}) // steal the URN back from Alexandria whilst simulataneously adding new URN to Cathy and not-changing anything for George affected, err = models.UpdateContactURNs(ctx, rt.DB, oa, []*models.ContactURNsChanged{ diff --git a/core/models/groups_test.go b/core/models/groups_test.go index dad77daf4..ab7df5e1e 100644 --- a/core/models/groups_test.go +++ b/core/models/groups_test.go @@ -28,8 +28,8 @@ func TestLoadGroups(t *testing.T) { query string expectedCount int }{ - {testdata.ActiveGroup, "Active", "", 124}, - {testdata.BlockedGroup, "Blocked", "", 0}, + {testdata.ActiveGroup, "\\Active", "", 124}, + {testdata.BlockedGroup, "\\Blocked", "", 0}, {testdata.DoctorsGroup, "Doctors", "", 121}, {testdata.OpenTicketsGroup, "Open Tickets", "tickets > 0", 0}, } diff --git a/web/org/metrics.go b/web/org/metrics.go index f473a6d6e..6ea56799d 100644 --- a/web/org/metrics.go +++ b/web/org/metrics.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "strings" "github.com/golang/protobuf/proto" "github.com/nyaruka/goflow/assets" @@ -64,7 +65,7 @@ func calculateGroupCounts(ctx context.Context, rt *runtime.Runtime, org *models. Label: []*dto.LabelPair{ { Name: proto.String("group_name"), - Value: proto.String(row.Name), + Value: proto.String(strings.TrimPrefix(row.Name, "\\")), }, { Name: proto.String("group_uuid"), From c70f8f10c0b96130da3668595f87762063cc28fa Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 16 Oct 2024 16:37:06 -0500 Subject: [PATCH 134/216] Update CHANGELOG.md for v9.3.45 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac638bc8e..b789c0b84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.45 (2024-10-16) +------------------------- + * Fix metrics endpoint to strip \ from group names + * Read user team from orgmembership instead of usersettings + v9.3.44 (2024-10-07) ------------------------- * Replace status groups with status condition when searching in Elastic From 3317549c04a365ae1b8140d64d02dfe61043bdf3 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 16 Oct 2024 17:01:15 -0500 Subject: [PATCH 135/216] Update deps --- go.mod | 105 +++++++++++++++------------ go.sum | 222 +++++++++++++++++++++++++++++++++------------------------ 2 files changed, 190 insertions(+), 137 deletions(-) diff --git a/go.mod b/go.mod index 8f59e4494..fc6208af1 100644 --- a/go.mod +++ b/go.mod @@ -6,13 +6,13 @@ require ( firebase.google.com/go/v4 v4.14.1 github.com/Masterminds/semver v1.5.0 github.com/appleboy/go-fcm v1.2.1 - github.com/aws/aws-sdk-go-v2 v1.31.0 - github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.8 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.35.3 - github.com/aws/aws-sdk-go-v2/service/s3 v1.63.3 + github.com/aws/aws-sdk-go-v2 v1.32.2 + github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.12 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2 + github.com/aws/aws-sdk-go-v2/service/s3 v1.66.0 github.com/buger/jsonparser v1.1.1 github.com/elastic/go-elasticsearch/v8 v8.15.0 - github.com/getsentry/sentry-go v0.29.0 + github.com/getsentry/sentry-go v0.29.1 github.com/go-chi/chi/v5 v5.1.0 github.com/go-playground/validator/v10 v10.22.1 github.com/golang-jwt/jwt v3.2.2+incompatible @@ -26,53 +26,63 @@ require ( github.com/nyaruka/goflow v0.222.5 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 - github.com/nyaruka/rp-indexer/v9 v9.2.0 + github.com/nyaruka/rp-indexer/v9 v9.2.1 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_model v0.6.1 github.com/prometheus/common v0.60.0 - github.com/samber/slog-multi v1.2.2 + github.com/samber/slog-multi v1.2.3 github.com/samber/slog-sentry v1.2.2 github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.9.0 - google.golang.org/api v0.199.0 + google.golang.org/api v0.201.0 ) require ( - cloud.google.com/go v0.115.1 // indirect - cloud.google.com/go/auth v0.9.7 // indirect + cel.dev/expr v0.16.1 // indirect + cloud.google.com/go v0.116.0 // indirect + cloud.google.com/go/auth v0.9.8 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect cloud.google.com/go/compute/metadata v0.5.2 // indirect cloud.google.com/go/firestore v1.17.0 // indirect cloud.google.com/go/iam v1.2.1 // indirect cloud.google.com/go/longrunning v0.6.1 // indirect - cloud.google.com/go/storage v1.43.0 // indirect + cloud.google.com/go/monitoring v1.21.1 // indirect + cloud.google.com/go/storage v1.44.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.1 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect github.com/MicahParks/keyfunc v1.9.0 // indirect github.com/Shopify/gomail v0.0.0-20220729171026-0784ece65e69 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5 // indirect - github.com/aws/aws-sdk-go-v2/config v1.27.39 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.37 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 // indirect + github.com/aws/aws-sdk-go-v2/config v1.28.0 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.41 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18 // indirect - github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.23.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.19 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.23.3 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.3 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.31.3 // indirect - github.com/aws/smithy-go v1.21.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 // indirect + github.com/aws/smithy-go v1.22.0 // indirect github.com/blevesearch/segment v0.9.1 // indirect + github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/elastic/elastic-transport-go/v8 v8.6.0 // indirect + github.com/envoyproxy/go-control-plane v0.13.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/gabriel-vasile/mimetype v1.4.5 // indirect + github.com/gabriel-vasile/mimetype v1.4.6 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -91,30 +101,35 @@ require ( github.com/naoina/toml v0.1.1 // indirect github.com/nyaruka/librato v1.1.1 // indirect github.com/nyaruka/null/v2 v2.0.3 // indirect - github.com/nyaruka/phonenumbers v1.4.0 // indirect + github.com/nyaruka/phonenumbers v1.4.1 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/samber/lo v1.47.0 // indirect github.com/sergi/go-diff v1.3.1 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect - go.opentelemetry.io/otel v1.30.0 // indirect - go.opentelemetry.io/otel/metric v1.30.0 // indirect - go.opentelemetry.io/otel/trace v1.30.0 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect - golang.org/x/net v0.29.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.29.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect + go.opentelemetry.io/otel v1.31.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.31.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect + golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect - golang.org/x/time v0.6.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + golang.org/x/time v0.7.0 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect - google.golang.org/genproto v0.0.0-20240930140551-af27646dc61f // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect + google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect google.golang.org/grpc v1.67.1 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index ba7ee3dfe..a68b9c077 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,10 @@ +cel.dev/expr v0.16.1 h1:NR0+oFYzR1CqLFhTAqg3ql59G9VfN8fKq1TCHJ6gq1g= +cel.dev/expr v0.16.1/go.mod h1:AsGA5zb3WruAEQeQng1RZdGEXmBj0jvMWh6l5SnNuC8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ= -cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc= -cloud.google.com/go/auth v0.9.7 h1:ha65jNwOfI48YmUzNfMaUDfqt5ykuYIUnSartpU1+BA= -cloud.google.com/go/auth v0.9.7/go.mod h1:Xo0n7n66eHyOWWCnitop6870Ilwo3PiZyodVkkH1xWM= +cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go/auth v0.9.8 h1:+CSJ0Gw9iVeSENVCKJoLHhdUykDgXSc4Qn+gu2BRtR8= +cloud.google.com/go/auth v0.9.8/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= @@ -11,15 +13,29 @@ cloud.google.com/go/firestore v1.17.0 h1:iEd1LBbkDZTFsLw3sTH50eyg4qe8eoG6CjocmEX cloud.google.com/go/firestore v1.17.0/go.mod h1:69uPx1papBsY8ZETooc71fOhoKkD70Q1DwMrtKuOT/Y= cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU= cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g= +cloud.google.com/go/logging v1.11.0 h1:v3ktVzXMV7CwHq1MBF65wcqLMA7i+z3YxbUsoK7mOKs= +cloud.google.com/go/logging v1.11.0/go.mod h1:5LDiJC/RxTt+fHc1LAt20R9TKiUTReDg6RuuFOZ67+A= cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc= cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= -cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= -cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= +cloud.google.com/go/monitoring v1.21.1 h1:zWtbIoBMnU5LP9A/fz8LmWMGHpk4skdfeiaa66QdFGc= +cloud.google.com/go/monitoring v1.21.1/go.mod h1:Rj++LKrlht9uBi8+Eb530dIrzG/cU/lB8mt+lbeFK1c= +cloud.google.com/go/storage v1.44.0 h1:abBzXf4UJKMmQ04xxJf9dYM/fNl24KHoTuBjyJDX2AI= +cloud.google.com/go/storage v1.44.0/go.mod h1:wpPblkIuMP5jCB/E48Pz9zIo2S/zD8g+ITmxKkPCITE= +cloud.google.com/go/trace v1.11.1 h1:UNqdP+HYYtnm6lb91aNA5JQ0X14GnxkABGlfz2PzPew= +cloud.google.com/go/trace v1.11.1/go.mod h1:IQKNQuBzH72EGaXEodKlNJrWykGZxet2zgjtS60OtjA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= firebase.google.com/go/v4 v4.14.1 h1:4qiUETaFRWoFGE1XP5VbcEdtPX93Qs+8B/7KvP2825g= firebase.google.com/go/v4 v4.14.1/go.mod h1:fgk2XshgNDEKaioKco+AouiegSI9oTWVqRaBdTTGBoM= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.1 h1:pB2F2JKCj1Znmp2rwxxt1J0Fg0wezTMgWYk5Mpbi1kg= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.1/go.mod h1:itPGVDKf9cC/ov4MdvJ2QZ0khw4bfoo9jzwTJlaxy2k= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1 h1:oTX4vsorBZo/Zdum6OKPA4o7544hm6smoRv1QjpTwGo= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o= @@ -30,57 +46,63 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/appleboy/go-fcm v1.2.1 h1:NhpACabtRuAplYg6bTNfSr3LBwsSuutP55HsphzLU/g= github.com/appleboy/go-fcm v1.2.1/go.mod h1:5FzMN+9J2sxnkoys9h3y48GQH8HnI637Q/ro/uP2Qsk= -github.com/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72QmG5U= -github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5 h1:xDAuZTn4IMm8o1LnBZvmrL8JA1io4o3YWNXgohbf20g= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5/go.mod h1:wYSv6iDS621sEFLfKvpPE2ugjTuGlAG7iROg0hLOkfc= -github.com/aws/aws-sdk-go-v2/config v1.27.39 h1:FCylu78eTGzW1ynHcongXK9YHtoXD5AiiUqq3YfJYjU= -github.com/aws/aws-sdk-go-v2/config v1.27.39/go.mod h1:wczj2hbyskP4LjMKBEZwPRO1shXY+GsQleab+ZXT2ik= -github.com/aws/aws-sdk-go-v2/credentials v1.17.37 h1:G2aOH01yW8X373JK419THj5QVqu9vKEwxSEsGxihoW0= -github.com/aws/aws-sdk-go-v2/credentials v1.17.37/go.mod h1:0ecCjlb7htYCptRD45lXJ6aJDQac6D2NlKGpZqyTG6A= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.8 h1:YNkm1DPhE4wnslPKD8jLVfKPujd94R8eI175vgKvIHI= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.8/go.mod h1:Ipgx7ZeodWz/Fd1TxCQwy0rXkxk2WDxZBJUuoZLzpqw= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 h1:C/d03NAmh8C4BZXhuRNboF/DqhBkBCeDiJDcaqIT5pA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14/go.mod h1:7I0Ju7p9mCIdlrfS+JCgqcYD0VXz/N4yozsox+0o078= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 h1:kYQ3H1u0ANr9KEKlGs/jTLrBFPo8P8NaH/w7A01NeeM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18/go.mod h1:r506HmK5JDUh9+Mw4CfGJGSSoqIiLCndAuqXuhbv67Y= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 h1:Z7IdFUONvTcvS7YuhtVxN99v2cCoHRXOS4mTr0B/pUc= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18/go.mod h1:DkKMmksZVVyat+Y+r1dEOgJEfUeA7UngIHWeKsi0yNc= +github.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI= +github.com/aws/aws-sdk-go-v2 v1.32.2/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 h1:pT3hpW0cOHRJx8Y0DfJUEQuqPild8jRGmSFmBgvydr0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6/go.mod h1:j/I2++U0xX+cr44QjHay4Cvxj6FUbnxrgmqN3H1jTZA= +github.com/aws/aws-sdk-go-v2/config v1.28.0 h1:FosVYWcqEtWNxHn8gB/Vs6jOlNwSoyOCA/g/sxyySOQ= +github.com/aws/aws-sdk-go-v2/config v1.28.0/go.mod h1:pYhbtvg1siOOg8h5an77rXle9tVG8T+BWLWAo7cOukc= +github.com/aws/aws-sdk-go-v2/credentials v1.17.41 h1:7gXo+Axmp+R4Z+AK8YFQO0ZV3L0gizGINCOWxSLY9W8= +github.com/aws/aws-sdk-go-v2/credentials v1.17.41/go.mod h1:u4Eb8d3394YLubphT4jLEwN1rLNq2wFOlT6OuxFwPzU= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.12 h1:zYf8E8zaqolHA5nQ+VmX2r3wc4K6xw5i6xKvvMjZBL0= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.12/go.mod h1:vYGIVLASk19Gb0FGwAcwES+qQF/aekD7m2G/X6mBOdQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 h1:TMH3f/SCAWdNtXXVPPu5D6wrr4G5hI1rAxbcocKfC7Q= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17/go.mod h1:1ZRXLdTpzdJb9fwTMXiLipENRxkGMTn1sfKexGllQCw= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 h1:UAsR3xA31QGf79WzpG/ixT9FZvQlh5HY1NRqSHBNOCk= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21/go.mod h1:JNr43NFf5L9YaG3eKTm7HQzls9J+A9YYcGI5Quh1r2Y= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 h1:6jZVETqmYCadGFvrYEQfC5fAQmlo80CeL5psbno6r0s= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21/go.mod h1:1SR0GbLlnN3QUmYaflZNiH1ql+1qrSiB2vwcJ+4UM60= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18 h1:OWYvKL53l1rbsUmW7bQyJVsYU/Ii3bbAAQIIFNbM0Tk= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18/go.mod h1:CUx0G1v3wG6l01tUB+j7Y8kclA8NSqK4ef0YG79a4cg= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.35.3 h1:X4iS+RcIKHkAMQz47nDt/nHxZUCKdnfgw940yluJ29Q= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.35.3/go.mod h1:k5XW8MoMxsNZ20RJmsokakvENUwQyjv69R9GqrI4xdQ= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.23.3 h1:q+pKQ9hZfIJNyoYSwPWbj19GnEPWvLOXwHpR/HYyx4o= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.23.3/go.mod h1:NZQWaOwOszI7jnQ7s1i5kN/FUAglaaJIm2htZG7BJKw= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 h1:QFASJGfT8wMXtuP3D5CRmMjARHv9ZmzFUMJznHDOY3w= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5/go.mod h1:QdZ3OmoIjSX+8D1OPAzPxDfjXASbBMDsz9qvtyIhtik= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20 h1:rTWjG6AvWekO2B1LHeM3ktU7MqyX9rzWQ7hgzneZW7E= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20/go.mod h1:RGW2DDpVc8hu6Y6yG8G5CHVmVOAn1oV8rNKOHRJyswg= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.19 h1:dOxqOlOEa2e2heC/74+ZzcJOa27+F1aXFZpYgY/4QfA= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.19/go.mod h1:aV6U1beLFvk3qAgognjS3wnGGoDId8hlPEiBsLHXVZE= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 h1:Xbwbmk44URTiHNx6PNo0ujDE6ERlsCKJD3u1zfnzAPg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20/go.mod h1:oAfOFzUB14ltPZj1rWwRc3d/6OgD76R8KlvU3EqM9Fg= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18 h1:eb+tFOIl9ZsUe2259/BKPeniKuz4/02zZFH/i4Nf8Rg= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18/go.mod h1:GVCC2IJNJTmdlyEsSmofEy7EfJncP7DNnXDzRjJ5Keg= -github.com/aws/aws-sdk-go-v2/service/s3 v1.63.3 h1:3zt8qqznMuAZWDTDpcwv9Xr11M/lVj2FsRR7oYBt0OA= -github.com/aws/aws-sdk-go-v2/service/s3 v1.63.3/go.mod h1:NLTqRLe3pUNu3nTEHI6XlHLKYmc8fbHUdMxAB6+s41Q= -github.com/aws/aws-sdk-go-v2/service/sso v1.23.3 h1:rs4JCczF805+FDv2tRhZ1NU0RB2H6ryAvsWPanAr72Y= -github.com/aws/aws-sdk-go-v2/service/sso v1.23.3/go.mod h1:XRlMvmad0ZNL+75C5FYdMvbbLkd6qiqz6foR1nA1PXY= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.3 h1:S7EPdMVZod8BGKQQPTBK+FcX9g7bKR7c4+HxWqHP7Vg= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.3/go.mod h1:FnvDM4sfa+isJ3kDXIzAB9GAwVSzFzSy97uZ3IsHo4E= -github.com/aws/aws-sdk-go-v2/service/sts v1.31.3 h1:VzudTFrDCIDakXtemR7l6Qzt2+JYsVqo2MxBPt5k8T8= -github.com/aws/aws-sdk-go-v2/service/sts v1.31.3/go.mod h1:yMWe0F+XG0DkRZK5ODZhG7BEFYhLXi2dqGsv6tX0cgI= -github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA= -github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21 h1:7edmS3VOBDhK00b/MwGtGglCm7hhwNYnjJs/PgFdMQE= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21/go.mod h1:Q9o5h4HoIWG8XfzxqiuK/CGUbepCJ8uTlaE3bAbxytQ= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2 h1:kJqyYcGqhWFmXqjRrtFFD4Oc9FXiskhsll2xnlpe8Do= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2/go.mod h1:+t2Zc5VNOzhaWzpGE+cEYZADsgAAQT5v55AO+fhU+2s= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.2 h1:E7Tuo0ipWpBl0f3uThz8cZsuyD5H8jLCnbtbKR4YL2s= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.2/go.mod h1:txOfweuNPBLhHodsV+C2lvPPRTommVTWbts9SZV6Myc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2 h1:4FMHqLfk0efmTqhXVRL5xYRqlEBNBiRI7N6w4jsEdd4= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2/go.mod h1:LWoqeWlK9OZeJxsROW2RqrSPvQHKTpp69r/iDjwsSaw= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2 h1:1G7TTQNPNv5fhCyIQGYk8FOggLgkzKq6c4Y1nOGzAOE= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2/go.mod h1:+ybYGLXoF7bcD7wIcMcklxyABZQmuBf1cHUhvY6FGIo= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 h1:s7NA1SOw8q/5c0wr8477yOPp0z+uBaXBnLE0XYb0POA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2/go.mod h1:fnjjWyAW/Pj5HYOxl9LJqWtEwS7W2qgcRLWP+uWbss0= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2 h1:t7iUP9+4wdc5lt3E41huP+GvQZJD38WLsgVp4iOtAjg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2/go.mod h1:/niFCtmuQNxqx9v8WAPq5qh7EH25U4BF6tjoyq9bObM= +github.com/aws/aws-sdk-go-v2/service/s3 v1.66.0 h1:xA6XhTF7PE89BCNHJbQi8VvPzcgMtmGC5dr8S8N7lHk= +github.com/aws/aws-sdk-go-v2/service/s3 v1.66.0/go.mod h1:cB6oAuus7YXRZhWCc1wIwPywwZ1XwweNp2TVAEGYeB8= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 h1:bSYXVyUzoTHoKalBmwaZxs97HU9DWWI3ehHSAMa7xOk= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.2/go.mod h1:skMqY7JElusiOUjMJMOv1jJsP7YUg7DrhgqZZWuzu1U= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 h1:AhmO1fHINP9vFYUE0LHzCWg/LfUWUF+zFPEcY9QXb7o= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2/go.mod h1:o8aQygT2+MVP0NaV6kbdE1YnnIM8RRVQzoeUH45GOdI= +github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 h1:CiS7i0+FUe+/YY1GvIBLLrR/XNGZ4CtM1Ll0XavNuVo= +github.com/aws/aws-sdk-go-v2/service/sts v1.32.2/go.mod h1:HtaiBI8CjYoNVde8arShXb94UbQQi9L4EMr6D+xGBwo= +github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= +github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= +github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -91,15 +113,19 @@ github.com/elastic/go-elasticsearch/v8 v8.15.0/go.mod h1:HCON3zj4btpqs2N1jjsAy4a github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.13.0 h1:HzkeUz1Knt+3bK+8LG1bxOO/jzWZmdxpwC51i202les= +github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnvMg4d7nvT/wl9WgVXn3Q8= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= +github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= -github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= -github.com/getsentry/sentry-go v0.29.0 h1:YtWluuCFg9OfcqnaujpY918N/AhCCwarIDWOYSBAjCA= -github.com/getsentry/sentry-go v0.29.0/go.mod h1:jhPesDAL0Q0W2+2YEuVOvdWmVtdsr1+jtBrlDEVWwLY= +github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= +github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= +github.com/getsentry/sentry-go v0.29.1 h1:DyZuChN8Hz3ARxGVV8ePaNXh1dQ7d76AiB117xcREwA= +github.com/getsentry/sentry-go v0.29.1/go.mod h1:x3AtIzN01d6SiWkderzaH28Tm0lgkafpJ5Bm3li39O0= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -173,11 +199,13 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= @@ -204,18 +232,20 @@ github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw github.com/nyaruka/null/v2 v2.0.3/go.mod h1:OCVeCkCXwrg5/qE6RU0c1oUVZBy+ZDrT+xYg1XSaIWA= github.com/nyaruka/null/v3 v3.0.0 h1:JvOiNuKmRBFHxzZFt4sWii+ewmMkCQ1vO7X0clTNn6E= github.com/nyaruka/null/v3 v3.0.0/go.mod h1:Sus286RmC8P0VihFuQDDQPib/xJQ7++TsaPLdRuwgVc= -github.com/nyaruka/phonenumbers v1.4.0 h1:ddhWiHnHCIX3n6ETDA58Zq5dkxkjlvgrDWM2OHHPCzU= -github.com/nyaruka/phonenumbers v1.4.0/go.mod h1:gv+CtldaFz+G3vHHnasBSirAi3O2XLqZzVWz4V1pl2E= +github.com/nyaruka/phonenumbers v1.4.1 h1:dNsiYGirahC2lMRz3p2dxmmyLbzD3arCgmj/hPEVRPY= +github.com/nyaruka/phonenumbers v1.4.1/go.mod h1:gv+CtldaFz+G3vHHnasBSirAi3O2XLqZzVWz4V1pl2E= github.com/nyaruka/redisx v0.8.1 h1:d9Hc8nfSKTSEU+bx+YrB13d6bzAgiiHygk4jg/Q4nb4= github.com/nyaruka/redisx v0.8.1/go.mod h1:2TUmkDvprPInnmInR5AEbCm0zRRewkvSDVLsO+Do6iI= -github.com/nyaruka/rp-indexer/v9 v9.2.0 h1:YisG0GiQNg15LDQtA0m7SNnEHlxzIbmimu95SVXxSVI= -github.com/nyaruka/rp-indexer/v9 v9.2.0/go.mod h1:NzcuE4Zxrzde7gQinlWfwq2jeyEbamBj8hqVkm+eQLg= +github.com/nyaruka/rp-indexer/v9 v9.2.1 h1:gQa0QHiU+LjhmgpToHpoGRKRC8oI1EdW4dDaN9inhSk= +github.com/nyaruka/rp-indexer/v9 v9.2.1/go.mod h1:NzcuE4Zxrzde7gQinlWfwq2jeyEbamBj8hqVkm+eQLg= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -223,10 +253,12 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= -github.com/samber/slog-multi v1.2.2 h1:tJfAyxFDk7CGiEumFTj1iXpD3Uu9rFPUKldpsTgUTGk= -github.com/samber/slog-multi v1.2.2/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo= +github.com/samber/slog-multi v1.2.3 h1:np8YoAZbGP699xA92SYZxs7zzKpL1/yBYk6q8/caXpc= +github.com/samber/slog-multi v1.2.3/go.mod h1:ACuZ5B6heK57TfMVkVknN2UZHoFfjCwRxR0Q2OXKHlo= github.com/samber/slog-sentry v1.2.2 h1:S0glIVITlGCCfSvIOte2Sh63HMHJpYN3hDr+97hILIk= github.com/samber/slog-sentry v1.2.2/go.mod h1:bHm8jm1dks0p+xc/lH2i4TIFwnPcMTvZeHgCBj5+uhA= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= @@ -245,26 +277,30 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 h1:hCq2hNMwsegUvPzI7sPOvtO9cqyy5GbWt/Ybp2xrx8Q= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0/go.mod h1:LqaApwGx/oUmzsbqxkzuBvyoPpkxk3JQWnqfVrJ3wCA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= -go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= -go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= -go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= +go.opentelemetry.io/contrib/detectors/gcp v1.29.0 h1:TiaiXB4DpGD3sdzNlYQxruQngn5Apwzi1X0DRhuGvDQ= +go.opentelemetry.io/contrib/detectors/gcp v1.29.0/go.mod h1:GW2aWZNwR2ZxDLdv8OyC2G8zkRoQBuURgV7RPQgcPoU= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 h1:yMkBS9yViCc7U7yeLzJPM2XizlfdVvBRSmsQDWu6qc0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0/go.mod h1:n8MR6/liuGB5EmTETUBeU5ZgqMOlqKRxUaqPQBOANZ8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= -go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= +go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= +go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -278,8 +314,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -297,18 +333,18 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= -golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -318,8 +354,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.199.0 h1:aWUXClp+VFJmqE0JPvpZOK3LDQMyFKYIow4etYd9qxs= -google.golang.org/api v0.199.0/go.mod h1:ohG4qSztDJmZdjK/Ar6MhbAmb/Rpi4JHOqagsh90K28= +google.golang.org/api v0.201.0 h1:+7AD9JNM3tREtawRMu8sOjSbb8VYcYXJG/2eEOmfDu0= +google.golang.org/api v0.201.0/go.mod h1:HVY0FCHVs89xIW9fzf/pBvOEm+OolHa86G/txFezyq4= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw= @@ -327,12 +363,12 @@ google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240930140551-af27646dc61f h1:mCJ6SGikSxVlt9scCayUl2dMq0msUgmBArqRY6umieI= -google.golang.org/genproto v0.0.0-20240930140551-af27646dc61f/go.mod h1:xtVODtPkMQRUZ4kqOTgp6JrXQrPevvfCSdk4mJtHUbM= -google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f h1:jTm13A2itBi3La6yTGqn8bVSrc3ZZ1r8ENHlIXBfnRA= -google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f/go.mod h1:CLGoBuH1VHxAUXVPP8FfPwPEVJB6lz3URE5mY2SuayE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f h1:cUMEy+8oS78BWIH9OWazBkzbr090Od9tWBNtZHkOhf0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 h1:Df6WuGvthPzc+JiQ/G+m+sNX24kc0aTBqoDN/0yyykE= +google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53/go.mod h1:fheguH3Am2dGp1LfXkrvwqC/KlFq8F0nLq3LryOMrrE= +google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U= +google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -340,6 +376,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a h1:UIpYSuWdWHSzjwcAFRLjKcPXFZVVLXGEM23W+NWqipw= +google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a/go.mod h1:9i1T9n4ZinTUZGgzENMi8MDDgbGC5mqTS75JAv6xN3A= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -351,8 +389,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 6e4bde7d9b120a67a63b5560fb67653247d7b6ff Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 5 Nov 2024 10:54:14 -0500 Subject: [PATCH 136/216] More logging for invalid locales --- core/models/msgs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/models/msgs.go b/core/models/msgs.go index f1295b9a1..b57f6b394 100644 --- a/core/models/msgs.go +++ b/core/models/msgs.go @@ -393,7 +393,7 @@ func newOutgoingTextMsg(rt *runtime.Runtime, org *Org, channel *Channel, contact // TODO: temporary fix for invalid locales if len(m.Locale) > 6 { - slog.Error("invalid locale, defaulting to eng-US", "locale", m.Locale) + slog.Error("invalid locale, defaulting to eng-US", "locale", m.Locale, "urn", out.URN(), "org_id", org.ID()) m.Locale = "eng" } From c08362fe0740f15bb919afa668be9ce62a9d4df7 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 5 Nov 2024 11:08:59 -0500 Subject: [PATCH 137/216] Update deps --- go.mod | 40 ++++++++++++++-------------- go.sum | 84 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/go.mod b/go.mod index fc6208af1..4d8da47ec 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,13 @@ module github.com/nyaruka/mailroom go 1.23 require ( - firebase.google.com/go/v4 v4.14.1 + firebase.google.com/go/v4 v4.15.0 github.com/Masterminds/semver v1.5.0 github.com/appleboy/go-fcm v1.2.1 - github.com/aws/aws-sdk-go-v2 v1.32.2 - github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.12 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2 - github.com/aws/aws-sdk-go-v2/service/s3 v1.66.0 + github.com/aws/aws-sdk-go-v2 v1.32.3 + github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.13 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.3 + github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2 github.com/buger/jsonparser v1.1.1 github.com/elastic/go-elasticsearch/v8 v8.15.0 github.com/getsentry/sentry-go v0.29.1 @@ -29,19 +29,19 @@ require ( github.com/nyaruka/rp-indexer/v9 v9.2.1 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_model v0.6.1 - github.com/prometheus/common v0.60.0 - github.com/samber/slog-multi v1.2.3 + github.com/prometheus/common v0.60.1 + github.com/samber/slog-multi v1.2.4 github.com/samber/slog-sentry v1.2.2 github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.9.0 - google.golang.org/api v0.201.0 + google.golang.org/api v0.204.0 ) require ( cel.dev/expr v0.16.1 // indirect cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/auth v0.9.8 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect + cloud.google.com/go/auth v0.10.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.5 // indirect cloud.google.com/go/compute/metadata v0.5.2 // indirect cloud.google.com/go/firestore v1.17.0 // indirect cloud.google.com/go/iam v1.2.1 // indirect @@ -58,16 +58,16 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.28.0 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.41 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21 // indirect - github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.2 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.22 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.3 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 // indirect @@ -124,9 +124,9 @@ require ( golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.7.0 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect - google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 // indirect + google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect google.golang.org/grpc v1.67.1 // indirect google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a // indirect google.golang.org/protobuf v1.35.1 // indirect diff --git a/go.sum b/go.sum index a68b9c077..fc5a24840 100644 --- a/go.sum +++ b/go.sum @@ -3,18 +3,18 @@ cel.dev/expr v0.16.1/go.mod h1:AsGA5zb3WruAEQeQng1RZdGEXmBj0jvMWh6l5SnNuC8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= -cloud.google.com/go/auth v0.9.8 h1:+CSJ0Gw9iVeSENVCKJoLHhdUykDgXSc4Qn+gu2BRtR8= -cloud.google.com/go/auth v0.9.8/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= -cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= -cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= +cloud.google.com/go/auth v0.10.0 h1:tWlkvFAh+wwTOzXIjrwM64karR1iTBZ/GRr0S/DULYo= +cloud.google.com/go/auth v0.10.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth/oauth2adapt v0.2.5 h1:2p29+dePqsCHPP1bqDJcKj4qxRyYCcbzKpFyKGt3MTk= +cloud.google.com/go/auth/oauth2adapt v0.2.5/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= cloud.google.com/go/firestore v1.17.0 h1:iEd1LBbkDZTFsLw3sTH50eyg4qe8eoG6CjocmEXO9aQ= cloud.google.com/go/firestore v1.17.0/go.mod h1:69uPx1papBsY8ZETooc71fOhoKkD70Q1DwMrtKuOT/Y= cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU= cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g= -cloud.google.com/go/logging v1.11.0 h1:v3ktVzXMV7CwHq1MBF65wcqLMA7i+z3YxbUsoK7mOKs= -cloud.google.com/go/logging v1.11.0/go.mod h1:5LDiJC/RxTt+fHc1LAt20R9TKiUTReDg6RuuFOZ67+A= +cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk= +cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc= cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= cloud.google.com/go/monitoring v1.21.1 h1:zWtbIoBMnU5LP9A/fz8LmWMGHpk4skdfeiaa66QdFGc= @@ -25,8 +25,8 @@ cloud.google.com/go/trace v1.11.1 h1:UNqdP+HYYtnm6lb91aNA5JQ0X14GnxkABGlfz2PzPew cloud.google.com/go/trace v1.11.1/go.mod h1:IQKNQuBzH72EGaXEodKlNJrWykGZxet2zgjtS60OtjA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -firebase.google.com/go/v4 v4.14.1 h1:4qiUETaFRWoFGE1XP5VbcEdtPX93Qs+8B/7KvP2825g= -firebase.google.com/go/v4 v4.14.1/go.mod h1:fgk2XshgNDEKaioKco+AouiegSI9oTWVqRaBdTTGBoM= +firebase.google.com/go/v4 v4.15.0 h1:k27M+cHbyN1YpBI2Cf4NSjeHnnYRB9ldXwpqA5KikN0= +firebase.google.com/go/v4 v4.15.0/go.mod h1:S/4MJqVZn1robtXkHhpRUbwOC4gdYtgsiMMJQ4x+xmQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.1 h1:pB2F2JKCj1Znmp2rwxxt1J0Fg0wezTMgWYk5Mpbi1kg= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.1/go.mod h1:itPGVDKf9cC/ov4MdvJ2QZ0khw4bfoo9jzwTJlaxy2k= @@ -46,42 +46,42 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/appleboy/go-fcm v1.2.1 h1:NhpACabtRuAplYg6bTNfSr3LBwsSuutP55HsphzLU/g= github.com/appleboy/go-fcm v1.2.1/go.mod h1:5FzMN+9J2sxnkoys9h3y48GQH8HnI637Q/ro/uP2Qsk= -github.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI= -github.com/aws/aws-sdk-go-v2 v1.32.2/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= +github.com/aws/aws-sdk-go-v2 v1.32.3 h1:T0dRlFBKcdaUPGNtkBSwHZxrtis8CQU17UpNBZYd0wk= +github.com/aws/aws-sdk-go-v2 v1.32.3/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 h1:pT3hpW0cOHRJx8Y0DfJUEQuqPild8jRGmSFmBgvydr0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6/go.mod h1:j/I2++U0xX+cr44QjHay4Cvxj6FUbnxrgmqN3H1jTZA= github.com/aws/aws-sdk-go-v2/config v1.28.0 h1:FosVYWcqEtWNxHn8gB/Vs6jOlNwSoyOCA/g/sxyySOQ= github.com/aws/aws-sdk-go-v2/config v1.28.0/go.mod h1:pYhbtvg1siOOg8h5an77rXle9tVG8T+BWLWAo7cOukc= github.com/aws/aws-sdk-go-v2/credentials v1.17.41 h1:7gXo+Axmp+R4Z+AK8YFQO0ZV3L0gizGINCOWxSLY9W8= github.com/aws/aws-sdk-go-v2/credentials v1.17.41/go.mod h1:u4Eb8d3394YLubphT4jLEwN1rLNq2wFOlT6OuxFwPzU= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.12 h1:zYf8E8zaqolHA5nQ+VmX2r3wc4K6xw5i6xKvvMjZBL0= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.12/go.mod h1:vYGIVLASk19Gb0FGwAcwES+qQF/aekD7m2G/X6mBOdQ= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.13 h1:EiyBn76ZpKQJWRNhgxvgloj6Xmazck05+RS6j0gfy1Y= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.13/go.mod h1:gKf4BQBfUke2acRFz76+Tyqz4A9Me0aMEnDUZwEZ+R0= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 h1:TMH3f/SCAWdNtXXVPPu5D6wrr4G5hI1rAxbcocKfC7Q= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17/go.mod h1:1ZRXLdTpzdJb9fwTMXiLipENRxkGMTn1sfKexGllQCw= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 h1:UAsR3xA31QGf79WzpG/ixT9FZvQlh5HY1NRqSHBNOCk= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21/go.mod h1:JNr43NFf5L9YaG3eKTm7HQzls9J+A9YYcGI5Quh1r2Y= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 h1:6jZVETqmYCadGFvrYEQfC5fAQmlo80CeL5psbno6r0s= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21/go.mod h1:1SR0GbLlnN3QUmYaflZNiH1ql+1qrSiB2vwcJ+4UM60= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 h1:Jw50LwEkVjuVzE1NzkhNKkBf9cRN7MtE1F/b2cOKTUM= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22/go.mod h1:Y/SmAyPcOTmpeVaWSzSKiILfXTVJwrGmYZhcRbhWuEY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 h1:981MHwBaRZM7+9QSR6XamDzF/o7ouUGxFzr+nVSIhrs= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22/go.mod h1:1RA1+aBEfn+CAB/Mh0MB6LsdCYCnjZm7tKXtnk499ZQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21 h1:7edmS3VOBDhK00b/MwGtGglCm7hhwNYnjJs/PgFdMQE= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.21/go.mod h1:Q9o5h4HoIWG8XfzxqiuK/CGUbepCJ8uTlaE3bAbxytQ= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2 h1:kJqyYcGqhWFmXqjRrtFFD4Oc9FXiskhsll2xnlpe8Do= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2/go.mod h1:+t2Zc5VNOzhaWzpGE+cEYZADsgAAQT5v55AO+fhU+2s= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.2 h1:E7Tuo0ipWpBl0f3uThz8cZsuyD5H8jLCnbtbKR4YL2s= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.2/go.mod h1:txOfweuNPBLhHodsV+C2lvPPRTommVTWbts9SZV6Myc= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.22 h1:yV+hCAHZZYJQcwAaszoBNwLbPItHvApxT0kVIw6jRgs= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.22/go.mod h1:kbR1TL8llqB1eGnVbybcA4/wgScxdylOdyAd51yxPdw= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.3 h1:pS5ka5Z026eG29K3cce+yxG39i5COQARcgheeK9NKQE= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.3/go.mod h1:MBT8rSGSZjJiV6X7rlrVGoIt+mCoaw0VbpdVtsrsJfk= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.3 h1:BjzvhVB6Nnx+Xqlnc5JWkQYuWClxUFcvLzZIqFO31lI= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.3/go.mod h1:/6lakUr7RXajwpensF1miKadiR+xTlHV7mma5axITxY= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2 h1:4FMHqLfk0efmTqhXVRL5xYRqlEBNBiRI7N6w4jsEdd4= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.2/go.mod h1:LWoqeWlK9OZeJxsROW2RqrSPvQHKTpp69r/iDjwsSaw= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2 h1:1G7TTQNPNv5fhCyIQGYk8FOggLgkzKq6c4Y1nOGzAOE= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2/go.mod h1:+ybYGLXoF7bcD7wIcMcklxyABZQmuBf1cHUhvY6FGIo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 h1:s7NA1SOw8q/5c0wr8477yOPp0z+uBaXBnLE0XYb0POA= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2/go.mod h1:fnjjWyAW/Pj5HYOxl9LJqWtEwS7W2qgcRLWP+uWbss0= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2 h1:t7iUP9+4wdc5lt3E41huP+GvQZJD38WLsgVp4iOtAjg= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.2/go.mod h1:/niFCtmuQNxqx9v8WAPq5qh7EH25U4BF6tjoyq9bObM= -github.com/aws/aws-sdk-go-v2/service/s3 v1.66.0 h1:xA6XhTF7PE89BCNHJbQi8VvPzcgMtmGC5dr8S8N7lHk= -github.com/aws/aws-sdk-go-v2/service/s3 v1.66.0/go.mod h1:cB6oAuus7YXRZhWCc1wIwPywwZ1XwweNp2TVAEGYeB8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.3 h1:kT6BcZsmMtNkP/iYMcRG+mIEA/IbeiUimXtGmqF39y0= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.3/go.mod h1:Z8uGua2k4PPaGOYn66pK02rhMrot3Xk3tpBuUFPomZU= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.3 h1:wudRPcZMKytcywXERkR6PLqD8gPx754ZyIOo0iVg488= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.3/go.mod h1:yRo5Kj5+m/ScVIZpQOquQvDtSrDM1JLRCnvglBcdNmw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3 h1:qcxX0JYlgWH3hpPUnd6U0ikcl6LLA9sLkXE2w1fpMvY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3/go.mod h1:cLSNEmI45soc+Ef8K/L+8sEA3A3pYFEYf5B5UI+6bH4= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.3 h1:ZC7Y/XgKUxwqcdhO5LE8P6oGP1eh6xlQReWNKfhvJno= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.3/go.mod h1:WqfO7M9l9yUAw0HcHaikwRd/H6gzYdz7vjejCA5e2oY= +github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2 h1:p9TNFL8bFUMd+38YIpTAXpoxyz0MxC7FlbFEH4P4E1U= +github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2/go.mod h1:fNjyo0Coen9QTwQLWeV6WO2Nytwiu+cCcWaTdKCAqqE= github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 h1:bSYXVyUzoTHoKalBmwaZxs97HU9DWWI3ehHSAMa7xOk= github.com/aws/aws-sdk-go-v2/service/sso v1.24.2/go.mod h1:skMqY7JElusiOUjMJMOv1jJsP7YUg7DrhgqZZWuzu1U= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 h1:AhmO1fHINP9vFYUE0LHzCWg/LfUWUF+zFPEcY9QXb7o= @@ -251,14 +251,14 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= -github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= +github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= +github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= -github.com/samber/slog-multi v1.2.3 h1:np8YoAZbGP699xA92SYZxs7zzKpL1/yBYk6q8/caXpc= -github.com/samber/slog-multi v1.2.3/go.mod h1:ACuZ5B6heK57TfMVkVknN2UZHoFfjCwRxR0Q2OXKHlo= +github.com/samber/slog-multi v1.2.4 h1:k9x3JAWKJFPKffx+oXZ8TasaNuorIW4tG+TXxkt6Ry4= +github.com/samber/slog-multi v1.2.4/go.mod h1:ACuZ5B6heK57TfMVkVknN2UZHoFfjCwRxR0Q2OXKHlo= github.com/samber/slog-sentry v1.2.2 h1:S0glIVITlGCCfSvIOte2Sh63HMHJpYN3hDr+97hILIk= github.com/samber/slog-sentry v1.2.2/go.mod h1:bHm8jm1dks0p+xc/lH2i4TIFwnPcMTvZeHgCBj5+uhA= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= @@ -354,8 +354,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.201.0 h1:+7AD9JNM3tREtawRMu8sOjSbb8VYcYXJG/2eEOmfDu0= -google.golang.org/api v0.201.0/go.mod h1:HVY0FCHVs89xIW9fzf/pBvOEm+OolHa86G/txFezyq4= +google.golang.org/api v0.204.0 h1:3PjmQQEDkR/ENVZZwIYB4W/KzYtN8OrqnNcHWpeR8E4= +google.golang.org/api v0.204.0/go.mod h1:69y8QSoKIbL9F94bWgWAq6wGqGwyjBgi2y8rAK8zLag= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw= @@ -363,12 +363,12 @@ google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 h1:Df6WuGvthPzc+JiQ/G+m+sNX24kc0aTBqoDN/0yyykE= -google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53/go.mod h1:fheguH3Am2dGp1LfXkrvwqC/KlFq8F0nLq3LryOMrrE= +google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOskjbcSMcF2jiYMNiQYZ0c2KEJLKKU= +google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4= google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U= google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= From 97484ccec19588a9301825e22d4374521954b98d Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 5 Nov 2024 11:09:36 -0500 Subject: [PATCH 138/216] Update CHANGELOG.md for v9.3.46 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b789c0b84..79973f3c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.46 (2024-11-05) +------------------------- + * More logging for invalid locales + * Update deps + v9.3.45 (2024-10-16) ------------------------- * Fix metrics endpoint to strip \ from group names From ecf528ee7ac3783b1a3a89298eaaf35361f83ae7 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 7 Nov 2024 20:09:34 -0500 Subject: [PATCH 139/216] Update test database --- core/tasks/msgs/send_broadcast_test.go | 2 +- services/ivr/twiml/service_test.go | 2 +- testsuite/testdata/constants.go | 16 ++++++++-------- testsuite/testfiles/postgres.dump | Bin 1772010 -> 1769897 bytes web/contact/testdata/modify.json | 10 +++++----- web/org/base_test.go | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/core/tasks/msgs/send_broadcast_test.go b/core/tasks/msgs/send_broadcast_test.go index 3c42920f7..2e6d497e9 100644 --- a/core/tasks/msgs/send_broadcast_test.go +++ b/core/tasks/msgs/send_broadcast_test.go @@ -243,7 +243,7 @@ func TestSendBroadcastTask(t *testing.T) { createdByID: testdata.Agent.ID, queue: tasks.HandlerQueue, expectedBatches: 1, - expectedMsgs: map[string]int{"hi Cathy from Nyaruka goflow URN: tel:+12065551212 Gender: F": 1}, + expectedMsgs: map[string]int{"hi Cathy from TextIt goflow URN: tel:+12065551212 Gender: F": 1}, }, { translations: flows.BroadcastTranslations{ diff --git a/services/ivr/twiml/service_test.go b/services/ivr/twiml/service_test.go index 0f98e5aed..a11811b8a 100644 --- a/services/ivr/twiml/service_test.go +++ b/services/ivr/twiml/service_test.go @@ -120,7 +120,7 @@ func TestURNForRequest(t *testing.T) { s := twiml.NewService(http.DefaultClient, "12345", "sesame") makeRequest := func(body string) *http.Request { - r, _ := http.NewRequest("POST", "http://nyaruka.com/12345/incoming", strings.NewReader(body)) + r, _ := http.NewRequest("POST", "http://textit.com/12345/incoming", strings.NewReader(body)) r.Header.Add("Content-Type", "application/x-www-form-urlencoded") r.Header.Add("Content-Length", strconv.Itoa(len(body))) return r diff --git a/testsuite/testdata/constants.go b/testsuite/testdata/constants.go index 566fa72dd..0041a85f8 100644 --- a/testsuite/testdata/constants.go +++ b/testsuite/testdata/constants.go @@ -17,10 +17,10 @@ var AuthGroupIDs = map[string]int{ } var Org1 = &Org{1, "bf0514a5-9407-44c9-b0f9-3f36f9c18414"} -var Admin = &User{3, "admin1@nyaruka.com"} -var Editor = &User{4, "editor1@nyaruka.com"} -var Viewer = &User{5, "viewer1@nyaruka.com"} -var Agent = &User{6, "agent1@nyaruka.com"} +var Admin = &User{3, "admin1@textit.com"} +var Editor = &User{4, "editor1@textit.com"} +var Viewer = &User{5, "viewer1@textit.com"} +var Agent = &User{6, "agent1@textit.com"} var TwilioChannel = &Channel{10000, "74729f45-7f29-4868-9dc4-90e491e3c7d8", "T"} var VonageChannel = &Channel{10001, "19012bfd-3ce3-4cae-9bb9-76cf92c73d49", "NX"} @@ -62,12 +62,12 @@ var TestingLabel = &Label{10001, "a6338cdc-7938-4437-8b05-2d5d785e3a08"} var ReviveTemplate = &Template{10000, "9c22b594-fcab-4b29-9bcb-ce4404894a80"} var GoodbyeTemplate = &Template{10001, "3b8dd151-1a91-411f-90cb-dd9065bb7a71"} -var DefaultTopic = &Topic{1, "fd18a69d-7514-4b76-9fad-072641995e17"} +var DefaultTopic = &Topic{1, "4307df2e-b00b-42b6-922b-4a1dcfc268d8"} var SalesTopic = &Topic{2, "9ef2ff21-064a-41f1-8560-ccc990b4f937"} var SupportTopic = &Topic{3, "0a8f2e00-fef6-402c-bd79-d789446ec0e0"} -var Partners = &Team{1, "4321c30b-b596-46fa-adb4-4a46d37923f6"} -var Office = &Team{2, "f14c1762-d38b-4072-ae63-2705332a3719"} +var Partners = &Team{2, "4321c30b-b596-46fa-adb4-4a46d37923f6"} +var Office = &Team{3, "f14c1762-d38b-4072-ae63-2705332a3719"} var Luis = &Classifier{1, "097e026c-ae79-4740-af67-656dbedf0263"} var Wit = &Classifier{2, "ff2a817c-040a-4eb2-8404-7d92e8b79dd0"} @@ -80,7 +80,7 @@ var RemindersEvent3 = &CampaignEvent{10002, "3e4f06c2-e04f-47ca-a047-f5252b3160e // secondary org.. only a few things var Org2 = &Org{2, "3ae7cdeb-fd96-46e5-abc4-a4622f349921"} -var Org2Admin = &User{8, "admin2@nyaruka.com"} +var Org2Admin = &User{8, "admin2@textit.com"} var Org2Channel = &Channel{20000, "a89bc872-3763-4b95-91d9-31d4e56c6651", "T"} var Org2Contact = &Contact{20000, "26d20b72-f7d8-44dc-87f2-aae046dbff95", "tel:+250700000005", 20000} var Org2Favorites = &Flow{20000, "f161bd16-3c60-40bd-8c92-228ce815b9cd"} diff --git a/testsuite/testfiles/postgres.dump b/testsuite/testfiles/postgres.dump index bb750d5135bad296f71ac570a62b5ec235f5102d..549e1ef1a26fef9499e799354ea140f507eaef55 100644 GIT binary patch delta 93859 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 BTA%;` From 0b47a0b2d61dc064b40b2f350a5c2b8ff4002ffa Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 27 Nov 2024 15:22:37 -0500 Subject: [PATCH 157/216] Simplify how we store recent contacts for flow segments --- core/models/flow_stats.go | 39 ++++++++++++++-------------------- core/models/flow_stats_test.go | 4 ++-- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/core/models/flow_stats.go b/core/models/flow_stats.go index b639a0742..4853d19e4 100644 --- a/core/models/flow_stats.go +++ b/core/models/flow_stats.go @@ -30,19 +30,16 @@ func (s segmentID) String() string { return fmt.Sprintf("%s:%s", s.exitUUID, s.destUUID) } -type segmentContact struct { +type segmentRecentContact struct { contact *flows.Contact operand string time time.Time + rnd string } // RecordFlowStatistics records statistics from the given parallel slices of sessions and sprints func RecordFlowStatistics(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, sessions []flows.Session, sprints []flows.Sprint) error { - rc := rt.RP.Get() - defer rc.Close() - - segmentIDs := make([]segmentID, 0, 10) - recentBySegment := make(map[segmentID][]*segmentContact, 10) + recentBySegment := make(map[segmentID][]*segmentRecentContact, 10) nodeTypeCache := make(map[flows.NodeUUID]string) for i, sprint := range sprints { @@ -50,34 +47,30 @@ func RecordFlowStatistics(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, for _, seg := range sprint.Segments() { segID := segmentID{seg.Exit().UUID(), seg.Destination().UUID()} - uiNodeType := getNodeUIType(seg.Flow(), seg.Node(), nodeTypeCache) - // only store operand values for certain node types - operand := "" - if storeOperandsForTypes[uiNodeType] { - operand = seg.Operand() - } + // only store recent contact if we have less than the cap + if len(recentBySegment[segID]) < recentContactsCap { + uiNodeType := getNodeUIType(seg.Flow(), seg.Node(), nodeTypeCache) - if _, seen := recentBySegment[segID]; !seen { - segmentIDs = append(segmentIDs, segID) + // only store operand values for certain node types + operand := "" + if storeOperandsForTypes[uiNodeType] { + operand = seg.Operand() + } + recentBySegment[segID] = append(recentBySegment[segID], &segmentRecentContact{contact: session.Contact(), operand: operand, time: seg.Time(), rnd: redisx.RandomBase64(10)}) } - recentBySegment[segID] = append(recentBySegment[segID], &segmentContact{contact: session.Contact(), operand: operand, time: seg.Time()}) } } - for _, segID := range segmentIDs { - recentContacts := recentBySegment[segID] - - // trim recent set for each segment - no point in trying to add more values than we keep - if len(recentContacts) > recentContactsCap { - recentBySegment[segID] = recentContacts[:len(recentContacts)-recentContactsCap] - } + rc := rt.RP.Get() + defer rc.Close() + for segID, recentContacts := range recentBySegment { recentSet := redisx.NewCappedZSet(fmt.Sprintf(recentContactsKey, segID), recentContactsCap, recentContactsExpire) for _, recent := range recentContacts { // set members need to be unique, so we include a random string - value := fmt.Sprintf("%s|%d|%s", redisx.RandomBase64(10), recent.contact.ID(), stringsx.TruncateEllipsis(recent.operand, 100)) + value := fmt.Sprintf("%s|%d|%s", recent.rnd, recent.contact.ID(), stringsx.TruncateEllipsis(recent.operand, 100)) score := float64(recent.time.UnixNano()) / float64(1e9) // score is UNIX time as floating point err := recentSet.Add(rc, value, score) diff --git a/core/models/flow_stats_test.go b/core/models/flow_stats_test.go index a6b08bb46..21853e351 100644 --- a/core/models/flow_stats_test.go +++ b/core/models/flow_stats_test.go @@ -71,7 +71,7 @@ func TestRecordFlowStatistics(t *testing.T) { // check recent operands for color split :: Blue exit -> next node assertredis.ZRange(t, rc, "recent_contacts:c02fc3ba-369a-4c87-9bc4-c3b376bda6d2:57b50d33-2b5a-4726-82de-9848c61eff6e", 0, -1, - []string{"2SS5dyuJzp|123|blue", "6MBPV0gqT9|234|BLUE"}, + []string{"2SS5dyuJzp|123|blue", "2MsZZ/N3TH|234|BLUE"}, ) // check recent operands for color split :: Other exit -> next node @@ -81,6 +81,6 @@ func TestRecordFlowStatistics(t *testing.T) { // check recent operands for split by expression :: Other exit -> next node assertredis.ZRange(t, rc, "recent_contacts:2b698218-87e5-4ab8-922e-e65f91d12c10:88d8bf00-51ce-4e5e-aae8-4f957a0761a0", 0, -1, - []string{"2MsZZ/N3TH|123|0", "KKLrT60Tr9|234|0"}, + []string{"PLQQFoOgV9|123|0", "/cgnkcW6vA|234|0"}, ) } From 6d77cff2cd3a83b5e9326cac7dee3661c5dd1905 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 27 Nov 2024 16:08:09 -0500 Subject: [PATCH 158/216] Update test database --- testsuite/testfiles/postgres.dump | Bin 1772923 -> 1772927 bytes testsuite/testsuite.go | 3 +-- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/testsuite/testfiles/postgres.dump b/testsuite/testfiles/postgres.dump index deb33d2cf76bbdaaee55108d15e57d4c44ff3084..da66161a500083dda86b7768736a033c5f87ed83 100644 GIT binary patch delta 64308 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

E7!nfIF9Iw^%d~FMFp&|iVJ7FJ5|YWE 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%0AMDkyF$ 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

ywu(s0@SNtA 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 BWq1Gp diff --git a/testsuite/testsuite.go b/testsuite/testsuite.go index eb22d077a..a5102cbe0 100644 --- a/testsuite/testsuite.go +++ b/testsuite/testsuite.go @@ -281,9 +281,8 @@ DELETE FROM channels_channellog; DELETE FROM msgs_msg; DELETE FROM flows_flowrun; DELETE FROM flows_flowpathcount; -DELETE FROM flows_flownodecount; -DELETE FROM flows_flowrunstatuscount; DELETE FROM flows_flowcategorycount; +DELETE FROM flows_flowactivitycount; DELETE FROM flows_flowstartcount; DELETE FROM flows_flowstart_contacts; DELETE FROM flows_flowstart_groups; From b77580ecf21c6c39d6d0bfdf94538295aba9d760 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 27 Nov 2024 16:44:53 -0500 Subject: [PATCH 159/216] Start recording flow segment counts in flow activity table --- core/handlers/webhook_called.go | 4 +-- core/models/flow_stats.go | 54 +++++++++++++++++++++++++------- core/models/flow_stats_test.go | 55 +++++++++++++++++++++++++++------ 3 files changed, 91 insertions(+), 22 deletions(-) diff --git a/core/handlers/webhook_called.go b/core/handlers/webhook_called.go index 3e2244d5d..9200221b9 100644 --- a/core/handlers/webhook_called.go +++ b/core/handlers/webhook_called.go @@ -35,13 +35,13 @@ func handleWebhookCalled(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, } run, step := scene.Session().FindStep(e.StepUUID()) - flow, _ := oa.FlowByUUID(run.FlowReference().UUID) + flow := run.Flow().Asset().(*models.Flow) // create an HTTP log if flow != nil { httpLog := models.NewWebhookCalledLog( oa.OrgID(), - flow.(*models.Flow).ID(), + flow.ID(), event.URL, event.StatusCode, event.Request, event.Response, event.Status != flows.CallStatusSuccess, time.Millisecond*time.Duration(event.ElapsedMS), diff --git a/core/models/flow_stats.go b/core/models/flow_stats.go index 4853d19e4..c1a64572e 100644 --- a/core/models/flow_stats.go +++ b/core/models/flow_stats.go @@ -6,7 +6,6 @@ import ( "time" "github.com/buger/jsonparser" - "github.com/jmoiron/sqlx" "github.com/nyaruka/gocommon/stringsx" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/mailroom/runtime" @@ -16,18 +15,31 @@ import ( const ( recentContactsCap = 5 // number of recent contacts we keep per segment recentContactsExpire = time.Hour * 24 // how long we keep recent contacts - recentContactsKey = "recent_contacts:%s" + recentContactsKey = "recent_contacts:%s:%s" ) var storeOperandsForTypes = map[string]bool{"wait_for_response": true, "split_by_expression": true, "split_by_contact_field": true, "split_by_run_result": true} -type segmentID struct { - exitUUID flows.ExitUUID - destUUID flows.NodeUUID +const sqlInsertFlowActivityCount = ` +INSERT INTO flows_flowactivitycount( flow_id, scope, count, is_squashed) + VALUES(:flow_id, :scope, :count, FALSE) +` + +type FlowActivityCount struct { + FlowID FlowID `db:"flow_id"` + Scope string `db:"scope"` + Count int `db:"count"` } -func (s segmentID) String() string { - return fmt.Sprintf("%s:%s", s.exitUUID, s.destUUID) +// InsertFlowActivityCounts inserts the given flow activity counts into the database +func InsertFlowActivityCounts(ctx context.Context, db DBorTx, counts []*FlowActivityCount) error { + return BulkQuery(ctx, "insert flow activity counts", db, sqlInsertFlowActivityCount, counts) +} + +type segmentInfo struct { + flowID FlowID + exitUUID flows.ExitUUID + destUUID flows.NodeUUID } type segmentRecentContact struct { @@ -38,15 +50,22 @@ type segmentRecentContact struct { } // RecordFlowStatistics records statistics from the given parallel slices of sessions and sprints -func RecordFlowStatistics(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, sessions []flows.Session, sprints []flows.Sprint) error { - recentBySegment := make(map[segmentID][]*segmentRecentContact, 10) +func RecordFlowStatistics(ctx context.Context, rt *runtime.Runtime, db DBorTx, sessions []flows.Session, sprints []flows.Sprint) error { + countsBySegment := make(map[segmentInfo]int, 10) + recentBySegment := make(map[segmentInfo][]*segmentRecentContact, 10) nodeTypeCache := make(map[flows.NodeUUID]string) for i, sprint := range sprints { session := sessions[i] for _, seg := range sprint.Segments() { - segID := segmentID{seg.Exit().UUID(), seg.Destination().UUID()} + segID := segmentInfo{ + flowID: seg.Flow().Asset().(*Flow).ID(), + exitUUID: seg.Exit().UUID(), + destUUID: seg.Destination().UUID(), + } + + countsBySegment[segID]++ // only store recent contact if we have less than the cap if len(recentBySegment[segID]) < recentContactsCap { @@ -62,11 +81,24 @@ func RecordFlowStatistics(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, } } + counts := make([]*FlowActivityCount, 0, len(countsBySegment)) + for segID, count := range countsBySegment { + counts = append(counts, &FlowActivityCount{ + FlowID: segID.flowID, + Scope: fmt.Sprintf("segment:%s:%s", segID.exitUUID, segID.destUUID), + Count: count, + }) + } + + if err := InsertFlowActivityCounts(ctx, db, counts); err != nil { + return fmt.Errorf("error inserting flow segment counts: %w", err) + } + rc := rt.RP.Get() defer rc.Close() for segID, recentContacts := range recentBySegment { - recentSet := redisx.NewCappedZSet(fmt.Sprintf(recentContactsKey, segID), recentContactsCap, recentContactsExpire) + recentSet := redisx.NewCappedZSet(fmt.Sprintf(recentContactsKey, segID.exitUUID, segID.destUUID), recentContactsCap, recentContactsExpire) for _, recent := range recentContacts { // set members need to be unique, so we include a random string diff --git a/core/models/flow_stats_test.go b/core/models/flow_stats_test.go index 21853e351..cb368486f 100644 --- a/core/models/flow_stats_test.go +++ b/core/models/flow_stats_test.go @@ -1,15 +1,17 @@ package models_test import ( - "os" "testing" "github.com/nyaruka/gocommon/random" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/test" "github.com/nyaruka/mailroom/core/models" + "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/mailroom/testsuite" + "github.com/nyaruka/mailroom/testsuite/testdata" "github.com/nyaruka/redisx/assertredis" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -18,23 +20,35 @@ func TestRecordFlowStatistics(t *testing.T) { rc := rt.RP.Get() defer rc.Close() - defer testsuite.Reset(testsuite.ResetRedis) + defer testsuite.Reset(testsuite.ResetRedis | testsuite.ResetData) defer random.SetGenerator(random.DefaultGenerator) random.SetGenerator(random.NewSeededGenerator(123)) - assetsJSON, err := os.ReadFile("testdata/flow_stats_test.json") + testFlows := testdata.ImportFlows(rt, testdata.Org1, "testdata/flow_stats_test.json") + flow := testFlows[0] + + oa, err := models.GetOrgAssetsWithRefresh(ctx, rt, testdata.Org1.ID, models.RefreshFlows) require.NoError(t, err) - sa1, session1, session1Sprint1 := test.NewSessionBuilder().WithAssetsJSON(assetsJSON).WithFlow("19eab6aa-4a88-42a1-8882-b9956823c680"). + sa1, session1, session1Sprint1 := test.NewSessionBuilder().WithAssets(oa.SessionAssets()).WithFlow(flow.UUID). WithContact("4ad4f0a6-fb95-4845-b4cb-335f67eafe96", 123, "Bob", "eng", "").MustBuild() - sa2, session2, session2Sprint1 := test.NewSessionBuilder().WithAssetsJSON(assetsJSON).WithFlow("19eab6aa-4a88-42a1-8882-b9956823c680"). + sa2, session2, session2Sprint1 := test.NewSessionBuilder().WithAssets(oa.SessionAssets()).WithFlow(flow.UUID). WithContact("5cfe8b70-0d4a-4862-8fb5-e72603d832a9", 234, "Ann", "eng", "").MustBuild() - sa3, session3, session3Sprint1 := test.NewSessionBuilder().WithAssetsJSON(assetsJSON).WithFlow("19eab6aa-4a88-42a1-8882-b9956823c680"). + sa3, session3, session3Sprint1 := test.NewSessionBuilder().WithAssets(oa.SessionAssets()).WithFlow(flow.UUID). WithContact("367c8ef2-aac7-4264-9a03-40877371995d", 345, "Jim", "eng", "").MustBuild() - err = models.RecordFlowStatistics(ctx, rt, nil, []flows.Session{session1, session2, session3}, []flows.Sprint{session1Sprint1, session2Sprint1, session3Sprint1}) + err = models.RecordFlowStatistics(ctx, rt, rt.DB, []flows.Session{session1, session2, session3}, []flows.Sprint{session1Sprint1, session2Sprint1, session3Sprint1}) + require.NoError(t, err) + + // should have a single record of all 3 contacts going through the first segment + var counts []*models.FlowActivityCount + err = rt.DB.SelectContext(ctx, &counts, "SELECT flow_id, scope, count FROM flows_flowactivitycount ORDER BY flow_id, scope") require.NoError(t, err) + assert.Len(t, counts, 1) + assert.Equal(t, &models.FlowActivityCount{flow.ID, "segment:5fd2e537-0534-4c12-8425-bef87af09d46:072b95b3-61c3-4e0e-8dd1-eb7481083f94", 3}, counts[0]) + + assertFlowActivityCounts(t, rt, flow.ID, map[string]int{"segment:5fd2e537-0534-4c12-8425-bef87af09d46:072b95b3-61c3-4e0e-8dd1-eb7481083f94": 3}) assertredis.Keys(t, rc, "*", []string{ "recent_contacts:5fd2e537-0534-4c12-8425-bef87af09d46:072b95b3-61c3-4e0e-8dd1-eb7481083f94", // "what's your fav color" -> color split @@ -54,11 +68,21 @@ func TestRecordFlowStatistics(t *testing.T) { _, session3Sprint3, err := test.ResumeSession(session3, sa3, "azure") require.NoError(t, err) - err = models.RecordFlowStatistics(ctx, rt, nil, []flows.Session{session1, session2, session3}, []flows.Sprint{session1Sprint2, session2Sprint2, session3Sprint2}) + err = models.RecordFlowStatistics(ctx, rt, rt.DB, []flows.Session{session1, session2, session3}, []flows.Sprint{session1Sprint2, session2Sprint2, session3Sprint2}) require.NoError(t, err) - err = models.RecordFlowStatistics(ctx, rt, nil, []flows.Session{session3}, []flows.Sprint{session3Sprint3}) + err = models.RecordFlowStatistics(ctx, rt, rt.DB, []flows.Session{session3}, []flows.Sprint{session3Sprint3}) require.NoError(t, err) + assertFlowActivityCounts(t, rt, flow.ID, map[string]int{ + "segment:5fd2e537-0534-4c12-8425-bef87af09d46:072b95b3-61c3-4e0e-8dd1-eb7481083f94": 3, + "segment:0a4f2ea9-c47f-4e9c-a242-89ae5b38d679:072b95b3-61c3-4e0e-8dd1-eb7481083f94": 2, + "segment:2b698218-87e5-4ab8-922e-e65f91d12c10:88d8bf00-51ce-4e5e-aae8-4f957a0761a0": 2, + "segment:614e7451-e0bd-43d9-b317-2aded3c8d790:a1e649db-91e0-47c4-ab14-eba0d1475116": 2, + "segment:97cd44ce-dec2-4e19-8ca2-4e20db51dc08:0e1fe072-6f03-4f29-98aa-7bedbe930dab": 2, + "segment:c02fc3ba-369a-4c87-9bc4-c3b376bda6d2:57b50d33-2b5a-4726-82de-9848c61eff6e": 2, + "segment:ea6c38dc-11e2-4616-9f3e-577e44765d44:8712db6b-25ff-4789-892c-581f24eeeb95": 2, + }) + assertredis.Keys(t, rc, "*", []string{ "recent_contacts:5fd2e537-0534-4c12-8425-bef87af09d46:072b95b3-61c3-4e0e-8dd1-eb7481083f94", // "what's your fav color" -> color split "recent_contacts:c02fc3ba-369a-4c87-9bc4-c3b376bda6d2:57b50d33-2b5a-4726-82de-9848c61eff6e", // color split :: Blue exit -> next node @@ -84,3 +108,16 @@ func TestRecordFlowStatistics(t *testing.T) { []string{"PLQQFoOgV9|123|0", "/cgnkcW6vA|234|0"}, ) } + +func assertFlowActivityCounts(t *testing.T, rt *runtime.Runtime, flowID models.FlowID, expected map[string]int) { + var counts []*models.FlowActivityCount + err := rt.DB.Select(&counts, "SELECT flow_id, scope, SUM(count) AS count FROM flows_flowactivitycount WHERE flow_id = $1 GROUP BY flow_id, scope", flowID) + require.NoError(t, err) + + actual := make(map[string]int) + for _, c := range counts { + actual[c.Scope] = c.Count + } + + assert.Equal(t, expected, actual) +} From 5fdf89173903d8e303682c00d56df9f3e44ea464 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 27 Nov 2024 17:05:13 -0500 Subject: [PATCH 160/216] Improve test of flow segment counts --- core/models/flow_stats_test.go | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/core/models/flow_stats_test.go b/core/models/flow_stats_test.go index cb368486f..23ef3d7d3 100644 --- a/core/models/flow_stats_test.go +++ b/core/models/flow_stats_test.go @@ -65,22 +65,34 @@ func TestRecordFlowStatistics(t *testing.T) { require.NoError(t, err) session3, session3Sprint2, err := test.ResumeSession(session3, sa3, "teal") require.NoError(t, err) - _, session3Sprint3, err := test.ResumeSession(session3, sa3, "azure") - require.NoError(t, err) err = models.RecordFlowStatistics(ctx, rt, rt.DB, []flows.Session{session1, session2, session3}, []flows.Sprint{session1Sprint2, session2Sprint2, session3Sprint2}) require.NoError(t, err) + + assertFlowActivityCounts(t, rt, flow.ID, map[string]int{ + "segment:5fd2e537-0534-4c12-8425-bef87af09d46:072b95b3-61c3-4e0e-8dd1-eb7481083f94": 3, // "what's your fav color" -> color split + "segment:c02fc3ba-369a-4c87-9bc4-c3b376bda6d2:57b50d33-2b5a-4726-82de-9848c61eff6e": 2, // color split :: Blue exit -> next node + "segment:ea6c38dc-11e2-4616-9f3e-577e44765d44:8712db6b-25ff-4789-892c-581f24eeeb95": 1, // color split :: Other exit -> next node + "segment:2b698218-87e5-4ab8-922e-e65f91d12c10:88d8bf00-51ce-4e5e-aae8-4f957a0761a0": 2, // split by expression :: Other exit -> next node + "segment:0a4f2ea9-c47f-4e9c-a242-89ae5b38d679:072b95b3-61c3-4e0e-8dd1-eb7481083f94": 1, // "sorry I don't know that color" -> color split + "segment:97cd44ce-dec2-4e19-8ca2-4e20db51dc08:0e1fe072-6f03-4f29-98aa-7bedbe930dab": 2, // "X is a great color" -> split by expression + "segment:614e7451-e0bd-43d9-b317-2aded3c8d790:a1e649db-91e0-47c4-ab14-eba0d1475116": 2, // "you have X tickets" -> group split + }) + + _, session3Sprint3, err := test.ResumeSession(session3, sa3, "azure") + require.NoError(t, err) + err = models.RecordFlowStatistics(ctx, rt, rt.DB, []flows.Session{session3}, []flows.Sprint{session3Sprint3}) require.NoError(t, err) assertFlowActivityCounts(t, rt, flow.ID, map[string]int{ - "segment:5fd2e537-0534-4c12-8425-bef87af09d46:072b95b3-61c3-4e0e-8dd1-eb7481083f94": 3, - "segment:0a4f2ea9-c47f-4e9c-a242-89ae5b38d679:072b95b3-61c3-4e0e-8dd1-eb7481083f94": 2, - "segment:2b698218-87e5-4ab8-922e-e65f91d12c10:88d8bf00-51ce-4e5e-aae8-4f957a0761a0": 2, - "segment:614e7451-e0bd-43d9-b317-2aded3c8d790:a1e649db-91e0-47c4-ab14-eba0d1475116": 2, - "segment:97cd44ce-dec2-4e19-8ca2-4e20db51dc08:0e1fe072-6f03-4f29-98aa-7bedbe930dab": 2, - "segment:c02fc3ba-369a-4c87-9bc4-c3b376bda6d2:57b50d33-2b5a-4726-82de-9848c61eff6e": 2, - "segment:ea6c38dc-11e2-4616-9f3e-577e44765d44:8712db6b-25ff-4789-892c-581f24eeeb95": 2, + "segment:5fd2e537-0534-4c12-8425-bef87af09d46:072b95b3-61c3-4e0e-8dd1-eb7481083f94": 3, // "what's your fav color" -> color split + "segment:c02fc3ba-369a-4c87-9bc4-c3b376bda6d2:57b50d33-2b5a-4726-82de-9848c61eff6e": 2, // color split :: Blue exit -> next node + "segment:ea6c38dc-11e2-4616-9f3e-577e44765d44:8712db6b-25ff-4789-892c-581f24eeeb95": 2, // color split :: Other exit -> next node + "segment:2b698218-87e5-4ab8-922e-e65f91d12c10:88d8bf00-51ce-4e5e-aae8-4f957a0761a0": 2, // split by expression :: Other exit -> next node + "segment:0a4f2ea9-c47f-4e9c-a242-89ae5b38d679:072b95b3-61c3-4e0e-8dd1-eb7481083f94": 2, // "sorry I don't know that color" -> color split + "segment:97cd44ce-dec2-4e19-8ca2-4e20db51dc08:0e1fe072-6f03-4f29-98aa-7bedbe930dab": 2, // "X is a great color" -> split by expression + "segment:614e7451-e0bd-43d9-b317-2aded3c8d790:a1e649db-91e0-47c4-ab14-eba0d1475116": 2, // "you have X tickets" -> group split }) assertredis.Keys(t, rc, "*", []string{ From a9ac973d55529d17e10857e737d65fd79e510007 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 27 Nov 2024 17:06:59 -0500 Subject: [PATCH 161/216] Update CHANGELOG.md for v9.3.51 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5725502f1..93a2570e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.51 (2024-11-27) +------------------------- + * Start recording flow segment counts in flow activity table + v9.3.50 (2024-11-20) ------------------------- * Simplify FlowRun struct From db6893d968d594bcca4d77b92a3d9571153009c8 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 29 Nov 2024 11:48:40 -0500 Subject: [PATCH 162/216] Update to latest gocommon that fixes country parsing from phone numbers --- core/models/msgs.go | 7 ------- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/core/models/msgs.go b/core/models/msgs.go index b57f6b394..3707fd976 100644 --- a/core/models/msgs.go +++ b/core/models/msgs.go @@ -391,13 +391,6 @@ func newOutgoingTextMsg(rt *runtime.Runtime, org *Org, channel *Channel, contact m.CreatedOn = createdOn m.Metadata = null.Map[any](buildMsgMetadata(out)) - // TODO: temporary fix for invalid locales - if len(m.Locale) > 6 { - slog.Error("invalid locale, defaulting to eng-US", "locale", m.Locale, "urn", out.URN(), "org_id", org.ID()) - - m.Locale = "eng" - } - if out.Templating() != nil { m.Templating = &Templating{MsgTemplating: out.Templating()} } diff --git a/go.mod b/go.mod index d325b0ca0..6f9125fa8 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/aws/aws-sdk-go-v2 v1.32.5 github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.17 github.com/aws/aws-sdk-go-v2/service/dynamodb v1.37.1 - github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.69.0 github.com/buger/jsonparser v1.1.1 github.com/elastic/go-elasticsearch/v8 v8.16.0 github.com/getsentry/sentry-go v0.29.1 @@ -22,7 +22,7 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 - github.com/nyaruka/gocommon v1.59.2 + github.com/nyaruka/gocommon v1.59.3 github.com/nyaruka/goflow v0.223.0 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 @@ -33,7 +33,7 @@ require ( github.com/samber/slog-multi v1.2.4 github.com/samber/slog-sentry v1.2.2 github.com/shopspring/decimal v1.4.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 google.golang.org/api v0.206.0 ) @@ -82,7 +82,7 @@ require ( github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/gabriel-vasile/mimetype v1.4.6 // indirect + github.com/gabriel-vasile/mimetype v1.4.7 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -101,7 +101,7 @@ require ( github.com/naoina/toml v0.1.1 // indirect github.com/nyaruka/librato v1.1.1 // indirect github.com/nyaruka/null/v2 v2.0.3 // indirect - github.com/nyaruka/phonenumbers v1.4.2 // indirect + github.com/nyaruka/phonenumbers v1.4.3 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/samber/lo v1.47.0 // indirect diff --git a/go.sum b/go.sum index e9861a398..853057ab7 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 h1:wtpJ4zcwr github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5/go.mod h1:qu/W9HXQbbQ4+1+JcZp0ZNPV31ym537ZJN+fiS7Ti8E= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 h1:P1doBzv5VEg1ONxnJss1Kh5ZG/ewoIE4MQtKKc6Crgg= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5/go.mod h1:NOP+euMW7W3Ukt28tAxPuoWao4rhhqJD3QEBk7oCg7w= -github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1 h1:LXLnDfjT/P6SPIaCE86xCOjJROPn4FNB2EdN68vMK5c= -github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1/go.mod h1:ralv4XawHjEMaHOWnTFushl0WRqim/gQWesAMF6hTow= +github.com/aws/aws-sdk-go-v2/service/s3 v1.69.0 h1:Q2ax8S21clKOnHhhr933xm3JxdJebql+R7aNo7p7GBQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.69.0/go.mod h1:ralv4XawHjEMaHOWnTFushl0WRqim/gQWesAMF6hTow= github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 h1:3zu537oLmsPfDMyjnUS2g+F2vITgy5pB74tHI+JBNoM= github.com/aws/aws-sdk-go-v2/service/sso v1.24.6/go.mod h1:WJSZH2ZvepM6t6jwu4w/Z45Eoi75lPN7DcydSRtJg6Y= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 h1:K0OQAsDywb0ltlFrZm0JHPY3yZp/S9OaoLU33S7vPS8= @@ -122,8 +122,8 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= -github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= +github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA= +github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU= github.com/getsentry/sentry-go v0.29.1 h1:DyZuChN8Hz3ARxGVV8ePaNXh1dQ7d76AiB117xcREwA= github.com/getsentry/sentry-go v0.29.1/go.mod h1:x3AtIzN01d6SiWkderzaH28Tm0lgkafpJ5Bm3li39O0= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= @@ -222,8 +222,8 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= -github.com/nyaruka/gocommon v1.59.2 h1:WHMM8O4w8K6lYwBBRPQxJr/wPdgnNgg6eq/gi1ogvVY= -github.com/nyaruka/gocommon v1.59.2/go.mod h1:0G8y8vhbsjkRhc614wYJ5cy9zRffy+a2+cv8ko4xB10= +github.com/nyaruka/gocommon v1.59.3 h1:fdjs9Z7aH+zog7FXlEpvJ0GtI6XNdNdBtjFxw5kVB7s= +github.com/nyaruka/gocommon v1.59.3/go.mod h1:peOpluiVBMeQu81Ar+7EPQVT7vawN6ho9Kh1k/Gj8Vk= github.com/nyaruka/goflow v0.223.0 h1:a4FixeUI5qiuL+c9Nb3FB2CxKHAT/5tRdH7mmBtVoQ4= github.com/nyaruka/goflow v0.223.0/go.mod h1:VBgCOWQONCf0eU0O4Zl6GjXUQ15p+JTIiLjfxyGK4FY= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= @@ -232,8 +232,8 @@ github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw github.com/nyaruka/null/v2 v2.0.3/go.mod h1:OCVeCkCXwrg5/qE6RU0c1oUVZBy+ZDrT+xYg1XSaIWA= github.com/nyaruka/null/v3 v3.0.0 h1:JvOiNuKmRBFHxzZFt4sWii+ewmMkCQ1vO7X0clTNn6E= github.com/nyaruka/null/v3 v3.0.0/go.mod h1:Sus286RmC8P0VihFuQDDQPib/xJQ7++TsaPLdRuwgVc= -github.com/nyaruka/phonenumbers v1.4.2 h1:V791/B74Sb5i/X7Od2AVKA0BkvcNaInf7DWykPS2YSU= -github.com/nyaruka/phonenumbers v1.4.2/go.mod h1:gv+CtldaFz+G3vHHnasBSirAi3O2XLqZzVWz4V1pl2E= +github.com/nyaruka/phonenumbers v1.4.3 h1:tR71UJ+DZu7TSkxoG8JI8HzHJkPD/m4KNiUX34Fvmlo= +github.com/nyaruka/phonenumbers v1.4.3/go.mod h1:gv+CtldaFz+G3vHHnasBSirAi3O2XLqZzVWz4V1pl2E= github.com/nyaruka/redisx v0.8.1 h1:d9Hc8nfSKTSEU+bx+YrB13d6bzAgiiHygk4jg/Q4nb4= github.com/nyaruka/redisx v0.8.1/go.mod h1:2TUmkDvprPInnmInR5AEbCm0zRRewkvSDVLsO+Do6iI= github.com/nyaruka/rp-indexer/v9 v9.2.1 h1:gQa0QHiU+LjhmgpToHpoGRKRC8oI1EdW4dDaN9inhSk= @@ -272,8 +272,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= From 722745788b28044a3466ef62cb5a73c688ceecaa Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 29 Nov 2024 12:03:36 -0500 Subject: [PATCH 163/216] Update CHANGELOG.md for v9.3.52 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93a2570e3..47d37226b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.52 (2024-11-29) +------------------------- + * Update to latest gocommon that fixes country parsing from phone numbers + v9.3.51 (2024-11-27) ------------------------- * Start recording flow segment counts in flow activity table From 01f35b66ef4030b935700dae31ead035ae8de0fa Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 2 Dec 2024 17:42:39 -0500 Subject: [PATCH 164/216] Update to latest goflow --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6f9125fa8..a88341f79 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 github.com/nyaruka/gocommon v1.59.3 - github.com/nyaruka/goflow v0.223.0 + github.com/nyaruka/goflow v0.224.1 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 github.com/nyaruka/rp-indexer/v9 v9.2.1 diff --git a/go.sum b/go.sum index 853057ab7..c341e710f 100644 --- a/go.sum +++ b/go.sum @@ -224,8 +224,8 @@ github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= github.com/nyaruka/gocommon v1.59.3 h1:fdjs9Z7aH+zog7FXlEpvJ0GtI6XNdNdBtjFxw5kVB7s= github.com/nyaruka/gocommon v1.59.3/go.mod h1:peOpluiVBMeQu81Ar+7EPQVT7vawN6ho9Kh1k/Gj8Vk= -github.com/nyaruka/goflow v0.223.0 h1:a4FixeUI5qiuL+c9Nb3FB2CxKHAT/5tRdH7mmBtVoQ4= -github.com/nyaruka/goflow v0.223.0/go.mod h1:VBgCOWQONCf0eU0O4Zl6GjXUQ15p+JTIiLjfxyGK4FY= +github.com/nyaruka/goflow v0.224.1 h1:JhQ3QzT7yO6JnPdFxCJ4o+vgbxfl3+seKaKzf3tcmxA= +github.com/nyaruka/goflow v0.224.1/go.mod h1:1Up4YMccDCugTrVcXEbNCxDJqZRJindQR75oiMBCZvY= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= From ca538bf2bfb6a15277dc5f61312e8e2ad4325437 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 2 Dec 2024 18:19:02 -0500 Subject: [PATCH 165/216] Update CHANGELOG.md for v9.3.53 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47d37226b..6bc7215bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.53 (2024-12-02) +------------------------- + * Update to latest goflow + v9.3.52 (2024-11-29) ------------------------- * Update to latest gocommon that fixes country parsing from phone numbers From 77e9088011fbfc982e3760e5f4bcfdb262f765aa Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 3 Dec 2024 12:05:33 -0500 Subject: [PATCH 166/216] Update test database --- testsuite/testfiles/postgres.dump | Bin 1772927 -> 1769708 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/testsuite/testfiles/postgres.dump b/testsuite/testfiles/postgres.dump index da66161a500083dda86b7768736a033c5f87ed83..94746c9dd38f515a97b0d0ea1b0d7aff6100d4af 100644 GIT binary patch delta 72128 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 O$aWGrPU0yb!u}7dp2C6v From 6341578762f986d36699fda21182a61bd43b3ca7 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 3 Dec 2024 12:00:02 -0500 Subject: [PATCH 167/216] Update to latest goflow that adds flow spec 13.6 --- core/goflow/flows_test.go | 14 ++++++++--- core/goflow/testdata/migrate/13.6.0.json | 31 ++++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 +-- web/flow/testdata/change_language.json | 6 ++--- web/flow/testdata/inspect.json | 2 +- web/flow/testdata/migrate.json | 4 +-- web/po/testdata/import.json | 2 +- 8 files changed, 51 insertions(+), 14 deletions(-) create mode 100644 core/goflow/testdata/migrate/13.6.0.json diff --git a/core/goflow/flows_test.go b/core/goflow/flows_test.go index 344954de4..bf81891b5 100644 --- a/core/goflow/flows_test.go +++ b/core/goflow/flows_test.go @@ -16,7 +16,7 @@ import ( ) func TestSpecVersion(t *testing.T) { - assert.Equal(t, semver.MustParse("13.5.0"), goflow.SpecVersion()) + assert.Equal(t, semver.MustParse("13.6.0"), goflow.SpecVersion()) } func TestReadFlow(t *testing.T) { @@ -64,6 +64,7 @@ func TestMigrateDefinition(t *testing.T) { v13_3_0 := testsuite.ReadFile("testdata/migrate/13.3.0.json") v13_4_0 := testsuite.ReadFile("testdata/migrate/13.4.0.json") v13_5_0 := testsuite.ReadFile("testdata/migrate/13.5.0.json") + v13_6_0 := testsuite.ReadFile("testdata/migrate/13.6.0.json") // 13.0 > 13.1 migrated, err := goflow.MigrateDefinition(rt.Config, v13_0_0, semver.MustParse("13.1.0")) @@ -90,8 +91,13 @@ func TestMigrateDefinition(t *testing.T) { assert.NoError(t, err) test.AssertEqualJSON(t, v13_5_0, migrated) - // 13.0 > 13.5 - migrated, err = goflow.MigrateDefinition(rt.Config, v13_0_0, semver.MustParse("13.5.0")) + // 13.5 > 13.6 + migrated, err = goflow.MigrateDefinition(rt.Config, migrated, semver.MustParse("13.6.0")) assert.NoError(t, err) - test.AssertEqualJSON(t, v13_5_0, migrated) + test.AssertEqualJSON(t, v13_6_0, migrated) + + // 13.0 > 13.6 + migrated, err = goflow.MigrateDefinition(rt.Config, v13_0_0, semver.MustParse("13.6.0")) + assert.NoError(t, err) + test.AssertEqualJSON(t, v13_6_0, migrated) } diff --git a/core/goflow/testdata/migrate/13.6.0.json b/core/goflow/testdata/migrate/13.6.0.json new file mode 100644 index 000000000..08c082e0e --- /dev/null +++ b/core/goflow/testdata/migrate/13.6.0.json @@ -0,0 +1,31 @@ +{ + "uuid": "502c3ee4-3249-4dee-8e71-c62070667d52", + "name": "New", + "spec_version": "13.6.0", + "type": "messaging", + "language": "und", + "nodes": [ + { + "uuid": "d26486b1-193d-4512-85f0-c6db696f1e1c", + "actions": [ + { + "uuid": "82a1de5f-af1a-45ef-8511-4d60c160e486", + "type": "send_msg", + "text": "Hello @webhook.json", + "template": { + "uuid": "641b8b05-082a-497e-bf63-38aa48b1f0c4", + "name": "welcome" + }, + "template_variables": [ + "@contact.name" + ] + } + ], + "exits": [ + { + "uuid": "fdd370e0-ffa9-48b3-8148-b9241d74fc72" + } + ] + } + ] +} \ No newline at end of file diff --git a/go.mod b/go.mod index a88341f79..a10d5b34a 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 github.com/nyaruka/gocommon v1.59.3 - github.com/nyaruka/goflow v0.224.1 + github.com/nyaruka/goflow v0.225.0 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 github.com/nyaruka/rp-indexer/v9 v9.2.1 diff --git a/go.sum b/go.sum index c341e710f..8bea9896a 100644 --- a/go.sum +++ b/go.sum @@ -224,8 +224,8 @@ github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= github.com/nyaruka/gocommon v1.59.3 h1:fdjs9Z7aH+zog7FXlEpvJ0GtI6XNdNdBtjFxw5kVB7s= github.com/nyaruka/gocommon v1.59.3/go.mod h1:peOpluiVBMeQu81Ar+7EPQVT7vawN6ho9Kh1k/Gj8Vk= -github.com/nyaruka/goflow v0.224.1 h1:JhQ3QzT7yO6JnPdFxCJ4o+vgbxfl3+seKaKzf3tcmxA= -github.com/nyaruka/goflow v0.224.1/go.mod h1:1Up4YMccDCugTrVcXEbNCxDJqZRJindQR75oiMBCZvY= +github.com/nyaruka/goflow v0.225.0 h1:N3LF00hTaQNrKzjRF2knNQzjJY7Kc3i7lV2q2j7ovOo= +github.com/nyaruka/goflow v0.225.0/go.mod h1:1Up4YMccDCugTrVcXEbNCxDJqZRJindQR75oiMBCZvY= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= diff --git a/web/flow/testdata/change_language.json b/web/flow/testdata/change_language.json index ace06bf0e..9f25d0d42 100644 --- a/web/flow/testdata/change_language.json +++ b/web/flow/testdata/change_language.json @@ -171,7 +171,7 @@ "response": { "uuid": "19cad1f2-9110-4271-98d4-1b968bf19410", "name": "Change Language", - "spec_version": "13.5.0", + "spec_version": "13.6.0", "language": "kin", "type": "messaging", "revision": 16, @@ -517,7 +517,7 @@ "response": { "uuid": "19cad1f2-9110-4271-98d4-1b968bf19410", "name": "Change Language", - "spec_version": "13.5.0", + "spec_version": "13.6.0", "language": "ara", "type": "messaging", "revision": 16, @@ -842,7 +842,7 @@ "response": { "uuid": "19cad1f2-9110-4271-98d4-1b968bf19410", "name": "Change Language", - "spec_version": "13.5.0", + "spec_version": "13.6.0", "language": "spa", "type": "messaging", "revision": 16, diff --git a/web/flow/testdata/inspect.json b/web/flow/testdata/inspect.json index f056fcf04..df6ccbf39 100644 --- a/web/flow/testdata/inspect.json +++ b/web/flow/testdata/inspect.json @@ -350,7 +350,7 @@ "flow": { "uuid": "8f107d42-7416-4cf2-9a51-9490361ad517", "name": "Invalid Flow", - "spec_version": "13.5.0", + "spec_version": "13.6.0", "language": "base", "type": "messaging", "revision": 1, diff --git a/web/flow/testdata/migrate.json b/web/flow/testdata/migrate.json index ed38e8383..dc8126953 100644 --- a/web/flow/testdata/migrate.json +++ b/web/flow/testdata/migrate.json @@ -83,7 +83,7 @@ } ], "revision": 1, - "spec_version": "13.5.0", + "spec_version": "13.6.0", "type": "messaging", "uuid": "42362831-f376-4df1-b6d9-a80b102821d9" } @@ -169,7 +169,7 @@ } ], "revision": 1, - "spec_version": "13.5.0", + "spec_version": "13.6.0", "type": "messaging", "uuid": "42362831-f376-4df1-b6d9-a80b102821d9" } diff --git a/web/po/testdata/import.json b/web/po/testdata/import.json index 62f8cb5ad..9dbf653ac 100644 --- a/web/po/testdata/import.json +++ b/web/po/testdata/import.json @@ -38,7 +38,7 @@ { "uuid": "9de3663f-c5c5-4c92-9f45-ecbc09abcc85", "name": "Favorites", - "spec_version": "13.5.0", + "spec_version": "13.6.0", "language": "und", "type": "messaging", "revision": 1, From a685c67a348e2581a827e4bb0f6f28afe667481f Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 3 Dec 2024 13:12:15 -0500 Subject: [PATCH 168/216] Update CHANGELOG.md for v9.3.54 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bc7215bd..d9c96420e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.54 (2024-12-03) +------------------------- + * Update to latest goflow that adds flow spec 13.6 + v9.3.53 (2024-12-02) ------------------------- * Update to latest goflow From 6f9fad7c812d6df6a474d847c367ef4295180a51 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 3 Dec 2024 14:39:19 -0500 Subject: [PATCH 169/216] Start writing new flow run path fields (path_nodes and path_times) --- core/models/runs.go | 69 ++++++++++++++++++++++++++++++++-------- core/models/runs_test.go | 67 ++++++++++++++++++++++++++++++++++++++ core/models/sessions.go | 30 +++-------------- 3 files changed, 127 insertions(+), 39 deletions(-) diff --git a/core/models/runs.go b/core/models/runs.go index df3d33707..12f81e5cf 100644 --- a/core/models/runs.go +++ b/core/models/runs.go @@ -7,6 +7,7 @@ import ( "time" "github.com/jmoiron/sqlx" + "github.com/lib/pq" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/events" @@ -48,8 +49,10 @@ type FlowRun struct { Responded bool `db:"responded"` Results string `db:"results"` Path string `db:"path"` + PathNodes pq.StringArray `db:"path_nodes"` + PathTimes pq.GenericArray `db:"path_times"` CurrentNodeUUID null.String `db:"current_node_uuid"` - ContactID flows.ContactID `db:"contact_id"` + ContactID ContactID `db:"contact_id"` FlowID FlowID `db:"flow_id"` OrgID OrgID `db:"org_id"` SessionID SessionID `db:"session_id"` @@ -67,25 +70,20 @@ type Step struct { ExitUUID flows.ExitUUID `json:"exit_uuid,omitempty"` } -const sqlInsertRun = ` -INSERT INTO -flows_flowrun(uuid, created_on, modified_on, exited_on, status, responded, results, path, - current_node_uuid, contact_id, flow_id, org_id, session_id, start_id) - VALUES(:uuid, :created_on, NOW(), :exited_on, :status, :responded, :results, :path, - :current_node_uuid, :contact_id, :flow_id, :org_id, :session_id, :start_id) -RETURNING id -` - -// newRun writes the passed in flow run to our database, also applying any events in those runs as -// appropriate. (IE, writing db messages etc..) +// newRun creates a flow run we can save to the database func newRun(ctx context.Context, tx *sqlx.Tx, oa *OrgAssets, session *Session, fr flows.Run) (*FlowRun, error) { // build our path elements path := make([]Step, len(fr.Path())) + pathNodes := make(pq.StringArray, len(fr.Path())) + pathTimes := make([]time.Time, len(fr.Path())) for i, p := range fr.Path() { path[i].UUID = p.UUID() path[i].NodeUUID = p.NodeUUID() path[i].ArrivedOn = p.ArrivedOn() path[i].ExitUUID = p.ExitUUID() + + pathNodes[i] = string(p.NodeUUID()) + pathTimes[i] = p.ArrivedOn() } flowID, err := FlowIDForUUID(ctx, tx, oa, fr.FlowReference().UUID) @@ -99,12 +97,14 @@ func newRun(ctx context.Context, tx *sqlx.Tx, oa *OrgAssets, session *Session, f CreatedOn: fr.CreatedOn(), ExitedOn: fr.ExitedOn(), ModifiedOn: fr.ModifiedOn(), - ContactID: fr.Contact().ID(), + ContactID: ContactID(fr.Contact().ID()), FlowID: flowID, OrgID: oa.OrgID(), SessionID: session.ID(), StartID: NilStartID, Path: string(jsonx.MustMarshal(path)), + PathNodes: pathNodes, + PathTimes: pq.GenericArray{A: pathTimes}, Results: string(jsonx.MustMarshal(fr.Results())), run: fr, @@ -125,6 +125,49 @@ func newRun(ctx context.Context, tx *sqlx.Tx, oa *OrgAssets, session *Session, f return r, nil } +const sqlInsertRun = ` +INSERT INTO +flows_flowrun(uuid, created_on, modified_on, exited_on, status, responded, results, path, path_nodes, path_times, + current_node_uuid, contact_id, flow_id, org_id, session_id, start_id) + VALUES(:uuid, :created_on, NOW(), :exited_on, :status, :responded, :results, :path, :path_nodes, :path_times, + :current_node_uuid, :contact_id, :flow_id, :org_id, :session_id, :start_id) +RETURNING id +` + +func InsertRuns(ctx context.Context, tx *sqlx.Tx, runs []*FlowRun) error { + if err := BulkQuery(ctx, "insert runs", tx, sqlInsertRun, runs); err != nil { + return fmt.Errorf("error inserting runs: %w", err) + } + return nil +} + +const sqlUpdateRun = ` +UPDATE + flows_flowrun fr +SET + status = r.status, + exited_on = r.exited_on::timestamptz, + responded = r.responded::bool, + results = r.results, + path = r.path::jsonb, + path_nodes = r.path_nodes::uuid[], + path_times = r.path_times::timestamptz[], + current_node_uuid = r.current_node_uuid::uuid, + modified_on = NOW() +FROM ( + VALUES(:uuid, :status, :exited_on, :responded, :results, :path, :path_nodes, :path_times, :current_node_uuid) +) AS + r(uuid, status, exited_on, responded, results, path, path_nodes, path_times, current_node_uuid) +WHERE + fr.uuid = r.uuid::uuid` + +func UpdateRuns(ctx context.Context, tx *sqlx.Tx, runs []*FlowRun) error { + if err := BulkQuery(ctx, "update runs", tx, sqlUpdateRun, runs); err != nil { + return fmt.Errorf("error updating runs: %w", err) + } + return nil +} + // GetContactIDsAtNode returns the ids of contacts currently waiting or active at the given flow node func GetContactIDsAtNode(ctx context.Context, rt *runtime.Runtime, orgID OrgID, nodeUUID flows.NodeUUID) ([]ContactID, error) { rows, err := rt.ReadonlyDB.QueryContext(ctx, diff --git a/core/models/runs_test.go b/core/models/runs_test.go index f71d8f9c9..4ec778d3a 100644 --- a/core/models/runs_test.go +++ b/core/models/runs_test.go @@ -2,7 +2,10 @@ package models_test import ( "testing" + "time" + "github.com/lib/pq" + "github.com/nyaruka/gocommon/dbutil/assertdb" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/testsuite" @@ -10,6 +13,70 @@ import ( "github.com/stretchr/testify/assert" ) +func TestInsertAndUpdateRuns(t *testing.T) { + ctx, rt := testsuite.Runtime() + + defer testsuite.Reset(testsuite.ResetData) + + sessionID := testdata.InsertFlowSession(rt, testdata.Org1, testdata.Cathy, models.FlowTypeMessaging, models.SessionStatusWaiting, testdata.Favorites, models.NilCallID) + + t1 := time.Date(2024, 12, 3, 14, 29, 30, 0, time.UTC) + t2 := time.Date(2024, 12, 3, 15, 13, 45, 0, time.UTC) + t3 := time.Date(2024, 12, 3, 16, 5, 15, 0, time.UTC) + + run := &models.FlowRun{ + UUID: "bdf93247-6629-4558-a016-433ec305757f", + Status: models.RunStatusWaiting, + CreatedOn: t1, + ModifiedOn: t2, + Responded: true, + Results: `{}`, + Path: `[]`, + PathNodes: []string{"1895cae0-d3c0-4470-83df-0b4cf9449438", "3ea3c026-e1c0-4950-bb94-d4c532b1459f"}, + PathTimes: pq.GenericArray{A: []interface{}{t1, t2}}, + CurrentNodeUUID: "5f0d8d24-0178-4b10-ae35-b3ccdc785777", + ContactID: testdata.Cathy.ID, + FlowID: testdata.Favorites.ID, + OrgID: testdata.Org1.ID, + SessionID: sessionID, + StartID: models.NilStartID, + } + + tx := rt.DB.MustBegin() + err := models.InsertRuns(ctx, tx, []*models.FlowRun{run}) + assert.NoError(t, err) + assert.NoError(t, tx.Commit()) + + assertdb.Query(t, rt.DB, "SELECT status, path_nodes[1]::text AS path_node1, path_nodes[2]::text AS path_node2, path_times[1]::timestamptz AS path_time1, path_times[2]::timestamptz AS path_time2 FROM flows_flowrun").Columns(map[string]any{ + "status": "W", + "path_node1": "1895cae0-d3c0-4470-83df-0b4cf9449438", + "path_node2": "3ea3c026-e1c0-4950-bb94-d4c532b1459f", + "path_time1": t1, + "path_time2": t2, + }) + + run.Status = models.RunStatusCompleted + run.ModifiedOn = t3 + run.ExitedOn = &t3 + run.PathNodes = []string{"1895cae0-d3c0-4470-83df-0b4cf9449438", "3ea3c026-e1c0-4950-bb94-d4c532b1459f", "5f0d8d24-0178-4b10-ae35-b3ccdc785777"} + run.PathTimes = pq.GenericArray{A: []interface{}{t1, t2, t3}} + + tx = rt.DB.MustBegin() + err = models.UpdateRuns(ctx, tx, []*models.FlowRun{run}) + assert.NoError(t, err) + assert.NoError(t, tx.Commit()) + + assertdb.Query(t, rt.DB, "SELECT status, path_nodes[1]::text AS path_node1, path_nodes[2]::text AS path_node2, path_nodes[3]::text AS path_node3, path_times[1]::timestamptz AS path_time1, path_times[2]::timestamptz AS path_time2, path_times[3]::timestamptz AS path_time3 FROM flows_flowrun").Columns(map[string]any{ + "status": "C", + "path_node1": "1895cae0-d3c0-4470-83df-0b4cf9449438", + "path_node2": "3ea3c026-e1c0-4950-bb94-d4c532b1459f", + "path_node3": "5f0d8d24-0178-4b10-ae35-b3ccdc785777", + "path_time1": t1, + "path_time2": t2, + "path_time3": t3, + }) +} + func TestGetContactIDsAtNode(t *testing.T) { ctx, rt := testsuite.Runtime() diff --git a/core/models/sessions.go b/core/models/sessions.go index 122dcbef2..f82130f6a 100644 --- a/core/models/sessions.go +++ b/core/models/sessions.go @@ -274,25 +274,6 @@ WHERE id = :id ` -const sqlUpdateRun = ` -UPDATE - flows_flowrun fr -SET - status = r.status, - exited_on = r.exited_on::timestamp with time zone, - responded = r.responded::bool, - results = r.results, - path = r.path::jsonb, - current_node_uuid = r.current_node_uuid::uuid, - modified_on = NOW() -FROM ( - VALUES(:uuid, :status, :exited_on, :responded, :results, :path, :current_node_uuid) -) AS - r(uuid, status, exited_on, responded, results, path, current_node_uuid) -WHERE - fr.uuid = r.uuid::uuid -` - // Update updates the session based on the state passed in from our engine session, this also takes care of applying any event hooks func (s *Session) Update(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, oa *OrgAssets, fs flows.Session, sprint flows.Sprint, contact *Contact, hook SessionCommitHook) error { // make sure we have our seen runs @@ -424,16 +405,13 @@ func (s *Session) Update(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, } // update all modified runs at once - err = BulkQuery(ctx, "update runs", tx, sqlUpdateRun, updatedRuns) - if err != nil { - slog.Error("error while updating runs for session", "error", err, "session", string(output)) - return fmt.Errorf("error updating runs: %w", err) + if err := UpdateRuns(ctx, tx, updatedRuns); err != nil { + return fmt.Errorf("error updating existing runs: %w", err) } // insert all new runs at once - err = BulkQuery(ctx, "insert runs", tx, sqlInsertRun, newRuns) - if err != nil { - return fmt.Errorf("error writing runs: %w", err) + if err := InsertRuns(ctx, tx, newRuns); err != nil { + return fmt.Errorf("error inserting new runs: %w", err) } if err := RecordFlowStatistics(ctx, rt, tx, []flows.Session{fs}, []flows.Sprint{sprint}); err != nil { From 7b75539848b5c0ed0963c61759fdad8c8b24b35e Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 3 Dec 2024 15:22:01 -0500 Subject: [PATCH 170/216] Consistently use timestampz instead of timestamp with time zone --- core/models/campaigns.go | 2 +- core/models/msgs.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/models/campaigns.go b/core/models/campaigns.go index c05eed716..61d54eb4f 100644 --- a/core/models/campaigns.go +++ b/core/models/campaigns.go @@ -343,7 +343,7 @@ const sqlMarkEventsFired = ` UPDATE campaigns_eventfire f SET - fired = r.fired::timestamp with time zone, + fired = r.fired::timestampz, fired_result = r.fired_result::varchar FROM ( VALUES(:fire_id, :fired, :fired_result) diff --git a/core/models/msgs.go b/core/models/msgs.go index 3707fd976..ca826669b 100644 --- a/core/models/msgs.go +++ b/core/models/msgs.go @@ -656,7 +656,7 @@ func MarkMessagesQueued(ctx context.Context, db DBorTx, msgs []*Msg) error { const sqlUpdateMsgStatus = ` UPDATE msgs_msg - SET status = m.status, next_attempt = m.next_attempt::timestamp with time zone + SET status = m.status, next_attempt = m.next_attempt::timestampz FROM (VALUES(:id, :status, :next_attempt)) AS m(id, status, next_attempt) WHERE msgs_msg.id = m.id::bigint` From b6c604031a4169482cc6b29261ee9ba160422c89 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 3 Dec 2024 15:43:04 -0500 Subject: [PATCH 171/216] Fix timestampz to timestamptz --- core/models/campaigns.go | 2 +- core/models/msgs.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/models/campaigns.go b/core/models/campaigns.go index 61d54eb4f..06736f6c1 100644 --- a/core/models/campaigns.go +++ b/core/models/campaigns.go @@ -343,7 +343,7 @@ const sqlMarkEventsFired = ` UPDATE campaigns_eventfire f SET - fired = r.fired::timestampz, + fired = r.fired::timestamptz, fired_result = r.fired_result::varchar FROM ( VALUES(:fire_id, :fired, :fired_result) diff --git a/core/models/msgs.go b/core/models/msgs.go index ca826669b..11a80a550 100644 --- a/core/models/msgs.go +++ b/core/models/msgs.go @@ -656,7 +656,7 @@ func MarkMessagesQueued(ctx context.Context, db DBorTx, msgs []*Msg) error { const sqlUpdateMsgStatus = ` UPDATE msgs_msg - SET status = m.status, next_attempt = m.next_attempt::timestampz + SET status = m.status, next_attempt = m.next_attempt::timestamptz FROM (VALUES(:id, :status, :next_attempt)) AS m(id, status, next_attempt) WHERE msgs_msg.id = m.id::bigint` From 26b67039c44e06d0d3790d51c942928999ebb3c1 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 4 Dec 2024 10:49:06 -0500 Subject: [PATCH 172/216] Update CHANGELOG.md for v9.3.55 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9c96420e..9aa8f407e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.55 (2024-12-04) +------------------------- + * Start writing new flow run path fields (path_nodes and path_times) + v9.3.54 (2024-12-03) ------------------------- * Update to latest goflow that adds flow spec 13.6 From dce3b1239c461955c651074b622db697d9ee54df Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 4 Dec 2024 18:02:14 -0500 Subject: [PATCH 173/216] Stop writing flowrun.path --- core/models/runs.go | 11 ++++------- core/models/runs_test.go | 1 - 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/core/models/runs.go b/core/models/runs.go index 12f81e5cf..fc5ceb3f8 100644 --- a/core/models/runs.go +++ b/core/models/runs.go @@ -48,7 +48,6 @@ type FlowRun struct { ExitedOn *time.Time `db:"exited_on"` Responded bool `db:"responded"` Results string `db:"results"` - Path string `db:"path"` PathNodes pq.StringArray `db:"path_nodes"` PathTimes pq.GenericArray `db:"path_times"` CurrentNodeUUID null.String `db:"current_node_uuid"` @@ -102,7 +101,6 @@ func newRun(ctx context.Context, tx *sqlx.Tx, oa *OrgAssets, session *Session, f OrgID: oa.OrgID(), SessionID: session.ID(), StartID: NilStartID, - Path: string(jsonx.MustMarshal(path)), PathNodes: pathNodes, PathTimes: pq.GenericArray{A: pathTimes}, Results: string(jsonx.MustMarshal(fr.Results())), @@ -127,9 +125,9 @@ func newRun(ctx context.Context, tx *sqlx.Tx, oa *OrgAssets, session *Session, f const sqlInsertRun = ` INSERT INTO -flows_flowrun(uuid, created_on, modified_on, exited_on, status, responded, results, path, path_nodes, path_times, +flows_flowrun(uuid, created_on, modified_on, exited_on, status, responded, results, path_nodes, path_times, current_node_uuid, contact_id, flow_id, org_id, session_id, start_id) - VALUES(:uuid, :created_on, NOW(), :exited_on, :status, :responded, :results, :path, :path_nodes, :path_times, + VALUES(:uuid, :created_on, NOW(), :exited_on, :status, :responded, :results, :path_nodes, :path_times, :current_node_uuid, :contact_id, :flow_id, :org_id, :session_id, :start_id) RETURNING id ` @@ -149,15 +147,14 @@ SET exited_on = r.exited_on::timestamptz, responded = r.responded::bool, results = r.results, - path = r.path::jsonb, path_nodes = r.path_nodes::uuid[], path_times = r.path_times::timestamptz[], current_node_uuid = r.current_node_uuid::uuid, modified_on = NOW() FROM ( - VALUES(:uuid, :status, :exited_on, :responded, :results, :path, :path_nodes, :path_times, :current_node_uuid) + VALUES(:uuid, :status, :exited_on, :responded, :results, :path_nodes, :path_times, :current_node_uuid) ) AS - r(uuid, status, exited_on, responded, results, path, path_nodes, path_times, current_node_uuid) + r(uuid, status, exited_on, responded, results, path_nodes, path_times, current_node_uuid) WHERE fr.uuid = r.uuid::uuid` diff --git a/core/models/runs_test.go b/core/models/runs_test.go index 4ec778d3a..e703c15e6 100644 --- a/core/models/runs_test.go +++ b/core/models/runs_test.go @@ -31,7 +31,6 @@ func TestInsertAndUpdateRuns(t *testing.T) { ModifiedOn: t2, Responded: true, Results: `{}`, - Path: `[]`, PathNodes: []string{"1895cae0-d3c0-4470-83df-0b4cf9449438", "3ea3c026-e1c0-4950-bb94-d4c532b1459f"}, PathTimes: pq.GenericArray{A: []interface{}{t1, t2}}, CurrentNodeUUID: "5f0d8d24-0178-4b10-ae35-b3ccdc785777", From b5276005d3fb27a9050431331f9e86de8e367eb9 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 5 Dec 2024 15:44:17 -0500 Subject: [PATCH 174/216] Update to latest goflow --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a10d5b34a..fbd313ad5 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 github.com/nyaruka/gocommon v1.59.3 - github.com/nyaruka/goflow v0.225.0 + github.com/nyaruka/goflow v0.225.1 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 github.com/nyaruka/rp-indexer/v9 v9.2.1 diff --git a/go.sum b/go.sum index 8bea9896a..1fb4b63f3 100644 --- a/go.sum +++ b/go.sum @@ -224,8 +224,8 @@ github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= github.com/nyaruka/gocommon v1.59.3 h1:fdjs9Z7aH+zog7FXlEpvJ0GtI6XNdNdBtjFxw5kVB7s= github.com/nyaruka/gocommon v1.59.3/go.mod h1:peOpluiVBMeQu81Ar+7EPQVT7vawN6ho9Kh1k/Gj8Vk= -github.com/nyaruka/goflow v0.225.0 h1:N3LF00hTaQNrKzjRF2knNQzjJY7Kc3i7lV2q2j7ovOo= -github.com/nyaruka/goflow v0.225.0/go.mod h1:1Up4YMccDCugTrVcXEbNCxDJqZRJindQR75oiMBCZvY= +github.com/nyaruka/goflow v0.225.1 h1:CIY2QVxMj6wG7KyBUn0nsxmo1mlR99wcH/gWvZrnOIg= +github.com/nyaruka/goflow v0.225.1/go.mod h1:1Up4YMccDCugTrVcXEbNCxDJqZRJindQR75oiMBCZvY= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= From 03c203e61147b47865324519a29e5fa7bc351f65 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 5 Dec 2024 16:00:13 -0500 Subject: [PATCH 175/216] Update CHANGELOG.md for v9.3.56 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aa8f407e..7d88c7097 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.56 (2024-12-05) +------------------------- + * Update to latest goflow + v9.3.55 (2024-12-04) ------------------------- * Start writing new flow run path fields (path_nodes and path_times) From 5e7d1b1de28cf42387c5cb3a6f4838bf0c1011e4 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 5 Dec 2024 16:02:12 -0500 Subject: [PATCH 176/216] Fix tests --- core/runner/runner_test.go | 4 ++-- core/tasks/starts/start_flow_batch_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/runner/runner_test.go b/core/runner/runner_test.go index 850a654d4..650c9272b 100644 --- a/core/runner/runner_test.go +++ b/core/runner/runner_test.go @@ -48,7 +48,7 @@ func TestStartFlowBatch(t *testing.T) { Returns(2) assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun WHERE contact_id = ANY($1) and flow_id = $2 AND responded = FALSE AND org_id = 1 AND status = 'C' - AND results IS NOT NULL AND path IS NOT NULL AND session_id IS NOT NULL`, pq.Array([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}), testdata.SingleMessage.ID). + AND results IS NOT NULL AND path_nodes IS NOT NULL AND session_id IS NOT NULL`, pq.Array([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}), testdata.SingleMessage.ID). Returns(2) assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_msg WHERE contact_id = ANY($1) AND text = 'Hey, how are you?' AND org_id = 1 AND status = 'Q' @@ -133,7 +133,7 @@ func TestResume(t *testing.T) { runQuery := `SELECT count(*) FROM flows_flowrun WHERE contact_id = $1 AND flow_id = $2 AND status = $3 AND responded = TRUE AND org_id = 1 AND current_node_uuid IS NOT NULL - AND json_array_length(path::json) = $4 AND session_id IS NOT NULL` + AND array_length(path_nodes, 1) = $4 AND session_id IS NOT NULL` assertdb.Query(t, rt.DB, runQuery, modelContact.ID(), flow.ID(), tc.RunStatus, tc.PathLength). Returns(1, "%d: didn't find expected run", i) diff --git a/core/tasks/starts/start_flow_batch_test.go b/core/tasks/starts/start_flow_batch_test.go index eb6fb5236..843881489 100644 --- a/core/tasks/starts/start_flow_batch_test.go +++ b/core/tasks/starts/start_flow_batch_test.go @@ -43,7 +43,7 @@ func TestStartFlowBatchTask(t *testing.T) { Returns(2) assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun WHERE contact_id = ANY($1) and flow_id = $2 AND responded = FALSE AND org_id = 1 AND status = 'C' - AND results IS NOT NULL AND path IS NOT NULL AND session_id IS NOT NULL`, pq.Array([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}), testdata.SingleMessage.ID). + AND results IS NOT NULL AND path_nodes IS NOT NULL AND session_id IS NOT NULL`, pq.Array([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}), testdata.SingleMessage.ID). Returns(2) assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_msg WHERE contact_id = ANY($1) AND text = 'Hey, how are you?' AND org_id = 1 AND status = 'Q' From 1ce2d6580c54504b6e9fcf81c8f4488422eb2122 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 9 Dec 2024 12:51:15 -0500 Subject: [PATCH 177/216] Update deps including goflow --- go.mod | 82 ++++++++++++++--------------- go.sum | 164 ++++++++++++++++++++++++++++----------------------------- 2 files changed, 123 insertions(+), 123 deletions(-) diff --git a/go.mod b/go.mod index fbd313ad5..c42cb553f 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,16 @@ module github.com/nyaruka/mailroom go 1.23 require ( - firebase.google.com/go/v4 v4.15.0 + firebase.google.com/go/v4 v4.15.1 github.com/Masterminds/semver v1.5.0 github.com/appleboy/go-fcm v1.2.1 - github.com/aws/aws-sdk-go-v2 v1.32.5 - github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.17 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.37.1 - github.com/aws/aws-sdk-go-v2/service/s3 v1.69.0 + github.com/aws/aws-sdk-go-v2 v1.32.6 + github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.20 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.38.0 + github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 github.com/buger/jsonparser v1.1.1 github.com/elastic/go-elasticsearch/v8 v8.16.0 - github.com/getsentry/sentry-go v0.29.1 + github.com/getsentry/sentry-go v0.30.0 github.com/go-chi/chi/v5 v5.1.0 github.com/go-playground/validator/v10 v10.23.0 github.com/golang-jwt/jwt v3.2.2+incompatible @@ -23,31 +23,31 @@ require ( github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 github.com/nyaruka/gocommon v1.59.3 - github.com/nyaruka/goflow v0.225.1 + github.com/nyaruka/goflow v0.225.2 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 github.com/nyaruka/rp-indexer/v9 v9.2.1 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_model v0.6.1 - github.com/prometheus/common v0.60.1 + github.com/prometheus/common v0.61.0 github.com/samber/slog-multi v1.2.4 github.com/samber/slog-sentry v1.2.2 github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.10.0 - google.golang.org/api v0.206.0 + google.golang.org/api v0.210.0 ) require ( - cel.dev/expr v0.18.0 // indirect + cel.dev/expr v0.19.1 // indirect cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/auth v0.10.2 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.5 // indirect + cloud.google.com/go/auth v0.12.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect cloud.google.com/go/compute/metadata v0.5.2 // indirect cloud.google.com/go/firestore v1.17.0 // indirect - cloud.google.com/go/iam v1.2.2 // indirect - cloud.google.com/go/longrunning v0.6.2 // indirect - cloud.google.com/go/monitoring v1.21.2 // indirect - cloud.google.com/go/storage v1.47.0 // indirect + cloud.google.com/go/iam v1.3.0 // indirect + cloud.google.com/go/longrunning v0.6.3 // indirect + cloud.google.com/go/monitoring v1.22.0 // indirect + cloud.google.com/go/storage v1.48.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect @@ -55,22 +55,22 @@ require ( github.com/Shopify/gomail v0.0.0-20220729171026-0784ece65e69 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect - github.com/aws/aws-sdk-go-v2/config v1.28.5 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.46 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 // indirect + github.com/aws/aws-sdk-go-v2/config v1.28.6 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.47 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 // indirect - github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.6 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.8 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 // indirect github.com/aws/smithy-go v1.22.1 // indirect github.com/blevesearch/segment v0.9.1 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect @@ -88,7 +88,7 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/s2a-go v0.1.8 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect @@ -115,19 +115,19 @@ require ( go.opentelemetry.io/otel/sdk v1.32.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect - golang.org/x/crypto v0.29.0 // indirect - golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect - golang.org/x/net v0.31.0 // indirect + golang.org/x/crypto v0.30.0 // indirect + golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect + golang.org/x/net v0.32.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect - golang.org/x/sync v0.9.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.20.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.8.0 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect - google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect - google.golang.org/grpc v1.68.0 // indirect + google.golang.org/genproto v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/grpc v1.68.1 // indirect google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3 // indirect google.golang.org/protobuf v1.35.2 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect diff --git a/go.sum b/go.sum index 1fb4b63f3..01a4c3819 100644 --- a/go.sum +++ b/go.sum @@ -1,32 +1,32 @@ -cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo= -cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= +cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= -cloud.google.com/go/auth v0.10.2 h1:oKF7rgBfSHdp/kuhXtqU/tNDr0mZqhYbEh+6SiqzkKo= -cloud.google.com/go/auth v0.10.2/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= -cloud.google.com/go/auth/oauth2adapt v0.2.5 h1:2p29+dePqsCHPP1bqDJcKj4qxRyYCcbzKpFyKGt3MTk= -cloud.google.com/go/auth/oauth2adapt v0.2.5/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= +cloud.google.com/go/auth v0.12.0 h1:ARAD8r0lkiHw2go7kEnmviF6TOYhzLM+yDGcDt9mP68= +cloud.google.com/go/auth v0.12.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= +cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= cloud.google.com/go/firestore v1.17.0 h1:iEd1LBbkDZTFsLw3sTH50eyg4qe8eoG6CjocmEXO9aQ= cloud.google.com/go/firestore v1.17.0/go.mod h1:69uPx1papBsY8ZETooc71fOhoKkD70Q1DwMrtKuOT/Y= -cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= -cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= +cloud.google.com/go/iam v1.3.0 h1:4Wo2qTaGKFtajbLpF6I4mywg900u3TLlHDb6mriLDPU= +cloud.google.com/go/iam v1.3.0/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk= cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= -cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= -cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= -cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU= -cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= -cloud.google.com/go/storage v1.47.0 h1:ajqgt30fnOMmLfWfu1PWcb+V9Dxz6n+9WKjdNg5R4HM= -cloud.google.com/go/storage v1.47.0/go.mod h1:Ks0vP374w0PW6jOUameJbapbQKXqkjGd/OJRp2fb9IQ= +cloud.google.com/go/longrunning v0.6.3 h1:A2q2vuyXysRcwzqDpMMLSI6mb6o39miS52UEG/Rd2ng= +cloud.google.com/go/longrunning v0.6.3/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +cloud.google.com/go/monitoring v1.22.0 h1:mQ0040B7dpuRq1+4YiQD43M2vW9HgoVxY98xhqGT+YI= +cloud.google.com/go/monitoring v1.22.0/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= +cloud.google.com/go/storage v1.48.0 h1:FhBDHACbVtdPx7S/AbcKujPWiHvfO6F8OXGgCEbB2+o= +cloud.google.com/go/storage v1.48.0/go.mod h1:aFoDYNMAjv67lp+xcuZqjUKv/ctmplzQ3wJgodA7b+M= cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI= cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -firebase.google.com/go/v4 v4.15.0 h1:k27M+cHbyN1YpBI2Cf4NSjeHnnYRB9ldXwpqA5KikN0= -firebase.google.com/go/v4 v4.15.0/go.mod h1:S/4MJqVZn1robtXkHhpRUbwOC4gdYtgsiMMJQ4x+xmQ= +firebase.google.com/go/v4 v4.15.1 h1:tR2dzKw1MIfCfG2bhAyxa5KQ57zcE7iFKmeYClET6ZM= +firebase.google.com/go/v4 v4.15.1/go.mod h1:eunxbsh4UXI2rA8po3sOiebvWYuW0DVxAdZFO0I6wdY= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= @@ -46,48 +46,48 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/appleboy/go-fcm v1.2.1 h1:NhpACabtRuAplYg6bTNfSr3LBwsSuutP55HsphzLU/g= github.com/appleboy/go-fcm v1.2.1/go.mod h1:5FzMN+9J2sxnkoys9h3y48GQH8HnI637Q/ro/uP2Qsk= -github.com/aws/aws-sdk-go-v2 v1.32.5 h1:U8vdWJuY7ruAkzaOdD7guwJjD06YSKmnKCJs7s3IkIo= -github.com/aws/aws-sdk-go-v2 v1.32.5/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= +github.com/aws/aws-sdk-go-v2 v1.32.6 h1:7BokKRgRPuGmKkFMhEg/jSul+tB9VvXhcViILtfG8b4= +github.com/aws/aws-sdk-go-v2 v1.32.6/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= -github.com/aws/aws-sdk-go-v2/config v1.28.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0= -github.com/aws/aws-sdk-go-v2/config v1.28.5/go.mod h1:4VsPbHP8JdcdUDmbTVgNL/8w9SqOkM5jyY8ljIxLO3o= -github.com/aws/aws-sdk-go-v2/credentials v1.17.46 h1:AU7RcriIo2lXjUfHFnFKYsLCwgbz1E7Mm95ieIRDNUg= -github.com/aws/aws-sdk-go-v2/credentials v1.17.46/go.mod h1:1FmYyLGL08KQXQ6mcTlifyFXfJVCNJTVGuQP4m0d/UA= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.17 h1:36xxDfD/hD9cMBjANIBSr+kZ0/+IYKHql4KPGN/DvM4= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.17/go.mod h1:A4XQVRy4yJ70Sk5Qz2tuCQX6J5kXcRa53nGP6wtgntM= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 h1:sDSXIrlsFSFJtWKLQS4PUWRvrT580rrnuLydJrCQ/yA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20/go.mod h1:WZ/c+w0ofps+/OUqMwWgnfrgzZH1DZO1RIkktICsqnY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 h1:4usbeaes3yJnCFC7kfeyhkdkPtoRYPa/hTmCqMpKpLI= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24/go.mod h1:5CI1JemjVwde8m2WG3cz23qHKPOxbpkq0HaoreEgLIY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 h1:N1zsICrQglfzaBnrfM0Ys00860C+QFwu6u/5+LomP+o= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24/go.mod h1:dCn9HbJ8+K31i8IQ8EWmWj0EiIk0+vKiHNMxTTYveAg= +github.com/aws/aws-sdk-go-v2/config v1.28.6 h1:D89IKtGrs/I3QXOLNTH93NJYtDhm8SYa9Q5CsPShmyo= +github.com/aws/aws-sdk-go-v2/config v1.28.6/go.mod h1:GDzxJ5wyyFSCoLkS+UhGB0dArhb9mI+Co4dHtoTxbko= +github.com/aws/aws-sdk-go-v2/credentials v1.17.47 h1:48bA+3/fCdi2yAwVt+3COvmatZ6jUDNkDTIsqDiMUdw= +github.com/aws/aws-sdk-go-v2/credentials v1.17.47/go.mod h1:+KdckOejLW3Ks3b0E3b5rHsr2f9yuORBum0WPnE5o5w= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.20 h1:bwHhhCScKRAYJtaWVT+jDpt74GybN2nxI6+InkRjqGM= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.20/go.mod h1:/RfYH8CUMQuq/3CIEVGHLkqkA9KtbBF5omt2Ae8xc0s= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 h1:AmoU1pziydclFT/xRV+xXE/Vb8fttJCLRPv8oAkprc0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21/go.mod h1:AjUdLYe4Tgs6kpH4Bv7uMZo7pottoyHMn4eTcIcneaY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 h1:s/fF4+yDQDoElYhfIVvSNyeCydfbuTKzhxSXDXCPasU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25/go.mod h1:IgPfDv5jqFIzQSNbUEMoitNooSMXjRSDkhXv8jiROvU= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 h1:ZntTCl5EsYnhN/IygQEUugpdwbhdkom9uHcbCftiGgA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25/go.mod h1:DBdPrgeocww+CSl1C8cEV8PN1mHMBhuCDLpXezyvWkE= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 h1:JX70yGKLj25+lMC5Yyh8wBtvB01GDilyRuJvXJ4piD0= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24/go.mod h1:+Ln60j9SUTD0LEwnhEB0Xhg61DHqplBrbZpLgyjoEHg= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.37.1 h1:vucMirlM6D+RDU8ncKaSZ/5dGrXNajozVwpmWNPn2gQ= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.37.1/go.mod h1:fceORfs010mNxZbQhfqUjUeHlTwANmIT4mvHamuUaUg= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.6 h1:hIl7Z1zcfdzsl5SiV32acFj4gY/cZ5Xr9wd6PpoNYGE= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.6/go.mod h1:VswWf/9ztSHHnMP3SMtGqrFOooVXI6NTDNjTcyLQ2HY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 h1:r67ps7oHCYnflpgDy2LZU0MAQtQbYIOqNNnqGO6xQkE= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25/go.mod h1:GrGY+Q4fIokYLtjCVB/aFfCVL6hhGUFl8inD18fDalE= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.38.0 h1:isKhHsjpQR3CypQJ4G1g8QWx7zNpiC/xKw1zjgJYVno= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.38.0/go.mod h1:xDvUyIkwBwNtVZJdHEwAuhFly3mezwdEWkbJ5oNYwIw= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.8 h1:ntqHwZb+ZyVz0CFYUG0sQ02KMMJh+iXeV3bXoba+s4A= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.8/go.mod h1:Hcjb2SiUo9v1GhpXjRNW7hAwfzAPfrsgnlKpP5UYEPY= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 h1:gvZOjQKPxFXy1ft3QnEyXmT+IqneM9QAUWlM3r0mfqw= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5/go.mod h1:DLWnfvIcm9IET/mmjdxeXbBKmTCm0ZB8p1za9BVteM8= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.5 h1:3Y457U2eGukmjYjeHG6kanZpDzJADa2m0ADqnuePYVQ= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.5/go.mod h1:CfwEHGkTjYZpkQ/5PvcbEtT7AJlG68KkEvmtwU8z3/U= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 h1:wtpJ4zcwrSbwhECWQoI/g6WM9zqCcSpHDJIWSbMLOu4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5/go.mod h1:qu/W9HXQbbQ4+1+JcZp0ZNPV31ym537ZJN+fiS7Ti8E= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 h1:P1doBzv5VEg1ONxnJss1Kh5ZG/ewoIE4MQtKKc6Crgg= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5/go.mod h1:NOP+euMW7W3Ukt28tAxPuoWao4rhhqJD3QEBk7oCg7w= -github.com/aws/aws-sdk-go-v2/service/s3 v1.69.0 h1:Q2ax8S21clKOnHhhr933xm3JxdJebql+R7aNo7p7GBQ= -github.com/aws/aws-sdk-go-v2/service/s3 v1.69.0/go.mod h1:ralv4XawHjEMaHOWnTFushl0WRqim/gQWesAMF6hTow= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 h1:3zu537oLmsPfDMyjnUS2g+F2vITgy5pB74tHI+JBNoM= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.6/go.mod h1:WJSZH2ZvepM6t6jwu4w/Z45Eoi75lPN7DcydSRtJg6Y= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 h1:K0OQAsDywb0ltlFrZm0JHPY3yZp/S9OaoLU33S7vPS8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5/go.mod h1:ORITg+fyuMoeiQFiVGoqB3OydVTLkClw/ljbblMq6Cc= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 h1:6SZUVRQNvExYlMLbHdlKB48x0fLbc2iVROyaNEwBHbU= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.1/go.mod h1:GqWyYCwLXnlUB1lOAXQyNSPqPLQJvmo8J0DWBzp9mtg= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 h1:HCpPsWqmYQieU7SS6E9HXfdAMSud0pteVXieJmcpIRI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6/go.mod h1:ngUiVRCco++u+soRRVBIvBZxSMMvOVMXA4PJ36JLfSw= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.6 h1:nbmKXZzXPJn41CcD4HsHsGWqvKjLKz9kWu6XxvLmf1s= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.6/go.mod h1:SJhcisfKfAawsdNQoZMBEjg+vyN2lH6rO6fP+T94z5Y= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 h1:50+XsN70RS7dwJ2CkVNXzj7U2L1HKP8nqTd3XWEXBN4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6/go.mod h1:WqgLmwY7so32kG01zD8CPTJWVWM+TzJoOVHwTg4aPug= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 h1:BbGDtTi0T1DYlmjBiCr/le3wzhA37O8QTC5/Ab8+EXk= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6/go.mod h1:hLMJt7Q8ePgViKupeymbqI0la+t9/iYFBjxQCFwuAwI= +github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 h1:nyuzXooUNJexRT0Oy0UQY6AhOzxPxhtt4DcBIHyCnmw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0/go.mod h1:sT/iQz8JK3u/5gZkT+Hmr7GzVZehUMkRZpOaAwYXeGY= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 h1:rLnYAfXQ3YAccocshIH5mzNNwZBkBo+bP6EhIxak6Hw= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.7/go.mod h1:ZHtuQJ6t9A/+YDuxOLnbryAmITtr8UysSny3qcyvJTc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 h1:JnhTZR3PiYDNKlXy50/pNeix9aGMo6lLpXwJ1mw8MD4= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6/go.mod h1:URronUEGfXZN1VpdktPSD1EkAL9mfrV+2F4sjH38qOY= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 h1:s4074ZO1Hk8qv65GqNXqDjmkf4HSQqJukaLuuW0TpDA= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.2/go.mod h1:mVggCnIWoM09jP71Wh+ea7+5gAp53q+49wDFs1SW5z8= github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= @@ -124,8 +124,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA= github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU= -github.com/getsentry/sentry-go v0.29.1 h1:DyZuChN8Hz3ARxGVV8ePaNXh1dQ7d76AiB117xcREwA= -github.com/getsentry/sentry-go v0.29.1/go.mod h1:x3AtIzN01d6SiWkderzaH28Tm0lgkafpJ5Bm3li39O0= +github.com/getsentry/sentry-go v0.30.0 h1:lWUwDnY7sKHaVIoZ9wYqRHJ5iEmoc0pqcRqFkosKzBo= +github.com/getsentry/sentry-go v0.30.0/go.mod h1:WU9B9/1/sHDqeV8T+3VwwbjeR5MSXs/6aqG3mqZrezA= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -152,8 +152,8 @@ github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQg github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -224,8 +224,8 @@ github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= github.com/nyaruka/gocommon v1.59.3 h1:fdjs9Z7aH+zog7FXlEpvJ0GtI6XNdNdBtjFxw5kVB7s= github.com/nyaruka/gocommon v1.59.3/go.mod h1:peOpluiVBMeQu81Ar+7EPQVT7vawN6ho9Kh1k/Gj8Vk= -github.com/nyaruka/goflow v0.225.1 h1:CIY2QVxMj6wG7KyBUn0nsxmo1mlR99wcH/gWvZrnOIg= -github.com/nyaruka/goflow v0.225.1/go.mod h1:1Up4YMccDCugTrVcXEbNCxDJqZRJindQR75oiMBCZvY= +github.com/nyaruka/goflow v0.225.2 h1:v91XjD94qd/1v437AFU80T8eYJjtRCMfrYPWJKySqrg= +github.com/nyaruka/goflow v0.225.2/go.mod h1:1Up4YMccDCugTrVcXEbNCxDJqZRJindQR75oiMBCZvY= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= @@ -251,8 +251,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= -github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= +github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= +github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= @@ -298,11 +298,11 @@ go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= +golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= -golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= +golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0= +golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -316,8 +316,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -325,8 +325,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -335,16 +335,16 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -356,8 +356,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.206.0 h1:A27GClesCSheW5P2BymVHjpEeQ2XHH8DI8Srs2HI2L8= -google.golang.org/api v0.206.0/go.mod h1:BtB8bfjTYIrai3d8UyvPmV9REGgox7coh+ZRwm0b+W8= +google.golang.org/api v0.210.0 h1:HMNffZ57OoZCRYSbdWVRoqOa8V8NIHLL0CzdBPLztWk= +google.golang.org/api v0.210.0/go.mod h1:B9XDZGnx2NtyjzVkOVTGrFSAVZgPcbedzKg/gTLwqBs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw= @@ -365,19 +365,19 @@ google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= -google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= -google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 h1:pgr/4QbFyktUv9CtQ/Fq4gzEE6/Xs7iCXbktaGzLHbQ= -google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto v0.0.0-20241209162323-e6fa225c2576 h1:k48HcZ4FE6in0o8IflZCkc1lTc2u37nhGd8P+fo4r24= +google.golang.org/genproto v0.0.0-20241209162323-e6fa225c2576/go.mod h1:DV2u3tCn/AcVjjmGYZKt6HyvY4w4y3ipAdHkMbe/0i4= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= -google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= +google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3 h1:hUfOButuEtpc0UvYiaYRbNwxVYr0mQQOWq6X8beJ9Gc= google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3/go.mod h1:jzYlkSMbKypzuu6xoAEijsNVo9ZeDF1u/zCfFgsx7jg= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= From d71f8b190f5552184b58076cb8172ca3836b5f6b Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 9 Dec 2024 13:09:42 -0500 Subject: [PATCH 178/216] Update CHANGELOG.md for v9.3.57 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d88c7097..7a8010a62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.57 (2024-12-09) +------------------------- + * Update deps including goflow + v9.3.56 (2024-12-05) ------------------------- * Update to latest goflow From bae638ffc1aa2723e405b67befade7f4b51376c1 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 9 Dec 2024 15:06:04 -0500 Subject: [PATCH 179/216] Update goflow --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c42cb553f..38b8fd634 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 github.com/nyaruka/gocommon v1.59.3 - github.com/nyaruka/goflow v0.225.2 + github.com/nyaruka/goflow v0.225.3 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 github.com/nyaruka/rp-indexer/v9 v9.2.1 diff --git a/go.sum b/go.sum index 01a4c3819..fcf405e18 100644 --- a/go.sum +++ b/go.sum @@ -224,8 +224,8 @@ github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= github.com/nyaruka/gocommon v1.59.3 h1:fdjs9Z7aH+zog7FXlEpvJ0GtI6XNdNdBtjFxw5kVB7s= github.com/nyaruka/gocommon v1.59.3/go.mod h1:peOpluiVBMeQu81Ar+7EPQVT7vawN6ho9Kh1k/Gj8Vk= -github.com/nyaruka/goflow v0.225.2 h1:v91XjD94qd/1v437AFU80T8eYJjtRCMfrYPWJKySqrg= -github.com/nyaruka/goflow v0.225.2/go.mod h1:1Up4YMccDCugTrVcXEbNCxDJqZRJindQR75oiMBCZvY= +github.com/nyaruka/goflow v0.225.3 h1:NUK6o8sBIUSz7fvc0Mz/jqpIJLcKwwvUtldUY11OlcU= +github.com/nyaruka/goflow v0.225.3/go.mod h1:1Up4YMccDCugTrVcXEbNCxDJqZRJindQR75oiMBCZvY= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= From 7322013777685eba6cbfee2e0dbf8a8cc83539f3 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 9 Dec 2024 15:17:30 -0500 Subject: [PATCH 180/216] Update goflow which has new 13.6.1 flow migration --- core/goflow/flows_test.go | 14 ++++++++--- core/goflow/testdata/migrate/13.0.0.json | 2 +- core/goflow/testdata/migrate/13.1.0.json | 2 +- core/goflow/testdata/migrate/13.2.0.json | 2 +- core/goflow/testdata/migrate/13.3.0.json | 2 +- core/goflow/testdata/migrate/13.4.0.json | 2 +- core/goflow/testdata/migrate/13.5.0.json | 2 +- core/goflow/testdata/migrate/13.6.0.json | 2 +- core/goflow/testdata/migrate/13.6.1.json | 31 ++++++++++++++++++++++++ web/flow/testdata/change_language.json | 6 ++--- web/flow/testdata/migrate.json | 4 +-- web/po/testdata/import.json | 2 +- 12 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 core/goflow/testdata/migrate/13.6.1.json diff --git a/core/goflow/flows_test.go b/core/goflow/flows_test.go index bf81891b5..f9c965645 100644 --- a/core/goflow/flows_test.go +++ b/core/goflow/flows_test.go @@ -16,7 +16,7 @@ import ( ) func TestSpecVersion(t *testing.T) { - assert.Equal(t, semver.MustParse("13.6.0"), goflow.SpecVersion()) + assert.Equal(t, semver.MustParse("13.6.1"), goflow.SpecVersion()) } func TestReadFlow(t *testing.T) { @@ -65,6 +65,7 @@ func TestMigrateDefinition(t *testing.T) { v13_4_0 := testsuite.ReadFile("testdata/migrate/13.4.0.json") v13_5_0 := testsuite.ReadFile("testdata/migrate/13.5.0.json") v13_6_0 := testsuite.ReadFile("testdata/migrate/13.6.0.json") + v13_6_1 := testsuite.ReadFile("testdata/migrate/13.6.1.json") // 13.0 > 13.1 migrated, err := goflow.MigrateDefinition(rt.Config, v13_0_0, semver.MustParse("13.1.0")) @@ -96,8 +97,13 @@ func TestMigrateDefinition(t *testing.T) { assert.NoError(t, err) test.AssertEqualJSON(t, v13_6_0, migrated) - // 13.0 > 13.6 - migrated, err = goflow.MigrateDefinition(rt.Config, v13_0_0, semver.MustParse("13.6.0")) + // 13.6 > 13.6.1 + migrated, err = goflow.MigrateDefinition(rt.Config, migrated, semver.MustParse("13.6.1")) assert.NoError(t, err) - test.AssertEqualJSON(t, v13_6_0, migrated) + test.AssertEqualJSON(t, v13_6_1, migrated) + + // 13.0 > 13.6.1 + migrated, err = goflow.MigrateDefinition(rt.Config, v13_0_0, semver.MustParse("13.6.1")) + assert.NoError(t, err) + test.AssertEqualJSON(t, v13_6_1, migrated) } diff --git a/core/goflow/testdata/migrate/13.0.0.json b/core/goflow/testdata/migrate/13.0.0.json index ad551db60..2d8c16018 100644 --- a/core/goflow/testdata/migrate/13.0.0.json +++ b/core/goflow/testdata/migrate/13.0.0.json @@ -11,7 +11,7 @@ { "uuid": "82a1de5f-af1a-45ef-8511-4d60c160e486", "type": "send_msg", - "text": "Hello @webhook", + "text": "Hello @webhook from @results.this_is_a_reference_to_a_result_whose_name_will_have_been_truncated", "templating": { "template": { "uuid": "641b8b05-082a-497e-bf63-38aa48b1f0c4", diff --git a/core/goflow/testdata/migrate/13.1.0.json b/core/goflow/testdata/migrate/13.1.0.json index 9c8d7c0b5..2405d8246 100644 --- a/core/goflow/testdata/migrate/13.1.0.json +++ b/core/goflow/testdata/migrate/13.1.0.json @@ -11,7 +11,7 @@ { "uuid": "82a1de5f-af1a-45ef-8511-4d60c160e486", "type": "send_msg", - "text": "Hello @webhook", + "text": "Hello @webhook from @results.this_is_a_reference_to_a_result_whose_name_will_have_been_truncated", "templating": { "uuid": "1ae96956-4b34-433e-8d1a-f05fe6923d6d", "template": { diff --git a/core/goflow/testdata/migrate/13.2.0.json b/core/goflow/testdata/migrate/13.2.0.json index f6fc1d3af..95359140f 100644 --- a/core/goflow/testdata/migrate/13.2.0.json +++ b/core/goflow/testdata/migrate/13.2.0.json @@ -11,7 +11,7 @@ { "uuid": "82a1de5f-af1a-45ef-8511-4d60c160e486", "type": "send_msg", - "text": "Hello @webhook", + "text": "Hello @webhook from @results.this_is_a_reference_to_a_result_whose_name_will_have_been_truncated", "templating": { "uuid": "1ae96956-4b34-433e-8d1a-f05fe6923d6d", "template": { diff --git a/core/goflow/testdata/migrate/13.3.0.json b/core/goflow/testdata/migrate/13.3.0.json index e0068b831..9e4aaabc9 100644 --- a/core/goflow/testdata/migrate/13.3.0.json +++ b/core/goflow/testdata/migrate/13.3.0.json @@ -11,7 +11,7 @@ { "uuid": "82a1de5f-af1a-45ef-8511-4d60c160e486", "type": "send_msg", - "text": "Hello @webhook.json", + "text": "Hello @webhook.json from @results.this_is_a_reference_to_a_result_whose_name_will_have_been_truncated", "templating": { "uuid": "1ae96956-4b34-433e-8d1a-f05fe6923d6d", "template": { diff --git a/core/goflow/testdata/migrate/13.4.0.json b/core/goflow/testdata/migrate/13.4.0.json index d01d5c85e..11e6fb7f9 100644 --- a/core/goflow/testdata/migrate/13.4.0.json +++ b/core/goflow/testdata/migrate/13.4.0.json @@ -11,7 +11,7 @@ { "uuid": "82a1de5f-af1a-45ef-8511-4d60c160e486", "type": "send_msg", - "text": "Hello @webhook.json", + "text": "Hello @webhook.json from @results.this_is_a_reference_to_a_result_whose_name_will_have_been_truncated", "templating": { "template": { "uuid": "641b8b05-082a-497e-bf63-38aa48b1f0c4", diff --git a/core/goflow/testdata/migrate/13.5.0.json b/core/goflow/testdata/migrate/13.5.0.json index 75eaacbde..2d35ea1f8 100644 --- a/core/goflow/testdata/migrate/13.5.0.json +++ b/core/goflow/testdata/migrate/13.5.0.json @@ -11,7 +11,7 @@ { "uuid": "82a1de5f-af1a-45ef-8511-4d60c160e486", "type": "send_msg", - "text": "Hello @webhook.json", + "text": "Hello @webhook.json from @results.this_is_a_reference_to_a_result_whose_name_will_have_been_truncated", "template": { "uuid": "641b8b05-082a-497e-bf63-38aa48b1f0c4", "name": "welcome" diff --git a/core/goflow/testdata/migrate/13.6.0.json b/core/goflow/testdata/migrate/13.6.0.json index 08c082e0e..e32de9994 100644 --- a/core/goflow/testdata/migrate/13.6.0.json +++ b/core/goflow/testdata/migrate/13.6.0.json @@ -11,7 +11,7 @@ { "uuid": "82a1de5f-af1a-45ef-8511-4d60c160e486", "type": "send_msg", - "text": "Hello @webhook.json", + "text": "Hello @webhook.json from @results.this_is_a_reference_to_a_result_whose_name_will_have_been_truncated", "template": { "uuid": "641b8b05-082a-497e-bf63-38aa48b1f0c4", "name": "welcome" diff --git a/core/goflow/testdata/migrate/13.6.1.json b/core/goflow/testdata/migrate/13.6.1.json new file mode 100644 index 000000000..4abf2ac46 --- /dev/null +++ b/core/goflow/testdata/migrate/13.6.1.json @@ -0,0 +1,31 @@ +{ + "uuid": "502c3ee4-3249-4dee-8e71-c62070667d52", + "name": "New", + "spec_version": "13.6.1", + "type": "messaging", + "language": "und", + "nodes": [ + { + "uuid": "d26486b1-193d-4512-85f0-c6db696f1e1c", + "actions": [ + { + "uuid": "82a1de5f-af1a-45ef-8511-4d60c160e486", + "type": "send_msg", + "text": "Hello @webhook.json from @results.this_is_a_reference_to_a_result_whose_name_will_have_been_trunca", + "template": { + "uuid": "641b8b05-082a-497e-bf63-38aa48b1f0c4", + "name": "welcome" + }, + "template_variables": [ + "@contact.name" + ] + } + ], + "exits": [ + { + "uuid": "fdd370e0-ffa9-48b3-8148-b9241d74fc72" + } + ] + } + ] +} \ No newline at end of file diff --git a/web/flow/testdata/change_language.json b/web/flow/testdata/change_language.json index 9f25d0d42..56a260733 100644 --- a/web/flow/testdata/change_language.json +++ b/web/flow/testdata/change_language.json @@ -171,7 +171,7 @@ "response": { "uuid": "19cad1f2-9110-4271-98d4-1b968bf19410", "name": "Change Language", - "spec_version": "13.6.0", + "spec_version": "13.6.1", "language": "kin", "type": "messaging", "revision": 16, @@ -517,7 +517,7 @@ "response": { "uuid": "19cad1f2-9110-4271-98d4-1b968bf19410", "name": "Change Language", - "spec_version": "13.6.0", + "spec_version": "13.6.1", "language": "ara", "type": "messaging", "revision": 16, @@ -842,7 +842,7 @@ "response": { "uuid": "19cad1f2-9110-4271-98d4-1b968bf19410", "name": "Change Language", - "spec_version": "13.6.0", + "spec_version": "13.6.1", "language": "spa", "type": "messaging", "revision": 16, diff --git a/web/flow/testdata/migrate.json b/web/flow/testdata/migrate.json index dc8126953..42ea99de5 100644 --- a/web/flow/testdata/migrate.json +++ b/web/flow/testdata/migrate.json @@ -83,7 +83,7 @@ } ], "revision": 1, - "spec_version": "13.6.0", + "spec_version": "13.6.1", "type": "messaging", "uuid": "42362831-f376-4df1-b6d9-a80b102821d9" } @@ -169,7 +169,7 @@ } ], "revision": 1, - "spec_version": "13.6.0", + "spec_version": "13.6.1", "type": "messaging", "uuid": "42362831-f376-4df1-b6d9-a80b102821d9" } diff --git a/web/po/testdata/import.json b/web/po/testdata/import.json index 9dbf653ac..a849fdd80 100644 --- a/web/po/testdata/import.json +++ b/web/po/testdata/import.json @@ -38,7 +38,7 @@ { "uuid": "9de3663f-c5c5-4c92-9f45-ecbc09abcc85", "name": "Favorites", - "spec_version": "13.6.0", + "spec_version": "13.6.1", "language": "und", "type": "messaging", "revision": 1, From 9c2550dcfaff7f94f72bd3385459f4a9b5ee50fb Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 9 Dec 2024 15:46:04 -0500 Subject: [PATCH 181/216] Update CHANGELOG.md for v9.3.58 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a8010a62..bf0d58210 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.58 (2024-12-09) +------------------------- + * Update goflow which has new 13.6.1 flow migration + v9.3.57 (2024-12-09) ------------------------- * Update deps including goflow From 92e3ec96c5af1287be03c742a1c2fadcbfcad1b0 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 9 Dec 2024 17:01:50 -0500 Subject: [PATCH 182/216] Always migrate to latest patch version --- core/goflow/flows.go | 5 +++++ core/goflow/flows_test.go | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/core/goflow/flows.go b/core/goflow/flows.go index f8908489b..3082a8524 100644 --- a/core/goflow/flows.go +++ b/core/goflow/flows.go @@ -53,6 +53,11 @@ func CloneDefinition(data []byte, depMapping map[uuids.UUID]uuids.UUID) ([]byte, // MigrateDefinition migrates the given flow definition to the specified version func MigrateDefinition(cfg *runtime.Config, data []byte, toVersion *semver.Version) ([]byte, error) { + // if requested version only differs by patch from current version, use current version + if toVersion == nil || (toVersion.LessThan(definition.CurrentSpecVersion) && toVersion.Major() == definition.CurrentSpecVersion.Major() && toVersion.Minor() == definition.CurrentSpecVersion.Minor()) { + toVersion = definition.CurrentSpecVersion + } + f, err := migrations.MigrateToVersion(data, toVersion, MigrationConfig(cfg)) if err != nil { return nil, &FlowDefError{cause: err} diff --git a/core/goflow/flows_test.go b/core/goflow/flows_test.go index f9c965645..e3c33132b 100644 --- a/core/goflow/flows_test.go +++ b/core/goflow/flows_test.go @@ -64,7 +64,7 @@ func TestMigrateDefinition(t *testing.T) { v13_3_0 := testsuite.ReadFile("testdata/migrate/13.3.0.json") v13_4_0 := testsuite.ReadFile("testdata/migrate/13.4.0.json") v13_5_0 := testsuite.ReadFile("testdata/migrate/13.5.0.json") - v13_6_0 := testsuite.ReadFile("testdata/migrate/13.6.0.json") + //v13_6_0 := testsuite.ReadFile("testdata/migrate/13.6.0.json") v13_6_1 := testsuite.ReadFile("testdata/migrate/13.6.1.json") // 13.0 > 13.1 @@ -95,7 +95,7 @@ func TestMigrateDefinition(t *testing.T) { // 13.5 > 13.6 migrated, err = goflow.MigrateDefinition(rt.Config, migrated, semver.MustParse("13.6.0")) assert.NoError(t, err) - test.AssertEqualJSON(t, v13_6_0, migrated) + test.AssertEqualJSON(t, v13_6_1, migrated) // because we bump to the latest patch version // 13.6 > 13.6.1 migrated, err = goflow.MigrateDefinition(rt.Config, migrated, semver.MustParse("13.6.1")) From 9d24ba81315acadf65c759289f1bace61879b11d Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 9 Dec 2024 18:51:33 -0500 Subject: [PATCH 183/216] Update CHANGELOG.md for v9.3.59 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf0d58210..568e91f7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.59 (2024-12-09) +------------------------- + * Always migrate to latest patch version of flow spec + v9.3.58 (2024-12-09) ------------------------- * Update goflow which has new 13.6.1 flow migration From bfd91d0f93d86881eae3dc78719a4a1aad63a34b Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 10 Dec 2024 12:09:36 -0500 Subject: [PATCH 184/216] Udpate to latest goflow --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 38b8fd634..f69741ecb 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 github.com/nyaruka/gocommon v1.59.3 - github.com/nyaruka/goflow v0.225.3 + github.com/nyaruka/goflow v0.225.5 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 github.com/nyaruka/rp-indexer/v9 v9.2.1 diff --git a/go.sum b/go.sum index fcf405e18..deda0e20a 100644 --- a/go.sum +++ b/go.sum @@ -224,8 +224,8 @@ github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= github.com/nyaruka/gocommon v1.59.3 h1:fdjs9Z7aH+zog7FXlEpvJ0GtI6XNdNdBtjFxw5kVB7s= github.com/nyaruka/gocommon v1.59.3/go.mod h1:peOpluiVBMeQu81Ar+7EPQVT7vawN6ho9Kh1k/Gj8Vk= -github.com/nyaruka/goflow v0.225.3 h1:NUK6o8sBIUSz7fvc0Mz/jqpIJLcKwwvUtldUY11OlcU= -github.com/nyaruka/goflow v0.225.3/go.mod h1:1Up4YMccDCugTrVcXEbNCxDJqZRJindQR75oiMBCZvY= +github.com/nyaruka/goflow v0.225.5 h1:Czdq6PR1kgeDGbS8g6AXwO2/fhixLSLOmZMqI50uyYs= +github.com/nyaruka/goflow v0.225.5/go.mod h1:spXtSWgS7dusHIfUFCvJGjSMc7d4FX9Abl6S7tg49ks= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= From e84cd5125f0ae8eaa8d858d3fc4337cc42f9e427 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 10 Dec 2024 13:06:11 -0500 Subject: [PATCH 185/216] Update CHANGELOG.md for v9.3.60 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 568e91f7a..257972ff0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.60 (2024-12-10) +------------------------- + * Stop writing flowrun.path + v9.3.59 (2024-12-09) ------------------------- * Always migrate to latest patch version of flow spec From 7fc419035ebbbb8589dd0af69a7a08551417dd17 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 11 Dec 2024 11:26:26 -0500 Subject: [PATCH 186/216] Read outbox size check from new msg folder counts --- core/models/orgs.go | 2 +- core/tasks/starts/throttle_queue_test.go | 4 ++-- testsuite/testsuite.go | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/core/models/orgs.go b/core/models/orgs.go index 1eda075c7..3770af810 100644 --- a/core/models/orgs.go +++ b/core/models/orgs.go @@ -245,7 +245,7 @@ SELECT ROW_TO_JSON(o) FROM (SELECT GROUP BY c.country ORDER BY count(c.country) desc, country LIMIT 1 ), '' ) AS default_country, - (SELECT SUM(count) FROM msgs_systemlabelcount WHERE org_id = $1 AND label_type = 'O') AS outbox_count + (SELECT SUM(count) FROM orgs_itemcount WHERE org_id = $1 AND scope = 'msgs:folder:O') AS outbox_count FROM orgs_org o WHERE id = $1 ) o` diff --git a/core/tasks/starts/throttle_queue_test.go b/core/tasks/starts/throttle_queue_test.go index 12f80f5b6..95ba3ab1c 100644 --- a/core/tasks/starts/throttle_queue_test.go +++ b/core/tasks/starts/throttle_queue_test.go @@ -31,7 +31,7 @@ func TestThrottleQueue(t *testing.T) { assert.Equal(t, map[string]any{"paused": 0, "resumed": 1}, res) // make it look like org 1 has 20,000 messages in its outbox - rt.DB.MustExec(`INSERT INTO msgs_systemlabelcount(org_id, label_type, count, is_squashed) VALUES (1, 'O', 10050, FALSE)`) + rt.DB.MustExec(`INSERT INTO orgs_itemcount(org_id, scope, count, is_squashed) VALUES (1, 'msgs:folder:O', 10050, FALSE)`) models.FlushCache() @@ -40,7 +40,7 @@ func TestThrottleQueue(t *testing.T) { assert.Equal(t, map[string]any{"paused": 1, "resumed": 0}, res) // make it look like most of the inbox has cleared - rt.DB.MustExec(`INSERT INTO msgs_systemlabelcount(org_id, label_type, count, is_squashed) VALUES (1, 'O', -10000, FALSE)`) + rt.DB.MustExec(`INSERT INTO orgs_itemcount(org_id, scope, count, is_squashed) VALUES (1, 'msgs:folder:O', -10000, FALSE)`) models.FlushCache() diff --git a/testsuite/testsuite.go b/testsuite/testsuite.go index a5102cbe0..979ea1cf2 100644 --- a/testsuite/testsuite.go +++ b/testsuite/testsuite.go @@ -280,7 +280,6 @@ DELETE FROM channels_channelevent; DELETE FROM channels_channellog; DELETE FROM msgs_msg; DELETE FROM flows_flowrun; -DELETE FROM flows_flowpathcount; DELETE FROM flows_flowcategorycount; DELETE FROM flows_flowactivitycount; DELETE FROM flows_flowstartcount; @@ -292,7 +291,6 @@ DELETE FROM flows_flowrevision WHERE flow_id >= 30000; DELETE FROM flows_flow WHERE id >= 30000; DELETE FROM ivr_call; DELETE FROM campaigns_eventfire; -DELETE FROM msgs_systemlabelcount; DELETE FROM msgs_msg_labels; DELETE FROM msgs_msg; DELETE FROM msgs_broadcast_groups; @@ -312,6 +310,7 @@ DELETE FROM contacts_contactgroup_contacts WHERE contact_id >= 30000 OR contactg DELETE FROM contacts_contact WHERE id >= 30000; DELETE FROM contacts_contactgroupcount WHERE group_id >= 30000; DELETE FROM contacts_contactgroup WHERE id >= 30000; +DELETE FROM orgs_itemcount; ALTER SEQUENCE flows_flow_id_seq RESTART WITH 30000; ALTER SEQUENCE tickets_ticket_id_seq RESTART WITH 1; From 70d3aff30a88e9fc4e53fbc41c521a55614a7ba8 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 11 Dec 2024 12:01:27 -0500 Subject: [PATCH 187/216] Update CHANGELOG.md for v9.3.61 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 257972ff0..489b14db2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.61 (2024-12-11) +------------------------- + * Read outbox size check from new msg folder counts + v9.3.60 (2024-12-10) ------------------------- * Stop writing flowrun.path From 191a8782af98a513e6639741f151149b90002f02 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 11 Dec 2024 17:17:12 -0500 Subject: [PATCH 188/216] Update to latest goflow --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f69741ecb..7fc89ab7a 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 github.com/nyaruka/gocommon v1.59.3 - github.com/nyaruka/goflow v0.225.5 + github.com/nyaruka/goflow v0.225.6 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 github.com/nyaruka/rp-indexer/v9 v9.2.1 diff --git a/go.sum b/go.sum index deda0e20a..6fe68a5d6 100644 --- a/go.sum +++ b/go.sum @@ -224,8 +224,8 @@ github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= github.com/nyaruka/gocommon v1.59.3 h1:fdjs9Z7aH+zog7FXlEpvJ0GtI6XNdNdBtjFxw5kVB7s= github.com/nyaruka/gocommon v1.59.3/go.mod h1:peOpluiVBMeQu81Ar+7EPQVT7vawN6ho9Kh1k/Gj8Vk= -github.com/nyaruka/goflow v0.225.5 h1:Czdq6PR1kgeDGbS8g6AXwO2/fhixLSLOmZMqI50uyYs= -github.com/nyaruka/goflow v0.225.5/go.mod h1:spXtSWgS7dusHIfUFCvJGjSMc7d4FX9Abl6S7tg49ks= +github.com/nyaruka/goflow v0.225.6 h1:FjlfpnDEeVa+SiKc6Cm8pkgs4EUtbqVi+o62YJyjBH4= +github.com/nyaruka/goflow v0.225.6/go.mod h1:spXtSWgS7dusHIfUFCvJGjSMc7d4FX9Abl6S7tg49ks= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= From f7a89c4bf055a2ff7c221d31024f4b9cf135d1ff Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 11 Dec 2024 17:20:27 -0500 Subject: [PATCH 189/216] Update CHANGELOG.md for v9.3.62 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 489b14db2..b00270962 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.62 (2024-12-11) +------------------------- + * Update to latest goflow + v9.3.61 (2024-12-11) ------------------------- * Read outbox size check from new msg folder counts From 60dee038ddabcd02e001665bbbe4524f24f5a259 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 11 Dec 2024 17:55:38 -0500 Subject: [PATCH 190/216] Update to latest goflow --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7fc89ab7a..07783f20c 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 github.com/nyaruka/gocommon v1.59.3 - github.com/nyaruka/goflow v0.225.6 + github.com/nyaruka/goflow v0.225.7 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 github.com/nyaruka/rp-indexer/v9 v9.2.1 diff --git a/go.sum b/go.sum index 6fe68a5d6..997f15e48 100644 --- a/go.sum +++ b/go.sum @@ -224,8 +224,8 @@ github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= github.com/nyaruka/gocommon v1.59.3 h1:fdjs9Z7aH+zog7FXlEpvJ0GtI6XNdNdBtjFxw5kVB7s= github.com/nyaruka/gocommon v1.59.3/go.mod h1:peOpluiVBMeQu81Ar+7EPQVT7vawN6ho9Kh1k/Gj8Vk= -github.com/nyaruka/goflow v0.225.6 h1:FjlfpnDEeVa+SiKc6Cm8pkgs4EUtbqVi+o62YJyjBH4= -github.com/nyaruka/goflow v0.225.6/go.mod h1:spXtSWgS7dusHIfUFCvJGjSMc7d4FX9Abl6S7tg49ks= +github.com/nyaruka/goflow v0.225.7 h1:5YpEvVnjfc1pCVuaMdjsUAW4xhWGPeQf9Pi07HbavJc= +github.com/nyaruka/goflow v0.225.7/go.mod h1:spXtSWgS7dusHIfUFCvJGjSMc7d4FX9Abl6S7tg49ks= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= From 34b45a7582d1912cf0bd69af2a4093ed6a556e45 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 11 Dec 2024 17:55:56 -0500 Subject: [PATCH 191/216] Update CHANGELOG.md for v9.3.63 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b00270962..c2941801f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.63 (2024-12-11) +------------------------- + * Update to latest goflow + v9.3.62 (2024-12-11) ------------------------- * Update to latest goflow From 7c9febfe415089fe2f424c9eaf33208e5d2bf351 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 12 Dec 2024 09:23:34 -0500 Subject: [PATCH 192/216] Update to latest goflow --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 07783f20c..54f96e27f 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 github.com/nyaruka/gocommon v1.59.3 - github.com/nyaruka/goflow v0.225.7 + github.com/nyaruka/goflow v0.225.8 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 github.com/nyaruka/rp-indexer/v9 v9.2.1 diff --git a/go.sum b/go.sum index 997f15e48..4ab291d3b 100644 --- a/go.sum +++ b/go.sum @@ -224,8 +224,8 @@ github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= github.com/nyaruka/gocommon v1.59.3 h1:fdjs9Z7aH+zog7FXlEpvJ0GtI6XNdNdBtjFxw5kVB7s= github.com/nyaruka/gocommon v1.59.3/go.mod h1:peOpluiVBMeQu81Ar+7EPQVT7vawN6ho9Kh1k/Gj8Vk= -github.com/nyaruka/goflow v0.225.7 h1:5YpEvVnjfc1pCVuaMdjsUAW4xhWGPeQf9Pi07HbavJc= -github.com/nyaruka/goflow v0.225.7/go.mod h1:spXtSWgS7dusHIfUFCvJGjSMc7d4FX9Abl6S7tg49ks= +github.com/nyaruka/goflow v0.225.8 h1:rDc3P3KL8sNlXUFBi0UmBvMOd3eCUhUrkO3RbYYiW7o= +github.com/nyaruka/goflow v0.225.8/go.mod h1:spXtSWgS7dusHIfUFCvJGjSMc7d4FX9Abl6S7tg49ks= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= From add424dfb80f9d038f984b868eca74e278caf9e3 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 12 Dec 2024 09:34:42 -0500 Subject: [PATCH 193/216] Update CHANGELOG.md for v9.3.64 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2941801f..e79e8b719 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.64 (2024-12-12) +------------------------- + * Update to latest goflow + v9.3.63 (2024-12-11) ------------------------- * Update to latest goflow From 527b342dfa85628ef1150b1f5b0959de9607ad79 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 12 Dec 2024 14:30:05 -0500 Subject: [PATCH 194/216] Move logic for recording cron stats up into app layer --- core/tasks/cron.go | 67 +++++++++++++++++++++++++++++++++++++- core/tasks/cron_test.go | 50 +++++++++++++++++++++++++++++ utils/crons/cron.go | 69 ++-------------------------------------- utils/crons/cron_test.go | 13 ++------ 4 files changed, 121 insertions(+), 78 deletions(-) diff --git a/core/tasks/cron.go b/core/tasks/cron.go index d697d8d34..bfa1a6dbb 100644 --- a/core/tasks/cron.go +++ b/core/tasks/cron.go @@ -2,13 +2,34 @@ package tasks import ( "context" + "log/slog" "sync" "time" + "github.com/nyaruka/gocommon/analytics" + "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/mailroom/utils/crons" ) +const ( + cronStatsExpires = 60 * 60 * 48 // 2 days + cronStatsKeyBase = "cron_stats" + cronStatsLastStartKey = cronStatsKeyBase + ":last_start" + cronStatsLastTimeKey = cronStatsKeyBase + ":last_time" + cronStatsLastResultKey = cronStatsKeyBase + ":last_result" + cronStatsCallCountKey = cronStatsKeyBase + ":call_count" + cronStatsTotalTimeKey = cronStatsKeyBase + ":total_time" +) + +var statsKeys = []string{ + cronStatsLastStartKey, + cronStatsLastTimeKey, + cronStatsLastResultKey, + cronStatsCallCountKey, + cronStatsTotalTimeKey, +} + // Cron is a task to be repeated on a schedule type Cron interface { // Next returns the next schedule time @@ -32,7 +53,51 @@ func RegisterCron(name string, c Cron) { // StartCrons starts all registered cron jobs func StartCrons(rt *runtime.Runtime, wg *sync.WaitGroup, quit chan bool) { for name, c := range registeredCrons { - crons.Start(rt, wg, name, c.AllInstances(), c.Run, c.Next, time.Minute*5, quit) + crons.Start(rt, wg, name, c.AllInstances(), recordCronExecution(name, c.Run), c.Next, time.Minute*5, quit) + } +} + +func recordCronExecution(name string, r func(context.Context, *runtime.Runtime) (map[string]any, error)) func(context.Context, *runtime.Runtime) error { + return func(ctx context.Context, rt *runtime.Runtime) error { + log := slog.With("cron", name) + started := time.Now() + + results, err := r(ctx, rt) + + elapsed := time.Since(started) + elapsedSeconds := elapsed.Seconds() + analytics.Gauge("mr.cron_"+name, elapsedSeconds) + + rc := rt.RP.Get() + defer rc.Close() + + rc.Send("HSET", cronStatsLastStartKey, name, started.Format(time.RFC3339)) + rc.Send("HSET", cronStatsLastTimeKey, name, elapsedSeconds) + rc.Send("HSET", cronStatsLastResultKey, name, jsonx.MustMarshal(results)) + rc.Send("HINCRBY", cronStatsCallCountKey, name, 1) + rc.Send("HINCRBYFLOAT", cronStatsTotalTimeKey, name, elapsedSeconds) + for _, key := range statsKeys { + rc.Send("EXPIRE", key, cronStatsExpires) + } + + if err := rc.Flush(); err != nil { + log.Error("error writing cron results to redis") + } + + logResults := make([]any, 0, len(results)*2) + for k, v := range results { + logResults = append(logResults, k, v) + } + log = log.With("elapsed", elapsedSeconds, slog.Group("results", logResults...)) + + // if cron too longer than a minute, log as error + if elapsed > time.Minute { + log.Error("cron took too long") + } else { + log.Info("cron completed") + } + + return err } } diff --git a/core/tasks/cron_test.go b/core/tasks/cron_test.go index c25d8de3c..3f57c0581 100644 --- a/core/tasks/cron_test.go +++ b/core/tasks/cron_test.go @@ -1,10 +1,15 @@ package tasks_test import ( + "context" + "sync" "testing" "time" "github.com/nyaruka/mailroom/core/tasks" + "github.com/nyaruka/mailroom/runtime" + "github.com/nyaruka/mailroom/testsuite" + "github.com/nyaruka/redisx/assertredis" "github.com/stretchr/testify/assert" ) @@ -26,3 +31,48 @@ func TestNextFire(t *testing.T) { assert.Equal(t, tc.expected, actual, "next fire mismatch for %s + %s", tc.last, tc.interval) } } + +type TestCron struct { + ran bool +} + +func (c *TestCron) Next(last time.Time) time.Time { + return tasks.CronNext(last, time.Minute*5) +} + +func (c *TestCron) AllInstances() bool { + return false +} + +func (c *TestCron) Run(ctx context.Context, rt *runtime.Runtime) (map[string]any, error) { + c.ran = true + return map[string]any{"foo": 123}, nil +} + +func TestCronStats(t *testing.T) { + _, rt := testsuite.Runtime() + rc := rt.RP.Get() + defer rc.Close() + + defer testsuite.Reset(testsuite.ResetRedis) + + cron := &TestCron{} + tasks.RegisterCron("test1", cron) + + wg := &sync.WaitGroup{} + quit := make(chan bool) + + tasks.StartCrons(rt, wg, quit) + + for !cron.ran { + time.Sleep(time.Millisecond * 10) + } + + assertredis.Exists(t, rc, "cron_stats:last_start") + assertredis.Exists(t, rc, "cron_stats:last_time") + assertredis.HGet(t, rc, "cron_stats:last_result", "test1", `{"foo":123}`) + assertredis.HGet(t, rc, "cron_stats:call_count", "test1", "1") + assertredis.Exists(t, rc, "cron_stats:total_time") + + close(quit) +} diff --git a/utils/crons/cron.go b/utils/crons/cron.go index e3fc8ea5c..7222a6878 100644 --- a/utils/crons/cron.go +++ b/utils/crons/cron.go @@ -9,33 +9,12 @@ import ( "time" "github.com/getsentry/sentry-go" - "github.com/gomodule/redigo/redis" - "github.com/nyaruka/gocommon/analytics" - "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/redisx" ) -const ( - statsExpires = 60 * 60 * 48 // 2 days - statsKeyBase = "cron_stats" - statsLastStartKey = statsKeyBase + ":last_start" - statsLastTimeKey = statsKeyBase + ":last_time" - statsLastResultKey = statsKeyBase + ":last_result" - statsCallCountKey = statsKeyBase + ":call_count" - statsTotalTimeKey = statsKeyBase + ":total_time" -) - -var statsKeys = []string{ - statsLastStartKey, - statsLastTimeKey, - statsLastResultKey, - statsCallCountKey, - statsTotalTimeKey, -} - // Function is the function that will be called on our schedule -type Function func(context.Context, *runtime.Runtime) (map[string]any, error) +type Function func(context.Context, *runtime.Runtime) error // Start calls the passed in function every interval, making sure it acquires a // lock so that only one process is running at once. Note that across processes @@ -85,14 +64,9 @@ func Start(rt *runtime.Runtime, wg *sync.WaitGroup, name string, allInstances bo } // ok, got the lock, run our cron function - started := time.Now() - results, err := fireCron(rt, cronFunc, timeout) - if err != nil { + if err := fireCron(rt, cronFunc, timeout); err != nil { log.Error("error while running cron", "error", err) } - ended := time.Now() - - recordCompletion(rt.RP, name, started, ended, results) // release our lock err = locker.Release(rt.RP, lock) @@ -113,7 +87,7 @@ func Start(rt *runtime.Runtime, wg *sync.WaitGroup, name string, allInstances bo // fireCron is just a wrapper around the cron function we will call for the purposes of // catching and logging panics -func fireCron(rt *runtime.Runtime, cronFunc Function, timeout time.Duration) (map[string]any, error) { +func fireCron(rt *runtime.Runtime, cronFunc Function, timeout time.Duration) error { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -128,40 +102,3 @@ func fireCron(rt *runtime.Runtime, cronFunc Function, timeout time.Duration) (ma return cronFunc(ctx, rt) } - -func recordCompletion(rp *redis.Pool, name string, started, ended time.Time, results map[string]any) { - log := slog.With("cron", name) - elapsed := ended.Sub(started) - elapsedSeconds := elapsed.Seconds() - - rc := rp.Get() - defer rc.Close() - - rc.Send("HSET", statsLastStartKey, name, started.Format(time.RFC3339)) - rc.Send("HSET", statsLastTimeKey, name, elapsedSeconds) - rc.Send("HSET", statsLastResultKey, name, jsonx.MustMarshal(results)) - rc.Send("HINCRBY", statsCallCountKey, name, 1) - rc.Send("HINCRBYFLOAT", statsTotalTimeKey, name, elapsedSeconds) - for _, key := range statsKeys { - rc.Send("EXPIRE", key, statsExpires) - } - - if err := rc.Flush(); err != nil { - log.Error("error writing cron results to redis") - } - - analytics.Gauge("mr.cron_"+name, elapsedSeconds) - - logResults := make([]any, 0, len(results)*2) - for k, v := range results { - logResults = append(logResults, k, v) - } - log = log.With("elapsed", elapsedSeconds, slog.Group("results", logResults...)) - - // if cron too longer than a minute, log as error - if elapsed > time.Minute { - log.Error("cron took too long") - } else { - log.Info("cron completed") - } -} diff --git a/utils/crons/cron_test.go b/utils/crons/cron_test.go index 622ba29b7..0ce6d02d5 100644 --- a/utils/crons/cron_test.go +++ b/utils/crons/cron_test.go @@ -9,7 +9,6 @@ import ( "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/mailroom/testsuite" "github.com/nyaruka/mailroom/utils/crons" - "github.com/nyaruka/redisx/assertredis" "github.com/stretchr/testify/assert" ) @@ -18,15 +17,13 @@ func TestCron(t *testing.T) { rc := rt.RP.Get() defer rc.Close() - defer testsuite.Reset(testsuite.ResetRedis) - align := func() { untilNextSecond := time.Nanosecond * time.Duration(1_000_000_000-time.Now().Nanosecond()) // time until next second boundary time.Sleep(untilNextSecond) // wait until after second boundary } createCronFunc := func(running *bool, fired *int, delays map[int]time.Duration, defaultDelay time.Duration) crons.Function { - return func(ctx context.Context, rt *runtime.Runtime) (map[string]any, error) { + return func(ctx context.Context, rt *runtime.Runtime) error { if *running { assert.Fail(t, "more than 1 thread is trying to run our cron job") } @@ -39,7 +36,7 @@ func TestCron(t *testing.T) { time.Sleep(delay) *fired++ *running = false - return map[string]any{"fired": *fired}, nil + return nil } } @@ -72,12 +69,6 @@ func TestCron(t *testing.T) { quit = make(chan bool) running = false - assertredis.Exists(t, rc, "cron_stats:last_start") - assertredis.Exists(t, rc, "cron_stats:last_time") - assertredis.HGet(t, rc, "cron_stats:last_result", "test1", `{"fired":4}`) - assertredis.HGet(t, rc, "cron_stats:call_count", "test1", "4") - assertredis.Exists(t, rc, "cron_stats:total_time") - align() // simulate the job taking 400ms to run on the second fire, thus skipping the third fire From 9fd4235426862ae86e4f689b89ef30b7f44430c0 Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Fri, 13 Dec 2024 13:52:35 +0200 Subject: [PATCH 195/216] Add cloudwatch config variables --- runtime/config.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/runtime/config.go b/runtime/config.go index f1c37a6eb..fc630c8ed 100644 --- a/runtime/config.go +++ b/runtime/config.go @@ -73,6 +73,9 @@ type Config struct { LibratoUsername string `help:"the username that will be used to authenticate to Librato"` LibratoToken string `help:"the token that will be used to authenticate to Librato"` + CloudwatchNamespace string `help:"the namespace to use for cloudwatch metrics"` + DeploymentID string `help:"the deployment identifier to use for metrics"` + AndroidCredentialsFile string `help:"path to JSON file with FCM service account credentials used to sync Android relayers"` InstanceID string `help:"the unique identifier of this instance, defaults to hostname"` @@ -128,6 +131,9 @@ func NewDefaultConfig() *Config { S3AttachmentsBucket: "temba-attachments", S3SessionsBucket: "temba-sessions", + CloudwatchNamespace: "Temba", + DeploymentID: "dev", + InstanceID: hostname, LogLevel: slog.LevelWarn, UUIDSeed: 0, From e61c18e4900de340d2b9cf96a4c149e84346ea93 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 13 Dec 2024 12:03:05 -0500 Subject: [PATCH 196/216] Add cloudwatch and start sending cron times there --- core/tasks/cron.go | 11 +++++++++-- go.mod | 15 ++++++++------- go.sum | 30 ++++++++++++++++-------------- mailroom.go | 13 +++++++++++++ runtime/runtime.go | 2 ++ testsuite/testsuite.go | 9 ++++++++- utils/crons/cron.go | 5 +---- 7 files changed, 57 insertions(+), 28 deletions(-) diff --git a/core/tasks/cron.go b/core/tasks/cron.go index bfa1a6dbb..5c8277b12 100644 --- a/core/tasks/cron.go +++ b/core/tasks/cron.go @@ -6,7 +6,8 @@ import ( "sync" "time" - "github.com/nyaruka/gocommon/analytics" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/mailroom/utils/crons" @@ -66,7 +67,13 @@ func recordCronExecution(name string, r func(context.Context, *runtime.Runtime) elapsed := time.Since(started) elapsedSeconds := elapsed.Seconds() - analytics.Gauge("mr.cron_"+name, elapsedSeconds) + + rt.CW.Queue(types.MetricDatum{ + MetricName: aws.String("CronTime"), + Dimensions: []types.Dimension{{Name: aws.String("TaskName"), Value: aws.String(name)}}, + Value: aws.Float64(elapsedSeconds), + Unit: types.StandardUnitSeconds, + }) rc := rt.RP.Get() defer rc.Close() diff --git a/go.mod b/go.mod index 54f96e27f..ac8ba542f 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,8 @@ require ( github.com/Masterminds/semver v1.5.0 github.com/appleboy/go-fcm v1.2.1 github.com/aws/aws-sdk-go-v2 v1.32.6 - github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.20 + github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.21 + github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.43.3 github.com/aws/aws-sdk-go-v2/service/dynamodb v1.38.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 github.com/buger/jsonparser v1.1.1 @@ -22,11 +23,11 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 - github.com/nyaruka/gocommon v1.59.3 + github.com/nyaruka/gocommon v1.60.3 github.com/nyaruka/goflow v0.225.8 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 - github.com/nyaruka/rp-indexer/v9 v9.2.1 + github.com/nyaruka/rp-indexer/v9 v9.3.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_model v0.6.1 github.com/prometheus/common v0.61.0 @@ -34,13 +35,13 @@ require ( github.com/samber/slog-sentry v1.2.2 github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.10.0 - google.golang.org/api v0.210.0 + google.golang.org/api v0.211.0 ) require ( cel.dev/expr v0.19.1 // indirect cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/auth v0.12.0 // indirect + cloud.google.com/go/auth v0.12.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect cloud.google.com/go/compute/metadata v0.5.2 // indirect cloud.google.com/go/firestore v1.17.0 // indirect @@ -62,7 +63,7 @@ require ( github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 // indirect - github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.8 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.9 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.6 // indirect @@ -116,7 +117,7 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect golang.org/x/crypto v0.30.0 // indirect - golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect + golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect golang.org/x/net v0.32.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/sync v0.10.0 // indirect diff --git a/go.sum b/go.sum index 4ab291d3b..ebce0bdd3 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= -cloud.google.com/go/auth v0.12.0 h1:ARAD8r0lkiHw2go7kEnmviF6TOYhzLM+yDGcDt9mP68= -cloud.google.com/go/auth v0.12.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth v0.12.1 h1:n2Bj25BUMM0nvE9D2XLTiImanwZhO3DkfWSYS/SAJP4= +cloud.google.com/go/auth v0.12.1/go.mod h1:BFMu+TNpF3DmvfBO9ClqTR/SiqVIm7LukKF9mbendF4= cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= @@ -54,8 +54,8 @@ github.com/aws/aws-sdk-go-v2/config v1.28.6 h1:D89IKtGrs/I3QXOLNTH93NJYtDhm8SYa9 github.com/aws/aws-sdk-go-v2/config v1.28.6/go.mod h1:GDzxJ5wyyFSCoLkS+UhGB0dArhb9mI+Co4dHtoTxbko= github.com/aws/aws-sdk-go-v2/credentials v1.17.47 h1:48bA+3/fCdi2yAwVt+3COvmatZ6jUDNkDTIsqDiMUdw= github.com/aws/aws-sdk-go-v2/credentials v1.17.47/go.mod h1:+KdckOejLW3Ks3b0E3b5rHsr2f9yuORBum0WPnE5o5w= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.20 h1:bwHhhCScKRAYJtaWVT+jDpt74GybN2nxI6+InkRjqGM= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.20/go.mod h1:/RfYH8CUMQuq/3CIEVGHLkqkA9KtbBF5omt2Ae8xc0s= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.21 h1:FdDxp4HNtJWPBAOdkJ+84Dfx2TOA7Dq+cH72GDHhjnA= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.21/go.mod h1:doHEXGiMWQBxcTJy3YN1Ao2HCgCuMWumuvTULGndCuQ= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 h1:AmoU1pziydclFT/xRV+xXE/Vb8fttJCLRPv8oAkprc0= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21/go.mod h1:AjUdLYe4Tgs6kpH4Bv7uMZo7pottoyHMn4eTcIcneaY= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 h1:s/fF4+yDQDoElYhfIVvSNyeCydfbuTKzhxSXDXCPasU= @@ -66,10 +66,12 @@ github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvK github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 h1:r67ps7oHCYnflpgDy2LZU0MAQtQbYIOqNNnqGO6xQkE= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25/go.mod h1:GrGY+Q4fIokYLtjCVB/aFfCVL6hhGUFl8inD18fDalE= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.43.3 h1:nQLG9irjDGUFXVPDHzjCGEEwh0hZ6BcxTvHOod1YsP4= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.43.3/go.mod h1:URs8sqsyaxiAZkKP6tOEmhcs9j2ynFIomqOKY/CAHJc= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.38.0 h1:isKhHsjpQR3CypQJ4G1g8QWx7zNpiC/xKw1zjgJYVno= github.com/aws/aws-sdk-go-v2/service/dynamodb v1.38.0/go.mod h1:xDvUyIkwBwNtVZJdHEwAuhFly3mezwdEWkbJ5oNYwIw= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.8 h1:ntqHwZb+ZyVz0CFYUG0sQ02KMMJh+iXeV3bXoba+s4A= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.8/go.mod h1:Hcjb2SiUo9v1GhpXjRNW7hAwfzAPfrsgnlKpP5UYEPY= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.9 h1:yhB2XYpHeWeAv5u3w9PFiSVIariSyhK5jcyQUFJpnIQ= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.9/go.mod h1:Hcjb2SiUo9v1GhpXjRNW7hAwfzAPfrsgnlKpP5UYEPY= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 h1:HCpPsWqmYQieU7SS6E9HXfdAMSud0pteVXieJmcpIRI= @@ -222,8 +224,8 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= -github.com/nyaruka/gocommon v1.59.3 h1:fdjs9Z7aH+zog7FXlEpvJ0GtI6XNdNdBtjFxw5kVB7s= -github.com/nyaruka/gocommon v1.59.3/go.mod h1:peOpluiVBMeQu81Ar+7EPQVT7vawN6ho9Kh1k/Gj8Vk= +github.com/nyaruka/gocommon v1.60.3 h1:fPQ9t6NX+mu7JQ7nXefgpBs8paqGvGXq3eA7VscsAVo= +github.com/nyaruka/gocommon v1.60.3/go.mod h1:kFJuOq8COneV7ssfK6xgCMJ8gP8fQifLQnNXBnE4YL0= github.com/nyaruka/goflow v0.225.8 h1:rDc3P3KL8sNlXUFBi0UmBvMOd3eCUhUrkO3RbYYiW7o= github.com/nyaruka/goflow v0.225.8/go.mod h1:spXtSWgS7dusHIfUFCvJGjSMc7d4FX9Abl6S7tg49ks= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= @@ -236,8 +238,8 @@ github.com/nyaruka/phonenumbers v1.4.3 h1:tR71UJ+DZu7TSkxoG8JI8HzHJkPD/m4KNiUX34 github.com/nyaruka/phonenumbers v1.4.3/go.mod h1:gv+CtldaFz+G3vHHnasBSirAi3O2XLqZzVWz4V1pl2E= github.com/nyaruka/redisx v0.8.1 h1:d9Hc8nfSKTSEU+bx+YrB13d6bzAgiiHygk4jg/Q4nb4= github.com/nyaruka/redisx v0.8.1/go.mod h1:2TUmkDvprPInnmInR5AEbCm0zRRewkvSDVLsO+Do6iI= -github.com/nyaruka/rp-indexer/v9 v9.2.1 h1:gQa0QHiU+LjhmgpToHpoGRKRC8oI1EdW4dDaN9inhSk= -github.com/nyaruka/rp-indexer/v9 v9.2.1/go.mod h1:NzcuE4Zxrzde7gQinlWfwq2jeyEbamBj8hqVkm+eQLg= +github.com/nyaruka/rp-indexer/v9 v9.3.0 h1:8Thnt7k6/anEYcM3hIY+ObzF3JtO2/8EbDmb4JEAzsc= +github.com/nyaruka/rp-indexer/v9 v9.3.0/go.mod h1:YrHQx+ImBKRUQ4RWFJad1IlcMWlMyry/72SxAVCCgIU= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= @@ -301,8 +303,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0= -golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU= +golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -356,8 +358,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.210.0 h1:HMNffZ57OoZCRYSbdWVRoqOa8V8NIHLL0CzdBPLztWk= -google.golang.org/api v0.210.0/go.mod h1:B9XDZGnx2NtyjzVkOVTGrFSAVZgPcbedzKg/gTLwqBs= +google.golang.org/api v0.211.0 h1:IUpLjq09jxBSV1lACO33CGY3jsRcbctfGzhj+ZSE/Bg= +google.golang.org/api v0.211.0/go.mod h1:XOloB4MXFH4UTlQSGuNUxw0UT74qdENK8d6JNsXKLi0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw= diff --git a/mailroom.go b/mailroom.go index ebf04620b..cf33cfeb0 100644 --- a/mailroom.go +++ b/mailroom.go @@ -12,6 +12,7 @@ import ( "github.com/elastic/go-elasticsearch/v8" "github.com/jmoiron/sqlx" "github.com/nyaruka/gocommon/analytics" + "github.com/nyaruka/gocommon/aws/cwatch" "github.com/nyaruka/gocommon/aws/dynamo" "github.com/nyaruka/gocommon/aws/s3x" "github.com/nyaruka/mailroom/core/tasks" @@ -139,6 +140,16 @@ func (mr *Mailroom) Start() error { analytics.Start() + // configure and start cloudwatch + mr.rt.CW, err = cwatch.NewService(c.AWSAccessKeyID, c.AWSSecretAccessKey, c.AWSRegion, c.CloudwatchNamespace, c.DeploymentID) + if err != nil { + log.Error("cloudwatch not available", "error", err) + } else { + log.Info("cloudwatch ok") + } + + mr.rt.CW.StartQueue(mr.wg, time.Second*3) + // init our foremen and start it mr.handlerForeman.Start() mr.batchForeman.Start() @@ -164,7 +175,9 @@ func (mr *Mailroom) Stop() error { mr.batchForeman.Stop() mr.throttledForeman.Stop() + mr.rt.CW.StopQueue() analytics.Stop() + close(mr.quit) mr.cancel() diff --git a/runtime/runtime.go b/runtime/runtime.go index 81b43e046..64e0ee643 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -8,6 +8,7 @@ import ( "github.com/elastic/go-elasticsearch/v8" "github.com/gomodule/redigo/redis" "github.com/jmoiron/sqlx" + "github.com/nyaruka/gocommon/aws/cwatch" "github.com/nyaruka/gocommon/aws/dynamo" "github.com/nyaruka/gocommon/aws/s3x" ) @@ -21,6 +22,7 @@ type Runtime struct { Dynamo *dynamo.Service S3 *s3x.Service ES *elasticsearch.TypedClient + CW *cwatch.Service FCM FCMClient Config *Config } diff --git a/testsuite/testsuite.go b/testsuite/testsuite.go index 979ea1cf2..426107cfa 100644 --- a/testsuite/testsuite.go +++ b/testsuite/testsuite.go @@ -15,6 +15,7 @@ import ( "github.com/elastic/go-elasticsearch/v8" "github.com/gomodule/redigo/redis" "github.com/jmoiron/sqlx" + "github.com/nyaruka/gocommon/aws/cwatch" "github.com/nyaruka/gocommon/aws/dynamo" "github.com/nyaruka/gocommon/aws/s3x" "github.com/nyaruka/gocommon/jsonx" @@ -22,6 +23,7 @@ import ( "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/redisx/assertredis" "github.com/nyaruka/rp-indexer/v9/indexers" + ixruntime "github.com/nyaruka/rp-indexer/v9/runtime" ) var _db *sqlx.DB @@ -72,6 +74,7 @@ func Reset(what ResetFlag) { // Runtime returns the various runtime things a test might need func Runtime() (context.Context, *runtime.Runtime) { cfg := runtime.NewDefaultConfig() + cfg.DeploymentID = "test" cfg.Port = 8091 cfg.ElasticContactsIndex = elasticContactsIndex cfg.AWSAccessKeyID = "root" @@ -89,6 +92,9 @@ func Runtime() (context.Context, *runtime.Runtime) { s3svc, err := s3x.NewService(cfg.AWSAccessKeyID, cfg.AWSSecretAccessKey, cfg.AWSRegion, cfg.S3Endpoint, cfg.S3Minio) noError(err) + cwSvc, err := cwatch.NewService(cfg.AWSAccessKeyID, cfg.AWSSecretAccessKey, cfg.AWSRegion, cfg.CloudwatchNamespace, cfg.DeploymentID) + noError(err) + dbx := getDB() rt := &runtime.Runtime{ DB: dbx, @@ -97,6 +103,7 @@ func Runtime() (context.Context, *runtime.Runtime) { Dynamo: dyna, S3: s3svc, ES: getES(), + CW: cwSvc, FCM: &MockFCMClient{ValidTokens: []string{"FCMID3", "FCMID4", "FCMID5"}}, Config: cfg, } @@ -112,7 +119,7 @@ func ReindexElastic(ctx context.Context) { es := getES() contactsIndexer := indexers.NewContactIndexer(elasticURL, elasticContactsIndex, 1, 1, 100) - contactsIndexer.Index(db.DB, false, false) + contactsIndexer.Index(&ixruntime.Runtime{DB: db.DB}, false, false) _, err := es.Indices.Refresh().Index(elasticContactsIndex).Do(ctx) noError(err) diff --git a/utils/crons/cron.go b/utils/crons/cron.go index 7222a6878..532e1d660 100644 --- a/utils/crons/cron.go +++ b/utils/crons/cron.go @@ -38,10 +38,7 @@ func Start(rt *runtime.Runtime, wg *sync.WaitGroup, name string, allInstances bo log := slog.With("cron", name) go func() { - defer func() { - log.Info("cron exiting") - wg.Done() - }() + defer func() { wg.Done() }() for { select { From 8403f1ba158d4ba617060b4ece7ad1a1b1b4191d Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 13 Dec 2024 14:26:19 -0500 Subject: [PATCH 197/216] Update CHANGELOG.md for v9.3.65 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e79e8b719..2c5186e7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.65 (2024-12-13) +------------------------- + * Add cloudwatch and start sending cron times there + v9.3.64 (2024-12-12) ------------------------- * Update to latest goflow From d6bc88ddad3a1a6963e5736379bc49d27d7db57d Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 13 Dec 2024 15:45:36 -0500 Subject: [PATCH 198/216] Convert all remaining metrics to cloudwatch --- cmd/mailroom/main.go | 2 +- core/tasks/analytics/cron.go | 89 ------------- core/tasks/campaigns/fire_campaign_event.go | 7 -- core/tasks/cron.go | 4 +- core/tasks/handler/handle_contact_event.go | 18 ++- core/tasks/metrics/cron.go | 132 ++++++++++++++++++++ mailroom.go | 9 -- runtime/config.go | 21 ++-- 8 files changed, 156 insertions(+), 126 deletions(-) delete mode 100644 core/tasks/analytics/cron.go create mode 100644 core/tasks/metrics/cron.go diff --git a/cmd/mailroom/main.go b/cmd/mailroom/main.go index 1ab05b52c..5a1ce1e6a 100644 --- a/cmd/mailroom/main.go +++ b/cmd/mailroom/main.go @@ -19,7 +19,6 @@ import ( _ "github.com/nyaruka/mailroom/core/handlers" _ "github.com/nyaruka/mailroom/core/hooks" - _ "github.com/nyaruka/mailroom/core/tasks/analytics" _ "github.com/nyaruka/mailroom/core/tasks/campaigns" _ "github.com/nyaruka/mailroom/core/tasks/contacts" _ "github.com/nyaruka/mailroom/core/tasks/expirations" @@ -28,6 +27,7 @@ import ( _ "github.com/nyaruka/mailroom/core/tasks/incidents" _ "github.com/nyaruka/mailroom/core/tasks/interrupts" _ "github.com/nyaruka/mailroom/core/tasks/ivr" + _ "github.com/nyaruka/mailroom/core/tasks/metrics" _ "github.com/nyaruka/mailroom/core/tasks/msgs" _ "github.com/nyaruka/mailroom/core/tasks/schedules" _ "github.com/nyaruka/mailroom/core/tasks/starts" diff --git a/core/tasks/analytics/cron.go b/core/tasks/analytics/cron.go deleted file mode 100644 index b7752c897..000000000 --- a/core/tasks/analytics/cron.go +++ /dev/null @@ -1,89 +0,0 @@ -package analytics - -import ( - "context" - "log/slog" - "time" - - "github.com/nyaruka/gocommon/analytics" - "github.com/nyaruka/mailroom/core/tasks" - "github.com/nyaruka/mailroom/runtime" -) - -func init() { - tasks.RegisterCron("analytics", &analyticsCron{}) -} - -// calculates a bunch of stats every minute and both logs them and sends them to librato -type analyticsCron struct { - // both sqlx and redis provide wait stats which are cummulative that we need to make into increments - dbWaitDuration time.Duration - dbWaitCount int64 - redisWaitDuration time.Duration - redisWaitCount int64 -} - -func (c *analyticsCron) Next(last time.Time) time.Time { - return tasks.CronNext(last, time.Minute) -} - -func (c *analyticsCron) AllInstances() bool { - return true -} - -func (c *analyticsCron) Run(ctx context.Context, rt *runtime.Runtime) (map[string]any, error) { - // We wait 15 seconds since we fire at the top of the minute, the same as expirations. - // That way any metrics related to the size of our queue are a bit more accurate (all expirations can - // usually be handled in 15 seconds). Something more complicated would take into account the age of - // the items in our queues. - time.Sleep(time.Second * 15) - - rc := rt.RP.Get() - defer rc.Close() - - // calculate size of batch queue - batchSize, err := tasks.BatchQueue.Size(rc) - if err != nil { - slog.Error("error calculating batch queue size", "error", err) - } - - // and size of handler queue - handlerSize, err := tasks.HandlerQueue.Size(rc) - if err != nil { - slog.Error("error calculating handler queue size", "error", err) - } - - // get our DB and redis stats - dbStats := rt.DB.Stats() - redisStats := rt.RP.Stats() - - dbWaitDurationInPeriod := dbStats.WaitDuration - c.dbWaitDuration - dbWaitCountInPeriod := dbStats.WaitCount - c.dbWaitCount - redisWaitDurationInPeriod := redisStats.WaitDuration - c.redisWaitDuration - redisWaitCountInPeriod := redisStats.WaitCount - c.redisWaitCount - - c.dbWaitDuration = dbStats.WaitDuration - c.dbWaitCount = dbStats.WaitCount - c.redisWaitDuration = redisStats.WaitDuration - c.redisWaitCount = redisStats.WaitCount - - analytics.Gauge("mr.db_busy", float64(dbStats.InUse)) - analytics.Gauge("mr.db_idle", float64(dbStats.Idle)) - analytics.Gauge("mr.db_wait_ms", float64(dbWaitDurationInPeriod/time.Millisecond)) - analytics.Gauge("mr.db_wait_count", float64(dbWaitCountInPeriod)) - analytics.Gauge("mr.redis_wait_ms", float64(redisWaitDurationInPeriod/time.Millisecond)) - analytics.Gauge("mr.redis_wait_count", float64(redisWaitCountInPeriod)) - analytics.Gauge("mr.handler_queue", float64(handlerSize)) - analytics.Gauge("mr.batch_queue", float64(batchSize)) - - return map[string]any{ - "db_busy": dbStats.InUse, - "db_idle": dbStats.Idle, - "db_wait_time": dbWaitDurationInPeriod, - "db_wait_count": dbWaitCountInPeriod, - "redis_wait_time": dbWaitDurationInPeriod, - "redis_wait_count": dbWaitCountInPeriod, - "handler_size": handlerSize, - "batch_size": batchSize, - }, nil -} diff --git a/core/tasks/campaigns/fire_campaign_event.go b/core/tasks/campaigns/fire_campaign_event.go index da1ff365d..dadce54d8 100644 --- a/core/tasks/campaigns/fire_campaign_event.go +++ b/core/tasks/campaigns/fire_campaign_event.go @@ -10,7 +10,6 @@ import ( "github.com/gomodule/redigo/redis" "github.com/jmoiron/sqlx" - "github.com/nyaruka/gocommon/analytics" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/triggers" @@ -118,8 +117,6 @@ func (t *FireCampaignEventTask) Perform(ctx context.Context, rt *runtime.Runtime // FireCampaignEvents tries to handle the given event fires, returning those that were handled (i.e. skipped, fired or deleted) func FireCampaignEvents(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets, fires []*models.EventFire, flowUUID assets.FlowUUID, campaign *triggers.CampaignReference, eventUUID triggers.CampaignEventUUID) ([]*models.EventFire, error) { - start := time.Now() - // get the capmaign event object dbEvent := oa.CampaignEventByID(fires[0].EventID) if dbEvent == nil { @@ -232,9 +229,5 @@ func FireCampaignEvents(ctx context.Context, rt *runtime.Runtime, oa *models.Org slog.Error("error starting flow for campaign event", "error", err, "event", eventUUID) } - // log both our total and average - analytics.Gauge("mr.campaign_event_elapsed", float64(time.Since(start))/float64(time.Second)) - analytics.Gauge("mr.campaign_event_count", float64(len(handled))) - return handled, nil } diff --git a/core/tasks/cron.go b/core/tasks/cron.go index 5c8277b12..50c02732e 100644 --- a/core/tasks/cron.go +++ b/core/tasks/cron.go @@ -40,7 +40,7 @@ type Cron interface { Run(context.Context, *runtime.Runtime) (map[string]any, error) // AllInstances returns whether cron runs on all instances - i.e. locking is instance specific. This is for crons - // like analytics which report instance specific stats. Other crons are synchronized across all instances. + // like metrics which report instance specific stats. Other crons are synchronized across all instances. AllInstances() bool } @@ -69,7 +69,7 @@ func recordCronExecution(name string, r func(context.Context, *runtime.Runtime) elapsedSeconds := elapsed.Seconds() rt.CW.Queue(types.MetricDatum{ - MetricName: aws.String("CronTime"), + MetricName: aws.String("CronTaskDuration"), Dimensions: []types.Dimension{{Name: aws.String("TaskName"), Value: aws.String(name)}}, Value: aws.Float64(elapsedSeconds), Unit: types.StandardUnitSeconds, diff --git a/core/tasks/handler/handle_contact_event.go b/core/tasks/handler/handle_contact_event.go index 089a9cee6..dc5211871 100644 --- a/core/tasks/handler/handle_contact_event.go +++ b/core/tasks/handler/handle_contact_event.go @@ -7,9 +7,10 @@ import ( "log/slog" "time" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" "github.com/gomodule/redigo/redis" "github.com/jmoiron/sqlx" - "github.com/nyaruka/gocommon/analytics" "github.com/nyaruka/gocommon/dbutil" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/mailroom/core/models" @@ -98,11 +99,16 @@ func (t *HandleContactEventTask) Perform(ctx context.Context, rt *runtime.Runtim err = performHandlerTask(ctx, rt, oa, t.ContactID, ctask) - // log our processing time to librato - analytics.Gauge(fmt.Sprintf("mr.%s_elapsed", taskPayload.Type), float64(time.Since(start))/float64(time.Second)) - - // and total latency for this task since it was queued - analytics.Gauge(fmt.Sprintf("mr.%s_latency", taskPayload.Type), float64(time.Since(taskPayload.QueuedOn))/float64(time.Second)) + // send metrics for processing time and lag from queue time + rt.CW.Queue(types.MetricDatum{ + MetricName: aws.String("HandlerTaskDuration"), + Value: aws.Float64(float64(time.Since(start)) / float64(time.Second)), + Unit: types.StandardUnitSeconds, + }, types.MetricDatum{ + MetricName: aws.String("HandlerTaskLatency"), + Value: aws.Float64(float64(time.Since(taskPayload.QueuedOn)) / float64(time.Second)), + Unit: types.StandardUnitSeconds, + }) // if we get an error processing an event, requeue it for later and return our error if err != nil { diff --git a/core/tasks/metrics/cron.go b/core/tasks/metrics/cron.go new file mode 100644 index 000000000..02c84e79a --- /dev/null +++ b/core/tasks/metrics/cron.go @@ -0,0 +1,132 @@ +package analytics + +import ( + "context" + "log/slog" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" + "github.com/nyaruka/mailroom/core/tasks" + "github.com/nyaruka/mailroom/runtime" +) + +func init() { + tasks.RegisterCron("metrics", &metricsCron{}) +} + +// calculates a bunch of stats every minute and both logs them and sends them to cloudwatch +type metricsCron struct { + // both sqlx and redis provide wait stats which are cummulative that we need to make into increments + dbWaitDuration time.Duration + redisWaitDuration time.Duration +} + +func (c *metricsCron) Next(last time.Time) time.Time { + return tasks.CronNext(last, time.Minute) +} + +func (c *metricsCron) AllInstances() bool { + return true +} + +func (c *metricsCron) Run(ctx context.Context, rt *runtime.Runtime) (map[string]any, error) { + // TODO replace with offset passed to tasks.CronNext + // We wait 15 seconds since we fire at the top of the minute, the same as expirations. + // That way any metrics related to the size of our queue are a bit more accurate (all expirations can + // usually be handled in 15 seconds). Something more complicated would take into account the age of + // the items in our queues. + time.Sleep(time.Second * 15) + + handlerSize, batchSize, throttledSize := getQueueSizes(rt) + + // get our DB and redis stats + dbStats := rt.DB.Stats() + redisStats := rt.RP.Stats() + + dbWaitDurationInPeriod := dbStats.WaitDuration - c.dbWaitDuration + redisWaitDurationInPeriod := redisStats.WaitDuration - c.redisWaitDuration + + c.dbWaitDuration = dbStats.WaitDuration + c.redisWaitDuration = redisStats.WaitDuration + + dims := []types.Dimension{ + {Name: aws.String("Host"), Value: aws.String(rt.Config.InstanceID)}, + {Name: aws.String("App"), Value: aws.String("mailroom")}, + } + + rt.CW.Queue(types.MetricDatum{ + MetricName: aws.String("DBConnectionsInUse"), + Dimensions: dims, + Value: aws.Float64(float64(dbStats.InUse)), + Unit: types.StandardUnitCount, + }, types.MetricDatum{ + MetricName: aws.String("DBConnectionWaitDuration"), + Dimensions: dims, + Value: aws.Float64(float64(dbWaitDurationInPeriod / time.Second)), + Unit: types.StandardUnitSeconds, + }, types.MetricDatum{ + MetricName: aws.String("RedisConnectionsInUse"), + Dimensions: dims, + Value: aws.Float64(float64(redisStats.ActiveCount)), + Unit: types.StandardUnitCount, + }, types.MetricDatum{ + MetricName: aws.String("RedisConnectionsWaitDuration"), + Dimensions: dims, + Value: aws.Float64(float64(redisWaitDurationInPeriod / time.Second)), + Unit: types.StandardUnitSeconds, + }) + + rt.CW.Queue(types.MetricDatum{ + MetricName: aws.String("QueuedTasks"), + Dimensions: []types.Dimension{ + {Name: aws.String("QueueName"), Value: aws.String("handler")}, + }, + Value: aws.Float64(float64(handlerSize)), + Unit: types.StandardUnitCount, + }, types.MetricDatum{ + MetricName: aws.String("QueuedTasks"), + Dimensions: []types.Dimension{ + {Name: aws.String("QueueName"), Value: aws.String("batch")}, + }, + Value: aws.Float64(float64(batchSize)), + Unit: types.StandardUnitCount, + }, types.MetricDatum{ + MetricName: aws.String("QueuedTasks"), + Dimensions: []types.Dimension{ + {Name: aws.String("QueueName"), Value: aws.String("throttled")}, + }, + Value: aws.Float64(float64(throttledSize)), + Unit: types.StandardUnitCount, + }) + + return map[string]any{ + "db_inuse": dbStats.InUse, + "db_wait": dbWaitDurationInPeriod, + "redis_inuse": redisStats.ActiveCount, + "redis_wait": redisWaitDurationInPeriod, + "handler_size": handlerSize, + "batch_size": batchSize, + "throttled_size": throttledSize, + }, nil +} + +func getQueueSizes(rt *runtime.Runtime) (int, int, int) { + rc := rt.RP.Get() + defer rc.Close() + + handler, err := tasks.HandlerQueue.Size(rc) + if err != nil { + slog.Error("error calculating handler queue size", "error", err) + } + batch, err := tasks.BatchQueue.Size(rc) + if err != nil { + slog.Error("error calculating batch queue size", "error", err) + } + throttled, err := tasks.ThrottledQueue.Size(rc) + if err != nil { + slog.Error("error calculating throttled queue size", "error", err) + } + + return handler, batch, throttled +} diff --git a/mailroom.go b/mailroom.go index cf33cfeb0..68344332c 100644 --- a/mailroom.go +++ b/mailroom.go @@ -11,7 +11,6 @@ import ( "github.com/appleboy/go-fcm" "github.com/elastic/go-elasticsearch/v8" "github.com/jmoiron/sqlx" - "github.com/nyaruka/gocommon/analytics" "github.com/nyaruka/gocommon/aws/cwatch" "github.com/nyaruka/gocommon/aws/dynamo" "github.com/nyaruka/gocommon/aws/s3x" @@ -133,13 +132,6 @@ func (mr *Mailroom) Start() error { log.Info("elastic ok") } - // if we have a librato token, configure it - if c.LibratoToken != "" { - analytics.RegisterBackend(analytics.NewLibrato(c.LibratoUsername, c.LibratoToken, c.InstanceID, time.Second, mr.wg)) - } - - analytics.Start() - // configure and start cloudwatch mr.rt.CW, err = cwatch.NewService(c.AWSAccessKeyID, c.AWSSecretAccessKey, c.AWSRegion, c.CloudwatchNamespace, c.DeploymentID) if err != nil { @@ -176,7 +168,6 @@ func (mr *Mailroom) Stop() error { mr.throttledForeman.Stop() mr.rt.CW.StopQueue() - analytics.Stop() close(mr.quit) mr.cancel() diff --git a/runtime/config.go b/runtime/config.go index fc630c8ed..8eda94afd 100644 --- a/runtime/config.go +++ b/runtime/config.go @@ -69,19 +69,16 @@ type Config struct { S3SessionsBucket string `help:"S3 bucket to write flow sessions to"` S3Minio bool `help:"S3 is actually Minio or other compatible service"` - CourierAuthToken string `help:"the authentication token used for requests to Courier"` - LibratoUsername string `help:"the username that will be used to authenticate to Librato"` - LibratoToken string `help:"the token that will be used to authenticate to Librato"` - CloudwatchNamespace string `help:"the namespace to use for cloudwatch metrics"` DeploymentID string `help:"the deployment identifier to use for metrics"` + InstanceID string `help:"the instance identifier to use for metrics"` + CourierAuthToken string `help:"the authentication token used for requests to Courier"` AndroidCredentialsFile string `help:"path to JSON file with FCM service account credentials used to sync Android relayers"` - InstanceID string `help:"the unique identifier of this instance, defaults to hostname"` - LogLevel slog.Level `help:"the logging level courier should use"` - UUIDSeed int `help:"seed to use for UUID generation in a testing environment"` - Version string `help:"the version of this mailroom install"` + LogLevel slog.Level `help:"the logging level courier should use"` + UUIDSeed int `help:"seed to use for UUID generation in a testing environment"` + Version string `help:"the version of this mailroom install"` } // NewDefaultConfig returns a new default configuration object @@ -133,11 +130,11 @@ func NewDefaultConfig() *Config { CloudwatchNamespace: "Temba", DeploymentID: "dev", + InstanceID: hostname, - InstanceID: hostname, - LogLevel: slog.LevelWarn, - UUIDSeed: 0, - Version: "Dev", + LogLevel: slog.LevelWarn, + UUIDSeed: 0, + Version: "Dev", } } From 40ff1ce93821a9613676d14e227702b83e7bfd4f Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 13 Dec 2024 16:07:10 -0500 Subject: [PATCH 199/216] Update CHANGELOG.md for v9.3.66 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c5186e7d..941d62062 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.66 (2024-12-13) +------------------------- + * Convert all remaining metrics to cloudwatch + v9.3.65 (2024-12-13) ------------------------- * Add cloudwatch and start sending cron times there From 644d1718f727eccb12a3e7690ca9e53ebc31c67e Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 13 Dec 2024 16:08:19 -0500 Subject: [PATCH 200/216] Tweak metrics cron so that instead of sleeping there is an offset added to the next time --- core/tasks/metrics/cron.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/core/tasks/metrics/cron.go b/core/tasks/metrics/cron.go index 02c84e79a..daace2a9d 100644 --- a/core/tasks/metrics/cron.go +++ b/core/tasks/metrics/cron.go @@ -23,7 +23,9 @@ type metricsCron struct { } func (c *metricsCron) Next(last time.Time) time.Time { - return tasks.CronNext(last, time.Minute) + // We offset 30 seconds from the top of the minute so that metrics related to queue sizes don't see the spikes that + // typically occur every minute by tasks such as expirations, timeouts etc. + return tasks.CronNext(last, time.Minute).Add(time.Second * 30) } func (c *metricsCron) AllInstances() bool { @@ -31,13 +33,6 @@ func (c *metricsCron) AllInstances() bool { } func (c *metricsCron) Run(ctx context.Context, rt *runtime.Runtime) (map[string]any, error) { - // TODO replace with offset passed to tasks.CronNext - // We wait 15 seconds since we fire at the top of the minute, the same as expirations. - // That way any metrics related to the size of our queue are a bit more accurate (all expirations can - // usually be handled in 15 seconds). Something more complicated would take into account the age of - // the items in our queues. - time.Sleep(time.Second * 15) - handlerSize, batchSize, throttledSize := getQueueSizes(rt) // get our DB and redis stats From cd5a0fb577956f1aa346b805f58f9fd9c233f7fd Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 16 Dec 2024 16:02:53 -0500 Subject: [PATCH 201/216] Update to latest gocommon and goflow --- core/tasks/cron.go | 9 +-- core/tasks/handler/handle_contact_event.go | 15 ++--- core/tasks/metrics/cron.go | 66 ++++++---------------- go.mod | 21 ++++--- go.sum | 42 +++++++------- web/flow/testdata/inspect.json | 4 -- 6 files changed, 53 insertions(+), 104 deletions(-) diff --git a/core/tasks/cron.go b/core/tasks/cron.go index 50c02732e..2815ee459 100644 --- a/core/tasks/cron.go +++ b/core/tasks/cron.go @@ -6,8 +6,8 @@ import ( "sync" "time" - "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" + "github.com/nyaruka/gocommon/aws/cwatch" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/mailroom/utils/crons" @@ -68,12 +68,7 @@ func recordCronExecution(name string, r func(context.Context, *runtime.Runtime) elapsed := time.Since(started) elapsedSeconds := elapsed.Seconds() - rt.CW.Queue(types.MetricDatum{ - MetricName: aws.String("CronTaskDuration"), - Dimensions: []types.Dimension{{Name: aws.String("TaskName"), Value: aws.String(name)}}, - Value: aws.Float64(elapsedSeconds), - Unit: types.StandardUnitSeconds, - }) + rt.CW.Queue(cwatch.Datum("CronTaskDuration", elapsedSeconds, types.StandardUnitSeconds, cwatch.Dimension("TaskName", name))) rc := rt.RP.Get() defer rc.Close() diff --git a/core/tasks/handler/handle_contact_event.go b/core/tasks/handler/handle_contact_event.go index dc5211871..854989833 100644 --- a/core/tasks/handler/handle_contact_event.go +++ b/core/tasks/handler/handle_contact_event.go @@ -7,10 +7,10 @@ import ( "log/slog" "time" - "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" "github.com/gomodule/redigo/redis" "github.com/jmoiron/sqlx" + "github.com/nyaruka/gocommon/aws/cwatch" "github.com/nyaruka/gocommon/dbutil" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/mailroom/core/models" @@ -100,15 +100,10 @@ func (t *HandleContactEventTask) Perform(ctx context.Context, rt *runtime.Runtim err = performHandlerTask(ctx, rt, oa, t.ContactID, ctask) // send metrics for processing time and lag from queue time - rt.CW.Queue(types.MetricDatum{ - MetricName: aws.String("HandlerTaskDuration"), - Value: aws.Float64(float64(time.Since(start)) / float64(time.Second)), - Unit: types.StandardUnitSeconds, - }, types.MetricDatum{ - MetricName: aws.String("HandlerTaskLatency"), - Value: aws.Float64(float64(time.Since(taskPayload.QueuedOn)) / float64(time.Second)), - Unit: types.StandardUnitSeconds, - }) + rt.CW.Queue( + cwatch.Datum("HandlerTaskDuration", float64(time.Since(start))/float64(time.Second), types.StandardUnitSeconds), + cwatch.Datum("HandlerTaskLatency", float64(time.Since(taskPayload.QueuedOn))/float64(time.Second), types.StandardUnitSeconds), + ) // if we get an error processing an event, requeue it for later and return our error if err != nil { diff --git a/core/tasks/metrics/cron.go b/core/tasks/metrics/cron.go index 02c84e79a..e7d948a08 100644 --- a/core/tasks/metrics/cron.go +++ b/core/tasks/metrics/cron.go @@ -5,8 +5,8 @@ import ( "log/slog" "time" - "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" + "github.com/nyaruka/gocommon/aws/cwatch" "github.com/nyaruka/mailroom/core/tasks" "github.com/nyaruka/mailroom/runtime" ) @@ -50,55 +50,21 @@ func (c *metricsCron) Run(ctx context.Context, rt *runtime.Runtime) (map[string] c.dbWaitDuration = dbStats.WaitDuration c.redisWaitDuration = redisStats.WaitDuration - dims := []types.Dimension{ - {Name: aws.String("Host"), Value: aws.String(rt.Config.InstanceID)}, - {Name: aws.String("App"), Value: aws.String("mailroom")}, - } - - rt.CW.Queue(types.MetricDatum{ - MetricName: aws.String("DBConnectionsInUse"), - Dimensions: dims, - Value: aws.Float64(float64(dbStats.InUse)), - Unit: types.StandardUnitCount, - }, types.MetricDatum{ - MetricName: aws.String("DBConnectionWaitDuration"), - Dimensions: dims, - Value: aws.Float64(float64(dbWaitDurationInPeriod / time.Second)), - Unit: types.StandardUnitSeconds, - }, types.MetricDatum{ - MetricName: aws.String("RedisConnectionsInUse"), - Dimensions: dims, - Value: aws.Float64(float64(redisStats.ActiveCount)), - Unit: types.StandardUnitCount, - }, types.MetricDatum{ - MetricName: aws.String("RedisConnectionsWaitDuration"), - Dimensions: dims, - Value: aws.Float64(float64(redisWaitDurationInPeriod / time.Second)), - Unit: types.StandardUnitSeconds, - }) - - rt.CW.Queue(types.MetricDatum{ - MetricName: aws.String("QueuedTasks"), - Dimensions: []types.Dimension{ - {Name: aws.String("QueueName"), Value: aws.String("handler")}, - }, - Value: aws.Float64(float64(handlerSize)), - Unit: types.StandardUnitCount, - }, types.MetricDatum{ - MetricName: aws.String("QueuedTasks"), - Dimensions: []types.Dimension{ - {Name: aws.String("QueueName"), Value: aws.String("batch")}, - }, - Value: aws.Float64(float64(batchSize)), - Unit: types.StandardUnitCount, - }, types.MetricDatum{ - MetricName: aws.String("QueuedTasks"), - Dimensions: []types.Dimension{ - {Name: aws.String("QueueName"), Value: aws.String("throttled")}, - }, - Value: aws.Float64(float64(throttledSize)), - Unit: types.StandardUnitCount, - }) + hostDim := cwatch.Dimension("Host", rt.Config.InstanceID) + appDim := cwatch.Dimension("App", "mailroom") + + rt.CW.Queue( + cwatch.Datum("DBConnectionsInUse", float64(dbStats.InUse), types.StandardUnitCount, hostDim, appDim), + cwatch.Datum("DBConnectionWaitDuration", float64(dbWaitDurationInPeriod/time.Second), types.StandardUnitSeconds, hostDim, appDim), + cwatch.Datum("RedisConnectionsInUse", float64(redisStats.ActiveCount), types.StandardUnitCount, hostDim, appDim), + cwatch.Datum("RedisConnectionsWaitDuration", float64(redisWaitDurationInPeriod/time.Second), types.StandardUnitSeconds, hostDim, appDim), + ) + + rt.CW.Queue( + cwatch.Datum("QueuedTasks", float64(handlerSize), types.StandardUnitCount, cwatch.Dimension("QueueName", "handler")), + cwatch.Datum("QueuedTasks", float64(batchSize), types.StandardUnitCount, cwatch.Dimension("QueueName", "batch")), + cwatch.Datum("QueuedTasks", float64(throttledSize), types.StandardUnitCount, cwatch.Dimension("QueueName", "throttled")), + ) return map[string]any{ "db_inuse": dbStats.InUse, diff --git a/go.mod b/go.mod index ac8ba542f..ca15b62e2 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/buger/jsonparser v1.1.1 github.com/elastic/go-elasticsearch/v8 v8.16.0 github.com/getsentry/sentry-go v0.30.0 - github.com/go-chi/chi/v5 v5.1.0 + github.com/go-chi/chi/v5 v5.2.0 github.com/go-playground/validator/v10 v10.23.0 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang/protobuf v1.5.4 @@ -23,11 +23,11 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 - github.com/nyaruka/gocommon v1.60.3 - github.com/nyaruka/goflow v0.225.8 + github.com/nyaruka/gocommon v1.60.4 + github.com/nyaruka/goflow v0.226.0 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 - github.com/nyaruka/rp-indexer/v9 v9.3.0 + github.com/nyaruka/rp-indexer/v9 v9.3.4 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_model v0.6.1 github.com/prometheus/common v0.61.0 @@ -35,15 +35,15 @@ require ( github.com/samber/slog-sentry v1.2.2 github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.10.0 - google.golang.org/api v0.211.0 + google.golang.org/api v0.212.0 ) require ( cel.dev/expr v0.19.1 // indirect cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/auth v0.12.1 // indirect + cloud.google.com/go/auth v0.13.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect - cloud.google.com/go/compute/metadata v0.5.2 // indirect + cloud.google.com/go/compute/metadata v0.6.0 // indirect cloud.google.com/go/firestore v1.17.0 // indirect cloud.google.com/go/iam v1.3.0 // indirect cloud.google.com/go/longrunning v0.6.3 // indirect @@ -100,7 +100,6 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/naoina/go-stringutil v0.1.0 // indirect github.com/naoina/toml v0.1.1 // indirect - github.com/nyaruka/librato v1.1.1 // indirect github.com/nyaruka/null/v2 v2.0.3 // indirect github.com/nyaruka/phonenumbers v1.4.3 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect @@ -116,8 +115,8 @@ require ( go.opentelemetry.io/otel/sdk v1.32.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect - golang.org/x/crypto v0.30.0 // indirect - golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e // indirect golang.org/x/net v0.32.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/sync v0.10.0 // indirect @@ -130,7 +129,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/grpc v1.68.1 // indirect google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3 // indirect - google.golang.org/protobuf v1.35.2 // indirect + google.golang.org/protobuf v1.36.0 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index ebce0bdd3..29bf126cb 100644 --- a/go.sum +++ b/go.sum @@ -3,12 +3,12 @@ cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= -cloud.google.com/go/auth v0.12.1 h1:n2Bj25BUMM0nvE9D2XLTiImanwZhO3DkfWSYS/SAJP4= -cloud.google.com/go/auth v0.12.1/go.mod h1:BFMu+TNpF3DmvfBO9ClqTR/SiqVIm7LukKF9mbendF4= +cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= +cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= -cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= -cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/firestore v1.17.0 h1:iEd1LBbkDZTFsLw3sTH50eyg4qe8eoG6CjocmEXO9aQ= cloud.google.com/go/firestore v1.17.0/go.mod h1:69uPx1papBsY8ZETooc71fOhoKkD70Q1DwMrtKuOT/Y= cloud.google.com/go/iam v1.3.0 h1:4Wo2qTaGKFtajbLpF6I4mywg900u3TLlHDb6mriLDPU= @@ -128,8 +128,8 @@ github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU= github.com/getsentry/sentry-go v0.30.0 h1:lWUwDnY7sKHaVIoZ9wYqRHJ5iEmoc0pqcRqFkosKzBo= github.com/getsentry/sentry-go v0.30.0/go.mod h1:WU9B9/1/sHDqeV8T+3VwwbjeR5MSXs/6aqG3mqZrezA= -github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= -github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0= +github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -224,12 +224,10 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= -github.com/nyaruka/gocommon v1.60.3 h1:fPQ9t6NX+mu7JQ7nXefgpBs8paqGvGXq3eA7VscsAVo= -github.com/nyaruka/gocommon v1.60.3/go.mod h1:kFJuOq8COneV7ssfK6xgCMJ8gP8fQifLQnNXBnE4YL0= -github.com/nyaruka/goflow v0.225.8 h1:rDc3P3KL8sNlXUFBi0UmBvMOd3eCUhUrkO3RbYYiW7o= -github.com/nyaruka/goflow v0.225.8/go.mod h1:spXtSWgS7dusHIfUFCvJGjSMc7d4FX9Abl6S7tg49ks= -github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= -github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= +github.com/nyaruka/gocommon v1.60.4 h1:QpnSJailgaXpfjFjjuKTy/4NufgfmGfdFGib8khXm9o= +github.com/nyaruka/gocommon v1.60.4/go.mod h1:kFJuOq8COneV7ssfK6xgCMJ8gP8fQifLQnNXBnE4YL0= +github.com/nyaruka/goflow v0.226.0 h1:mVJBy1ojDSWdnPx/Y/YPflcKIMgmXjM3oC6VVCRTBo4= +github.com/nyaruka/goflow v0.226.0/go.mod h1:spXtSWgS7dusHIfUFCvJGjSMc7d4FX9Abl6S7tg49ks= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= github.com/nyaruka/null/v2 v2.0.3/go.mod h1:OCVeCkCXwrg5/qE6RU0c1oUVZBy+ZDrT+xYg1XSaIWA= github.com/nyaruka/null/v3 v3.0.0 h1:JvOiNuKmRBFHxzZFt4sWii+ewmMkCQ1vO7X0clTNn6E= @@ -238,8 +236,8 @@ github.com/nyaruka/phonenumbers v1.4.3 h1:tR71UJ+DZu7TSkxoG8JI8HzHJkPD/m4KNiUX34 github.com/nyaruka/phonenumbers v1.4.3/go.mod h1:gv+CtldaFz+G3vHHnasBSirAi3O2XLqZzVWz4V1pl2E= github.com/nyaruka/redisx v0.8.1 h1:d9Hc8nfSKTSEU+bx+YrB13d6bzAgiiHygk4jg/Q4nb4= github.com/nyaruka/redisx v0.8.1/go.mod h1:2TUmkDvprPInnmInR5AEbCm0zRRewkvSDVLsO+Do6iI= -github.com/nyaruka/rp-indexer/v9 v9.3.0 h1:8Thnt7k6/anEYcM3hIY+ObzF3JtO2/8EbDmb4JEAzsc= -github.com/nyaruka/rp-indexer/v9 v9.3.0/go.mod h1:YrHQx+ImBKRUQ4RWFJad1IlcMWlMyry/72SxAVCCgIU= +github.com/nyaruka/rp-indexer/v9 v9.3.4 h1:VtAvXSNNdzULbbvDKzWx+17E53CIgSjisYMeC0w0a3s= +github.com/nyaruka/rp-indexer/v9 v9.3.4/go.mod h1:BnbYed9lCPT8Q+p5BpO7+errhTMo7x0Y0AWjR1BraCk= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= @@ -300,11 +298,11 @@ go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU= -golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4= +golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -358,8 +356,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.211.0 h1:IUpLjq09jxBSV1lACO33CGY3jsRcbctfGzhj+ZSE/Bg= -google.golang.org/api v0.211.0/go.mod h1:XOloB4MXFH4UTlQSGuNUxw0UT74qdENK8d6JNsXKLi0= +google.golang.org/api v0.212.0 h1:BcRj3MJfHF3FYD29rk7u9kuu1SyfGqfHcA0hSwKqkHg= +google.golang.org/api v0.212.0/go.mod h1:gICpLlpp12/E8mycRMzgy3SQ9cFh2XnVJ6vJi/kQbvI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw= @@ -393,8 +391,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ= +google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/web/flow/testdata/inspect.json b/web/flow/testdata/inspect.json index df6ccbf39..a1fbd75f6 100644 --- a/web/flow/testdata/inspect.json +++ b/web/flow/testdata/inspect.json @@ -71,7 +71,6 @@ ], "issues": [], "results": [], - "waiting_exits": [], "parent_refs": [] } }, @@ -168,7 +167,6 @@ } ], "results": [], - "waiting_exits": [], "parent_refs": [] } }, @@ -241,7 +239,6 @@ ] } ], - "waiting_exits": [], "parent_refs": [] } }, @@ -336,7 +333,6 @@ "dependencies": [], "issues": [], "results": [], - "waiting_exits": [], "parent_refs": [] } }, From 20071071b43f7d85699643db3a37dab679eede5d Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 16 Dec 2024 16:26:45 -0500 Subject: [PATCH 202/216] Update CHANGELOG.md for v9.3.67 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 941d62062..227e6f036 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.67 (2024-12-16) +------------------------- + * Update to latest gocommon and goflow + * Tweak metrics cron so that instead of sleeping there is an offset added to the next time + v9.3.66 (2024-12-13) ------------------------- * Convert all remaining metrics to cloudwatch From 3a1e4b9897ff85c0c8975d89701468251945931a Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 16 Dec 2024 17:42:17 -0500 Subject: [PATCH 203/216] Fix cloudwatch service stopping before tasks are stopped --- go.mod | 2 +- go.sum | 4 ++-- mailroom.go | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index ca15b62e2..5d7bc8cde 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 - github.com/nyaruka/gocommon v1.60.4 + github.com/nyaruka/gocommon v1.60.5 github.com/nyaruka/goflow v0.226.0 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 diff --git a/go.sum b/go.sum index 29bf126cb..1a85f6483 100644 --- a/go.sum +++ b/go.sum @@ -224,8 +224,8 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= -github.com/nyaruka/gocommon v1.60.4 h1:QpnSJailgaXpfjFjjuKTy/4NufgfmGfdFGib8khXm9o= -github.com/nyaruka/gocommon v1.60.4/go.mod h1:kFJuOq8COneV7ssfK6xgCMJ8gP8fQifLQnNXBnE4YL0= +github.com/nyaruka/gocommon v1.60.5 h1:V10rosGzVArRspilfbi65TyHBZzjLQbwmaBeicr2Drw= +github.com/nyaruka/gocommon v1.60.5/go.mod h1:kFJuOq8COneV7ssfK6xgCMJ8gP8fQifLQnNXBnE4YL0= github.com/nyaruka/goflow v0.226.0 h1:mVJBy1ojDSWdnPx/Y/YPflcKIMgmXjM3oC6VVCRTBo4= github.com/nyaruka/goflow v0.226.0/go.mod h1:spXtSWgS7dusHIfUFCvJGjSMc7d4FX9Abl6S7tg49ks= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= diff --git a/mailroom.go b/mailroom.go index 68344332c..65b9cc500 100644 --- a/mailroom.go +++ b/mailroom.go @@ -140,7 +140,7 @@ func (mr *Mailroom) Start() error { log.Info("cloudwatch ok") } - mr.rt.CW.StartQueue(mr.wg, time.Second*3) + mr.rt.CW.StartQueue(time.Second * 3) // init our foremen and start it mr.handlerForeman.Start() @@ -167,16 +167,16 @@ func (mr *Mailroom) Stop() error { mr.batchForeman.Stop() mr.throttledForeman.Stop() - mr.rt.CW.StopQueue() - - close(mr.quit) + close(mr.quit) // tell workers and crons to stop mr.cancel() - // stop our web server mr.webserver.Stop() mr.wg.Wait() + // now that all tasks are finished, stop services they depend on + mr.rt.CW.StopQueue() + log.Info("mailroom stopped") return nil } From c3a17019ba55263f229f71efe91a773ee0b05ccb Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 16 Dec 2024 17:57:02 -0500 Subject: [PATCH 204/216] Update CHANGELOG.md for v9.3.68 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 227e6f036..5305cfbc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.68 (2024-12-16) +------------------------- + * Fix cloudwatch service stopping before tasks are stopped + v9.3.67 (2024-12-16) ------------------------- * Update to latest gocommon and goflow From 5cfca83a6d884a77cee043d03893330e241f8630 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 18 Dec 2024 10:20:56 -0500 Subject: [PATCH 205/216] Use same approach to metrics as courier - record events in stats, convert to metrics and send every minute --- cmd/mailroom/main.go | 1 - core/tasks/cron.go | 6 +- core/tasks/handler/handle_contact_event.go | 9 +- core/tasks/metrics/cron.go | 93 --------------------- mailroom.go | 96 ++++++++++++++++++++-- runtime/config.go | 2 +- runtime/runtime.go | 1 + runtime/stats.go | 88 ++++++++++++++++++++ testsuite/testsuite.go | 1 + 9 files changed, 186 insertions(+), 111 deletions(-) delete mode 100644 core/tasks/metrics/cron.go create mode 100644 runtime/stats.go diff --git a/cmd/mailroom/main.go b/cmd/mailroom/main.go index 5a1ce1e6a..c6b3bc344 100644 --- a/cmd/mailroom/main.go +++ b/cmd/mailroom/main.go @@ -27,7 +27,6 @@ import ( _ "github.com/nyaruka/mailroom/core/tasks/incidents" _ "github.com/nyaruka/mailroom/core/tasks/interrupts" _ "github.com/nyaruka/mailroom/core/tasks/ivr" - _ "github.com/nyaruka/mailroom/core/tasks/metrics" _ "github.com/nyaruka/mailroom/core/tasks/msgs" _ "github.com/nyaruka/mailroom/core/tasks/schedules" _ "github.com/nyaruka/mailroom/core/tasks/starts" diff --git a/core/tasks/cron.go b/core/tasks/cron.go index 2815ee459..46915c97c 100644 --- a/core/tasks/cron.go +++ b/core/tasks/cron.go @@ -6,8 +6,6 @@ import ( "sync" "time" - "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" - "github.com/nyaruka/gocommon/aws/cwatch" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/mailroom/utils/crons" @@ -68,7 +66,7 @@ func recordCronExecution(name string, r func(context.Context, *runtime.Runtime) elapsed := time.Since(started) elapsedSeconds := elapsed.Seconds() - rt.CW.Queue(cwatch.Datum("CronTaskDuration", elapsedSeconds, types.StandardUnitSeconds, cwatch.Dimension("TaskName", name))) + rt.Stats.RecordCronTask(name, elapsed) rc := rt.RP.Get() defer rc.Close() @@ -90,7 +88,7 @@ func recordCronExecution(name string, r func(context.Context, *runtime.Runtime) for k, v := range results { logResults = append(logResults, k, v) } - log = log.With("elapsed", elapsedSeconds, slog.Group("results", logResults...)) + log = log.With("elapsed", elapsed, slog.Group("results", logResults...)) // if cron too longer than a minute, log as error if elapsed > time.Minute { diff --git a/core/tasks/handler/handle_contact_event.go b/core/tasks/handler/handle_contact_event.go index 854989833..ee3cf0a06 100644 --- a/core/tasks/handler/handle_contact_event.go +++ b/core/tasks/handler/handle_contact_event.go @@ -7,10 +7,8 @@ import ( "log/slog" "time" - "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" "github.com/gomodule/redigo/redis" "github.com/jmoiron/sqlx" - "github.com/nyaruka/gocommon/aws/cwatch" "github.com/nyaruka/gocommon/dbutil" "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/mailroom/core/models" @@ -99,11 +97,8 @@ func (t *HandleContactEventTask) Perform(ctx context.Context, rt *runtime.Runtim err = performHandlerTask(ctx, rt, oa, t.ContactID, ctask) - // send metrics for processing time and lag from queue time - rt.CW.Queue( - cwatch.Datum("HandlerTaskDuration", float64(time.Since(start))/float64(time.Second), types.StandardUnitSeconds), - cwatch.Datum("HandlerTaskLatency", float64(time.Since(taskPayload.QueuedOn))/float64(time.Second), types.StandardUnitSeconds), - ) + // record metrics + rt.Stats.RecordHandlerTask(time.Since(start), time.Since(taskPayload.QueuedOn)) // if we get an error processing an event, requeue it for later and return our error if err != nil { diff --git a/core/tasks/metrics/cron.go b/core/tasks/metrics/cron.go deleted file mode 100644 index f0b18c46a..000000000 --- a/core/tasks/metrics/cron.go +++ /dev/null @@ -1,93 +0,0 @@ -package analytics - -import ( - "context" - "log/slog" - "time" - - "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" - "github.com/nyaruka/gocommon/aws/cwatch" - "github.com/nyaruka/mailroom/core/tasks" - "github.com/nyaruka/mailroom/runtime" -) - -func init() { - tasks.RegisterCron("metrics", &metricsCron{}) -} - -// calculates a bunch of stats every minute and both logs them and sends them to cloudwatch -type metricsCron struct { - // both sqlx and redis provide wait stats which are cummulative that we need to make into increments - dbWaitDuration time.Duration - redisWaitDuration time.Duration -} - -func (c *metricsCron) Next(last time.Time) time.Time { - // We offset 30 seconds from the top of the minute so that metrics related to queue sizes don't see the spikes that - // typically occur every minute by tasks such as expirations, timeouts etc. - return tasks.CronNext(last, time.Minute).Add(time.Second * 30) -} - -func (c *metricsCron) AllInstances() bool { - return true -} - -func (c *metricsCron) Run(ctx context.Context, rt *runtime.Runtime) (map[string]any, error) { - handlerSize, batchSize, throttledSize := getQueueSizes(rt) - - // get our DB and redis stats - dbStats := rt.DB.Stats() - redisStats := rt.RP.Stats() - - dbWaitDurationInPeriod := dbStats.WaitDuration - c.dbWaitDuration - redisWaitDurationInPeriod := redisStats.WaitDuration - c.redisWaitDuration - - c.dbWaitDuration = dbStats.WaitDuration - c.redisWaitDuration = redisStats.WaitDuration - - hostDim := cwatch.Dimension("Host", rt.Config.InstanceID) - appDim := cwatch.Dimension("App", "mailroom") - - rt.CW.Queue( - cwatch.Datum("DBConnectionsInUse", float64(dbStats.InUse), types.StandardUnitCount, hostDim, appDim), - cwatch.Datum("DBConnectionWaitDuration", float64(dbWaitDurationInPeriod/time.Second), types.StandardUnitSeconds, hostDim, appDim), - cwatch.Datum("RedisConnectionsInUse", float64(redisStats.ActiveCount), types.StandardUnitCount, hostDim, appDim), - cwatch.Datum("RedisConnectionsWaitDuration", float64(redisWaitDurationInPeriod/time.Second), types.StandardUnitSeconds, hostDim, appDim), - ) - - rt.CW.Queue( - cwatch.Datum("QueuedTasks", float64(handlerSize), types.StandardUnitCount, cwatch.Dimension("QueueName", "handler")), - cwatch.Datum("QueuedTasks", float64(batchSize), types.StandardUnitCount, cwatch.Dimension("QueueName", "batch")), - cwatch.Datum("QueuedTasks", float64(throttledSize), types.StandardUnitCount, cwatch.Dimension("QueueName", "throttled")), - ) - - return map[string]any{ - "db_inuse": dbStats.InUse, - "db_wait": dbWaitDurationInPeriod, - "redis_inuse": redisStats.ActiveCount, - "redis_wait": redisWaitDurationInPeriod, - "handler_size": handlerSize, - "batch_size": batchSize, - "throttled_size": throttledSize, - }, nil -} - -func getQueueSizes(rt *runtime.Runtime) (int, int, int) { - rc := rt.RP.Get() - defer rc.Close() - - handler, err := tasks.HandlerQueue.Size(rc) - if err != nil { - slog.Error("error calculating handler queue size", "error", err) - } - batch, err := tasks.BatchQueue.Size(rc) - if err != nil { - slog.Error("error calculating batch queue size", "error", err) - } - throttled, err := tasks.ThrottledQueue.Size(rc) - if err != nil { - slog.Error("error calculating throttled queue size", "error", err) - } - - return handler, batch, throttled -} diff --git a/mailroom.go b/mailroom.go index 65b9cc500..8a862419f 100644 --- a/mailroom.go +++ b/mailroom.go @@ -9,6 +9,7 @@ import ( "time" "github.com/appleboy/go-fcm" + "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" "github.com/elastic/go-elasticsearch/v8" "github.com/jmoiron/sqlx" "github.com/nyaruka/gocommon/aws/cwatch" @@ -34,6 +35,11 @@ type Mailroom struct { throttledForeman *Foreman webserver *web.Server + + // both sqlx and redis provide wait stats which are cummulative that we need to convert into increments by + // tracking their previous values + dbWaitDuration time.Duration + redisWaitDuration time.Duration } // NewMailroom creates and returns a new mailroom instance @@ -140,8 +146,6 @@ func (mr *Mailroom) Start() error { log.Info("cloudwatch ok") } - mr.rt.CW.StartQueue(time.Second * 3) - // init our foremen and start it mr.handlerForeman.Start() mr.batchForeman.Start() @@ -153,11 +157,76 @@ func (mr *Mailroom) Start() error { tasks.StartCrons(mr.rt, mr.wg, mr.quit) + mr.startMetricsReporter(time.Minute) + log.Info("mailroom started", "domain", c.Domain) return nil } +func (mr *Mailroom) startMetricsReporter(interval time.Duration) { + mr.wg.Add(1) + + report := func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + count, err := mr.reportMetrics(ctx) + cancel() + if err != nil { + slog.Error("error reporting metrics", "error", err) + } else { + slog.Info("sent metrics to cloudwatch", "count", count) + } + } + + go func() { + defer func() { + slog.Info("metrics reporter exiting") + mr.wg.Done() + }() + + for { + select { + case <-mr.quit: + report() + return + case <-time.After(interval): // TODO align to half minute marks for queue sizes? + report() + } + } + }() +} + +func (mr *Mailroom) reportMetrics(ctx context.Context) (int, error) { + metrics := mr.rt.Stats.Extract().ToMetrics() + + handlerSize, batchSize, throttledSize := getQueueSizes(mr.rt) + + // calculate DB and redis stats + dbStats := mr.rt.DB.Stats() + redisStats := mr.rt.RP.Stats() + dbWaitDurationInPeriod := dbStats.WaitDuration - mr.dbWaitDuration + redisWaitDurationInPeriod := redisStats.WaitDuration - mr.redisWaitDuration + mr.dbWaitDuration = dbStats.WaitDuration + mr.redisWaitDuration = redisStats.WaitDuration + + hostDim := cwatch.Dimension("Host", mr.rt.Config.InstanceID) + metrics = append(metrics, + cwatch.Datum("DBConnectionsInUse", float64(dbStats.InUse), types.StandardUnitCount, hostDim), + cwatch.Datum("DBConnectionWaitDuration", float64(dbWaitDurationInPeriod/time.Second), types.StandardUnitSeconds, hostDim), + cwatch.Datum("RedisConnectionsInUse", float64(redisStats.ActiveCount), types.StandardUnitCount, hostDim), + cwatch.Datum("RedisConnectionsWaitDuration", float64(redisWaitDurationInPeriod/time.Second), types.StandardUnitSeconds, hostDim), + cwatch.Datum("QueuedTasks", float64(handlerSize), types.StandardUnitCount, cwatch.Dimension("QueueName", "handler")), + cwatch.Datum("QueuedTasks", float64(batchSize), types.StandardUnitCount, cwatch.Dimension("QueueName", "batch")), + cwatch.Datum("QueuedTasks", float64(throttledSize), types.StandardUnitCount, cwatch.Dimension("QueueName", "throttled")), + ) + + if err := mr.rt.CW.Send(ctx, metrics...); err != nil { + return 0, fmt.Errorf("error sending metrics: %w", err) + } + + return len(metrics), nil +} + // Stop stops the mailroom service func (mr *Mailroom) Stop() error { log := slog.With("comp", "mailroom") @@ -174,9 +243,6 @@ func (mr *Mailroom) Stop() error { mr.wg.Wait() - // now that all tasks are finished, stop services they depend on - mr.rt.CW.StopQueue() - log.Info("mailroom stopped") return nil } @@ -199,3 +265,23 @@ func openAndCheckDBConnection(url string, maxOpenConns int) (*sql.DB, *sqlx.DB, return db.DB, db, err } + +func getQueueSizes(rt *runtime.Runtime) (int, int, int) { + rc := rt.RP.Get() + defer rc.Close() + + handler, err := tasks.HandlerQueue.Size(rc) + if err != nil { + slog.Error("error calculating handler queue size", "error", err) + } + batch, err := tasks.BatchQueue.Size(rc) + if err != nil { + slog.Error("error calculating batch queue size", "error", err) + } + throttled, err := tasks.ThrottledQueue.Size(rc) + if err != nil { + slog.Error("error calculating throttled queue size", "error", err) + } + + return handler, batch, throttled +} diff --git a/runtime/config.go b/runtime/config.go index 8eda94afd..107157678 100644 --- a/runtime/config.go +++ b/runtime/config.go @@ -128,7 +128,7 @@ func NewDefaultConfig() *Config { S3AttachmentsBucket: "temba-attachments", S3SessionsBucket: "temba-sessions", - CloudwatchNamespace: "Temba", + CloudwatchNamespace: "Temba/Mailroom", DeploymentID: "dev", InstanceID: hostname, diff --git a/runtime/runtime.go b/runtime/runtime.go index 64e0ee643..df55bd976 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -22,6 +22,7 @@ type Runtime struct { Dynamo *dynamo.Service S3 *s3x.Service ES *elasticsearch.TypedClient + Stats *StatsCollector CW *cwatch.Service FCM FCMClient Config *Config diff --git a/runtime/stats.go b/runtime/stats.go new file mode 100644 index 000000000..10d137e22 --- /dev/null +++ b/runtime/stats.go @@ -0,0 +1,88 @@ +package runtime + +import ( + "sync" + "time" + + "github.com/aws/aws-sdk-go-v2/service/cloudwatch/types" + "github.com/nyaruka/gocommon/aws/cwatch" +) + +type Stats struct { + HandlerTaskCount int // number of contact tasks handled + HandlerTaskDuration time.Duration // total time spent handling contact tasks + HandlerTaskLatency time.Duration // total time spent queuing and handling contact tasks + + CronTaskCount map[string]int // number of cron tasks run by type + CronTaskDuration map[string]time.Duration // total time spent running cron tasks +} + +func newStats() *Stats { + return &Stats{ + CronTaskCount: make(map[string]int), + CronTaskDuration: make(map[string]time.Duration), + } +} + +func (s *Stats) ToMetrics() []types.MetricDatum { + metrics := make([]types.MetricDatum, 0, 20) + + // convert handler task timings to averages + avgHandlerTaskDuration, avgHandlerTaskLatency := time.Duration(0), time.Duration(0) + if s.HandlerTaskCount > 0 { + avgHandlerTaskDuration = s.HandlerTaskDuration / time.Duration(s.HandlerTaskCount) + avgHandlerTaskLatency = s.HandlerTaskLatency / time.Duration(s.HandlerTaskCount) + } + + metrics = append(metrics, + cwatch.Datum("HandlerTaskCount", float64(s.HandlerTaskCount), types.StandardUnitCount), + cwatch.Datum("HandlerTaskDuration", float64(avgHandlerTaskDuration/time.Second), types.StandardUnitCount), + cwatch.Datum("HandlerTaskLatency", float64(avgHandlerTaskLatency/time.Second), types.StandardUnitCount), + ) + + for name, count := range s.CronTaskCount { + avgTime := s.CronTaskDuration[name] / time.Duration(count) + + metrics = append(metrics, + cwatch.Datum("CronTaskCount", float64(count), types.StandardUnitCount, cwatch.Dimension("TaskName", name)), + cwatch.Datum("CronTaskDuration", float64(avgTime/time.Second), types.StandardUnitSeconds, cwatch.Dimension("TaskName", name)), + ) + } + + return metrics +} + +// StatsCollector provides threadsafe stats collection +type StatsCollector struct { + mutex sync.Mutex + stats *Stats +} + +// NewStatsCollector creates a new stats collector +func NewStatsCollector() *StatsCollector { + return &StatsCollector{stats: newStats()} +} + +func (c *StatsCollector) RecordHandlerTask(d, l time.Duration) { + c.mutex.Lock() + c.stats.HandlerTaskCount++ + c.stats.HandlerTaskDuration += d + c.stats.HandlerTaskLatency += l + c.mutex.Unlock() +} + +func (c *StatsCollector) RecordCronTask(name string, d time.Duration) { + c.mutex.Lock() + c.stats.CronTaskCount[name]++ + c.stats.CronTaskDuration[name] += d + c.mutex.Unlock() +} + +// Extract returns the stats for the period since the last call +func (c *StatsCollector) Extract() *Stats { + c.mutex.Lock() + defer c.mutex.Unlock() + s := c.stats + c.stats = newStats() + return s +} diff --git a/testsuite/testsuite.go b/testsuite/testsuite.go index 426107cfa..601346091 100644 --- a/testsuite/testsuite.go +++ b/testsuite/testsuite.go @@ -104,6 +104,7 @@ func Runtime() (context.Context, *runtime.Runtime) { S3: s3svc, ES: getES(), CW: cwSvc, + Stats: runtime.NewStatsCollector(), FCM: &MockFCMClient{ValidTokens: []string{"FCMID3", "FCMID4", "FCMID5"}}, Config: cfg, } From 7d4139b5b76cd0002700324b93c6b2ed4018290b Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 18 Dec 2024 11:50:15 -0500 Subject: [PATCH 206/216] Update README.md --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 10d9887db..4f994c76a 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,11 @@ [![Build Status](https://github.com/nyaruka/mailroom/workflows/CI/badge.svg)](https://github.com/nyaruka/mailroom/actions?query=workflow%3ACI) [![codecov](https://codecov.io/gh/nyaruka/mailroom/branch/main/graph/badge.svg)](https://codecov.io/gh/nyaruka/mailroom) -Service for RapidPro/TextIt which does most of the heavy lifting. It interacts directly with the database and sends and -receives messages with [Courier](https://github.com/nyaruka/courier) for handling via Redis. +Task handling service for the RapidPro/TextIt platform. ## Deploying -As a Go application, it compiles to a binary and that binary along with the config file is all -you need to run it on your server. You can find bundles for each platform in the -[releases directory](https://github.com/nyaruka/mailroom/releases). We recommend running it -behind a reverse proxy such as nginx or Elastic Load Balancer that provides HTTPs encryption. +It compiles to a binary and can find bundles for each platform in the [releases directory](https://github.com/nyaruka/mailroom/releases). ## Configuration From 0e4e260313e3d84d20d4b9eb250a1590b69ad1a1 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 18 Dec 2024 11:57:40 -0500 Subject: [PATCH 207/216] Update CHANGELOG.md for v9.3.69 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5305cfbc8..96c668e94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.69 (2024-12-18) +------------------------- + * Use same approach to metrics as courier - record events in stats, convert to metrics and send every minute + v9.3.68 (2024-12-16) ------------------------- * Fix cloudwatch service stopping before tasks are stopped From 775f2ad2cd774e7ede5c79c211b6f05f4db81630 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 18 Dec 2024 13:21:40 -0500 Subject: [PATCH 208/216] Fix initializing stats collector --- mailroom.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mailroom.go b/mailroom.go index 8a862419f..515ac997b 100644 --- a/mailroom.go +++ b/mailroom.go @@ -45,7 +45,7 @@ type Mailroom struct { // NewMailroom creates and returns a new mailroom instance func NewMailroom(config *runtime.Config) *Mailroom { mr := &Mailroom{ - rt: &runtime.Runtime{Config: config}, + rt: &runtime.Runtime{Config: config, Stats: runtime.NewStatsCollector()}, quit: make(chan bool), wg: &sync.WaitGroup{}, } From d70011eb2a76408ed4172535a6ae2487124bcede Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 18 Dec 2024 13:22:01 -0500 Subject: [PATCH 209/216] Update CHANGELOG.md for v9.3.70 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96c668e94..425723c81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.70 (2024-12-18) +------------------------- + * Fix initializing stats collector + v9.3.69 (2024-12-18) ------------------------- * Use same approach to metrics as courier - record events in stats, convert to metrics and send every minute From de97f266507ffd93ab8b9bde08e521452c944285 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 18 Dec 2024 15:20:52 -0500 Subject: [PATCH 210/216] Fix integer rounding in duration metrics --- mailroom.go | 4 ++-- runtime/stats.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mailroom.go b/mailroom.go index 515ac997b..d25dbe191 100644 --- a/mailroom.go +++ b/mailroom.go @@ -212,9 +212,9 @@ func (mr *Mailroom) reportMetrics(ctx context.Context) (int, error) { hostDim := cwatch.Dimension("Host", mr.rt.Config.InstanceID) metrics = append(metrics, cwatch.Datum("DBConnectionsInUse", float64(dbStats.InUse), types.StandardUnitCount, hostDim), - cwatch.Datum("DBConnectionWaitDuration", float64(dbWaitDurationInPeriod/time.Second), types.StandardUnitSeconds, hostDim), + cwatch.Datum("DBConnectionWaitDuration", float64(dbWaitDurationInPeriod)/float64(time.Second), types.StandardUnitSeconds, hostDim), cwatch.Datum("RedisConnectionsInUse", float64(redisStats.ActiveCount), types.StandardUnitCount, hostDim), - cwatch.Datum("RedisConnectionsWaitDuration", float64(redisWaitDurationInPeriod/time.Second), types.StandardUnitSeconds, hostDim), + cwatch.Datum("RedisConnectionsWaitDuration", float64(redisWaitDurationInPeriod)/float64(time.Second), types.StandardUnitSeconds, hostDim), cwatch.Datum("QueuedTasks", float64(handlerSize), types.StandardUnitCount, cwatch.Dimension("QueueName", "handler")), cwatch.Datum("QueuedTasks", float64(batchSize), types.StandardUnitCount, cwatch.Dimension("QueueName", "batch")), cwatch.Datum("QueuedTasks", float64(throttledSize), types.StandardUnitCount, cwatch.Dimension("QueueName", "throttled")), diff --git a/runtime/stats.go b/runtime/stats.go index 10d137e22..8ea99863f 100644 --- a/runtime/stats.go +++ b/runtime/stats.go @@ -36,8 +36,8 @@ func (s *Stats) ToMetrics() []types.MetricDatum { metrics = append(metrics, cwatch.Datum("HandlerTaskCount", float64(s.HandlerTaskCount), types.StandardUnitCount), - cwatch.Datum("HandlerTaskDuration", float64(avgHandlerTaskDuration/time.Second), types.StandardUnitCount), - cwatch.Datum("HandlerTaskLatency", float64(avgHandlerTaskLatency/time.Second), types.StandardUnitCount), + cwatch.Datum("HandlerTaskDuration", float64(avgHandlerTaskDuration)/float64(time.Second), types.StandardUnitCount), + cwatch.Datum("HandlerTaskLatency", float64(avgHandlerTaskLatency)/float64(time.Second), types.StandardUnitCount), ) for name, count := range s.CronTaskCount { @@ -45,7 +45,7 @@ func (s *Stats) ToMetrics() []types.MetricDatum { metrics = append(metrics, cwatch.Datum("CronTaskCount", float64(count), types.StandardUnitCount, cwatch.Dimension("TaskName", name)), - cwatch.Datum("CronTaskDuration", float64(avgTime/time.Second), types.StandardUnitSeconds, cwatch.Dimension("TaskName", name)), + cwatch.Datum("CronTaskDuration", float64(avgTime)/float64(time.Second), types.StandardUnitSeconds, cwatch.Dimension("TaskName", name)), ) } From d50483a33f37bf8b7a493d6ee5bfbbee021af50f Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 18 Dec 2024 15:42:13 -0500 Subject: [PATCH 211/216] Update CHANGELOG.md for v9.3.71 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 425723c81..bfd20aab2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.71 (2024-12-18) +------------------------- + * Fix integer rounding in duration metrics + v9.3.70 (2024-12-18) ------------------------- * Fix initializing stats collector From a48faf325a99074cb4177d53304f4beddd159653 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 6 Jan 2025 10:24:02 -0500 Subject: [PATCH 212/216] Update deps includiung goflow and phonenumbers --- go.mod | 83 ++++++++++--------- go.sum | 249 ++++++++++++++++++++------------------------------------- 2 files changed, 127 insertions(+), 205 deletions(-) diff --git a/go.mod b/go.mod index 5d7bc8cde..ef41f4b24 100644 --- a/go.mod +++ b/go.mod @@ -6,13 +6,13 @@ require ( firebase.google.com/go/v4 v4.15.1 github.com/Masterminds/semver v1.5.0 github.com/appleboy/go-fcm v1.2.1 - github.com/aws/aws-sdk-go-v2 v1.32.6 - github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.21 - github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.43.3 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.38.0 - github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 + github.com/aws/aws-sdk-go-v2 v1.32.7 + github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.22 + github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.43.4 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.38.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.72.0 github.com/buger/jsonparser v1.1.1 - github.com/elastic/go-elasticsearch/v8 v8.16.0 + github.com/elastic/go-elasticsearch/v8 v8.17.0 github.com/getsentry/sentry-go v0.30.0 github.com/go-chi/chi/v5 v5.2.0 github.com/go-playground/validator/v10 v10.23.0 @@ -24,30 +24,30 @@ require ( github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 github.com/nyaruka/gocommon v1.60.5 - github.com/nyaruka/goflow v0.226.0 + github.com/nyaruka/goflow v0.226.1 github.com/nyaruka/null/v3 v3.0.0 - github.com/nyaruka/redisx v0.8.1 + github.com/nyaruka/redisx v0.9.0 github.com/nyaruka/rp-indexer/v9 v9.3.4 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_model v0.6.1 github.com/prometheus/common v0.61.0 - github.com/samber/slog-multi v1.2.4 + github.com/samber/slog-multi v1.3.3 github.com/samber/slog-sentry v1.2.2 github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.10.0 - google.golang.org/api v0.212.0 + google.golang.org/api v0.214.0 ) require ( cel.dev/expr v0.19.1 // indirect - cloud.google.com/go v0.116.0 // indirect + cloud.google.com/go v0.118.0 // indirect cloud.google.com/go/auth v0.13.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect cloud.google.com/go/compute/metadata v0.6.0 // indirect cloud.google.com/go/firestore v1.17.0 // indirect - cloud.google.com/go/iam v1.3.0 // indirect - cloud.google.com/go/longrunning v0.6.3 // indirect - cloud.google.com/go/monitoring v1.22.0 // indirect + cloud.google.com/go/iam v1.3.1 // indirect + cloud.google.com/go/longrunning v0.6.4 // indirect + cloud.google.com/go/monitoring v1.22.1 // indirect cloud.google.com/go/storage v1.48.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect @@ -56,34 +56,34 @@ require ( github.com/Shopify/gomail v0.0.0-20220729171026-0784ece65e69 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect - github.com/aws/aws-sdk-go-v2/config v1.28.6 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.47 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // indirect + github.com/aws/aws-sdk-go-v2/config v1.28.7 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.48 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 // indirect - github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.10 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.6 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 // indirect github.com/aws/smithy-go v1.22.1 // indirect github.com/blevesearch/segment v0.9.1 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect + github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/elastic/elastic-transport-go/v8 v8.6.0 // indirect github.com/envoyproxy/go-control-plane v0.13.1 // indirect github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/gabriel-vasile/mimetype v1.4.7 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -93,7 +93,7 @@ require ( github.com/google/s2a-go v0.1.8 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect - github.com/googleapis/gax-go/v2 v2.14.0 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect @@ -101,12 +101,11 @@ require ( github.com/naoina/go-stringutil v0.1.0 // indirect github.com/naoina/toml v0.1.1 // indirect github.com/nyaruka/null/v2 v2.0.3 // indirect - github.com/nyaruka/phonenumbers v1.4.3 // indirect + github.com/nyaruka/phonenumbers v1.4.4 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/samber/lo v1.47.0 // indirect github.com/sergi/go-diff v1.3.1 // indirect - go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.32.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect @@ -116,20 +115,20 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect golang.org/x/crypto v0.31.0 // indirect - golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e // indirect - golang.org/x/net v0.32.0 // indirect - golang.org/x/oauth2 v0.24.0 // indirect + golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/oauth2 v0.25.0 // indirect golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.8.0 // indirect + golang.org/x/time v0.9.0 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect - google.golang.org/genproto v0.0.0-20241209162323-e6fa225c2576 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 // indirect google.golang.org/grpc v1.68.1 // indirect google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3 // indirect - google.golang.org/protobuf v1.36.0 // indirect + google.golang.org/protobuf v1.36.1 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1a85f6483..1161e3f0b 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,7 @@ cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= -cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go v0.118.0 h1:tvZe1mgqRxpiVa3XlIGMiPcEUbP1gNXELgD4y/IXmeQ= +cloud.google.com/go v0.118.0/go.mod h1:zIt2pkedt/mo+DQjcT4/L3NDxzHPR29j5HcclNH+9PM= cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= @@ -11,23 +10,22 @@ cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4 cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/firestore v1.17.0 h1:iEd1LBbkDZTFsLw3sTH50eyg4qe8eoG6CjocmEXO9aQ= cloud.google.com/go/firestore v1.17.0/go.mod h1:69uPx1papBsY8ZETooc71fOhoKkD70Q1DwMrtKuOT/Y= -cloud.google.com/go/iam v1.3.0 h1:4Wo2qTaGKFtajbLpF6I4mywg900u3TLlHDb6mriLDPU= -cloud.google.com/go/iam v1.3.0/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= -cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk= -cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= -cloud.google.com/go/longrunning v0.6.3 h1:A2q2vuyXysRcwzqDpMMLSI6mb6o39miS52UEG/Rd2ng= -cloud.google.com/go/longrunning v0.6.3/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= -cloud.google.com/go/monitoring v1.22.0 h1:mQ0040B7dpuRq1+4YiQD43M2vW9HgoVxY98xhqGT+YI= -cloud.google.com/go/monitoring v1.22.0/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= +cloud.google.com/go/iam v1.3.1 h1:KFf8SaT71yYq+sQtRISn90Gyhyf4X8RGgeAVC8XGf3E= +cloud.google.com/go/iam v1.3.1/go.mod h1:3wMtuyT4NcbnYNPLMBzYRFiEfjKfJlLVLrisE7bwm34= +cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= +cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= +cloud.google.com/go/longrunning v0.6.4 h1:3tyw9rO3E2XVXzSApn1gyEEnH2K9SynNQjMlBi3uHLg= +cloud.google.com/go/longrunning v0.6.4/go.mod h1:ttZpLCe6e7EXvn9OxpBRx7kZEB0efv8yBO6YnVMfhJs= +cloud.google.com/go/monitoring v1.22.1 h1:KQbnAC4IAH+5x3iWuPZT5iN9VXqKMzzOgqcYB6fqPDE= +cloud.google.com/go/monitoring v1.22.1/go.mod h1:AuZZXAoN0WWWfsSvET1Cpc4/1D8LXq8KRDU87fMS6XY= cloud.google.com/go/storage v1.48.0 h1:FhBDHACbVtdPx7S/AbcKujPWiHvfO6F8OXGgCEbB2+o= cloud.google.com/go/storage v1.48.0/go.mod h1:aFoDYNMAjv67lp+xcuZqjUKv/ctmplzQ3wJgodA7b+M= -cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI= -cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= +cloud.google.com/go/trace v1.11.3 h1:c+I4YFjxRQjvAhRmSsmjpASUKq88chOX854ied0K/pE= +cloud.google.com/go/trace v1.11.3/go.mod h1:pt7zCYiDSQjC9Y2oqCsh9jF4GStB/hmjrYLsxRR27q8= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= firebase.google.com/go/v4 v4.15.1 h1:tR2dzKw1MIfCfG2bhAyxa5KQ57zcE7iFKmeYClET6ZM= firebase.google.com/go/v4 v4.15.1/go.mod h1:eunxbsh4UXI2rA8po3sOiebvWYuW0DVxAdZFO0I6wdY= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 h1:o90wcURuxekmXrtxmYWTyNla0+ZEHhud6DI1ZTxd1vI= @@ -46,86 +44,79 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/appleboy/go-fcm v1.2.1 h1:NhpACabtRuAplYg6bTNfSr3LBwsSuutP55HsphzLU/g= github.com/appleboy/go-fcm v1.2.1/go.mod h1:5FzMN+9J2sxnkoys9h3y48GQH8HnI637Q/ro/uP2Qsk= -github.com/aws/aws-sdk-go-v2 v1.32.6 h1:7BokKRgRPuGmKkFMhEg/jSul+tB9VvXhcViILtfG8b4= -github.com/aws/aws-sdk-go-v2 v1.32.6/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= +github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw= +github.com/aws/aws-sdk-go-v2 v1.32.7/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= -github.com/aws/aws-sdk-go-v2/config v1.28.6 h1:D89IKtGrs/I3QXOLNTH93NJYtDhm8SYa9Q5CsPShmyo= -github.com/aws/aws-sdk-go-v2/config v1.28.6/go.mod h1:GDzxJ5wyyFSCoLkS+UhGB0dArhb9mI+Co4dHtoTxbko= -github.com/aws/aws-sdk-go-v2/credentials v1.17.47 h1:48bA+3/fCdi2yAwVt+3COvmatZ6jUDNkDTIsqDiMUdw= -github.com/aws/aws-sdk-go-v2/credentials v1.17.47/go.mod h1:+KdckOejLW3Ks3b0E3b5rHsr2f9yuORBum0WPnE5o5w= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.21 h1:FdDxp4HNtJWPBAOdkJ+84Dfx2TOA7Dq+cH72GDHhjnA= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.21/go.mod h1:doHEXGiMWQBxcTJy3YN1Ao2HCgCuMWumuvTULGndCuQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 h1:AmoU1pziydclFT/xRV+xXE/Vb8fttJCLRPv8oAkprc0= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21/go.mod h1:AjUdLYe4Tgs6kpH4Bv7uMZo7pottoyHMn4eTcIcneaY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 h1:s/fF4+yDQDoElYhfIVvSNyeCydfbuTKzhxSXDXCPasU= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25/go.mod h1:IgPfDv5jqFIzQSNbUEMoitNooSMXjRSDkhXv8jiROvU= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 h1:ZntTCl5EsYnhN/IygQEUugpdwbhdkom9uHcbCftiGgA= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25/go.mod h1:DBdPrgeocww+CSl1C8cEV8PN1mHMBhuCDLpXezyvWkE= +github.com/aws/aws-sdk-go-v2/config v1.28.7 h1:GduUnoTXlhkgnxTD93g1nv4tVPILbdNQOzav+Wpg7AE= +github.com/aws/aws-sdk-go-v2/config v1.28.7/go.mod h1:vZGX6GVkIE8uECSUHB6MWAUsd4ZcG2Yq/dMa4refR3M= +github.com/aws/aws-sdk-go-v2/credentials v1.17.48 h1:IYdLD1qTJ0zanRavulofmqut4afs45mOWEI+MzZtTfQ= +github.com/aws/aws-sdk-go-v2/credentials v1.17.48/go.mod h1:tOscxHN3CGmuX9idQ3+qbkzrjVIx32lqDSU1/0d/qXs= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.22 h1:p2LDiYhvM9mMExEY1meHMAmjmVlzD1J1jVG+fGut+mE= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.22/go.mod h1:fo5T2fYMHVF2rHrym50h7Ue/+SECRJlUHUFZLjSX18g= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22 h1:kqOrpojG71DxJm/KDPO+Z/y1phm1JlC8/iT+5XRmAn8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.22/go.mod h1:NtSFajXVVL8TA2QNngagVZmUtXciyrHOt7xgz4faS/M= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 h1:I/5wmGMffY4happ8NOCuIUEWGUvvFp5NSeQcXl9RHcI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26/go.mod h1:FR8f4turZtNy6baO0KJ5FJUmXH/cSkI9fOngs0yl6mA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 h1:zXFLuEuMMUOvEARXFUVJdfqZ4bvvSgdGRq/ATcrQxzM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26/go.mod h1:3o2Wpy0bogG1kyOPrgkXA8pgIfEEv0+m19O9D5+W8y8= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25 h1:r67ps7oHCYnflpgDy2LZU0MAQtQbYIOqNNnqGO6xQkE= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.25/go.mod h1:GrGY+Q4fIokYLtjCVB/aFfCVL6hhGUFl8inD18fDalE= -github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.43.3 h1:nQLG9irjDGUFXVPDHzjCGEEwh0hZ6BcxTvHOod1YsP4= -github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.43.3/go.mod h1:URs8sqsyaxiAZkKP6tOEmhcs9j2ynFIomqOKY/CAHJc= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.38.0 h1:isKhHsjpQR3CypQJ4G1g8QWx7zNpiC/xKw1zjgJYVno= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.38.0/go.mod h1:xDvUyIkwBwNtVZJdHEwAuhFly3mezwdEWkbJ5oNYwIw= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.9 h1:yhB2XYpHeWeAv5u3w9PFiSVIariSyhK5jcyQUFJpnIQ= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.9/go.mod h1:Hcjb2SiUo9v1GhpXjRNW7hAwfzAPfrsgnlKpP5UYEPY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26 h1:GeNJsIFHB+WW5ap2Tec4K6dzcVTsRbsT1Lra46Hv9ME= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.26/go.mod h1:zfgMpwHDXX2WGoG84xG2H+ZlPTkJUU4YUvx2svLQYWo= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.43.4 h1:nv6UzNfGzyq/nNXwk2mH8PCmcC+5oAt+L7OETT2U0CE= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.43.4/go.mod h1:aBk4XbmWf8p4N15l6DPVgb2t/n5gpk+mZMbigYV3a1Y= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.38.1 h1:AnSNs7Ogi0LXHPMDBx4RE7imU4/JmzWFziqkMKJA2AY= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.38.1/go.mod h1:J8xqRbx7HIc8ids2P8JbrKx9irONPEYq7Z1FpLDpi3I= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.10 h1:aWEbNPNdGiTGSR6/Yy9S0Ad07sMVaT/CFaVq7GuDGx4= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.10/go.mod h1:HywkMgYwY0uaybPvvctx6fkm3L1ssRKeGv7TPZ6OQ/M= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6 h1:HCpPsWqmYQieU7SS6E9HXfdAMSud0pteVXieJmcpIRI= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.6/go.mod h1:ngUiVRCco++u+soRRVBIvBZxSMMvOVMXA4PJ36JLfSw= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.6 h1:nbmKXZzXPJn41CcD4HsHsGWqvKjLKz9kWu6XxvLmf1s= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.6/go.mod h1:SJhcisfKfAawsdNQoZMBEjg+vyN2lH6rO6fP+T94z5Y= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6 h1:50+XsN70RS7dwJ2CkVNXzj7U2L1HKP8nqTd3XWEXBN4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.6/go.mod h1:WqgLmwY7so32kG01zD8CPTJWVWM+TzJoOVHwTg4aPug= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 h1:BbGDtTi0T1DYlmjBiCr/le3wzhA37O8QTC5/Ab8+EXk= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6/go.mod h1:hLMJt7Q8ePgViKupeymbqI0la+t9/iYFBjxQCFwuAwI= -github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 h1:nyuzXooUNJexRT0Oy0UQY6AhOzxPxhtt4DcBIHyCnmw= -github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0/go.mod h1:sT/iQz8JK3u/5gZkT+Hmr7GzVZehUMkRZpOaAwYXeGY= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.7 h1:rLnYAfXQ3YAccocshIH5mzNNwZBkBo+bP6EhIxak6Hw= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.7/go.mod h1:ZHtuQJ6t9A/+YDuxOLnbryAmITtr8UysSny3qcyvJTc= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6 h1:JnhTZR3PiYDNKlXy50/pNeix9aGMo6lLpXwJ1mw8MD4= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.6/go.mod h1:URronUEGfXZN1VpdktPSD1EkAL9mfrV+2F4sjH38qOY= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.2 h1:s4074ZO1Hk8qv65GqNXqDjmkf4HSQqJukaLuuW0TpDA= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.2/go.mod h1:mVggCnIWoM09jP71Wh+ea7+5gAp53q+49wDFs1SW5z8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7 h1:tB4tNw83KcajNAzaIMhkhVI2Nt8fAZd5A5ro113FEMY= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.7/go.mod h1:lvpyBGkZ3tZ9iSsUIcC2EWp+0ywa7aK3BLT+FwZi+mQ= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.7 h1:EqGlayejoCRXmnVC6lXl6phCm9R2+k35e0gWsO9G5DI= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.7/go.mod h1:BTw+t+/E5F3ZnDai/wSOYM54WUVjSdewE7Jvwtb7o+w= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 h1:8eUsivBQzZHqe/3FE+cqwfH+0p5Jo8PFM/QYQSmeZ+M= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7/go.mod h1:kLPQvGUmxn/fqiCrDeohwG33bq2pQpGeY62yRO6Nrh0= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7 h1:Hi0KGbrnr57bEHWM0bJ1QcBzxLrL/k2DHvGYhb8+W1w= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.7/go.mod h1:wKNgWgExdjjrm4qvfbTorkvocEstaoDl4WCvGfeCy9c= +github.com/aws/aws-sdk-go-v2/service/s3 v1.72.0 h1:SAfh4pNx5LuTafKKWR02Y+hL3A+3TX8cTKG1OIAJaBk= +github.com/aws/aws-sdk-go-v2/service/s3 v1.72.0/go.mod h1:r+xl5yzMk9083rMR+sJ5TYj9Tihvf/l1oxzZXDgGj2Q= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 h1:CvuUmnXI7ebaUAhbJcDy9YQx8wHR69eZ9I7q5hszt/g= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.8/go.mod h1:XDeGv1opzwm8ubxddF0cgqkZWsyOtw4lr6dxwmb6YQg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7 h1:F2rBfNAL5UyswqoeWv9zs74N/NanhK16ydHW1pahX6E= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.7/go.mod h1:JfyQ0g2JG8+Krq0EuZNnRwX0mU0HrwY/tG6JNfcqh4k= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.3 h1:Xgv/hyNgvLda/M9l9qxXc4UFSgppnRczLxlMs5Ae/QY= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.3/go.mod h1:5Gn+d+VaaRgsjewpMvGazt0WfcFO+Md4wLOuBfGR9Bc= github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= -github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q= +github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/elastic/elastic-transport-go/v8 v8.6.0 h1:Y2S/FBjx1LlCv5m6pWAF2kDJAHoSjSRSJCApolgfthA= github.com/elastic/elastic-transport-go/v8 v8.6.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk= -github.com/elastic/go-elasticsearch/v8 v8.16.0 h1:f7bR+iBz8GTAVhwyFO3hm4ixsz2eMaEy0QroYnXV3jE= -github.com/elastic/go-elasticsearch/v8 v8.16.0/go.mod h1:lGMlgKIbYoRvay3xWBeKahAiJOgmFDsjZC39nmO3H64= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/elastic/go-elasticsearch/v8 v8.17.0 h1:e9cWksE/Fr7urDRmGPGp47Nsp4/mvNOrU8As1l2HQQ0= +github.com/elastic/go-elasticsearch/v8 v8.17.0/go.mod h1:lGMlgKIbYoRvay3xWBeKahAiJOgmFDsjZC39nmO3H64= github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE= github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA= -github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/getsentry/sentry-go v0.30.0 h1:lWUwDnY7sKHaVIoZ9wYqRHJ5iEmoc0pqcRqFkosKzBo= github.com/getsentry/sentry-go v0.30.0/go.mod h1:WU9B9/1/sHDqeV8T+3VwwbjeR5MSXs/6aqG3mqZrezA= github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0= @@ -152,31 +143,13 @@ github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s= github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -184,13 +157,12 @@ github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9 github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= -github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o= -github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= @@ -226,16 +198,16 @@ github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= github.com/nyaruka/gocommon v1.60.5 h1:V10rosGzVArRspilfbi65TyHBZzjLQbwmaBeicr2Drw= github.com/nyaruka/gocommon v1.60.5/go.mod h1:kFJuOq8COneV7ssfK6xgCMJ8gP8fQifLQnNXBnE4YL0= -github.com/nyaruka/goflow v0.226.0 h1:mVJBy1ojDSWdnPx/Y/YPflcKIMgmXjM3oC6VVCRTBo4= -github.com/nyaruka/goflow v0.226.0/go.mod h1:spXtSWgS7dusHIfUFCvJGjSMc7d4FX9Abl6S7tg49ks= +github.com/nyaruka/goflow v0.226.1 h1:fpEFy5FKufuJ7kfmJPhNXxiE3UjOAf3ASzKxk9NyMEU= +github.com/nyaruka/goflow v0.226.1/go.mod h1:spXtSWgS7dusHIfUFCvJGjSMc7d4FX9Abl6S7tg49ks= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= github.com/nyaruka/null/v2 v2.0.3/go.mod h1:OCVeCkCXwrg5/qE6RU0c1oUVZBy+ZDrT+xYg1XSaIWA= github.com/nyaruka/null/v3 v3.0.0 h1:JvOiNuKmRBFHxzZFt4sWii+ewmMkCQ1vO7X0clTNn6E= github.com/nyaruka/null/v3 v3.0.0/go.mod h1:Sus286RmC8P0VihFuQDDQPib/xJQ7++TsaPLdRuwgVc= -github.com/nyaruka/phonenumbers v1.4.3 h1:tR71UJ+DZu7TSkxoG8JI8HzHJkPD/m4KNiUX34Fvmlo= -github.com/nyaruka/phonenumbers v1.4.3/go.mod h1:gv+CtldaFz+G3vHHnasBSirAi3O2XLqZzVWz4V1pl2E= -github.com/nyaruka/redisx v0.8.1 h1:d9Hc8nfSKTSEU+bx+YrB13d6bzAgiiHygk4jg/Q4nb4= -github.com/nyaruka/redisx v0.8.1/go.mod h1:2TUmkDvprPInnmInR5AEbCm0zRRewkvSDVLsO+Do6iI= +github.com/nyaruka/phonenumbers v1.4.4 h1:9yo9jLvXD7J4exe7GJATApgTlB+05snF0joMDL1p7nQ= +github.com/nyaruka/phonenumbers v1.4.4/go.mod h1:gv+CtldaFz+G3vHHnasBSirAi3O2XLqZzVWz4V1pl2E= +github.com/nyaruka/redisx v0.9.0 h1:JeXHExKldp39XTOHYPyt/8VXQA770O6DZmsJrJDUOnQ= +github.com/nyaruka/redisx v0.9.0/go.mod h1:HN+l71YfLNaE+3V52NyxGxfF3LZVsPBN72M8FcNpLAk= github.com/nyaruka/rp-indexer/v9 v9.3.4 h1:VtAvXSNNdzULbbvDKzWx+17E53CIgSjisYMeC0w0a3s= github.com/nyaruka/rp-indexer/v9 v9.3.4/go.mod h1:BnbYed9lCPT8Q+p5BpO7+errhTMo7x0Y0AWjR1BraCk= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= @@ -248,7 +220,6 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgm github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= @@ -257,8 +228,8 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= -github.com/samber/slog-multi v1.2.4 h1:k9x3JAWKJFPKffx+oXZ8TasaNuorIW4tG+TXxkt6Ry4= -github.com/samber/slog-multi v1.2.4/go.mod h1:ACuZ5B6heK57TfMVkVknN2UZHoFfjCwRxR0Q2OXKHlo= +github.com/samber/slog-multi v1.3.3 h1:qhFXaYdW73FIWLt8SrXMXfPwY58NpluzKDwRdPvhWWY= +github.com/samber/slog-multi v1.3.3/go.mod h1:ACuZ5B6heK57TfMVkVknN2UZHoFfjCwRxR0Q2OXKHlo= github.com/samber/slog-sentry v1.2.2 h1:S0glIVITlGCCfSvIOte2Sh63HMHJpYN3hDr+97hILIk= github.com/samber/slog-sentry v1.2.2/go.mod h1:bHm8jm1dks0p+xc/lH2i4TIFwnPcMTvZeHgCBj5+uhA= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= @@ -266,12 +237,7 @@ github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NF github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -296,47 +262,30 @@ go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRY go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4= -golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 h1:9kj3STMvgqy3YA4VQXBrN7925ICMxD5wzMRcgA30588= +golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -345,54 +294,31 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= -golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.212.0 h1:BcRj3MJfHF3FYD29rk7u9kuu1SyfGqfHcA0hSwKqkHg= -google.golang.org/api v0.212.0/go.mod h1:gICpLlpp12/E8mycRMzgy3SQ9cFh2XnVJ6vJi/kQbvI= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/api v0.214.0 h1:h2Gkq07OYi6kusGOaT/9rnNljuXmqPnaig7WGPmKbwA= +google.golang.org/api v0.214.0/go.mod h1:bYPpLG8AyeMWwDU6NXoB00xC0DFkikVvd5MfwoxjLqE= google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw= google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20241209162323-e6fa225c2576 h1:k48HcZ4FE6in0o8IflZCkc1lTc2u37nhGd8P+fo4r24= -google.golang.org/genproto v0.0.0-20241209162323-e6fa225c2576/go.mod h1:DV2u3tCn/AcVjjmGYZKt6HyvY4w4y3ipAdHkMbe/0i4= -google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= -google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422 h1:6GUHKGv2huWOHKmDXLMNE94q3fBDlEHI+oTRIZSebK0= +google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422/go.mod h1:1NPAxoesyw/SgLPqaUp9u1f9PWCLAk/jVmhx7gJZStg= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 h1:3UsHvIr4Wc2aW4brOaSCmcxh9ksica6fHEr8P1XhkYw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3 h1:hUfOButuEtpc0UvYiaYRbNwxVYr0mQQOWq6X8beJ9Gc= google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3/go.mod h1:jzYlkSMbKypzuu6xoAEijsNVo9ZeDF1u/zCfFgsx7jg= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ= -google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -405,8 +331,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 7ed395ec06c3ecbd9060e0e035a483d5fe83167f Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 6 Jan 2025 11:11:40 -0500 Subject: [PATCH 213/216] Update sentry and slog support library --- cmd/mailroom/main.go | 2 +- go.mod | 5 +++-- go.sum | 12 ++++++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/cmd/mailroom/main.go b/cmd/mailroom/main.go index c6b3bc344..a5ac94217 100644 --- a/cmd/mailroom/main.go +++ b/cmd/mailroom/main.go @@ -15,7 +15,7 @@ import ( "github.com/nyaruka/mailroom" "github.com/nyaruka/mailroom/runtime" slogmulti "github.com/samber/slog-multi" - slogsentry "github.com/samber/slog-sentry" + slogsentry "github.com/samber/slog-sentry/v2" _ "github.com/nyaruka/mailroom/core/handlers" _ "github.com/nyaruka/mailroom/core/hooks" diff --git a/go.mod b/go.mod index ef41f4b24..b9c5b5f83 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.72.0 github.com/buger/jsonparser v1.1.1 github.com/elastic/go-elasticsearch/v8 v8.17.0 - github.com/getsentry/sentry-go v0.30.0 + github.com/getsentry/sentry-go v0.31.1 github.com/go-chi/chi/v5 v5.2.0 github.com/go-playground/validator/v10 v10.23.0 github.com/golang-jwt/jwt v3.2.2+incompatible @@ -32,7 +32,7 @@ require ( github.com/prometheus/client_model v0.6.1 github.com/prometheus/common v0.61.0 github.com/samber/slog-multi v1.3.3 - github.com/samber/slog-sentry v1.2.2 + github.com/samber/slog-sentry/v2 v2.9.2 github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.10.0 google.golang.org/api v0.214.0 @@ -105,6 +105,7 @@ require ( github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/samber/lo v1.47.0 // indirect + github.com/samber/slog-common v0.18.0 // indirect github.com/sergi/go-diff v1.3.1 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.32.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0 // indirect diff --git a/go.sum b/go.sum index 1161e3f0b..2fe8d5d1d 100644 --- a/go.sum +++ b/go.sum @@ -117,8 +117,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= -github.com/getsentry/sentry-go v0.30.0 h1:lWUwDnY7sKHaVIoZ9wYqRHJ5iEmoc0pqcRqFkosKzBo= -github.com/getsentry/sentry-go v0.30.0/go.mod h1:WU9B9/1/sHDqeV8T+3VwwbjeR5MSXs/6aqG3mqZrezA= +github.com/getsentry/sentry-go v0.31.1 h1:ELVc0h7gwyhnXHDouXkhqTFSO5oslsRDk0++eyE0KJ4= +github.com/getsentry/sentry-go v0.31.1/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY= github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0= github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -228,10 +228,12 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= +github.com/samber/slog-common v0.18.0 h1:zPeXHM+WhMl2zSx76Rg3EE0jwXdkut9s45K+pwhcO1c= +github.com/samber/slog-common v0.18.0/go.mod h1:6Krf+hemckfEiRDqy3J/sTpKTJQvmOoFLj9Riz3IkRU= github.com/samber/slog-multi v1.3.3 h1:qhFXaYdW73FIWLt8SrXMXfPwY58NpluzKDwRdPvhWWY= github.com/samber/slog-multi v1.3.3/go.mod h1:ACuZ5B6heK57TfMVkVknN2UZHoFfjCwRxR0Q2OXKHlo= -github.com/samber/slog-sentry v1.2.2 h1:S0glIVITlGCCfSvIOte2Sh63HMHJpYN3hDr+97hILIk= -github.com/samber/slog-sentry v1.2.2/go.mod h1:bHm8jm1dks0p+xc/lH2i4TIFwnPcMTvZeHgCBj5+uhA= +github.com/samber/slog-sentry/v2 v2.9.2 h1:JW3mQvza3YX+QN1EZ1ZL0ERGgS2uS/3RTZt5zp3hkfk= +github.com/samber/slog-sentry/v2 v2.9.2/go.mod h1:kPT5LvrwBwS0PqbsUcMzJHVeQrJLJd/xJMpYAzI409A= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= @@ -261,6 +263,8 @@ go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiy go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= From eabfcfd9f59f7d90519daae037463cffc4e93669 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 6 Jan 2025 11:56:26 -0500 Subject: [PATCH 214/216] Update CHANGELOG.md for v9.3.72 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfd20aab2..0d40ea64a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.72 (2025-01-06) +------------------------- + * Update deps including goflow and phonenumbers + v9.3.71 (2024-12-18) ------------------------- * Fix integer rounding in duration metrics From 80269b2fc759193eb8bafde3185819c14fd3f182 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 7 Jan 2025 09:43:21 -0500 Subject: [PATCH 215/216] Update README.md --- README.md | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 4f994c76a..78d9adb04 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,6 @@ We recommend running it with no changes to the configuration and no parameters, environment variables to configure it. You can use `% mailroom --help` to see a list of the environment variables and parameters and for more details on each option. -For use with RapidPro/TextIt, you will need to configure these settings: - - `MAILROOM_ADDRESS`: address to bind our web server to (default "localhost") - `MAILROOM_DOMAIN`: domain that mailroom is listening on - `MAILROOM_AUTH_TOKEN`: authentication token clients will need to for web requests (should match setting in RapidPro) @@ -35,31 +33,24 @@ For use with RapidPro/TextIt, you will need to configure these settings: - `MAILROOM_ELASTIC_USERNAME`: ElasticSearch username for Basic Auth - `MAILROOM_ELASTIC_PASSWORD`: ElasticSearch password for Basic Auth - `MAILROOM_COURIER_AUTH_TOKEN`: authentication token used for requests to Courier - -For writing of message attachments, you need an S3 compatible service which you configure with: +- `MAILROOM_SESSION_STORAGE`: where session output is stored which must be `db` (default) or `s3` + +### AWS services: - `MAILROOM_AWS_ACCESS_KEY_ID`: AWS access key id used to authenticate to AWS - `MAILROOM_AWS_SECRET_ACCESS_KEY` AWS secret access key used to authenticate to AWS -- `MAILROOM_S3_REGION`: region for your S3 bucket (ex: `eu-west-1`) +- `MAILROOM_AWS_REGION`: AWS region (ex: `eu-west-1`) - `MAILROOM_S3_ATTACHMENTS_BUCKET`: name of your S3 bucket (ex: `mailroom-attachments`) -- `MAILROOM_S3_ATTACHMENTS_PREFIX`: prefix to use for filenames of attachments added to your bucket (ex: `attachments`) - -You can use S3 storage for sessions and logs as well with: - -- `MAILROOM_SESSION_STORAGE`: where session output is stored which must be `db` (default) or `s3` - `MAILROOM_S3_SESSIONS_BUCKET`: name of your S3 bucket (ex: `mailroom-sessions`) -- `MAILROOM_S3_LOGS_BUCKET`: name of your S3 bucket (ex: `mailroom-logs`) -Flow engine configuration: +### Flow engine configuration: - `MAILROOM_MAX_STEPS_PER_SPRINT`: maximum number of steps allowed in a single engine sprint - `MAILROOM_MAX_RESUMES_PER_SESSION`: maximum number of resumes allowed in an engine session - `MAILROOM_MAX_VALUE_LENGTH`: maximum length in characters of contact field and run result values -Recommended settings for error and performance monitoring: +### Logging and error reporting: -- `MAILROOM_LIBRATO_USERNAME`: username to use for logging of events to Librato -- `MAILROOM_LIBRATO_TOKEN`: token to use for logging of events to Librato - `MAILROOM_SENTRY_DSN`: DSN to use when logging errors to Sentry - `MAILROOM_LOG_LEVEL`: logging level mailroom should use (default is `warn`) From 120223797a5691d749106f9bc1d24c6eafb9cf81 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 7 Jan 2025 09:43:49 -0500 Subject: [PATCH 216/216] Update CHANGELOG.md for v10.0.0 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d40ea64a..08e8b4dbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v10.0.0 (2025-01-07) +------------------------- + * Update README.md + v9.3.72 (2025-01-06) ------------------------- * Update deps including goflow and phonenumbers

1<|_pB+8cj4uQV(G$hW% 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⩔1Ri~4Ngsa< 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 Fk^p1J1ls@r delta 94154 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®b-`^hwS^e=apY1a0&$j@n%tJ}yObETMXmc1df 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 diff --git a/web/contact/testdata/modify.json b/web/contact/testdata/modify.json index 3ffcea58f..95bc8e87e 100644 --- a/web/contact/testdata/modify.json +++ b/web/contact/testdata/modify.json @@ -1370,7 +1370,7 @@ }, "note": "Need help", "assignee": { - "email": "admin1@nyaruka.com", + "email": "admin1@textit.com", "name": "Andy Admin" } } @@ -1400,7 +1400,7 @@ "name": "Support" }, "assignee": { - "email": "admin1@nyaruka.com", + "email": "admin1@textit.com", "name": "Andy Admin" } } @@ -1416,7 +1416,7 @@ "name": "Support" }, "assignee": { - "email": "admin1@nyaruka.com", + "email": "admin1@textit.com", "name": "Andy Admin" } }, @@ -1508,7 +1508,7 @@ "name": "Support" }, "assignee": { - "email": "admin1@nyaruka.com", + "email": "admin1@textit.com", "name": "Andy Admin" } } @@ -1597,7 +1597,7 @@ "name": "Support" }, "assignee": { - "email": "admin1@nyaruka.com", + "email": "admin1@textit.com", "name": "Andy Admin" } } diff --git a/web/org/base_test.go b/web/org/base_test.go index 9a3c03e1f..6ffe92f22 100644 --- a/web/org/base_test.go +++ b/web/org/base_test.go @@ -91,9 +91,9 @@ func TestMetrics(t *testing.T) { Username: "metrics", Password: promToken, Contains: []string{ - `rapidpro_group_contact_count{group_name="Active",group_uuid="b97f69f7-5edf-45c7-9fda-d37066eae91d",group_type="system",org="UNICEF"} 124`, - `rapidpro_group_contact_count{group_name="Doctors",group_uuid="c153e265-f7c9-4539-9dbc-9b358714b638",group_type="user",org="UNICEF"} 121`, - `rapidpro_channel_msg_count{channel_name="Vonage",channel_uuid="19012bfd-3ce3-4cae-9bb9-76cf92c73d49",channel_type="NX",msg_direction="out",msg_type="message",org="UNICEF"} 0`, + `rapidpro_group_contact_count{group_name="Active",group_uuid="b97f69f7-5edf-45c7-9fda-d37066eae91d",group_type="system",org="TextIt"} 124`, + `rapidpro_group_contact_count{group_name="Doctors",group_uuid="c153e265-f7c9-4539-9dbc-9b358714b638",group_type="user",org="TextIt"} 121`, + `rapidpro_channel_msg_count{channel_name="Vonage",channel_uuid="19012bfd-3ce3-4cae-9bb9-76cf92c73d49",channel_type="NX",msg_direction="out",msg_type="message",org="TextIt"} 0`, }, }, } From b33322315e2dc995d635cddb50f7bc8275fdee15 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 13 Nov 2024 10:53:57 -0500 Subject: [PATCH 140/216] Update to match goflow changes to airtime transfers --- core/goflow/engine.go | 10 +- core/goflow/engine_test.go | 9 +- core/handlers/airtime_transferred.go | 7 +- core/handlers/airtime_transferred_test.go | 4 +- core/models/airtime.go | 6 +- core/models/airtime_test.go | 4 +- go.mod | 56 +++++------ go.sum | 112 +++++++++++----------- 8 files changed, 101 insertions(+), 107 deletions(-) diff --git a/core/goflow/engine.go b/core/goflow/engine.go index 7f310f371..dbda34e0b 100644 --- a/core/goflow/engine.go +++ b/core/goflow/engine.go @@ -105,17 +105,15 @@ type simulatorAirtimeService struct{} func (s *simulatorAirtimeService) Transfer(sender urns.URN, recipient urns.URN, amounts map[string]decimal.Decimal, logHTTP flows.HTTPLogCallback) (*flows.AirtimeTransfer, error) { transfer := &flows.AirtimeTransfer{ - Sender: sender, - Recipient: recipient, - DesiredAmount: decimal.Zero, - ActualAmount: decimal.Zero, + Sender: sender, + Recipient: recipient, + Amount: decimal.Zero, } // pick arbitrary currency/amount pair in map for currency, amount := range amounts { transfer.Currency = currency - transfer.DesiredAmount = amount - transfer.ActualAmount = amount + transfer.Amount = amount break } diff --git a/core/goflow/engine_test.go b/core/goflow/engine_test.go index 5e30152b0..52847da31 100644 --- a/core/goflow/engine_test.go +++ b/core/goflow/engine_test.go @@ -48,11 +48,10 @@ func TestSimulatorAirtime(t *testing.T) { assert.NoError(t, err) assert.Equal(t, &flows.AirtimeTransfer{ - Sender: urns.URN("tel:+593979111111"), - Recipient: urns.URN("tel:+593979222222"), - Currency: "USD", - DesiredAmount: decimal.RequireFromString(`1.50`), - ActualAmount: decimal.RequireFromString(`1.50`), + Sender: urns.URN("tel:+593979111111"), + Recipient: urns.URN("tel:+593979222222"), + Currency: "USD", + Amount: decimal.RequireFromString(`1.50`), }, transfer) } diff --git a/core/handlers/airtime_transferred.go b/core/handlers/airtime_transferred.go index 85b2ad2b6..a1c546e48 100644 --- a/core/handlers/airtime_transferred.go +++ b/core/handlers/airtime_transferred.go @@ -22,10 +22,10 @@ func init() { func handleAirtimeTransferred(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, oa *models.OrgAssets, scene *models.Scene, e flows.Event) error { event := e.(*events.AirtimeTransferredEvent) - slog.Debug("airtime transferred", "contact", scene.ContactUUID(), "session", scene.SessionID(), "sender", event.Sender, "recipient", event.Recipient, "currency", event.Currency, "desired_amount", event.DesiredAmount.String(), "actual_amount", event.ActualAmount.String()) + slog.Debug("airtime transferred", "contact", scene.ContactUUID(), "session", scene.SessionID(), "sender", event.Sender, "recipient", event.Recipient, "currency", event.Currency, "amount", event.Amount.String()) status := models.AirtimeTransferStatusSuccess - if event.ActualAmount == decimal.Zero { + if event.Amount == decimal.Zero { status = models.AirtimeTransferStatusFailed } @@ -38,8 +38,7 @@ func handleAirtimeTransferred(ctx context.Context, rt *runtime.Runtime, tx *sqlx event.Sender, event.Recipient, event.Currency, - event.DesiredAmount, - event.ActualAmount, + event.Amount, event.CreatedOn(), ) diff --git a/core/handlers/airtime_transferred_test.go b/core/handlers/airtime_transferred_test.go index 551ceca17..a607100f7 100644 --- a/core/handlers/airtime_transferred_test.go +++ b/core/handlers/airtime_transferred_test.go @@ -292,7 +292,7 @@ func TestAirtimeTransferred(t *testing.T) { { Actions: handlers.ContactActionMap{ testdata.Cathy: []flows.Action{ - actions.NewTransferAirtime(handlers.NewActionUUID(), map[string]decimal.Decimal{"USD": decimal.RequireFromString(`3.50`)}, "Transfer"), + actions.NewTransferAirtime(handlers.NewActionUUID(), map[string]decimal.Decimal{"USD": decimal.RequireFromString(`3.0`)}, "Transfer"), }, }, SQLAssertions: []handlers.SQLAssertion{ @@ -311,7 +311,7 @@ func TestAirtimeTransferred(t *testing.T) { { Actions: handlers.ContactActionMap{ testdata.George: []flows.Action{ - actions.NewTransferAirtime(handlers.NewActionUUID(), map[string]decimal.Decimal{"USD": decimal.RequireFromString(`3.50`)}, "Transfer"), + actions.NewTransferAirtime(handlers.NewActionUUID(), map[string]decimal.Decimal{"USD": decimal.RequireFromString(`3`)}, "Transfer"), }, }, SQLAssertions: []handlers.SQLAssertion{ diff --git a/core/models/airtime.go b/core/models/airtime.go index e10af91d5..e88b039e1 100644 --- a/core/models/airtime.go +++ b/core/models/airtime.go @@ -49,7 +49,7 @@ type AirtimeTransfer struct { } // NewAirtimeTransfer creates a new airtime transfer returning the result -func NewAirtimeTransfer(uuid flows.AirtimeTransferUUID, orgID OrgID, status AirtimeTransferStatus, externalID string, contactID ContactID, sender urns.URN, recipient urns.URN, currency string, desiredAmount decimal.Decimal, actualAmount decimal.Decimal, createdOn time.Time) *AirtimeTransfer { +func NewAirtimeTransfer(uuid flows.AirtimeTransferUUID, orgID OrgID, status AirtimeTransferStatus, externalID string, contactID ContactID, sender urns.URN, recipient urns.URN, currency string, amount decimal.Decimal, createdOn time.Time) *AirtimeTransfer { t := &AirtimeTransfer{} t.t.UUID = uuid t.t.OrgID = orgID @@ -59,8 +59,8 @@ func NewAirtimeTransfer(uuid flows.AirtimeTransferUUID, orgID OrgID, status Airt t.t.Sender = null.String(string(sender)) t.t.Recipient = recipient t.t.Currency = null.String(currency) - t.t.DesiredAmount = desiredAmount - t.t.ActualAmount = actualAmount + t.t.DesiredAmount = amount + t.t.ActualAmount = amount t.t.CreatedOn = createdOn return t } diff --git a/core/models/airtime_test.go b/core/models/airtime_test.go index 276f2cd92..28acd032a 100644 --- a/core/models/airtime_test.go +++ b/core/models/airtime_test.go @@ -30,8 +30,7 @@ func TestAirtimeTransfers(t *testing.T) { urns.URN("tel:+250700000001"), urns.URN("tel:+250700000002"), "RWF", - decimal.RequireFromString(`1100`), - decimal.RequireFromString(`1000`), + decimal.RequireFromString(`100`), time.Now(), ) err := models.InsertAirtimeTransfers(ctx, rt.DB, []*models.AirtimeTransfer{transfer}) @@ -50,7 +49,6 @@ func TestAirtimeTransfers(t *testing.T) { urns.URN("tel:+250700000002"), "", decimal.Zero, - decimal.Zero, time.Now(), ) err = models.InsertAirtimeTransfers(ctx, rt.DB, []*models.AirtimeTransfer{transfer}) diff --git a/go.mod b/go.mod index 4d8da47ec..9b8f283db 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,10 @@ require ( firebase.google.com/go/v4 v4.15.0 github.com/Masterminds/semver v1.5.0 github.com/appleboy/go-fcm v1.2.1 - github.com/aws/aws-sdk-go-v2 v1.32.3 - github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.13 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.3 - github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2 + github.com/aws/aws-sdk-go-v2 v1.32.4 + github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.15 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.5 + github.com/aws/aws-sdk-go-v2/service/s3 v1.66.3 github.com/buger/jsonparser v1.1.1 github.com/elastic/go-elasticsearch/v8 v8.15.0 github.com/getsentry/sentry-go v0.29.1 @@ -22,8 +22,8 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 github.com/nyaruka/ezconf v0.3.0 - github.com/nyaruka/gocommon v1.59.1 - github.com/nyaruka/goflow v0.222.5 + github.com/nyaruka/gocommon v1.59.2 + github.com/nyaruka/goflow v0.223.0 github.com/nyaruka/null/v3 v3.0.0 github.com/nyaruka/redisx v0.8.1 github.com/nyaruka/rp-indexer/v9 v9.2.1 @@ -34,13 +34,13 @@ require ( github.com/samber/slog-sentry v1.2.2 github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.9.0 - google.golang.org/api v0.204.0 + google.golang.org/api v0.205.0 ) require ( cel.dev/expr v0.16.1 // indirect cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/auth v0.10.0 // indirect + cloud.google.com/go/auth v0.10.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.5 // indirect cloud.google.com/go/compute/metadata v0.5.2 // indirect cloud.google.com/go/firestore v1.17.0 // indirect @@ -55,22 +55,22 @@ require ( github.com/Shopify/gomail v0.0.0-20220729171026-0784ece65e69 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 // indirect - github.com/aws/aws-sdk-go-v2/config v1.28.0 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.41 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 // indirect + github.com/aws/aws-sdk-go-v2/config v1.28.3 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.44 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.19 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.23 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.22 // indirect - github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.23 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.5 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.3 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.32.4 // indirect github.com/aws/smithy-go v1.22.0 // indirect github.com/blevesearch/segment v0.9.1 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect @@ -115,13 +115,13 @@ require ( go.opentelemetry.io/otel/sdk v1.29.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect - golang.org/x/crypto v0.28.0 // indirect - golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect - golang.org/x/net v0.30.0 // indirect + golang.org/x/crypto v0.29.0 // indirect + golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect + golang.org/x/net v0.31.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect golang.org/x/time v0.7.0 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect diff --git a/go.sum b/go.sum index fc5a24840..59f5f2cc7 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ cel.dev/expr v0.16.1/go.mod h1:AsGA5zb3WruAEQeQng1RZdGEXmBj0jvMWh6l5SnNuC8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= -cloud.google.com/go/auth v0.10.0 h1:tWlkvFAh+wwTOzXIjrwM64karR1iTBZ/GRr0S/DULYo= -cloud.google.com/go/auth v0.10.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth v0.10.1 h1:TnK46qldSfHWt2a0b/hciaiVJsmDXWy9FqyUan0uYiI= +cloud.google.com/go/auth v0.10.1/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= cloud.google.com/go/auth/oauth2adapt v0.2.5 h1:2p29+dePqsCHPP1bqDJcKj4qxRyYCcbzKpFyKGt3MTk= cloud.google.com/go/auth/oauth2adapt v0.2.5/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= @@ -46,48 +46,48 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/appleboy/go-fcm v1.2.1 h1:NhpACabtRuAplYg6bTNfSr3LBwsSuutP55HsphzLU/g= github.com/appleboy/go-fcm v1.2.1/go.mod h1:5FzMN+9J2sxnkoys9h3y48GQH8HnI637Q/ro/uP2Qsk= -github.com/aws/aws-sdk-go-v2 v1.32.3 h1:T0dRlFBKcdaUPGNtkBSwHZxrtis8CQU17UpNBZYd0wk= -github.com/aws/aws-sdk-go-v2 v1.32.3/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= +github.com/aws/aws-sdk-go-v2 v1.32.4 h1:S13INUiTxgrPueTmrm5DZ+MiAo99zYzHEFh1UNkOxNE= +github.com/aws/aws-sdk-go-v2 v1.32.4/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 h1:pT3hpW0cOHRJx8Y0DfJUEQuqPild8jRGmSFmBgvydr0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6/go.mod h1:j/I2++U0xX+cr44QjHay4Cvxj6FUbnxrgmqN3H1jTZA= -github.com/aws/aws-sdk-go-v2/config v1.28.0 h1:FosVYWcqEtWNxHn8gB/Vs6jOlNwSoyOCA/g/sxyySOQ= -github.com/aws/aws-sdk-go-v2/config v1.28.0/go.mod h1:pYhbtvg1siOOg8h5an77rXle9tVG8T+BWLWAo7cOukc= -github.com/aws/aws-sdk-go-v2/credentials v1.17.41 h1:7gXo+Axmp+R4Z+AK8YFQO0ZV3L0gizGINCOWxSLY9W8= -github.com/aws/aws-sdk-go-v2/credentials v1.17.41/go.mod h1:u4Eb8d3394YLubphT4jLEwN1rLNq2wFOlT6OuxFwPzU= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.13 h1:EiyBn76ZpKQJWRNhgxvgloj6Xmazck05+RS6j0gfy1Y= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.13/go.mod h1:gKf4BQBfUke2acRFz76+Tyqz4A9Me0aMEnDUZwEZ+R0= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 h1:TMH3f/SCAWdNtXXVPPu5D6wrr4G5hI1rAxbcocKfC7Q= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17/go.mod h1:1ZRXLdTpzdJb9fwTMXiLipENRxkGMTn1sfKexGllQCw= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 h1:Jw50LwEkVjuVzE1NzkhNKkBf9cRN7MtE1F/b2cOKTUM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22/go.mod h1:Y/SmAyPcOTmpeVaWSzSKiILfXTVJwrGmYZhcRbhWuEY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 h1:981MHwBaRZM7+9QSR6XamDzF/o7ouUGxFzr+nVSIhrs= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22/go.mod h1:1RA1+aBEfn+CAB/Mh0MB6LsdCYCnjZm7tKXtnk499ZQ= +github.com/aws/aws-sdk-go-v2/config v1.28.3 h1:kL5uAptPcPKaJ4q0sDUjUIdueO18Q7JDzl64GpVwdOM= +github.com/aws/aws-sdk-go-v2/config v1.28.3/go.mod h1:SPEn1KA8YbgQnwiJ/OISU4fz7+F6Fe309Jf0QTsRCl4= +github.com/aws/aws-sdk-go-v2/credentials v1.17.44 h1:qqfs5kulLUHUEXlHEZXLJkgGoF3kkUeFUTVA585cFpU= +github.com/aws/aws-sdk-go-v2/credentials v1.17.44/go.mod h1:0Lm2YJ8etJdEdw23s+q/9wTpOeo2HhNE97XcRa7T8MA= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.15 h1:2HXPu4MCUKVA/hU0g2DWtYgXjVPsj7Ujd+xif/Yl2fc= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.15/go.mod h1:fqQI+CG2FX4yVDJORf6QAKLRw16yO+JcB6io1iubcm0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.19 h1:woXadbf0c7enQ2UGCi8gW/WuKmE0xIzxBF/eD94jMKQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.19/go.mod h1:zminj5ucw7w0r65bP6nhyOd3xL6veAUMc3ElGMoLVb4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.23 h1:A2w6m6Tmr+BNXjDsr7M90zkWjsu4JXHwrzPg235STs4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.23/go.mod h1:35EVp9wyeANdujZruvHiQUAo9E3vbhnIO1mTCAxMlY0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.23 h1:pgYW9FCabt2M25MoHYCfMrVY2ghiiBKYWUVXfwZs+sU= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.23/go.mod h1:c48kLgzO19wAu3CPkDWC28JbaJ+hfQlsdl7I2+oqIbk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.22 h1:yV+hCAHZZYJQcwAaszoBNwLbPItHvApxT0kVIw6jRgs= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.22/go.mod h1:kbR1TL8llqB1eGnVbybcA4/wgScxdylOdyAd51yxPdw= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.3 h1:pS5ka5Z026eG29K3cce+yxG39i5COQARcgheeK9NKQE= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.3/go.mod h1:MBT8rSGSZjJiV6X7rlrVGoIt+mCoaw0VbpdVtsrsJfk= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.3 h1:BjzvhVB6Nnx+Xqlnc5JWkQYuWClxUFcvLzZIqFO31lI= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.3/go.mod h1:/6lakUr7RXajwpensF1miKadiR+xTlHV7mma5axITxY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.23 h1:1SZBDiRzzs3sNhOMVApyWPduWYGAX0imGy06XiBnCAM= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.23/go.mod h1:i9TkxgbZmHVh2S0La6CAXtnyFhlCX/pJ0JsOvBAS6Mk= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.5 h1:VWun/99wjelZZ+d0DGeSrffiCBJhC481geypGc6rfn0= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.5/go.mod h1:P+1rrWglInpWvnBpN0pH8jIIhkLkBaolkRVG4X9Kous= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.5 h1:pc8+YeYe6bBe8D3QeBz9/S5kUZ9k9yoBMbljGIBMNK4= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.5/go.mod h1:R09/8/9eLYHJ50PQ8FlIGjZb3XA2t2XhcI5E5332eCI= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.3 h1:kT6BcZsmMtNkP/iYMcRG+mIEA/IbeiUimXtGmqF39y0= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.3/go.mod h1:Z8uGua2k4PPaGOYn66pK02rhMrot3Xk3tpBuUFPomZU= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.3 h1:wudRPcZMKytcywXERkR6PLqD8gPx754ZyIOo0iVg488= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.3/go.mod h1:yRo5Kj5+m/ScVIZpQOquQvDtSrDM1JLRCnvglBcdNmw= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3 h1:qcxX0JYlgWH3hpPUnd6U0ikcl6LLA9sLkXE2w1fpMvY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3/go.mod h1:cLSNEmI45soc+Ef8K/L+8sEA3A3pYFEYf5B5UI+6bH4= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.3 h1:ZC7Y/XgKUxwqcdhO5LE8P6oGP1eh6xlQReWNKfhvJno= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.3/go.mod h1:WqfO7M9l9yUAw0HcHaikwRd/H6gzYdz7vjejCA5e2oY= -github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2 h1:p9TNFL8bFUMd+38YIpTAXpoxyz0MxC7FlbFEH4P4E1U= -github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2/go.mod h1:fNjyo0Coen9QTwQLWeV6WO2Nytwiu+cCcWaTdKCAqqE= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 h1:bSYXVyUzoTHoKalBmwaZxs97HU9DWWI3ehHSAMa7xOk= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.2/go.mod h1:skMqY7JElusiOUjMJMOv1jJsP7YUg7DrhgqZZWuzu1U= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 h1:AhmO1fHINP9vFYUE0LHzCWg/LfUWUF+zFPEcY9QXb7o= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2/go.mod h1:o8aQygT2+MVP0NaV6kbdE1YnnIM8RRVQzoeUH45GOdI= -github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 h1:CiS7i0+FUe+/YY1GvIBLLrR/XNGZ4CtM1Ll0XavNuVo= -github.com/aws/aws-sdk-go-v2/service/sts v1.32.2/go.mod h1:HtaiBI8CjYoNVde8arShXb94UbQQi9L4EMr6D+xGBwo= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.4 h1:aaPpoG15S2qHkWm4KlEyF01zovK1nW4BBbyXuHNSE90= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.4/go.mod h1:eD9gS2EARTKgGr/W5xwgY/ik9z/zqpW+m/xOQbVxrMk= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.4 h1:rWKH6IiWDRIxmsTJUB/wEY+EIPp+P3C78Vidl+HXp6w= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.4/go.mod h1:MzOAfuiNZ6asjVrA+dNvXl5lI2nmzXakSpDFLOcOyJ4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.4 h1:tHxQi/XHPK0ctd/wdOw0t7Xrc2OxcRCnVzv8lwWPu0c= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.4/go.mod h1:4GQbF1vJzG60poZqWatZlhP31y8PGCCVTvIGPdaaYJ0= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.4 h1:E5ZAVOmI2apR8ADb72Q63KqwwwdW1XcMeXIlrZ1Psjg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.4/go.mod h1:wezzqVUOVVdk+2Z/JzQT4NxAU0NbhRe5W8pIE72jsWI= +github.com/aws/aws-sdk-go-v2/service/s3 v1.66.3 h1:neNOYJl72bHrz9ikAEED4VqWyND/Po0DnEx64RW6YM4= +github.com/aws/aws-sdk-go-v2/service/s3 v1.66.3/go.mod h1:TMhLIyRIyoGVlaEMAt+ITMbwskSTpcGsCPDq91/ihY0= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.5 h1:HJwZwRt2Z2Tdec+m+fPjvdmkq2s9Ra+VR0hjF7V2o40= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.5/go.mod h1:wrMCEwjFPms+V86TCQQeOxQF/If4vT44FGIOFiMC2ck= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.4 h1:zcx9LiGWZ6i6pjdcoE9oXAB6mUdeyC36Ia/QEiIvYdg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.4/go.mod h1:Tp/ly1cTjRLGBBmNccFumbZ8oqpZlpdhFf80SrRh4is= +github.com/aws/aws-sdk-go-v2/service/sts v1.32.4 h1:yDxvkz3/uOKfxnv8YhzOi9m+2OGIxF+on3KOISbK5IU= +github.com/aws/aws-sdk-go-v2/service/sts v1.32.4/go.mod h1:9XEUty5v5UAsMiFOBJrNibZgwCeOma73jgGwwhgffa8= github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= @@ -222,10 +222,10 @@ github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/nyaruka/ezconf v0.3.0 h1:kGvJqVN8AHowb4HdaHAviJ0Z3yI5Pyekp1WqibFEaGk= github.com/nyaruka/ezconf v0.3.0/go.mod h1:89GUW6EPRNLIxT7lC4LWnjWTgZeQwRoX7lBmc8ralAU= -github.com/nyaruka/gocommon v1.59.1 h1:A5E1xvzvK/vMR7jYDMWnWVpisDdZbYsJqZhLmi+2BSI= -github.com/nyaruka/gocommon v1.59.1/go.mod h1:Upj2DG1iL55YcfF7rve8CRrKGjMaEn0jWUIWbQQgTFQ= -github.com/nyaruka/goflow v0.222.5 h1:JpkNS9yJ632XaYp1jyKyVQKMtlun1mU8O/jAXjhQJUA= -github.com/nyaruka/goflow v0.222.5/go.mod h1:iwdjLwomV3thGZeWhybtmDhujbooIkpTn1vUbso5ReY= +github.com/nyaruka/gocommon v1.59.2 h1:WHMM8O4w8K6lYwBBRPQxJr/wPdgnNgg6eq/gi1ogvVY= +github.com/nyaruka/gocommon v1.59.2/go.mod h1:0G8y8vhbsjkRhc614wYJ5cy9zRffy+a2+cv8ko4xB10= +github.com/nyaruka/goflow v0.223.0 h1:a4FixeUI5qiuL+c9Nb3FB2CxKHAT/5tRdH7mmBtVoQ4= +github.com/nyaruka/goflow v0.223.0/go.mod h1:VBgCOWQONCf0eU0O4Zl6GjXUQ15p+JTIiLjfxyGK4FY= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= @@ -296,11 +296,11 @@ go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06F golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= +golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -314,8 +314,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -323,8 +323,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -333,16 +333,16 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -354,8 +354,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.204.0 h1:3PjmQQEDkR/ENVZZwIYB4W/KzYtN8OrqnNcHWpeR8E4= -google.golang.org/api v0.204.0/go.mod h1:69y8QSoKIbL9F94bWgWAq6wGqGwyjBgi2y8rAK8zLag= +google.golang.org/api v0.205.0 h1:LFaxkAIpDb/GsrWV20dMMo5MR0h8UARTbn24LmD+0Pg= +google.golang.org/api v0.205.0/go.mod h1:NrK1EMqO8Xk6l6QwRAmrXXg2v6dzukhlOyvkYtnvUuc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw= From 1fafb6fbc11b7382bf73be3385a5f88beb73b41c Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 13 Nov 2024 11:41:39 -0500 Subject: [PATCH 141/216] Update CHANGELOG.md for v9.3.47 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79973f3c9..cab0918db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v9.3.47 (2024-11-13) +------------------------- + * Update to match goflow changes to airtime transfers + v9.3.46 (2024-11-05) ------------------------- * More logging for invalid locales From 632710f3bd23cde570259bb0346f987de87d5c6e Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 14 Nov 2024 11:00:25 -0500 Subject: [PATCH 142/216] Create absraction for fair queues.. so it's possible to switch out the sorted set implementation --- core/hooks/create_broadcasts.go | 3 +- core/hooks/create_starts.go | 3 +- core/tasks/campaigns/cron.go | 3 +- .../campaigns/fire_campaign_event_test.go | 3 +- core/tasks/handler/handle_contact_event.go | 5 ++- core/tasks/handler/queue.go | 3 +- .../interrupts/interrupt_channel_test.go | 5 ++- core/tasks/ivr/cron_test.go | 3 +- core/tasks/ivr/start_ivr_flow_batch_test.go | 7 ++-- core/tasks/msgs/send_broadcast.go | 3 +- core/tasks/msgs/send_broadcast_test.go | 8 ++--- core/tasks/schedules/cron.go | 3 +- core/tasks/starts/start_flow.go | 3 +- core/tasks/starts/start_flow_batch_test.go | 11 +++--- core/tasks/starts/start_flow_test.go | 6 ++-- core/tasks/starts/throttle_queue.go | 2 +- core/tasks/starts/throttle_queue_test.go | 2 +- core/tasks/task.go | 2 +- testsuite/tasks.go | 4 +-- utils/queues/base.go | 28 +++++++++++++++ utils/queues/fair_sorted.go | 36 +++++-------------- utils/queues/fair_sorted_test.go | 33 ++++++++--------- web/ivr/ivr_test.go | 5 ++- web/msg/broadcast.go | 3 +- workers.go | 4 +-- workers_test.go | 8 ++--- 26 files changed, 96 insertions(+), 100 deletions(-) create mode 100644 utils/queues/base.go diff --git a/core/hooks/create_broadcasts.go b/core/hooks/create_broadcasts.go index 545010340..040871d0d 100644 --- a/core/hooks/create_broadcasts.go +++ b/core/hooks/create_broadcasts.go @@ -10,7 +10,6 @@ import ( "github.com/nyaruka/mailroom/core/tasks" "github.com/nyaruka/mailroom/core/tasks/msgs" "github.com/nyaruka/mailroom/runtime" - "github.com/nyaruka/mailroom/utils/queues" ) // CreateBroadcastsHook is our hook for creating broadcasts @@ -34,7 +33,7 @@ func (h *createBroadcastsHook) Apply(ctx context.Context, rt *runtime.Runtime, t return fmt.Errorf("error creating broadcast: %w", err) } - err = tasks.Queue(rc, tasks.BatchQueue, oa.OrgID(), &msgs.SendBroadcastTask{Broadcast: bcast}, queues.DefaultPriority) + err = tasks.Queue(rc, tasks.BatchQueue, oa.OrgID(), &msgs.SendBroadcastTask{Broadcast: bcast}, false) if err != nil { return fmt.Errorf("error queuing broadcast task: %w", err) } diff --git a/core/hooks/create_starts.go b/core/hooks/create_starts.go index 957ac96fe..d6220f288 100644 --- a/core/hooks/create_starts.go +++ b/core/hooks/create_starts.go @@ -11,7 +11,6 @@ import ( "github.com/nyaruka/mailroom/core/tasks" "github.com/nyaruka/mailroom/core/tasks/starts" "github.com/nyaruka/mailroom/runtime" - "github.com/nyaruka/mailroom/utils/queues" ) // CreateStartsHook is our hook to fire our scene starts @@ -74,7 +73,7 @@ func (h *createStartsHook) Apply(ctx context.Context, rt *runtime.Runtime, tx *s } } - err = tasks.Queue(rc, tasks.BatchQueue, oa.OrgID(), &starts.StartFlowTask{FlowStart: start}, queues.DefaultPriority) + err = tasks.Queue(rc, tasks.BatchQueue, oa.OrgID(), &starts.StartFlowTask{FlowStart: start}, false) if err != nil { return fmt.Errorf("error queuing flow start: %w", err) } diff --git a/core/tasks/campaigns/cron.go b/core/tasks/campaigns/cron.go index 8e2e5552c..f5c6d8868 100644 --- a/core/tasks/campaigns/cron.go +++ b/core/tasks/campaigns/cron.go @@ -11,7 +11,6 @@ import ( "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/core/tasks" "github.com/nyaruka/mailroom/runtime" - "github.com/nyaruka/mailroom/utils/queues" "github.com/nyaruka/redisx" ) @@ -118,7 +117,7 @@ func (c *QueueEventsCron) queueFiresTask(rp *redis.Pool, orgID models.OrgID, tas rc := rp.Get() defer rc.Close() - err := tasks.Queue(rc, tasks.BatchQueue, orgID, task, queues.DefaultPriority) + err := tasks.Queue(rc, tasks.BatchQueue, orgID, task, false) if err != nil { return fmt.Errorf("error queuing task: %w", err) } diff --git a/core/tasks/campaigns/fire_campaign_event_test.go b/core/tasks/campaigns/fire_campaign_event_test.go index 15391bd03..3d63e4dc7 100644 --- a/core/tasks/campaigns/fire_campaign_event_test.go +++ b/core/tasks/campaigns/fire_campaign_event_test.go @@ -12,7 +12,6 @@ import ( "github.com/nyaruka/mailroom/core/tasks/campaigns" "github.com/nyaruka/mailroom/testsuite" "github.com/nyaruka/mailroom/testsuite/testdata" - "github.com/nyaruka/mailroom/utils/queues" "github.com/nyaruka/redisx" "github.com/nyaruka/redisx/assertredis" "github.com/stretchr/testify/assert" @@ -54,7 +53,7 @@ func TestFireCampaignEvents(t *testing.T) { CampaignName: campaign.Name, } - err := tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, task, queues.DefaultPriority) + err := tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, task, false) assert.NoError(t, err) testsuite.FlushTasks(t, rt) diff --git a/core/tasks/handler/handle_contact_event.go b/core/tasks/handler/handle_contact_event.go index e46ac297e..089a9cee6 100644 --- a/core/tasks/handler/handle_contact_event.go +++ b/core/tasks/handler/handle_contact_event.go @@ -16,7 +16,6 @@ import ( "github.com/nyaruka/mailroom/core/tasks" "github.com/nyaruka/mailroom/core/tasks/ivr" "github.com/nyaruka/mailroom/runtime" - "github.com/nyaruka/mailroom/utils/queues" ) // TypeHandleContactEvent is the task type for flagging that a contact has handler tasks to be handled @@ -57,7 +56,7 @@ func (t *HandleContactEventTask) Perform(ctx context.Context, rt *runtime.Runtim if len(locks) == 0 { rc := rt.RP.Get() defer rc.Close() - err = tasks.Queue(rc, tasks.HandlerQueue, oa.OrgID(), &HandleContactEventTask{ContactID: t.ContactID}, queues.DefaultPriority) + err = tasks.Queue(rc, tasks.HandlerQueue, oa.OrgID(), &HandleContactEventTask{ContactID: t.ContactID}, false) if err != nil { return fmt.Errorf("error re-adding contact task after failing to get lock: %w", err) } @@ -186,7 +185,7 @@ func TriggerIVRFlow(ctx context.Context, rt *runtime.Runtime, orgID models.OrgID // queue this to our ivr starter, it will take care of creating the calls then calling back in rc := rt.RP.Get() defer rc.Close() - err = tasks.Queue(rc, tasks.BatchQueue, orgID, task, queues.HighPriority) + err = tasks.Queue(rc, tasks.BatchQueue, orgID, task, true) if err != nil { return fmt.Errorf("error queuing ivr flow start: %w", err) } diff --git a/core/tasks/handler/queue.go b/core/tasks/handler/queue.go index afc421450..e01f02549 100644 --- a/core/tasks/handler/queue.go +++ b/core/tasks/handler/queue.go @@ -12,7 +12,6 @@ import ( "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/core/tasks" "github.com/nyaruka/mailroom/runtime" - "github.com/nyaruka/mailroom/utils/queues" ) // Task is the interface for all contact tasks - tasks which operate on a single contact in real time @@ -73,7 +72,7 @@ func queueTask(rc redis.Conn, orgID models.OrgID, contactID models.ContactID, ta } // then add a handle task for that contact on our global handler queue to - err = tasks.Queue(rc, tasks.HandlerQueue, orgID, &HandleContactEventTask{ContactID: contactID}, queues.DefaultPriority) + err = tasks.Queue(rc, tasks.HandlerQueue, orgID, &HandleContactEventTask{ContactID: contactID}, false) if err != nil { return fmt.Errorf("error queuing handle task: %w", err) } diff --git a/core/tasks/interrupts/interrupt_channel_test.go b/core/tasks/interrupts/interrupt_channel_test.go index 08925b13a..343dde7a5 100644 --- a/core/tasks/interrupts/interrupt_channel_test.go +++ b/core/tasks/interrupts/interrupt_channel_test.go @@ -11,7 +11,6 @@ import ( "github.com/nyaruka/mailroom/core/tasks/msgs" "github.com/nyaruka/mailroom/testsuite" "github.com/nyaruka/mailroom/testsuite/testdata" - "github.com/nyaruka/mailroom/utils/queues" "github.com/stretchr/testify/require" ) @@ -55,7 +54,7 @@ func TestInterruptChannel(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_msg WHERE status = 'F' and channel_id = $1`, testdata.TwilioChannel.ID).Returns(0) // queue and perform a task to interrupt the Twilio channel - tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &interrupts.InterruptChannelTask{ChannelID: testdata.TwilioChannel.ID}, queues.DefaultPriority) + tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &interrupts.InterruptChannelTask{ChannelID: testdata.TwilioChannel.ID}, false) testsuite.FlushTasks(t, rt) assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_msg WHERE status = 'F' and channel_id = $1`, testdata.VonageChannel.ID).Returns(1) @@ -83,7 +82,7 @@ func TestInterruptChannel(t *testing.T) { }) // queue and perform a task to interrupt the Vonage channel - tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &interrupts.InterruptChannelTask{ChannelID: testdata.VonageChannel.ID}, queues.DefaultPriority) + tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &interrupts.InterruptChannelTask{ChannelID: testdata.VonageChannel.ID}, false) testsuite.FlushTasks(t, rt) assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_msg WHERE status = 'F' and failed_reason = 'R' and channel_id = $1`, testdata.VonageChannel.ID).Returns(6) diff --git a/core/tasks/ivr/cron_test.go b/core/tasks/ivr/cron_test.go index 1b63e6600..aa117d75a 100644 --- a/core/tasks/ivr/cron_test.go +++ b/core/tasks/ivr/cron_test.go @@ -11,7 +11,6 @@ import ( "github.com/nyaruka/mailroom/core/tasks/starts" "github.com/nyaruka/mailroom/testsuite" "github.com/nyaruka/mailroom/testsuite/testdata" - "github.com/nyaruka/mailroom/utils/queues" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -35,7 +34,7 @@ func TestRetryCallsCron(t *testing.T) { err := models.InsertFlowStarts(ctx, rt.DB, []*models.FlowStart{start}) require.NoError(t, err) - err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, queues.DefaultPriority) + err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) require.NoError(t, err) service.callError = nil diff --git a/core/tasks/ivr/start_ivr_flow_batch_test.go b/core/tasks/ivr/start_ivr_flow_batch_test.go index 7d7192299..441b23391 100644 --- a/core/tasks/ivr/start_ivr_flow_batch_test.go +++ b/core/tasks/ivr/start_ivr_flow_batch_test.go @@ -16,7 +16,6 @@ import ( "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/mailroom/testsuite" "github.com/nyaruka/mailroom/testsuite/testdata" - "github.com/nyaruka/mailroom/utils/queues" "github.com/stretchr/testify/require" ) @@ -41,7 +40,7 @@ func TestIVR(t *testing.T) { service.callError = fmt.Errorf("unable to create call") - err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, queues.DefaultPriority) + err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) require.NoError(t, err) testsuite.FlushTasks(t, rt) @@ -53,7 +52,7 @@ func TestIVR(t *testing.T) { service.callError = nil service.callID = ivr.CallID("call1") - err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, queues.DefaultPriority) + err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) require.NoError(t, err) testsuite.FlushTasks(t, rt) @@ -64,7 +63,7 @@ func TestIVR(t *testing.T) { service.callError = nil service.callID = ivr.CallID("call1") - err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, queues.DefaultPriority) + err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) require.NoError(t, err) testsuite.FlushTasks(t, rt) diff --git a/core/tasks/msgs/send_broadcast.go b/core/tasks/msgs/send_broadcast.go index 518af1174..43be459cb 100644 --- a/core/tasks/msgs/send_broadcast.go +++ b/core/tasks/msgs/send_broadcast.go @@ -12,7 +12,6 @@ import ( "github.com/nyaruka/mailroom/core/search" "github.com/nyaruka/mailroom/core/tasks" "github.com/nyaruka/mailroom/runtime" - "github.com/nyaruka/mailroom/utils/queues" ) const ( @@ -116,7 +115,7 @@ func createBroadcastBatches(ctx context.Context, rt *runtime.Runtime, oa *models isLast := (i == len(idBatches)-1) batch := bcast.CreateBatch(idBatch, isFirst, isLast) - err = tasks.Queue(rc, q, bcast.OrgID, &SendBroadcastBatchTask{BroadcastBatch: batch}, queues.DefaultPriority) + err = tasks.Queue(rc, q, bcast.OrgID, &SendBroadcastBatchTask{BroadcastBatch: batch}, false) if err != nil { if i == 0 { return fmt.Errorf("error queuing broadcast batch: %w", err) diff --git a/core/tasks/msgs/send_broadcast_test.go b/core/tasks/msgs/send_broadcast_test.go index 2e6d497e9..56a7b2855 100644 --- a/core/tasks/msgs/send_broadcast_test.go +++ b/core/tasks/msgs/send_broadcast_test.go @@ -58,7 +58,7 @@ func TestBroadcastsFromEvents(t *testing.T) { groups []*assets.GroupReference contacts []*flows.ContactReference urns []urns.URN - queue *queues.FairSorted + queue queues.Fair expectedBatchCount int expectedMsgCount int expectedMsgText string @@ -164,7 +164,7 @@ func TestBroadcastsFromEvents(t *testing.T) { bcast, err := models.NewBroadcastFromEvent(ctx, rt.DB, oa, event) assert.NoError(t, err) - err = tasks.Queue(rc, tc.queue, testdata.Org1.ID, &msgs.SendBroadcastTask{Broadcast: bcast}, queues.DefaultPriority) + err = tasks.Queue(rc, tc.queue, testdata.Org1.ID, &msgs.SendBroadcastTask{Broadcast: bcast}, false) assert.NoError(t, err) taskCounts := testsuite.FlushTasks(t, rt) @@ -213,7 +213,7 @@ func TestSendBroadcastTask(t *testing.T) { query string exclusions models.Exclusions createdByID models.UserID - queue *queues.FairSorted + queue queues.Fair expectedBatches int expectedMsgs map[string]int }{ @@ -288,7 +288,7 @@ func TestSendBroadcastTask(t *testing.T) { task := &msgs.SendBroadcastTask{Broadcast: bcast} - err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, task, queues.DefaultPriority) + err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, task, false) assert.NoError(t, err) taskCounts := testsuite.FlushTasks(t, rt) diff --git a/core/tasks/schedules/cron.go b/core/tasks/schedules/cron.go index 6b970588d..30c03a30d 100644 --- a/core/tasks/schedules/cron.go +++ b/core/tasks/schedules/cron.go @@ -11,7 +11,6 @@ import ( "github.com/nyaruka/mailroom/core/tasks/msgs" "github.com/nyaruka/mailroom/core/tasks/starts" "github.com/nyaruka/mailroom/runtime" - "github.com/nyaruka/mailroom/utils/queues" ) func init() { @@ -135,7 +134,7 @@ func (c *schedulesCron) Run(ctx context.Context, rt *runtime.Runtime) (map[strin // add our task if we have one if task != nil { - err = tasks.Queue(rc, tasks.BatchQueue, s.OrgID, task, queues.HighPriority) + err = tasks.Queue(rc, tasks.BatchQueue, s.OrgID, task, true) if err != nil { log.Error(fmt.Sprintf("error queueing %s task from schedule", task.Type()), "error", err) } diff --git a/core/tasks/starts/start_flow.go b/core/tasks/starts/start_flow.go index f82333c0e..4d76dbaf3 100644 --- a/core/tasks/starts/start_flow.go +++ b/core/tasks/starts/start_flow.go @@ -14,7 +14,6 @@ import ( "github.com/nyaruka/mailroom/core/tasks" "github.com/nyaruka/mailroom/core/tasks/ivr" "github.com/nyaruka/mailroom/runtime" - "github.com/nyaruka/mailroom/utils/queues" ) const ( @@ -136,7 +135,7 @@ func createFlowStartBatches(ctx context.Context, rt *runtime.Runtime, oa *models batchTask = &StartFlowBatchTask{FlowStartBatch: batch} } - err = tasks.Queue(rc, q, start.OrgID, batchTask, queues.DefaultPriority) + err = tasks.Queue(rc, q, start.OrgID, batchTask, false) if err != nil { if i == 0 { return fmt.Errorf("error queuing flow start batch: %w", err) diff --git a/core/tasks/starts/start_flow_batch_test.go b/core/tasks/starts/start_flow_batch_test.go index 168ec0391..eb6fb5236 100644 --- a/core/tasks/starts/start_flow_batch_test.go +++ b/core/tasks/starts/start_flow_batch_test.go @@ -11,7 +11,6 @@ import ( "github.com/nyaruka/mailroom/core/tasks/starts" "github.com/nyaruka/mailroom/testsuite" "github.com/nyaruka/mailroom/testsuite/testdata" - "github.com/nyaruka/mailroom/utils/queues" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -35,7 +34,7 @@ func TestStartFlowBatchTask(t *testing.T) { batch2 := start1.CreateBatch([]models.ContactID{testdata.George.ID, testdata.Alexandria.ID}, false, true, 4) // start the first batch... - err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: batch1}, queues.DefaultPriority) + err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: batch1}, false) assert.NoError(t, err) testsuite.FlushTasks(t, rt) @@ -54,7 +53,7 @@ func TestStartFlowBatchTask(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, start1.ID).Returns("S") // start the second and final batch... - err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: batch2}, queues.DefaultPriority) + err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: batch2}, false) assert.NoError(t, err) testsuite.FlushTasks(t, rt) @@ -71,7 +70,7 @@ func TestStartFlowBatchTask(t *testing.T) { start2Batch2 := start2.CreateBatch([]models.ContactID{testdata.George.ID, testdata.Alexandria.ID}, false, true, 4) // start the first batch... - err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: start2Batch1}, queues.DefaultPriority) + err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: start2Batch1}, false) assert.NoError(t, err) testsuite.FlushTasks(t, rt) @@ -81,7 +80,7 @@ func TestStartFlowBatchTask(t *testing.T) { rt.DB.MustExec(`UPDATE flows_flowstart SET status = 'I' WHERE id = $1`, start2.ID) // start the second batch... - err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: start2Batch2}, queues.DefaultPriority) + err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: start2Batch2}, false) assert.NoError(t, err) testsuite.FlushTasks(t, rt) @@ -104,7 +103,7 @@ func TestStartFlowBatchTaskNonPersistedStart(t *testing.T) { batch := start.CreateBatch([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}, true, true, 2) // start the first batch... - err := tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: batch}, queues.DefaultPriority) + err := tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: batch}, false) assert.NoError(t, err) testsuite.FlushTasks(t, rt) diff --git a/core/tasks/starts/start_flow_test.go b/core/tasks/starts/start_flow_test.go index 33837978f..b57576fa2 100644 --- a/core/tasks/starts/start_flow_test.go +++ b/core/tasks/starts/start_flow_test.go @@ -38,7 +38,7 @@ func TestStartFlowTask(t *testing.T) { query string excludeInAFlow bool excludeStartedPreviously bool - queue *queues.FairSorted + queue queues.Fair expectedContactCount int expectedBatchCount int expectedTotalCount int @@ -229,7 +229,7 @@ func TestStartFlowTask(t *testing.T) { err := models.InsertFlowStarts(ctx, rt.DB, []*models.FlowStart{start}) assert.NoError(t, err) - err = tasks.Queue(rc, tc.queue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, queues.DefaultPriority) + err = tasks.Queue(rc, tc.queue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) assert.NoError(t, err) taskCounts := testsuite.FlushTasks(t, rt) @@ -266,7 +266,7 @@ func TestStartFlowTaskNonPersistedStart(t *testing.T) { start := models.NewFlowStart(models.OrgID(1), models.StartTypeManual, testdata.SingleMessage.ID). WithContactIDs([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID}) - err := tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, queues.DefaultPriority) + err := tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) assert.NoError(t, err) testsuite.FlushTasks(t, rt) diff --git a/core/tasks/starts/throttle_queue.go b/core/tasks/starts/throttle_queue.go index 805c75ad4..4ec913165 100644 --- a/core/tasks/starts/throttle_queue.go +++ b/core/tasks/starts/throttle_queue.go @@ -20,7 +20,7 @@ func init() { } type ThrottleQueueCron struct { - Queue *queues.FairSorted + Queue queues.Fair } func (c *ThrottleQueueCron) Next(last time.Time) time.Time { diff --git a/core/tasks/starts/throttle_queue_test.go b/core/tasks/starts/throttle_queue_test.go index 01086e602..12f80f5b6 100644 --- a/core/tasks/starts/throttle_queue_test.go +++ b/core/tasks/starts/throttle_queue_test.go @@ -24,7 +24,7 @@ func TestThrottleQueue(t *testing.T) { require.NoError(t, err) assert.Equal(t, map[string]any{"paused": 0, "resumed": 0}, res) - queue.Push(rc, "type1", 1, "task1", queues.DefaultPriority) + queue.Push(rc, "type1", 1, "task1", false) res, err = cron.Run(ctx, rt) require.NoError(t, err) diff --git a/core/tasks/task.go b/core/tasks/task.go index 06a2f454f..60ab06820 100644 --- a/core/tasks/task.go +++ b/core/tasks/task.go @@ -57,7 +57,7 @@ func Perform(ctx context.Context, rt *runtime.Runtime, task *queues.Task) error } // Queue adds the given task to the given queue -func Queue(rc redis.Conn, q *queues.FairSorted, orgID models.OrgID, task Task, priority queues.Priority) error { +func Queue(rc redis.Conn, q queues.Fair, orgID models.OrgID, task Task, priority bool) error { return q.Push(rc, task.Type(), int(orgID), task, priority) } diff --git a/testsuite/tasks.go b/testsuite/tasks.go index 04971237d..c0570d45c 100644 --- a/testsuite/tasks.go +++ b/testsuite/tasks.go @@ -21,7 +21,7 @@ func QueueBatchTask(t *testing.T, rt *runtime.Runtime, org *testdata.Org, task t rc := rt.RP.Get() defer rc.Close() - err := tasks.Queue(rc, tasks.BatchQueue, org.ID, task, queues.DefaultPriority) + err := tasks.Queue(rc, tasks.BatchQueue, org.ID, task, false) require.NoError(t, err) } @@ -68,7 +68,7 @@ func FlushTasks(t *testing.T, rt *runtime.Runtime) map[string]int { var err error counts := make(map[string]int) - qs := []*queues.FairSorted{tasks.HandlerQueue, tasks.BatchQueue, tasks.ThrottledQueue} + qs := []queues.Fair{tasks.HandlerQueue, tasks.BatchQueue, tasks.ThrottledQueue} for { // look for a task in the queues diff --git a/utils/queues/base.go b/utils/queues/base.go new file mode 100644 index 000000000..926ad32dc --- /dev/null +++ b/utils/queues/base.go @@ -0,0 +1,28 @@ +package queues + +import ( + "encoding/json" + "time" + + "github.com/gomodule/redigo/redis" +) + +// Task is a wrapper for encoding a task +type Task struct { + Type string `json:"type"` + OwnerID int `json:"-"` + Task json.RawMessage `json:"task"` + QueuedOn time.Time `json:"queued_on"` + ErrorCount int `json:"error_count,omitempty"` +} + +// Fair is a queue that supports fair distribution of tasks between owners +type Fair interface { + Push(rc redis.Conn, taskType string, ownerID int, task any, priority bool) error + Pop(rc redis.Conn) (*Task, error) + Done(rc redis.Conn, ownerID int) error + Pause(rc redis.Conn, ownerID int) error + Resume(rc redis.Conn, ownerID int) error + Owners(rc redis.Conn) ([]int, error) + Size(rc redis.Conn) (int, error) +} diff --git a/utils/queues/fair_sorted.go b/utils/queues/fair_sorted.go index 7ca26a167..4ca0fd5a8 100644 --- a/utils/queues/fair_sorted.go +++ b/utils/queues/fair_sorted.go @@ -5,36 +5,12 @@ import ( "encoding/json" "fmt" "strconv" - "time" "github.com/gomodule/redigo/redis" "github.com/nyaruka/gocommon/dates" "github.com/nyaruka/gocommon/jsonx" ) -// Task is a wrapper for encoding a task -type Task struct { - Type string `json:"type"` - OwnerID int `json:"-"` - Task json.RawMessage `json:"task"` - QueuedOn time.Time `json:"queued_on"` - ErrorCount int `json:"error_count,omitempty"` -} - -// Priority is the priority for the task -type Priority int - -const ( - // HighPriority is the highest priority for tasks - HighPriority = Priority(-10000000) - - // DefaultPriority is the default priority for tasks - DefaultPriority = Priority(0) - - // LowPriority is the lowest priority for tasks - LowPriority = Priority(+10000000) -) - type FairSorted struct { keyBase string } @@ -48,7 +24,7 @@ func (q *FairSorted) String() string { } // Push adds the passed in task to our queue for execution -func (q *FairSorted) Push(rc redis.Conn, taskType string, ownerID int, task any, priority Priority) error { +func (q *FairSorted) Push(rc redis.Conn, taskType string, ownerID int, task any, priority bool) error { score := q.score(priority) taskBody, err := json.Marshal(task) @@ -88,8 +64,14 @@ func (q *FairSorted) queueKey(ownerID int) string { return fmt.Sprintf("%s:%d", q.keyBase, ownerID) } -func (q *FairSorted) score(priority Priority) string { - s := float64(dates.Now().UnixMicro())/float64(1000000) + float64(priority) +func (q *FairSorted) score(priority bool) string { + weight := float64(0) + if priority { + weight = -10000000 + } + + s := float64(dates.Now().UnixMicro())/float64(1000000) + weight + return strconv.FormatFloat(s, 'f', 6, 64) } diff --git a/utils/queues/fair_sorted_test.go b/utils/queues/fair_sorted_test.go index d44f84a26..74fcc6c7c 100644 --- a/utils/queues/fair_sorted_test.go +++ b/utils/queues/fair_sorted_test.go @@ -1,6 +1,7 @@ package queues_test import ( + "fmt" "testing" "time" @@ -22,8 +23,8 @@ func TestQueues(t *testing.T) { defer testsuite.Reset(testsuite.ResetRedis) - q := queues.NewFairSorted("test") - assert.Equal(t, "test", q.String()) + var q queues.Fair = queues.NewFairSorted("test") + assert.Equal(t, "test", fmt.Sprint(q)) assertPop := func(expectedOwnerID int, expectedBody string) { task, err := q.Pop(rc) @@ -50,11 +51,11 @@ func TestQueues(t *testing.T) { assertSize(0) - q.Push(rc, "type1", 1, "task1", queues.DefaultPriority) - q.Push(rc, "type1", 1, "task2", queues.HighPriority) - q.Push(rc, "type1", 2, "task3", queues.LowPriority) - q.Push(rc, "type2", 1, "task4", queues.DefaultPriority) - q.Push(rc, "type2", 2, "task5", queues.DefaultPriority) + q.Push(rc, "type1", 1, "task1", false) + q.Push(rc, "type1", 1, "task2", true) + q.Push(rc, "type1", 2, "task3", false) + q.Push(rc, "type2", 1, "task4", false) + q.Push(rc, "type2", 2, "task5", true) // nobody processing any tasks so no workers assigned in active set assertredis.ZGetAll(t, rc, "test:active", map[string]float64{"1": 0, "2": 0}) @@ -65,8 +66,8 @@ func TestQueues(t *testing.T) { `{"type":"type2","task":"task4","queued_on":"2022-01-01T12:01:09.123456789Z"}`: 1641038468.123456, }) assertredis.ZGetAll(t, rc, "test:2", map[string]float64{ - `{"type":"type1","task":"task3","queued_on":"2022-01-01T12:01:07.123456789Z"}`: 1651038466.123456, - `{"type":"type2","task":"task5","queued_on":"2022-01-01T12:01:11.123456789Z"}`: 1641038470.123456, + `{"type":"type1","task":"task3","queued_on":"2022-01-01T12:01:07.123456789Z"}`: 1641038466.123456, + `{"type":"type2","task":"task5","queued_on":"2022-01-01T12:01:11.123456789Z"}`: 1631038470.123456, }) assertSize(5) @@ -95,10 +96,10 @@ func TestQueues(t *testing.T) { assertredis.ZGetAll(t, rc, "test:active", map[string]float64{}) - q.Push(rc, "type1", 1, "task6", queues.DefaultPriority) - q.Push(rc, "type1", 1, "task7", queues.DefaultPriority) - q.Push(rc, "type1", 2, "task8", queues.DefaultPriority) - q.Push(rc, "type1", 2, "task9", queues.DefaultPriority) + q.Push(rc, "type1", 1, "task6", false) + q.Push(rc, "type1", 1, "task7", false) + q.Push(rc, "type1", 2, "task8", false) + q.Push(rc, "type1", 2, "task9", false) assertPop(1, `"task6"`) @@ -126,8 +127,8 @@ func TestQueues(t *testing.T) { q.Done(rc, 2) // if we somehow get into a state where an owner is in the active set but doesn't have queued tasks, pop will retry - q.Push(rc, "type1", 1, "task6", queues.DefaultPriority) - q.Push(rc, "type1", 2, "task7", queues.DefaultPriority) + q.Push(rc, "type1", 1, "task6", false) + q.Push(rc, "type1", 2, "task7", false) rc.Do("ZREMRANGEBYRANK", "test:1", 0, 1) @@ -135,7 +136,7 @@ func TestQueues(t *testing.T) { assertPop(0, "") // if we somehow call done too many times, we never get negative workers - q.Push(rc, "type1", 1, "task8", queues.DefaultPriority) + q.Push(rc, "type1", 1, "task8", false) q.Done(rc, 1) q.Done(rc, 1) diff --git a/web/ivr/ivr_test.go b/web/ivr/ivr_test.go index 9ed9864ce..23d63a056 100644 --- a/web/ivr/ivr_test.go +++ b/web/ivr/ivr_test.go @@ -27,7 +27,6 @@ import ( "github.com/nyaruka/mailroom/testsuite" "github.com/nyaruka/mailroom/testsuite/testdata" "github.com/nyaruka/mailroom/utils/clogs" - "github.com/nyaruka/mailroom/utils/queues" "github.com/nyaruka/mailroom/web" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -103,7 +102,7 @@ func TestTwilioIVR(t *testing.T) { err := models.InsertFlowStarts(ctx, rt.DB, []*models.FlowStart{start}) require.NoError(t, err) - err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, queues.DefaultPriority) + err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) require.NoError(t, err) testsuite.FlushTasks(t, rt) @@ -411,7 +410,7 @@ func TestVonageIVR(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT COUNT(*) FROM flows_flowstart`).Returns(1) assertdb.Query(t, rt.DB, `SELECT COUNT(*) FROM flows_flowstart WHERE params ->> 'ref_id' = '123'`).Returns(1) - err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, queues.DefaultPriority) + err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) require.NoError(t, err) testsuite.FlushTasks(t, rt) diff --git a/web/msg/broadcast.go b/web/msg/broadcast.go index c395ebd47..2c4d311d3 100644 --- a/web/msg/broadcast.go +++ b/web/msg/broadcast.go @@ -15,7 +15,6 @@ import ( "github.com/nyaruka/mailroom/core/tasks" "github.com/nyaruka/mailroom/core/tasks/msgs" "github.com/nyaruka/mailroom/runtime" - "github.com/nyaruka/mailroom/utils/queues" "github.com/nyaruka/mailroom/web" ) @@ -122,7 +121,7 @@ func handleBroadcast(ctx context.Context, rt *runtime.Runtime, r *broadcastReque rc := rt.RP.Get() defer rc.Close() - err = tasks.Queue(rc, tasks.BatchQueue, bcast.OrgID, task, queues.HighPriority) + err = tasks.Queue(rc, tasks.BatchQueue, bcast.OrgID, task, true) if err != nil { slog.Error("error queueing broadcast task", "error", err) } diff --git a/workers.go b/workers.go index e58ff5bf8..a0071a9a0 100644 --- a/workers.go +++ b/workers.go @@ -17,14 +17,14 @@ import ( type Foreman struct { rt *runtime.Runtime wg *sync.WaitGroup - queue *queues.FairSorted + queue queues.Fair workers []*Worker availableWorkers chan *Worker quit chan bool } // NewForeman creates a new Foreman for the passed in server with the number of max workers -func NewForeman(rt *runtime.Runtime, wg *sync.WaitGroup, q *queues.FairSorted, maxWorkers int) *Foreman { +func NewForeman(rt *runtime.Runtime, wg *sync.WaitGroup, q queues.Fair, maxWorkers int) *Foreman { foreman := &Foreman{ rt: rt, wg: wg, diff --git a/workers_test.go b/workers_test.go index 798d3c8c2..43e7936b7 100644 --- a/workers_test.go +++ b/workers_test.go @@ -35,15 +35,15 @@ func TestForemanAndWorkers(t *testing.T) { tasks.RegisterType("test", func() tasks.Task { return &testTask{} }) // queue up tasks of unknown type to ensure it doesn't break further processing - q.Push(rc, "spam", 1, "argh", queues.DefaultPriority) - q.Push(rc, "spam", 2, "argh", queues.DefaultPriority) + q.Push(rc, "spam", 1, "argh", false) + q.Push(rc, "spam", 2, "argh", false) // queue up 5 tasks for two orgs for range 5 { - q.Push(rc, "test", 1, &testTask{}, queues.DefaultPriority) + q.Push(rc, "test", 1, &testTask{}, false) } for range 5 { - q.Push(rc, "test", 2, &testTask{}, queues.DefaultPriority) + q.Push(rc, "test", 2, &testTask{}, false) } fm := mailroom.NewForeman(rt, wg, q, 2) From 63cc48e06b5a6459e37d51b3590a5139a1a719d5 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 15 Nov 2024 14:03:57 -0500 Subject: [PATCH 143/216] Allow tests to specify which task queues are flushed --- core/tasks/campaigns/fire_campaign_event_test.go | 2 +- core/tasks/campaigns/schedule_campaign_event_test.go | 8 ++++---- core/tasks/contacts/import_contact_batch_test.go | 4 ++-- core/tasks/handler/handle_contact_event_test.go | 2 +- core/tasks/interrupts/interrupt_channel_test.go | 4 ++-- core/tasks/ivr/cron_test.go | 2 +- core/tasks/ivr/start_ivr_flow_batch_test.go | 6 +++--- core/tasks/msgs/send_broadcast_test.go | 4 ++-- core/tasks/starts/start_flow_batch_test.go | 10 +++++----- core/tasks/starts/start_flow_test.go | 4 ++-- testsuite/assert.go | 5 +++-- testsuite/tasks.go | 10 ++++++++-- web/ivr/ivr_test.go | 4 ++-- 13 files changed, 36 insertions(+), 29 deletions(-) diff --git a/core/tasks/campaigns/fire_campaign_event_test.go b/core/tasks/campaigns/fire_campaign_event_test.go index 3d63e4dc7..da7017643 100644 --- a/core/tasks/campaigns/fire_campaign_event_test.go +++ b/core/tasks/campaigns/fire_campaign_event_test.go @@ -56,7 +56,7 @@ func TestFireCampaignEvents(t *testing.T) { err := tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, task, false) assert.NoError(t, err) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) // and left in redis marker for _, fid := range fireIDs { diff --git a/core/tasks/campaigns/schedule_campaign_event_test.go b/core/tasks/campaigns/schedule_campaign_event_test.go index 5e69be7b1..33449fe26 100644 --- a/core/tasks/campaigns/schedule_campaign_event_test.go +++ b/core/tasks/campaigns/schedule_campaign_event_test.go @@ -36,7 +36,7 @@ func TestScheduleCampaignEvent(t *testing.T) { // schedule first event... testsuite.QueueBatchTask(t, rt, testdata.Org1, &campaigns.ScheduleCampaignEventTask{CampaignEventID: testdata.RemindersEvent1.ID}) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) // cathy has no value for joined and alexandia has a value too far in past, but bob and george will have values... assertContactFires(t, rt.DB, testdata.RemindersEvent1.ID, map[models.ContactID]time.Time{ @@ -46,7 +46,7 @@ func TestScheduleCampaignEvent(t *testing.T) { // schedule second event... testsuite.QueueBatchTask(t, rt, testdata.Org1, &campaigns.ScheduleCampaignEventTask{CampaignEventID: testdata.RemindersEvent2.ID}) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) assertContactFires(t, rt.DB, testdata.RemindersEvent2.ID, map[models.ContactID]time.Time{ testdata.Bob.ID: time.Date(2030, 1, 1, 0, 10, 0, 0, time.UTC), @@ -69,7 +69,7 @@ func TestScheduleCampaignEvent(t *testing.T) { event3 := testdata.InsertCampaignFlowEvent(rt, testdata.RemindersCampaign, testdata.Favorites, testdata.CreatedOnField, 5, "M") testsuite.QueueBatchTask(t, rt, testdata.Org1, &campaigns.ScheduleCampaignEventTask{CampaignEventID: event3.ID}) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) // only cathy is in the group and new enough to have a fire assertContactFires(t, rt.DB, event3.ID, map[models.ContactID]time.Time{ @@ -83,7 +83,7 @@ func TestScheduleCampaignEvent(t *testing.T) { rt.DB.MustExec(`UPDATE contacts_contact SET last_seen_on = '2040-01-01T00:00:00Z' WHERE id = $1`, testdata.Bob.ID) testsuite.QueueBatchTask(t, rt, testdata.Org1, &campaigns.ScheduleCampaignEventTask{CampaignEventID: event4.ID}) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) assertContactFires(t, rt.DB, event4.ID, map[models.ContactID]time.Time{ testdata.Bob.ID: time.Date(2040, 1, 2, 0, 0, 0, 0, time.UTC), diff --git a/core/tasks/contacts/import_contact_batch_test.go b/core/tasks/contacts/import_contact_batch_test.go index ded378d9d..8957bccfa 100644 --- a/core/tasks/contacts/import_contact_batch_test.go +++ b/core/tasks/contacts/import_contact_batch_test.go @@ -31,14 +31,14 @@ func TestImportContactBatch(t *testing.T) { // perform first batch task... testsuite.QueueBatchTask(t, rt, testdata.Org1, &contacts.ImportContactBatchTask{ContactImportBatchID: batch1ID}) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) // import is still in progress assertdb.Query(t, rt.DB, `SELECT status FROM contacts_contactimport WHERE id = $1`, importID).Columns(map[string]any{"status": "O"}) // perform second batch task... testsuite.QueueBatchTask(t, rt, testdata.Org1, &contacts.ImportContactBatchTask{ContactImportBatchID: batch2ID}) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) assertdb.Query(t, rt.DB, `SELECT count(*) FROM contacts_contact WHERE id >= 30000`).Returns(3) assertdb.Query(t, rt.DB, `SELECT count(*) FROM contacts_contact WHERE name = 'Norbert' AND language = 'eng'`).Returns(1) diff --git a/core/tasks/handler/handle_contact_event_test.go b/core/tasks/handler/handle_contact_event_test.go index 2ad2afd30..e422b1b79 100644 --- a/core/tasks/handler/handle_contact_event_test.go +++ b/core/tasks/handler/handle_contact_event_test.go @@ -36,7 +36,7 @@ func TestHandleContactEvent(t *testing.T) { NewContact: false, }) - tasksRan := testsuite.FlushTasks(t, rt) + tasksRan := testsuite.FlushTasks(t, rt, nil) assert.Equal(t, map[string]int{"handle_contact_event": 2}, tasksRan) assertdb.Query(t, rt.DB, `SELECT count(*) FROM contacts_contact WHERE id = $1 AND status = 'S'`, testdata.Cathy.ID).Returns(1) diff --git a/core/tasks/interrupts/interrupt_channel_test.go b/core/tasks/interrupts/interrupt_channel_test.go index 343dde7a5..209806c6b 100644 --- a/core/tasks/interrupts/interrupt_channel_test.go +++ b/core/tasks/interrupts/interrupt_channel_test.go @@ -55,7 +55,7 @@ func TestInterruptChannel(t *testing.T) { // queue and perform a task to interrupt the Twilio channel tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &interrupts.InterruptChannelTask{ChannelID: testdata.TwilioChannel.ID}, false) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_msg WHERE status = 'F' and channel_id = $1`, testdata.VonageChannel.ID).Returns(1) assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_msg WHERE status = 'F' and failed_reason = 'R' and channel_id = $1`, testdata.VonageChannel.ID).Returns(0) @@ -83,7 +83,7 @@ func TestInterruptChannel(t *testing.T) { // queue and perform a task to interrupt the Vonage channel tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &interrupts.InterruptChannelTask{ChannelID: testdata.VonageChannel.ID}, false) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_msg WHERE status = 'F' and failed_reason = 'R' and channel_id = $1`, testdata.VonageChannel.ID).Returns(6) assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_msg WHERE status = 'F' and channel_id = $1`, testdata.VonageChannel.ID).Returns(7) diff --git a/core/tasks/ivr/cron_test.go b/core/tasks/ivr/cron_test.go index aa117d75a..9b193838a 100644 --- a/core/tasks/ivr/cron_test.go +++ b/core/tasks/ivr/cron_test.go @@ -40,7 +40,7 @@ func TestRetryCallsCron(t *testing.T) { service.callError = nil service.callID = ivr.CallID("call1") - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) assertdb.Query(t, rt.DB, `SELECT COUNT(*) FROM ivr_call WHERE contact_id = $1 AND status = $2 AND external_id = $3`, testdata.Cathy.ID, models.CallStatusWired, "call1").Returns(1) diff --git a/core/tasks/ivr/start_ivr_flow_batch_test.go b/core/tasks/ivr/start_ivr_flow_batch_test.go index 441b23391..8a0bd2b10 100644 --- a/core/tasks/ivr/start_ivr_flow_batch_test.go +++ b/core/tasks/ivr/start_ivr_flow_batch_test.go @@ -43,7 +43,7 @@ func TestIVR(t *testing.T) { err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) require.NoError(t, err) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) // should have one call in a failed state assertdb.Query(t, rt.DB, `SELECT COUNT(*) FROM ivr_call WHERE contact_id = $1 AND status = $2`, testdata.Cathy.ID, models.CallStatusFailed).Returns(1) @@ -55,7 +55,7 @@ func TestIVR(t *testing.T) { err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) require.NoError(t, err) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) assertdb.Query(t, rt.DB, `SELECT COUNT(*) FROM ivr_call WHERE contact_id = $1 AND status = $2 AND external_id = $3`, testdata.Cathy.ID, models.CallStatusWired, "call1").Returns(1) @@ -66,7 +66,7 @@ func TestIVR(t *testing.T) { err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) require.NoError(t, err) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) assertdb.Query(t, rt.DB, `SELECT COUNT(*) FROM ivr_call WHERE contact_id = $1 AND status = $2 AND next_attempt IS NOT NULL;`, testdata.Cathy.ID, models.CallStatusQueued).Returns(1) } diff --git a/core/tasks/msgs/send_broadcast_test.go b/core/tasks/msgs/send_broadcast_test.go index 56a7b2855..6d7b1462a 100644 --- a/core/tasks/msgs/send_broadcast_test.go +++ b/core/tasks/msgs/send_broadcast_test.go @@ -167,7 +167,7 @@ func TestBroadcastsFromEvents(t *testing.T) { err = tasks.Queue(rc, tc.queue, testdata.Org1.ID, &msgs.SendBroadcastTask{Broadcast: bcast}, false) assert.NoError(t, err) - taskCounts := testsuite.FlushTasks(t, rt) + taskCounts := testsuite.FlushTasks(t, rt, nil) // assert our count of batches assert.Equal(t, tc.expectedBatchCount, taskCounts["send_broadcast_batch"], "%d: unexpected batch count", i) @@ -291,7 +291,7 @@ func TestSendBroadcastTask(t *testing.T) { err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, task, false) assert.NoError(t, err) - taskCounts := testsuite.FlushTasks(t, rt) + taskCounts := testsuite.FlushTasks(t, rt, nil) // assert our count of batches assert.Equal(t, tc.expectedBatches, taskCounts["send_broadcast_batch"], "%d: unexpected batch count", i) diff --git a/core/tasks/starts/start_flow_batch_test.go b/core/tasks/starts/start_flow_batch_test.go index eb6fb5236..9089d5274 100644 --- a/core/tasks/starts/start_flow_batch_test.go +++ b/core/tasks/starts/start_flow_batch_test.go @@ -36,7 +36,7 @@ func TestStartFlowBatchTask(t *testing.T) { // start the first batch... err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: batch1}, false) assert.NoError(t, err) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowsession WHERE contact_id = ANY($1) AND status = 'C' AND responded = FALSE AND org_id = 1 AND call_id IS NULL AND output IS NOT NULL`, pq.Array([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID})). @@ -55,7 +55,7 @@ func TestStartFlowBatchTask(t *testing.T) { // start the second and final batch... err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: batch2}, false) assert.NoError(t, err) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun WHERE start_id = $1`, start1.ID).Returns(4) assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, start1.ID).Returns("C") @@ -72,7 +72,7 @@ func TestStartFlowBatchTask(t *testing.T) { // start the first batch... err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: start2Batch1}, false) assert.NoError(t, err) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun WHERE start_id = $1`, start2.ID).Returns(2) @@ -82,7 +82,7 @@ func TestStartFlowBatchTask(t *testing.T) { // start the second batch... err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: start2Batch2}, false) assert.NoError(t, err) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) // check that second batch didn't create any runs and start status is still interrupted assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun WHERE start_id = $1`, start2.ID).Returns(2) @@ -105,7 +105,7 @@ func TestStartFlowBatchTaskNonPersistedStart(t *testing.T) { // start the first batch... err := tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: batch}, false) assert.NoError(t, err) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun`).Returns(2) } diff --git a/core/tasks/starts/start_flow_test.go b/core/tasks/starts/start_flow_test.go index b57576fa2..8acf21570 100644 --- a/core/tasks/starts/start_flow_test.go +++ b/core/tasks/starts/start_flow_test.go @@ -232,7 +232,7 @@ func TestStartFlowTask(t *testing.T) { err = tasks.Queue(rc, tc.queue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) assert.NoError(t, err) - taskCounts := testsuite.FlushTasks(t, rt) + taskCounts := testsuite.FlushTasks(t, rt, nil) // assert our count of batches assert.Equal(t, tc.expectedBatchCount, taskCounts["start_flow_batch"], "%d: unexpected batch count", i) @@ -268,7 +268,7 @@ func TestStartFlowTaskNonPersistedStart(t *testing.T) { err := tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) assert.NoError(t, err) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun`).Returns(2) } diff --git a/testsuite/assert.go b/testsuite/assert.go index e7e7197a4..337b3db30 100644 --- a/testsuite/assert.go +++ b/testsuite/assert.go @@ -9,6 +9,7 @@ import ( "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/goflow/test" "github.com/nyaruka/mailroom/core/models" + "github.com/nyaruka/mailroom/testsuite/testdata" "github.com/nyaruka/mailroom/utils/queues" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -49,11 +50,11 @@ func AssertCourierQueues(t *testing.T, expected map[string][]int, errMsg ...any) } // AssertContactTasks asserts that the given contact has the given tasks queued for them -func AssertContactTasks(t *testing.T, orgID models.OrgID, contactID models.ContactID, expected []string, msgAndArgs ...any) { +func AssertContactTasks(t *testing.T, org *testdata.Org, contact *testdata.Contact, expected []string, msgAndArgs ...any) { rc := getRC() defer rc.Close() - tasks, err := redis.Strings(rc.Do("LRANGE", fmt.Sprintf("c:%d:%d", orgID, contactID), 0, -1)) + tasks, err := redis.Strings(rc.Do("LRANGE", fmt.Sprintf("c:%d:%d", org.ID, contact.ID), 0, -1)) require.NoError(t, err) expectedJSON := jsonx.MustMarshal(expected) diff --git a/testsuite/tasks.go b/testsuite/tasks.go index c0570d45c..23d542cff 100644 --- a/testsuite/tasks.go +++ b/testsuite/tasks.go @@ -3,6 +3,7 @@ package testsuite import ( "context" "fmt" + "slices" "testing" "github.com/gomodule/redigo/redis" @@ -60,7 +61,7 @@ func CurrentTasks(t *testing.T, rt *runtime.Runtime, qname string) map[models.Or return tasks } -func FlushTasks(t *testing.T, rt *runtime.Runtime) map[string]int { +func FlushTasks(t *testing.T, rt *runtime.Runtime, qnames []string) map[string]int { rc := rt.RP.Get() defer rc.Close() @@ -68,7 +69,12 @@ func FlushTasks(t *testing.T, rt *runtime.Runtime) map[string]int { var err error counts := make(map[string]int) - qs := []queues.Fair{tasks.HandlerQueue, tasks.BatchQueue, tasks.ThrottledQueue} + var qs []queues.Fair + for _, q := range []queues.Fair{tasks.HandlerQueue, tasks.BatchQueue, tasks.ThrottledQueue} { + if len(qnames) == 0 || slices.Contains(qnames, fmt.Sprint(q)[6:]) { + qs = append(qs, q) + } + } for { // look for a task in the queues diff --git a/web/ivr/ivr_test.go b/web/ivr/ivr_test.go index 23d63a056..a4fea5f29 100644 --- a/web/ivr/ivr_test.go +++ b/web/ivr/ivr_test.go @@ -105,7 +105,7 @@ func TestTwilioIVR(t *testing.T) { err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) require.NoError(t, err) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) // check our 3 contacts have 3 wired calls assertdb.Query(t, rt.DB, `SELECT COUNT(*) FROM ivr_call WHERE contact_id = $1 AND status = $2 AND external_id = $3`, @@ -413,7 +413,7 @@ func TestVonageIVR(t *testing.T) { err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) require.NoError(t, err) - testsuite.FlushTasks(t, rt) + testsuite.FlushTasks(t, rt, nil) assertdb.Query(t, rt.DB, `SELECT COUNT(*) FROM ivr_call WHERE contact_id = $1 AND status = $2 AND external_id = $3`, testdata.Cathy.ID, models.CallStatusWired, "Call1").Returns(1) From 4eaa22ea5d25de08323515353995a3acbe643e3d Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 15 Nov 2024 14:40:55 -0500 Subject: [PATCH 144/216] Add new task to handle bulk session timeouts --- core/tasks/timeouts/bulk_timeout.go | 53 ++++++++++++++++++++++++ core/tasks/timeouts/bulk_timeout_test.go | 36 ++++++++++++++++ core/tasks/timeouts/cron.go | 8 ++-- 3 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 core/tasks/timeouts/bulk_timeout.go create mode 100644 core/tasks/timeouts/bulk_timeout_test.go diff --git a/core/tasks/timeouts/bulk_timeout.go b/core/tasks/timeouts/bulk_timeout.go new file mode 100644 index 000000000..d0cd48603 --- /dev/null +++ b/core/tasks/timeouts/bulk_timeout.go @@ -0,0 +1,53 @@ +package timeouts + +import ( + "context" + "fmt" + "time" + + "github.com/nyaruka/mailroom/core/models" + "github.com/nyaruka/mailroom/core/tasks" + "github.com/nyaruka/mailroom/core/tasks/handler" + "github.com/nyaruka/mailroom/core/tasks/handler/ctasks" + "github.com/nyaruka/mailroom/runtime" +) + +// TypeBulkTimeout is the type of the task +const TypeBulkTimeout = "bulk_timeout" + +func init() { + tasks.RegisterType(TypeBulkTimeout, func() tasks.Task { return &BulkTimeoutTask{} }) +} + +// BulkTimeoutTask is the payload of the task +type BulkTimeoutTask struct { + Timeouts []Timeout `json:"timeouts"` +} + +func (t *BulkTimeoutTask) Type() string { + return TypeBulkTimeout +} + +// Timeout is the maximum amount of time the task can run for +func (t *BulkTimeoutTask) Timeout() time.Duration { + return time.Hour +} + +func (t *BulkTimeoutTask) WithAssets() models.Refresh { + return models.RefreshNone +} + +// Perform creates the actual task +func (t *BulkTimeoutTask) Perform(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets) error { + rc := rt.RP.Get() + defer rc.Close() + + for _, timeout := range t.Timeouts { + err := handler.QueueTask(rc, oa.OrgID(), timeout.ContactID, ctasks.NewWaitTimeout(timeout.SessionID, timeout.TimeoutOn)) + if err != nil { + return fmt.Errorf("error queuing handle task for timeout on session #%d: %w", timeout.SessionID, err) + } + } + + return nil +} diff --git a/core/tasks/timeouts/bulk_timeout_test.go b/core/tasks/timeouts/bulk_timeout_test.go new file mode 100644 index 000000000..0ed5d4f66 --- /dev/null +++ b/core/tasks/timeouts/bulk_timeout_test.go @@ -0,0 +1,36 @@ +package timeouts_test + +import ( + "testing" + "time" + + "github.com/nyaruka/gocommon/dates" + "github.com/nyaruka/mailroom/core/tasks/timeouts" + "github.com/nyaruka/mailroom/testsuite" + "github.com/nyaruka/mailroom/testsuite/testdata" + "github.com/stretchr/testify/assert" +) + +func TestBulkTimeout(t *testing.T) { + _, rt := testsuite.Runtime() + defer testsuite.Reset(testsuite.ResetRedis) + + defer dates.SetNowFunc(time.Now) + dates.SetNowFunc(dates.NewFixedNow(time.Date(2024, 11, 15, 13, 59, 0, 0, time.UTC))) + + testsuite.QueueBatchTask(t, rt, testdata.Org1, &timeouts.BulkTimeoutTask{ + Timeouts: []timeouts.Timeout{ + {SessionID: 123456, ContactID: testdata.Cathy.ID, TimeoutOn: time.Date(2024, 11, 15, 13, 57, 0, 0, time.UTC)}, + {SessionID: 234567, ContactID: testdata.Bob.ID, TimeoutOn: time.Date(2024, 11, 15, 13, 58, 0, 0, time.UTC)}, + }, + }) + + assert.Equal(t, map[string]int{"bulk_timeout": 1}, testsuite.FlushTasks(t, rt, []string{"batch", "throttled"})) + + testsuite.AssertContactTasks(t, testdata.Org1, testdata.Cathy, []string{ + `{"type":"timeout_event","task":{"session_id":123456,"time":"2024-11-15T13:57:00Z"},"queued_on":"2024-11-15T13:59:00Z"}`, + }) + testsuite.AssertContactTasks(t, testdata.Org1, testdata.Bob, []string{ + `{"type":"timeout_event","task":{"session_id":234567,"time":"2024-11-15T13:58:00Z"},"queued_on":"2024-11-15T13:59:00Z"}`, + }) +} diff --git a/core/tasks/timeouts/cron.go b/core/tasks/timeouts/cron.go index 465653cc2..93a5bd1f4 100644 --- a/core/tasks/timeouts/cron.go +++ b/core/tasks/timeouts/cron.go @@ -97,8 +97,8 @@ ORDER BY timeout_on ASC LIMIT 25000` type Timeout struct { - SessionID models.SessionID `db:"session_id"` - OrgID models.OrgID `db:"org_id"` - ContactID models.ContactID `db:"contact_id"` - TimeoutOn time.Time `db:"timeout_on"` + SessionID models.SessionID `db:"session_id" json:"session_id"` + OrgID models.OrgID `db:"org_id" json:"-"` + ContactID models.ContactID `db:"contact_id" json:"contact_id"` + TimeoutOn time.Time `db:"timeout_on" json:"timeout_on"` } From 5dd3fa709c59907d9de6aafd5e837ffcfbe72d16 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 15 Nov 2024 18:20:58 -0500 Subject: [PATCH 145/216] Rework timeouts cron --- core/tasks/timeouts/bulk_timeout.go | 2 +- core/tasks/timeouts/bulk_timeout_test.go | 2 +- core/tasks/timeouts/cron.go | 75 +++++++++++++++--------- core/tasks/timeouts/cron_test.go | 68 +++++++++++++++------ 4 files changed, 100 insertions(+), 47 deletions(-) diff --git a/core/tasks/timeouts/bulk_timeout.go b/core/tasks/timeouts/bulk_timeout.go index d0cd48603..47f24778d 100644 --- a/core/tasks/timeouts/bulk_timeout.go +++ b/core/tasks/timeouts/bulk_timeout.go @@ -21,7 +21,7 @@ func init() { // BulkTimeoutTask is the payload of the task type BulkTimeoutTask struct { - Timeouts []Timeout `json:"timeouts"` + Timeouts []*Timeout `json:"timeouts"` } func (t *BulkTimeoutTask) Type() string { diff --git a/core/tasks/timeouts/bulk_timeout_test.go b/core/tasks/timeouts/bulk_timeout_test.go index 0ed5d4f66..230b605ea 100644 --- a/core/tasks/timeouts/bulk_timeout_test.go +++ b/core/tasks/timeouts/bulk_timeout_test.go @@ -19,7 +19,7 @@ func TestBulkTimeout(t *testing.T) { dates.SetNowFunc(dates.NewFixedNow(time.Date(2024, 11, 15, 13, 59, 0, 0, time.UTC))) testsuite.QueueBatchTask(t, rt, testdata.Org1, &timeouts.BulkTimeoutTask{ - Timeouts: []timeouts.Timeout{ + Timeouts: []*timeouts.Timeout{ {SessionID: 123456, ContactID: testdata.Cathy.ID, TimeoutOn: time.Date(2024, 11, 15, 13, 57, 0, 0, time.UTC)}, {SessionID: 234567, ContactID: testdata.Bob.ID, TimeoutOn: time.Date(2024, 11, 15, 13, 58, 0, 0, time.UTC)}, }, diff --git a/core/tasks/timeouts/cron.go b/core/tasks/timeouts/cron.go index 93a5bd1f4..e94e9f670 100644 --- a/core/tasks/timeouts/cron.go +++ b/core/tasks/timeouts/cron.go @@ -3,6 +3,7 @@ package timeouts import ( "context" "fmt" + "slices" "time" "github.com/nyaruka/mailroom/core/models" @@ -14,16 +15,20 @@ import ( ) func init() { - tasks.RegisterCron("sessions_timeouts", newTimeoutsCron()) + tasks.RegisterCron("sessions_timeouts", NewTimeoutsCron(100, 100)) } type timeoutsCron struct { - marker *redisx.IntervalSet + marker *redisx.IntervalSet + bulkThreshold int // use bulk task for any org with this or more timeouts + bulkBatchSize int // number of timeouts to queue in a single bulk task } -func newTimeoutsCron() tasks.Cron { +func NewTimeoutsCron(bulkThreshold, bulkBatchSize int) tasks.Cron { return &timeoutsCron{ - marker: redisx.NewIntervalSet("session_timeouts", time.Hour*24, 2), + marker: redisx.NewIntervalSet("session_timeouts", time.Hour*24, 2), + bulkThreshold: bulkThreshold, + bulkBatchSize: bulkBatchSize, } } @@ -35,32 +40,32 @@ func (c *timeoutsCron) AllInstances() bool { return false } -// timeoutRuns looks for any runs that have timed out and schedules for them to continue -// TODO: extend lock func (c *timeoutsCron) Run(ctx context.Context, rt *runtime.Runtime) (map[string]any, error) { // find all sessions that need to be expired (we exclude IVR runs) - rows, err := rt.DB.QueryxContext(ctx, timedoutSessionsSQL) + rows, err := rt.DB.QueryxContext(ctx, sqlSelectTimedoutSessions) if err != nil { return nil, fmt.Errorf("error selecting timed out sessions: %w", err) } defer rows.Close() + taskID := func(t *Timeout) string { return fmt.Sprintf("%d:%s", t.SessionID, t.TimeoutOn.Format(time.RFC3339)) } + + // scan and organize by org + byOrg := make(map[models.OrgID][]*Timeout, 50) + rc := rt.RP.Get() defer rc.Close() - numQueued, numDupes := 0, 0 + numDupes, numQueuedHandler, numQueuedBulk := 0, 0, 0 - // add a timeout task for each run - timeout := &Timeout{} for rows.Next() { - err := rows.StructScan(timeout) - if err != nil { + timeout := &Timeout{} + if err := rows.StructScan(timeout); err != nil { return nil, fmt.Errorf("error scanning timeout: %w", err) } // check whether we've already queued this - taskID := fmt.Sprintf("%d:%s", timeout.SessionID, timeout.TimeoutOn.Format(time.RFC3339)) - queued, err := c.marker.IsMember(rc, taskID) + queued, err := c.marker.IsMember(rc, taskID(timeout)) if err != nil { return nil, fmt.Errorf("error checking whether task is queued: %w", err) } @@ -71,25 +76,41 @@ func (c *timeoutsCron) Run(ctx context.Context, rt *runtime.Runtime) (map[string continue } - // ok, queue this task - err = handler.QueueTask(rc, timeout.OrgID, timeout.ContactID, ctasks.NewWaitTimeout(timeout.SessionID, timeout.TimeoutOn)) - if err != nil { - return nil, fmt.Errorf("error adding new handle task: %w", err) - } + byOrg[timeout.OrgID] = append(byOrg[timeout.OrgID], timeout) + } - // and mark it as queued - err = c.marker.Add(rc, taskID) - if err != nil { - return nil, fmt.Errorf("error marking timeout task as queued: %w", err) + for orgID, timeouts := range byOrg { + throttle := len(timeouts) >= c.bulkThreshold + + for batch := range slices.Chunk(timeouts, c.bulkBatchSize) { + if throttle { + if err := tasks.Queue(rc, tasks.ThrottledQueue, orgID, &BulkTimeoutTask{Timeouts: batch}, true); err != nil { + return nil, fmt.Errorf("error queuing bulk timeout task to throttle queue: %w", err) + } + numQueuedBulk += len(batch) + } + + for _, timeout := range batch { + if !throttle { + err := handler.QueueTask(rc, timeout.OrgID, timeout.ContactID, ctasks.NewWaitTimeout(timeout.SessionID, timeout.TimeoutOn)) + if err != nil { + return nil, fmt.Errorf("error queuing timeout task to handler queue: %w", err) + } + numQueuedHandler++ + } + + // mark as queued + if err = c.marker.Add(rc, taskID(timeout)); err != nil { + return nil, fmt.Errorf("error marking timeout task as queued: %w", err) + } + } } - - numQueued++ } - return map[string]any{"dupes": numDupes, "queued": numQueued}, nil + return map[string]any{"dupes": numDupes, "queued_handler": numQueuedHandler, "queued_bulk": numQueuedBulk}, nil } -const timedoutSessionsSQL = ` +const sqlSelectTimedoutSessions = ` SELECT id as session_id, org_id, contact_id, timeout_on FROM flows_flowsession WHERE status = 'W' AND timeout_on < NOW() AND call_id IS NULL diff --git a/core/tasks/timeouts/cron_test.go b/core/tasks/timeouts/cron_test.go index 74baaf985..dd7df4148 100644 --- a/core/tasks/timeouts/cron_test.go +++ b/core/tasks/timeouts/cron_test.go @@ -1,14 +1,19 @@ -package timeouts +package timeouts_test import ( - "encoding/json" + "fmt" "testing" "time" + "github.com/nyaruka/gocommon/i18n" + "github.com/nyaruka/gocommon/jsonx" + "github.com/nyaruka/gocommon/uuids" + "github.com/nyaruka/goflow/flows" _ "github.com/nyaruka/mailroom/core/handlers" "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/core/tasks" "github.com/nyaruka/mailroom/core/tasks/handler" + "github.com/nyaruka/mailroom/core/tasks/timeouts" "github.com/nyaruka/mailroom/testsuite" "github.com/nyaruka/mailroom/testsuite/testdata" @@ -22,35 +27,62 @@ func TestTimeouts(t *testing.T) { defer testsuite.Reset(testsuite.ResetData | testsuite.ResetRedis) - // need to create a session that has an expired timeout - s1TimeoutOn := time.Now() + // create sessions for Bob and Cathy that have timed out and session for George that has not + s1TimeoutOn := time.Now().Add(-time.Second) testdata.InsertWaitingSession(rt, testdata.Org1, testdata.Cathy, models.FlowTypeMessaging, testdata.Favorites, models.NilCallID, time.Now(), time.Now(), false, &s1TimeoutOn) - s2TimeoutOn := time.Now().Add(time.Hour * 24) - testdata.InsertWaitingSession(rt, testdata.Org1, testdata.George, models.FlowTypeMessaging, testdata.Favorites, models.NilCallID, time.Now(), time.Now(), false, &s2TimeoutOn) + s2TimeoutOn := time.Now().Add(-time.Second * 30) + testdata.InsertWaitingSession(rt, testdata.Org1, testdata.Bob, models.FlowTypeMessaging, testdata.Favorites, models.NilCallID, time.Now(), time.Now(), false, &s2TimeoutOn) + s3TimeoutOn := time.Now().Add(time.Hour * 24) + testdata.InsertWaitingSession(rt, testdata.Org1, testdata.George, models.FlowTypeMessaging, testdata.Favorites, models.NilCallID, time.Now(), time.Now(), false, &s3TimeoutOn) - time.Sleep(10 * time.Millisecond) + // for other org create 6 waiting sessions + for i := range 6 { + c := testdata.InsertContact(rt, testdata.Org2, flows.ContactUUID(uuids.NewV4()), fmt.Sprint(i), i18n.NilLanguage, models.ContactStatusActive) + timeoutOn := time.Now().Add(-time.Second * 10) + testdata.InsertWaitingSession(rt, testdata.Org2, c, models.FlowTypeMessaging, testdata.Favorites, models.NilCallID, time.Now(), time.Now(), false, &timeoutOn) + } // schedule our timeouts - cron := newTimeoutsCron() + cron := timeouts.NewTimeoutsCron(3, 5) res, err := cron.Run(ctx, rt) assert.NoError(t, err) - assert.Equal(t, map[string]any{"dupes": 0, "queued": 1}, res) + assert.Equal(t, map[string]any{"dupes": 0, "queued_handler": 2, "queued_bulk": 6}, res) - // should have created one task - task, err := tasks.HandlerQueue.Pop(rc) + // should have created two handler tasks for org 1 + task1, err := tasks.HandlerQueue.Pop(rc) assert.NoError(t, err) - assert.NotNil(t, task) - - // decode the task - eventTask := &handler.HandleContactEventTask{} - err = json.Unmarshal(task.Task, eventTask) + assert.Equal(t, int(testdata.Org1.ID), task1.OwnerID) + assert.Equal(t, "handle_contact_event", task1.Type) + task2, err := tasks.HandlerQueue.Pop(rc) assert.NoError(t, err) + assert.Equal(t, int(testdata.Org1.ID), task1.OwnerID) + assert.Equal(t, "handle_contact_event", task1.Type) - // assert its the right contact + // decode the tasks to check contacts + eventTask := &handler.HandleContactEventTask{} + jsonx.MustUnmarshal(task1.Task, eventTask) + assert.Equal(t, testdata.Bob.ID, eventTask.ContactID) + eventTask = &handler.HandleContactEventTask{} + jsonx.MustUnmarshal(task2.Task, eventTask) assert.Equal(t, testdata.Cathy.ID, eventTask.ContactID) // no other - task, err = tasks.HandlerQueue.Pop(rc) + task, err := tasks.HandlerQueue.Pop(rc) assert.NoError(t, err) assert.Nil(t, task) + + // should have created two throttled bulk tasks for org 2 + task3, err := tasks.ThrottledQueue.Pop(rc) + assert.NoError(t, err) + assert.Equal(t, int(testdata.Org2.ID), task3.OwnerID) + assert.Equal(t, "bulk_timeout", task3.Type) + task4, err := tasks.ThrottledQueue.Pop(rc) + assert.NoError(t, err) + assert.Equal(t, int(testdata.Org2.ID), task4.OwnerID) + assert.Equal(t, "bulk_timeout", task4.Type) + + // if task runs again, these timeouts won't be re-queued + res, err = cron.Run(ctx, rt) + assert.NoError(t, err) + assert.Equal(t, map[string]any{"dupes": 8, "queued_handler": 0, "queued_bulk": 0}, res) } From 0c6fd11000ee3c8d85f0673f10767eec6c1abdfa Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 18 Nov 2024 10:50:01 -0500 Subject: [PATCH 146/216] Tweak signature of testsuite.FlushTasks --- core/tasks/campaigns/fire_campaign_event_test.go | 2 +- core/tasks/campaigns/schedule_campaign_event_test.go | 8 ++++---- core/tasks/contacts/import_contact_batch_test.go | 4 ++-- core/tasks/handler/handle_contact_event_test.go | 2 +- core/tasks/interrupts/interrupt_channel_test.go | 4 ++-- core/tasks/ivr/cron_test.go | 2 +- core/tasks/ivr/start_ivr_flow_batch_test.go | 6 +++--- core/tasks/msgs/send_broadcast_test.go | 4 ++-- core/tasks/starts/start_flow_batch_test.go | 10 +++++----- core/tasks/starts/start_flow_test.go | 4 ++-- testsuite/tasks.go | 2 +- web/ivr/ivr_test.go | 4 ++-- 12 files changed, 26 insertions(+), 26 deletions(-) diff --git a/core/tasks/campaigns/fire_campaign_event_test.go b/core/tasks/campaigns/fire_campaign_event_test.go index da7017643..3d63e4dc7 100644 --- a/core/tasks/campaigns/fire_campaign_event_test.go +++ b/core/tasks/campaigns/fire_campaign_event_test.go @@ -56,7 +56,7 @@ func TestFireCampaignEvents(t *testing.T) { err := tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, task, false) assert.NoError(t, err) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) // and left in redis marker for _, fid := range fireIDs { diff --git a/core/tasks/campaigns/schedule_campaign_event_test.go b/core/tasks/campaigns/schedule_campaign_event_test.go index 33449fe26..5e69be7b1 100644 --- a/core/tasks/campaigns/schedule_campaign_event_test.go +++ b/core/tasks/campaigns/schedule_campaign_event_test.go @@ -36,7 +36,7 @@ func TestScheduleCampaignEvent(t *testing.T) { // schedule first event... testsuite.QueueBatchTask(t, rt, testdata.Org1, &campaigns.ScheduleCampaignEventTask{CampaignEventID: testdata.RemindersEvent1.ID}) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) // cathy has no value for joined and alexandia has a value too far in past, but bob and george will have values... assertContactFires(t, rt.DB, testdata.RemindersEvent1.ID, map[models.ContactID]time.Time{ @@ -46,7 +46,7 @@ func TestScheduleCampaignEvent(t *testing.T) { // schedule second event... testsuite.QueueBatchTask(t, rt, testdata.Org1, &campaigns.ScheduleCampaignEventTask{CampaignEventID: testdata.RemindersEvent2.ID}) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) assertContactFires(t, rt.DB, testdata.RemindersEvent2.ID, map[models.ContactID]time.Time{ testdata.Bob.ID: time.Date(2030, 1, 1, 0, 10, 0, 0, time.UTC), @@ -69,7 +69,7 @@ func TestScheduleCampaignEvent(t *testing.T) { event3 := testdata.InsertCampaignFlowEvent(rt, testdata.RemindersCampaign, testdata.Favorites, testdata.CreatedOnField, 5, "M") testsuite.QueueBatchTask(t, rt, testdata.Org1, &campaigns.ScheduleCampaignEventTask{CampaignEventID: event3.ID}) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) // only cathy is in the group and new enough to have a fire assertContactFires(t, rt.DB, event3.ID, map[models.ContactID]time.Time{ @@ -83,7 +83,7 @@ func TestScheduleCampaignEvent(t *testing.T) { rt.DB.MustExec(`UPDATE contacts_contact SET last_seen_on = '2040-01-01T00:00:00Z' WHERE id = $1`, testdata.Bob.ID) testsuite.QueueBatchTask(t, rt, testdata.Org1, &campaigns.ScheduleCampaignEventTask{CampaignEventID: event4.ID}) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) assertContactFires(t, rt.DB, event4.ID, map[models.ContactID]time.Time{ testdata.Bob.ID: time.Date(2040, 1, 2, 0, 0, 0, 0, time.UTC), diff --git a/core/tasks/contacts/import_contact_batch_test.go b/core/tasks/contacts/import_contact_batch_test.go index 8957bccfa..ded378d9d 100644 --- a/core/tasks/contacts/import_contact_batch_test.go +++ b/core/tasks/contacts/import_contact_batch_test.go @@ -31,14 +31,14 @@ func TestImportContactBatch(t *testing.T) { // perform first batch task... testsuite.QueueBatchTask(t, rt, testdata.Org1, &contacts.ImportContactBatchTask{ContactImportBatchID: batch1ID}) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) // import is still in progress assertdb.Query(t, rt.DB, `SELECT status FROM contacts_contactimport WHERE id = $1`, importID).Columns(map[string]any{"status": "O"}) // perform second batch task... testsuite.QueueBatchTask(t, rt, testdata.Org1, &contacts.ImportContactBatchTask{ContactImportBatchID: batch2ID}) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) assertdb.Query(t, rt.DB, `SELECT count(*) FROM contacts_contact WHERE id >= 30000`).Returns(3) assertdb.Query(t, rt.DB, `SELECT count(*) FROM contacts_contact WHERE name = 'Norbert' AND language = 'eng'`).Returns(1) diff --git a/core/tasks/handler/handle_contact_event_test.go b/core/tasks/handler/handle_contact_event_test.go index e422b1b79..2ad2afd30 100644 --- a/core/tasks/handler/handle_contact_event_test.go +++ b/core/tasks/handler/handle_contact_event_test.go @@ -36,7 +36,7 @@ func TestHandleContactEvent(t *testing.T) { NewContact: false, }) - tasksRan := testsuite.FlushTasks(t, rt, nil) + tasksRan := testsuite.FlushTasks(t, rt) assert.Equal(t, map[string]int{"handle_contact_event": 2}, tasksRan) assertdb.Query(t, rt.DB, `SELECT count(*) FROM contacts_contact WHERE id = $1 AND status = 'S'`, testdata.Cathy.ID).Returns(1) diff --git a/core/tasks/interrupts/interrupt_channel_test.go b/core/tasks/interrupts/interrupt_channel_test.go index 209806c6b..343dde7a5 100644 --- a/core/tasks/interrupts/interrupt_channel_test.go +++ b/core/tasks/interrupts/interrupt_channel_test.go @@ -55,7 +55,7 @@ func TestInterruptChannel(t *testing.T) { // queue and perform a task to interrupt the Twilio channel tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &interrupts.InterruptChannelTask{ChannelID: testdata.TwilioChannel.ID}, false) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_msg WHERE status = 'F' and channel_id = $1`, testdata.VonageChannel.ID).Returns(1) assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_msg WHERE status = 'F' and failed_reason = 'R' and channel_id = $1`, testdata.VonageChannel.ID).Returns(0) @@ -83,7 +83,7 @@ func TestInterruptChannel(t *testing.T) { // queue and perform a task to interrupt the Vonage channel tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &interrupts.InterruptChannelTask{ChannelID: testdata.VonageChannel.ID}, false) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_msg WHERE status = 'F' and failed_reason = 'R' and channel_id = $1`, testdata.VonageChannel.ID).Returns(6) assertdb.Query(t, rt.DB, `SELECT count(*) FROM msgs_msg WHERE status = 'F' and channel_id = $1`, testdata.VonageChannel.ID).Returns(7) diff --git a/core/tasks/ivr/cron_test.go b/core/tasks/ivr/cron_test.go index 9b193838a..aa117d75a 100644 --- a/core/tasks/ivr/cron_test.go +++ b/core/tasks/ivr/cron_test.go @@ -40,7 +40,7 @@ func TestRetryCallsCron(t *testing.T) { service.callError = nil service.callID = ivr.CallID("call1") - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) assertdb.Query(t, rt.DB, `SELECT COUNT(*) FROM ivr_call WHERE contact_id = $1 AND status = $2 AND external_id = $3`, testdata.Cathy.ID, models.CallStatusWired, "call1").Returns(1) diff --git a/core/tasks/ivr/start_ivr_flow_batch_test.go b/core/tasks/ivr/start_ivr_flow_batch_test.go index 8a0bd2b10..441b23391 100644 --- a/core/tasks/ivr/start_ivr_flow_batch_test.go +++ b/core/tasks/ivr/start_ivr_flow_batch_test.go @@ -43,7 +43,7 @@ func TestIVR(t *testing.T) { err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) require.NoError(t, err) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) // should have one call in a failed state assertdb.Query(t, rt.DB, `SELECT COUNT(*) FROM ivr_call WHERE contact_id = $1 AND status = $2`, testdata.Cathy.ID, models.CallStatusFailed).Returns(1) @@ -55,7 +55,7 @@ func TestIVR(t *testing.T) { err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) require.NoError(t, err) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) assertdb.Query(t, rt.DB, `SELECT COUNT(*) FROM ivr_call WHERE contact_id = $1 AND status = $2 AND external_id = $3`, testdata.Cathy.ID, models.CallStatusWired, "call1").Returns(1) @@ -66,7 +66,7 @@ func TestIVR(t *testing.T) { err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) require.NoError(t, err) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) assertdb.Query(t, rt.DB, `SELECT COUNT(*) FROM ivr_call WHERE contact_id = $1 AND status = $2 AND next_attempt IS NOT NULL;`, testdata.Cathy.ID, models.CallStatusQueued).Returns(1) } diff --git a/core/tasks/msgs/send_broadcast_test.go b/core/tasks/msgs/send_broadcast_test.go index 6d7b1462a..56a7b2855 100644 --- a/core/tasks/msgs/send_broadcast_test.go +++ b/core/tasks/msgs/send_broadcast_test.go @@ -167,7 +167,7 @@ func TestBroadcastsFromEvents(t *testing.T) { err = tasks.Queue(rc, tc.queue, testdata.Org1.ID, &msgs.SendBroadcastTask{Broadcast: bcast}, false) assert.NoError(t, err) - taskCounts := testsuite.FlushTasks(t, rt, nil) + taskCounts := testsuite.FlushTasks(t, rt) // assert our count of batches assert.Equal(t, tc.expectedBatchCount, taskCounts["send_broadcast_batch"], "%d: unexpected batch count", i) @@ -291,7 +291,7 @@ func TestSendBroadcastTask(t *testing.T) { err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, task, false) assert.NoError(t, err) - taskCounts := testsuite.FlushTasks(t, rt, nil) + taskCounts := testsuite.FlushTasks(t, rt) // assert our count of batches assert.Equal(t, tc.expectedBatches, taskCounts["send_broadcast_batch"], "%d: unexpected batch count", i) diff --git a/core/tasks/starts/start_flow_batch_test.go b/core/tasks/starts/start_flow_batch_test.go index 9089d5274..eb6fb5236 100644 --- a/core/tasks/starts/start_flow_batch_test.go +++ b/core/tasks/starts/start_flow_batch_test.go @@ -36,7 +36,7 @@ func TestStartFlowBatchTask(t *testing.T) { // start the first batch... err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: batch1}, false) assert.NoError(t, err) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowsession WHERE contact_id = ANY($1) AND status = 'C' AND responded = FALSE AND org_id = 1 AND call_id IS NULL AND output IS NOT NULL`, pq.Array([]models.ContactID{testdata.Cathy.ID, testdata.Bob.ID})). @@ -55,7 +55,7 @@ func TestStartFlowBatchTask(t *testing.T) { // start the second and final batch... err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: batch2}, false) assert.NoError(t, err) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun WHERE start_id = $1`, start1.ID).Returns(4) assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowstart WHERE id = $1`, start1.ID).Returns("C") @@ -72,7 +72,7 @@ func TestStartFlowBatchTask(t *testing.T) { // start the first batch... err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: start2Batch1}, false) assert.NoError(t, err) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun WHERE start_id = $1`, start2.ID).Returns(2) @@ -82,7 +82,7 @@ func TestStartFlowBatchTask(t *testing.T) { // start the second batch... err = tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: start2Batch2}, false) assert.NoError(t, err) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) // check that second batch didn't create any runs and start status is still interrupted assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun WHERE start_id = $1`, start2.ID).Returns(2) @@ -105,7 +105,7 @@ func TestStartFlowBatchTaskNonPersistedStart(t *testing.T) { // start the first batch... err := tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowBatchTask{FlowStartBatch: batch}, false) assert.NoError(t, err) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun`).Returns(2) } diff --git a/core/tasks/starts/start_flow_test.go b/core/tasks/starts/start_flow_test.go index 8acf21570..b57576fa2 100644 --- a/core/tasks/starts/start_flow_test.go +++ b/core/tasks/starts/start_flow_test.go @@ -232,7 +232,7 @@ func TestStartFlowTask(t *testing.T) { err = tasks.Queue(rc, tc.queue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) assert.NoError(t, err) - taskCounts := testsuite.FlushTasks(t, rt, nil) + taskCounts := testsuite.FlushTasks(t, rt) // assert our count of batches assert.Equal(t, tc.expectedBatchCount, taskCounts["start_flow_batch"], "%d: unexpected batch count", i) @@ -268,7 +268,7 @@ func TestStartFlowTaskNonPersistedStart(t *testing.T) { err := tasks.Queue(rc, tasks.ThrottledQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) assert.NoError(t, err) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) assertdb.Query(t, rt.DB, `SELECT count(*) FROM flows_flowrun`).Returns(2) } diff --git a/testsuite/tasks.go b/testsuite/tasks.go index 23d542cff..d19fd4b9f 100644 --- a/testsuite/tasks.go +++ b/testsuite/tasks.go @@ -61,7 +61,7 @@ func CurrentTasks(t *testing.T, rt *runtime.Runtime, qname string) map[models.Or return tasks } -func FlushTasks(t *testing.T, rt *runtime.Runtime, qnames []string) map[string]int { +func FlushTasks(t *testing.T, rt *runtime.Runtime, qnames ...string) map[string]int { rc := rt.RP.Get() defer rc.Close() diff --git a/web/ivr/ivr_test.go b/web/ivr/ivr_test.go index a4fea5f29..23d63a056 100644 --- a/web/ivr/ivr_test.go +++ b/web/ivr/ivr_test.go @@ -105,7 +105,7 @@ func TestTwilioIVR(t *testing.T) { err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) require.NoError(t, err) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) // check our 3 contacts have 3 wired calls assertdb.Query(t, rt.DB, `SELECT COUNT(*) FROM ivr_call WHERE contact_id = $1 AND status = $2 AND external_id = $3`, @@ -413,7 +413,7 @@ func TestVonageIVR(t *testing.T) { err = tasks.Queue(rc, tasks.BatchQueue, testdata.Org1.ID, &starts.StartFlowTask{FlowStart: start}, false) require.NoError(t, err) - testsuite.FlushTasks(t, rt, nil) + testsuite.FlushTasks(t, rt) assertdb.Query(t, rt.DB, `SELECT COUNT(*) FROM ivr_call WHERE contact_id = $1 AND status = $2 AND external_id = $3`, testdata.Cathy.ID, models.CallStatusWired, "Call1").Returns(1) From ec06eeb59c383635db5914327deb5b4f4813e794 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 18 Nov 2024 10:51:44 -0500 Subject: [PATCH 147/216] Reduce threshold for using bulk timeout task to 10 --- core/tasks/timeouts/bulk_timeout_test.go | 2 +- core/tasks/timeouts/cron.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/tasks/timeouts/bulk_timeout_test.go b/core/tasks/timeouts/bulk_timeout_test.go index 230b605ea..a2ec71485 100644 --- a/core/tasks/timeouts/bulk_timeout_test.go +++ b/core/tasks/timeouts/bulk_timeout_test.go @@ -25,7 +25,7 @@ func TestBulkTimeout(t *testing.T) { }, }) - assert.Equal(t, map[string]int{"bulk_timeout": 1}, testsuite.FlushTasks(t, rt, []string{"batch", "throttled"})) + assert.Equal(t, map[string]int{"bulk_timeout": 1}, testsuite.FlushTasks(t, rt, "batch", "throttled")) testsuite.AssertContactTasks(t, testdata.Org1, testdata.Cathy, []string{ `{"type":"timeout_event","task":{"session_id":123456,"time":"2024-11-15T13:57:00Z"},"queued_on":"2024-11-15T13:59:00Z"}`, diff --git a/core/tasks/timeouts/cron.go b/core/tasks/timeouts/cron.go index e94e9f670..9a592c091 100644 --- a/core/tasks/timeouts/cron.go +++ b/core/tasks/timeouts/cron.go @@ -15,7 +15,7 @@ import ( ) func init() { - tasks.RegisterCron("sessions_timeouts", NewTimeoutsCron(100, 100)) + tasks.RegisterCron("sessions_timeouts", NewTimeoutsCron(10, 100)) } type timeoutsCron struct { From 0eafabc14f41782d083b4ef601751eabf344a7cb Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 19 Nov 2024 09:35:50 -0500 Subject: [PATCH 148/216] Update CHANGELOG.md for v9.3.48 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cab0918db..c1a7ba365 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.48 (2024-11-19) +------------------------- + * Add new task to handle bulk session timeouts + * Create absraction for fair queues + v9.3.47 (2024-11-13) ------------------------- * Update to match goflow changes to airtime transfers From ee6a39256f9e0961cd3936455df1e5e54b9f9cf3 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 19 Nov 2024 10:22:05 -0500 Subject: [PATCH 149/216] Add task to handle throttled expirations --- core/tasks/expirations/bulk_expire.go | 53 +++++++++ core/tasks/expirations/bulk_expire_test.go | 36 ++++++ core/tasks/expirations/cron.go | 126 +++++++++++---------- core/tasks/expirations/cron_test.go | 55 ++++++--- core/tasks/timeouts/cron.go | 7 +- core/tasks/timeouts/cron_test.go | 4 +- 6 files changed, 200 insertions(+), 81 deletions(-) create mode 100644 core/tasks/expirations/bulk_expire.go create mode 100644 core/tasks/expirations/bulk_expire_test.go diff --git a/core/tasks/expirations/bulk_expire.go b/core/tasks/expirations/bulk_expire.go new file mode 100644 index 000000000..150591795 --- /dev/null +++ b/core/tasks/expirations/bulk_expire.go @@ -0,0 +1,53 @@ +package expirations + +import ( + "context" + "fmt" + "time" + + "github.com/nyaruka/mailroom/core/models" + "github.com/nyaruka/mailroom/core/tasks" + "github.com/nyaruka/mailroom/core/tasks/handler" + "github.com/nyaruka/mailroom/core/tasks/handler/ctasks" + "github.com/nyaruka/mailroom/runtime" +) + +// TypeBulkExpire is the type of the task +const TypeBulkExpire = "bulk_expire" + +func init() { + tasks.RegisterType(TypeBulkExpire, func() tasks.Task { return &BulkExpireTask{} }) +} + +// BulkExpireTask is the payload of the task +type BulkExpireTask struct { + Expirations []*ExpiredWait `json:"expirations"` +} + +func (t *BulkExpireTask) Type() string { + return TypeBulkExpire +} + +// Timeout is the maximum amount of time the task can run for +func (t *BulkExpireTask) Timeout() time.Duration { + return time.Hour +} + +func (t *BulkExpireTask) WithAssets() models.Refresh { + return models.RefreshNone +} + +// Perform creates the actual task +func (t *BulkExpireTask) Perform(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets) error { + rc := rt.RP.Get() + defer rc.Close() + + for _, exp := range t.Expirations { + err := handler.QueueTask(rc, oa.OrgID(), exp.ContactID, ctasks.NewWaitExpiration(exp.SessionID, exp.WaitExpiresOn)) + if err != nil { + return fmt.Errorf("error queuing handle task for expiration on session #%d: %w", exp.SessionID, err) + } + } + + return nil +} diff --git a/core/tasks/expirations/bulk_expire_test.go b/core/tasks/expirations/bulk_expire_test.go new file mode 100644 index 000000000..cf5f58f2d --- /dev/null +++ b/core/tasks/expirations/bulk_expire_test.go @@ -0,0 +1,36 @@ +package expirations_test + +import ( + "testing" + "time" + + "github.com/nyaruka/gocommon/dates" + "github.com/nyaruka/mailroom/core/tasks/expirations" + "github.com/nyaruka/mailroom/testsuite" + "github.com/nyaruka/mailroom/testsuite/testdata" + "github.com/stretchr/testify/assert" +) + +func TestBulkExpire(t *testing.T) { + _, rt := testsuite.Runtime() + defer testsuite.Reset(testsuite.ResetRedis) + + defer dates.SetNowFunc(time.Now) + dates.SetNowFunc(dates.NewFixedNow(time.Date(2024, 11, 15, 13, 59, 0, 0, time.UTC))) + + testsuite.QueueBatchTask(t, rt, testdata.Org1, &expirations.BulkExpireTask{ + Expirations: []*expirations.ExpiredWait{ + {SessionID: 123456, ContactID: testdata.Cathy.ID, WaitExpiresOn: time.Date(2024, 11, 15, 13, 57, 0, 0, time.UTC)}, + {SessionID: 234567, ContactID: testdata.Bob.ID, WaitExpiresOn: time.Date(2024, 11, 15, 13, 58, 0, 0, time.UTC)}, + }, + }) + + assert.Equal(t, map[string]int{"bulk_expire": 1}, testsuite.FlushTasks(t, rt, "batch", "throttled")) + + testsuite.AssertContactTasks(t, testdata.Org1, testdata.Cathy, []string{ + `{"type":"expiration_event","task":{"session_id":123456,"time":"2024-11-15T13:57:00Z"},"queued_on":"2024-11-15T13:59:00Z"}`, + }) + testsuite.AssertContactTasks(t, testdata.Org1, testdata.Bob, []string{ + `{"type":"expiration_event","task":{"session_id":234567,"time":"2024-11-15T13:58:00Z"},"queued_on":"2024-11-15T13:59:00Z"}`, + }) +} diff --git a/core/tasks/expirations/cron.go b/core/tasks/expirations/cron.go index 7421630cb..139fd20b2 100644 --- a/core/tasks/expirations/cron.go +++ b/core/tasks/expirations/cron.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log/slog" + "slices" "time" "github.com/nyaruka/mailroom/core/ivr" @@ -15,22 +16,22 @@ import ( "github.com/nyaruka/redisx" ) -const ( - expireBatchSize = 500 -) - func init() { - tasks.RegisterCron("run_expirations", NewExpirationsCron()) + tasks.RegisterCron("run_expirations", NewExpirationsCron(10, 100)) tasks.RegisterCron("expire_ivr_calls", &VoiceExpirationsCron{}) } type ExpirationsCron struct { - marker *redisx.IntervalSet + marker *redisx.IntervalSet + bulkThreshold int // use bulk task for any org with this or more expirations + bulkBatchSize int // number of expirations to queue in a single bulk task } -func NewExpirationsCron() *ExpirationsCron { +func NewExpirationsCron(bulkThreshold, bulkBatchSize int) *ExpirationsCron { return &ExpirationsCron{ - marker: redisx.NewIntervalSet("run_expirations", time.Hour*24, 2), + marker: redisx.NewIntervalSet("run_expirations", time.Hour*24, 2), + bulkThreshold: bulkThreshold, + bulkBatchSize: bulkBatchSize, } } @@ -45,50 +46,42 @@ func (c *ExpirationsCron) AllInstances() bool { // handles waiting messaging sessions whose waits have expired, resuming those that can be resumed, // and expiring those that can't func (c *ExpirationsCron) Run(ctx context.Context, rt *runtime.Runtime) (map[string]any, error) { - rc := rt.RP.Get() - defer rc.Close() - - // we expire sessions that can't be resumed in batches - expiredSessions := make([]models.SessionID, 0, expireBatchSize) - - // select messaging sessions with expired waits rows, err := rt.DB.QueryxContext(ctx, sqlSelectExpiredWaits) if err != nil { - return nil, fmt.Errorf("error querying for expired waits: %w", err) + return nil, fmt.Errorf("error querying sessions with expired waits: %w", err) } defer rows.Close() - numExpired, numDupes, numQueued := 0, 0, 0 + taskID := func(w *ExpiredWait) string { + return fmt.Sprintf("%d:%s", w.SessionID, w.WaitExpiresOn.Format(time.RFC3339)) + } + + // scan and organize by org + byOrg := make(map[models.OrgID][]*ExpiredWait, 50) + + // the sessions that can't be resumed and will be exited + toExit := make([]models.SessionID, 0, 100) + + rc := rt.RP.Get() + defer rc.Close() + + numDupes, numQueuedHandler, numQueuedBulk, numExited := 0, 0, 0, 0 for rows.Next() { expiredWait := &ExpiredWait{} - err := rows.StructScan(expiredWait) - if err != nil { + if err := rows.StructScan(expiredWait); err != nil { return nil, fmt.Errorf("error scanning expired wait: %w", err) } - // if it can't be resumed, add to batch to be expired if !expiredWait.WaitResumes { - expiredSessions = append(expiredSessions, expiredWait.SessionID) - - // batch is full? commit it - if len(expiredSessions) == expireBatchSize { - err = models.ExitSessions(ctx, rt.DB, expiredSessions, models.SessionStatusExpired) - if err != nil { - return nil, fmt.Errorf("error expiring batch of sessions: %w", err) - } - expiredSessions = expiredSessions[:0] - } - - numExpired++ + toExit = append(toExit, expiredWait.SessionID) continue } - // create a contact task to resume this session - taskID := fmt.Sprintf("%d:%s", expiredWait.SessionID, expiredWait.WaitExpiresOn.Format(time.RFC3339)) - queued, err := c.marker.IsMember(rc, taskID) + // check whether we've already queued this + queued, err := c.marker.IsMember(rc, taskID(expiredWait)) if err != nil { - return nil, fmt.Errorf("error checking whether expiration is queued: %w", err) + return nil, fmt.Errorf("error checking whether expiration is already queued: %w", err) } // already queued? move on @@ -97,30 +90,47 @@ func (c *ExpirationsCron) Run(ctx context.Context, rt *runtime.Runtime) (map[str continue } - // ok, queue this task - err = handler.QueueTask(rc, expiredWait.OrgID, expiredWait.ContactID, ctasks.NewWaitExpiration(expiredWait.SessionID, expiredWait.WaitExpiresOn)) - if err != nil { - return nil, fmt.Errorf("error adding new expiration task: %w", err) - } + byOrg[expiredWait.OrgID] = append(byOrg[expiredWait.OrgID], expiredWait) + } - // and mark it as queued - err = c.marker.Add(rc, taskID) - if err != nil { - return nil, fmt.Errorf("error marking expiration task as queued: %w", err) - } + for orgID, expirations := range byOrg { + throttle := len(expirations) >= c.bulkThreshold + + for batch := range slices.Chunk(expirations, c.bulkBatchSize) { + if throttle { + if err := tasks.Queue(rc, tasks.ThrottledQueue, orgID, &BulkExpireTask{Expirations: batch}, true); err != nil { + return nil, fmt.Errorf("error queuing bulk expiration task to throttle queue: %w", err) + } + numQueuedBulk += len(batch) + } + + for _, exp := range batch { + if !throttle { + err := handler.QueueTask(rc, orgID, exp.ContactID, ctasks.NewWaitExpiration(exp.SessionID, exp.WaitExpiresOn)) + if err != nil { + return nil, fmt.Errorf("error queuing expiration task to handler queue: %w", err) + } + numQueuedHandler++ + } - numQueued++ + // mark as queued + if err = c.marker.Add(rc, taskID(exp)); err != nil { + return nil, fmt.Errorf("error marking expiration task as queued: %w", err) + } + } + } } - // commit any stragglers - if len(expiredSessions) > 0 { - err = models.ExitSessions(ctx, rt.DB, expiredSessions, models.SessionStatusExpired) + // exit the sessions that can't be resumed + for batch := range slices.Chunk(toExit, 500) { + err = models.ExitSessions(ctx, rt.DB, batch, models.SessionStatusExpired) if err != nil { - return nil, fmt.Errorf("error expiring runs and sessions: %w", err) + return nil, fmt.Errorf("error exiting expired sessions: %w", err) } + numExited += len(batch) } - return map[string]any{"expired": numExpired, "dupes": numDupes, "queued": numQueued}, nil + return map[string]any{"exited": numExited, "dupes": numDupes, "queued_handler": numQueuedHandler, "queued_bulk": numQueuedBulk}, nil } const sqlSelectExpiredWaits = ` @@ -131,11 +141,11 @@ const sqlSelectExpiredWaits = ` LIMIT 25000` type ExpiredWait struct { - SessionID models.SessionID `db:"session_id"` - OrgID models.OrgID `db:"org_id"` - WaitExpiresOn time.Time `db:"wait_expires_on"` - WaitResumes bool `db:"wait_resume_on_expire"` - ContactID models.ContactID `db:"contact_id"` + SessionID models.SessionID `db:"session_id" json:"session_id"` + OrgID models.OrgID `db:"org_id" json:"-"` + WaitExpiresOn time.Time `db:"wait_expires_on" json:"wait_expires_on"` + WaitResumes bool `db:"wait_resume_on_expire" json:"-"` + ContactID models.ContactID `db:"contact_id" json:"contact_id"` } type VoiceExpirationsCron struct{} @@ -158,7 +168,7 @@ func (c *VoiceExpirationsCron) Run(ctx context.Context, rt *runtime.Runtime) (ma // select voice sessions with expired waits rows, err := rt.DB.QueryxContext(ctx, sqlSelectExpiredVoiceWaits) if err != nil { - return nil, fmt.Errorf("error querying for expired waits: %w", err) + return nil, fmt.Errorf("error querying voice sessions with expired waits: %w", err) } defer rows.Close() diff --git a/core/tasks/expirations/cron_test.go b/core/tasks/expirations/cron_test.go index ffc0c84df..aa20233f8 100644 --- a/core/tasks/expirations/cron_test.go +++ b/core/tasks/expirations/cron_test.go @@ -1,12 +1,15 @@ package expirations_test import ( + "fmt" "testing" "time" "github.com/nyaruka/gocommon/dbutil/assertdb" "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/jsonx" + "github.com/nyaruka/gocommon/uuids" + "github.com/nyaruka/goflow/flows" _ "github.com/nyaruka/mailroom/core/handlers" "github.com/nyaruka/mailroom/core/models" "github.com/nyaruka/mailroom/core/tasks" @@ -50,13 +53,17 @@ func TestExpirations(t *testing.T) { r6ID := testdata.InsertFlowRun(rt, testdata.Org1, s5ID, blake, testdata.Favorites, models.RunStatusActive, "") r7ID := testdata.InsertFlowRun(rt, testdata.Org1, s5ID, blake, testdata.Favorites, models.RunStatusWaiting, "") - time.Sleep(5 * time.Millisecond) + // for other org create 6 waiting sessions that will expire + for i := range 6 { + c := testdata.InsertContact(rt, testdata.Org2, flows.ContactUUID(uuids.NewV4()), fmt.Sprint(i), i18n.NilLanguage, models.ContactStatusActive) + testdata.InsertWaitingSession(rt, testdata.Org2, c, models.FlowTypeMessaging, testdata.Favorites, models.NilCallID, time.Now(), time.Now(), true, nil) + } // expire our sessions... - cron := expirations.NewExpirationsCron() + cron := expirations.NewExpirationsCron(3, 5) res, err := cron.Run(ctx, rt) assert.NoError(t, err) - assert.Equal(t, map[string]any{"dupes": 0, "expired": 1, "queued": 2}, res) + assert.Equal(t, map[string]any{"exited": 1, "dupes": 0, "queued_bulk": 6, "queued_handler": 2}, res) // Cathy's session should be expired along with its runs assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowsession WHERE id = $1;`, s1ID).Columns(map[string]any{"status": "X"}) @@ -80,29 +87,43 @@ func TestExpirations(t *testing.T) { assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowrun WHERE id = $1;`, r6ID).Columns(map[string]any{"status": "A"}) assertdb.Query(t, rt.DB, `SELECT status FROM flows_flowrun WHERE id = $1;`, r7ID).Columns(map[string]any{"status": "W"}) - // should have created two expiration tasks - task, err := tasks.HandlerQueue.Pop(rc) + // should have created two handler tasks for org 1 + task1, err := tasks.HandlerQueue.Pop(rc) + assert.NoError(t, err) + assert.Equal(t, int(testdata.Org1.ID), task1.OwnerID) + assert.Equal(t, "handle_contact_event", task1.Type) + task2, err := tasks.HandlerQueue.Pop(rc) assert.NoError(t, err) - assert.NotNil(t, task) + assert.Equal(t, int(testdata.Org1.ID), task2.OwnerID) + assert.Equal(t, "handle_contact_event", task2.Type) - // check the first task + // decode the tasks to check contacts eventTask := &handler.HandleContactEventTask{} - jsonx.MustUnmarshal(task.Task, eventTask) + jsonx.MustUnmarshal(task1.Task, eventTask) assert.Equal(t, testdata.George.ID, eventTask.ContactID) - - task, err = tasks.HandlerQueue.Pop(rc) - assert.NoError(t, err) - assert.NotNil(t, task) - - // check the second task eventTask = &handler.HandleContactEventTask{} - jsonx.MustUnmarshal(task.Task, eventTask) + jsonx.MustUnmarshal(task2.Task, eventTask) assert.Equal(t, blake.ID, eventTask.ContactID) - // no other tasks - task, err = tasks.HandlerQueue.Pop(rc) + // no other + task, err := tasks.HandlerQueue.Pop(rc) assert.NoError(t, err) assert.Nil(t, task) + + // should have created two throttled bulk tasks for org 2 + task3, err := tasks.ThrottledQueue.Pop(rc) + assert.NoError(t, err) + assert.Equal(t, int(testdata.Org2.ID), task3.OwnerID) + assert.Equal(t, "bulk_expire", task3.Type) + task4, err := tasks.ThrottledQueue.Pop(rc) + assert.NoError(t, err) + assert.Equal(t, int(testdata.Org2.ID), task4.OwnerID) + assert.Equal(t, "bulk_expire", task4.Type) + + // if task runs again, these tasks won't be re-queued + res, err = cron.Run(ctx, rt) + assert.NoError(t, err) + assert.Equal(t, map[string]any{"exited": 0, "dupes": 8, "queued_handler": 0, "queued_bulk": 0}, res) } func TestExpireVoiceSessions(t *testing.T) { diff --git a/core/tasks/timeouts/cron.go b/core/tasks/timeouts/cron.go index 9a592c091..39bf066a0 100644 --- a/core/tasks/timeouts/cron.go +++ b/core/tasks/timeouts/cron.go @@ -41,10 +41,9 @@ func (c *timeoutsCron) AllInstances() bool { } func (c *timeoutsCron) Run(ctx context.Context, rt *runtime.Runtime) (map[string]any, error) { - // find all sessions that need to be expired (we exclude IVR runs) rows, err := rt.DB.QueryxContext(ctx, sqlSelectTimedoutSessions) if err != nil { - return nil, fmt.Errorf("error selecting timed out sessions: %w", err) + return nil, fmt.Errorf("error querying sessions with timed out waits: %w", err) } defer rows.Close() @@ -67,7 +66,7 @@ func (c *timeoutsCron) Run(ctx context.Context, rt *runtime.Runtime) (map[string // check whether we've already queued this queued, err := c.marker.IsMember(rc, taskID(timeout)) if err != nil { - return nil, fmt.Errorf("error checking whether task is queued: %w", err) + return nil, fmt.Errorf("error checking whether timeout is already queued: %w", err) } // already queued? move on @@ -92,7 +91,7 @@ func (c *timeoutsCron) Run(ctx context.Context, rt *runtime.Runtime) (map[string for _, timeout := range batch { if !throttle { - err := handler.QueueTask(rc, timeout.OrgID, timeout.ContactID, ctasks.NewWaitTimeout(timeout.SessionID, timeout.TimeoutOn)) + err := handler.QueueTask(rc, orgID, timeout.ContactID, ctasks.NewWaitTimeout(timeout.SessionID, timeout.TimeoutOn)) if err != nil { return nil, fmt.Errorf("error queuing timeout task to handler queue: %w", err) } diff --git a/core/tasks/timeouts/cron_test.go b/core/tasks/timeouts/cron_test.go index dd7df4148..92066ac55 100644 --- a/core/tasks/timeouts/cron_test.go +++ b/core/tasks/timeouts/cron_test.go @@ -55,8 +55,8 @@ func TestTimeouts(t *testing.T) { assert.Equal(t, "handle_contact_event", task1.Type) task2, err := tasks.HandlerQueue.Pop(rc) assert.NoError(t, err) - assert.Equal(t, int(testdata.Org1.ID), task1.OwnerID) - assert.Equal(t, "handle_contact_event", task1.Type) + assert.Equal(t, int(testdata.Org1.ID), task2.OwnerID) + assert.Equal(t, "handle_contact_event", task2.Type) // decode the tasks to check contacts eventTask := &handler.HandleContactEventTask{} From 533057129df4f1800c18c6f727995a43e17452e5 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 19 Nov 2024 10:30:46 -0500 Subject: [PATCH 150/216] Update deps --- go.mod | 106 +++++++++++++-------------- go.sum | 222 +++++++++++++++++++++++++++++---------------------------- 2 files changed, 165 insertions(+), 163 deletions(-) diff --git a/go.mod b/go.mod index 9b8f283db..d325b0ca0 100644 --- a/go.mod +++ b/go.mod @@ -6,15 +6,15 @@ require ( firebase.google.com/go/v4 v4.15.0 github.com/Masterminds/semver v1.5.0 github.com/appleboy/go-fcm v1.2.1 - github.com/aws/aws-sdk-go-v2 v1.32.4 - github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.15 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.5 - github.com/aws/aws-sdk-go-v2/service/s3 v1.66.3 + github.com/aws/aws-sdk-go-v2 v1.32.5 + github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.17 + github.com/aws/aws-sdk-go-v2/service/dynamodb v1.37.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1 github.com/buger/jsonparser v1.1.1 - github.com/elastic/go-elasticsearch/v8 v8.15.0 + github.com/elastic/go-elasticsearch/v8 v8.16.0 github.com/getsentry/sentry-go v0.29.1 github.com/go-chi/chi/v5 v5.1.0 - github.com/go-playground/validator/v10 v10.22.1 + github.com/go-playground/validator/v10 v10.23.0 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang/protobuf v1.5.4 github.com/gomodule/redigo v1.9.2 @@ -34,51 +34,51 @@ require ( github.com/samber/slog-sentry v1.2.2 github.com/shopspring/decimal v1.4.0 github.com/stretchr/testify v1.9.0 - google.golang.org/api v0.205.0 + google.golang.org/api v0.206.0 ) require ( - cel.dev/expr v0.16.1 // indirect + cel.dev/expr v0.18.0 // indirect cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/auth v0.10.1 // indirect + cloud.google.com/go/auth v0.10.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.5 // indirect cloud.google.com/go/compute/metadata v0.5.2 // indirect cloud.google.com/go/firestore v1.17.0 // indirect - cloud.google.com/go/iam v1.2.1 // indirect - cloud.google.com/go/longrunning v0.6.1 // indirect - cloud.google.com/go/monitoring v1.21.1 // indirect - cloud.google.com/go/storage v1.44.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.1 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect + cloud.google.com/go/iam v1.2.2 // indirect + cloud.google.com/go/longrunning v0.6.2 // indirect + cloud.google.com/go/monitoring v1.21.2 // indirect + cloud.google.com/go/storage v1.47.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect github.com/MicahParks/keyfunc v1.9.0 // indirect github.com/Shopify/gomail v0.0.0-20220729171026-0784ece65e69 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 // indirect - github.com/aws/aws-sdk-go-v2/config v1.28.3 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.44 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.19 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.23 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.23 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect + github.com/aws/aws-sdk-go-v2/config v1.28.5 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.46 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.23 // indirect - github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.24.5 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.32.4 // indirect - github.com/aws/smithy-go v1.22.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 // indirect + github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 // indirect + github.com/aws/smithy-go v1.22.1 // indirect github.com/blevesearch/segment v0.9.1 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/elastic/elastic-transport-go/v8 v8.6.0 // indirect - github.com/envoyproxy/go-control-plane v0.13.0 // indirect + github.com/envoyproxy/go-control-plane v0.13.1 // indirect github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -87,12 +87,12 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/s2a-go v0.1.8 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect - github.com/googleapis/gax-go/v2 v2.13.0 // indirect + github.com/googleapis/gax-go/v2 v2.14.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect @@ -101,35 +101,35 @@ require ( github.com/naoina/toml v0.1.1 // indirect github.com/nyaruka/librato v1.1.1 // indirect github.com/nyaruka/null/v2 v2.0.3 // indirect - github.com/nyaruka/phonenumbers v1.4.1 // indirect + github.com/nyaruka/phonenumbers v1.4.2 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/samber/lo v1.47.0 // indirect github.com/sergi/go-diff v1.3.1 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.29.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect - go.opentelemetry.io/otel v1.31.0 // indirect - go.opentelemetry.io/otel/metric v1.31.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.31.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.32.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect + go.opentelemetry.io/otel v1.32.0 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/otel/sdk v1.32.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect + go.opentelemetry.io/otel/trace v1.32.0 // indirect golang.org/x/crypto v0.29.0 // indirect golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/net v0.31.0 // indirect - golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/sync v0.9.0 // indirect golang.org/x/sys v0.27.0 // indirect golang.org/x/text v0.20.0 // indirect - golang.org/x/time v0.7.0 // indirect + golang.org/x/time v0.8.0 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect - google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect - google.golang.org/grpc v1.67.1 // indirect - google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a // indirect - google.golang.org/protobuf v1.35.1 // indirect + google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/grpc v1.68.0 // indirect + google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3 // indirect + google.golang.org/protobuf v1.35.2 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 59f5f2cc7..e9861a398 100644 --- a/go.sum +++ b/go.sum @@ -1,41 +1,41 @@ -cel.dev/expr v0.16.1 h1:NR0+oFYzR1CqLFhTAqg3ql59G9VfN8fKq1TCHJ6gq1g= -cel.dev/expr v0.16.1/go.mod h1:AsGA5zb3WruAEQeQng1RZdGEXmBj0jvMWh6l5SnNuC8= +cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo= +cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= -cloud.google.com/go/auth v0.10.1 h1:TnK46qldSfHWt2a0b/hciaiVJsmDXWy9FqyUan0uYiI= -cloud.google.com/go/auth v0.10.1/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth v0.10.2 h1:oKF7rgBfSHdp/kuhXtqU/tNDr0mZqhYbEh+6SiqzkKo= +cloud.google.com/go/auth v0.10.2/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= cloud.google.com/go/auth/oauth2adapt v0.2.5 h1:2p29+dePqsCHPP1bqDJcKj4qxRyYCcbzKpFyKGt3MTk= cloud.google.com/go/auth/oauth2adapt v0.2.5/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= cloud.google.com/go/firestore v1.17.0 h1:iEd1LBbkDZTFsLw3sTH50eyg4qe8eoG6CjocmEXO9aQ= cloud.google.com/go/firestore v1.17.0/go.mod h1:69uPx1papBsY8ZETooc71fOhoKkD70Q1DwMrtKuOT/Y= -cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU= -cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g= +cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= +cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk= cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= -cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc= -cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= -cloud.google.com/go/monitoring v1.21.1 h1:zWtbIoBMnU5LP9A/fz8LmWMGHpk4skdfeiaa66QdFGc= -cloud.google.com/go/monitoring v1.21.1/go.mod h1:Rj++LKrlht9uBi8+Eb530dIrzG/cU/lB8mt+lbeFK1c= -cloud.google.com/go/storage v1.44.0 h1:abBzXf4UJKMmQ04xxJf9dYM/fNl24KHoTuBjyJDX2AI= -cloud.google.com/go/storage v1.44.0/go.mod h1:wpPblkIuMP5jCB/E48Pz9zIo2S/zD8g+ITmxKkPCITE= -cloud.google.com/go/trace v1.11.1 h1:UNqdP+HYYtnm6lb91aNA5JQ0X14GnxkABGlfz2PzPew= -cloud.google.com/go/trace v1.11.1/go.mod h1:IQKNQuBzH72EGaXEodKlNJrWykGZxet2zgjtS60OtjA= +cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= +cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU= +cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= +cloud.google.com/go/storage v1.47.0 h1:ajqgt30fnOMmLfWfu1PWcb+V9Dxz6n+9WKjdNg5R4HM= +cloud.google.com/go/storage v1.47.0/go.mod h1:Ks0vP374w0PW6jOUameJbapbQKXqkjGd/OJRp2fb9IQ= +cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI= +cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= firebase.google.com/go/v4 v4.15.0 h1:k27M+cHbyN1YpBI2Cf4NSjeHnnYRB9ldXwpqA5KikN0= firebase.google.com/go/v4 v4.15.0/go.mod h1:S/4MJqVZn1robtXkHhpRUbwOC4gdYtgsiMMJQ4x+xmQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.1 h1:pB2F2JKCj1Znmp2rwxxt1J0Fg0wezTMgWYk5Mpbi1kg= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.1/go.mod h1:itPGVDKf9cC/ov4MdvJ2QZ0khw4bfoo9jzwTJlaxy2k= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1 h1:oTX4vsorBZo/Zdum6OKPA4o7544hm6smoRv1QjpTwGo= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 h1:o90wcURuxekmXrtxmYWTyNla0+ZEHhud6DI1ZTxd1vI= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0/go.mod h1:6fTWu4m3jocfUZLYF5KsZC1TUfRvEjs7lM4crme/irw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.49.0 h1:jJKWl98inONJAr/IZrdFQUWcwUO95DLY1XMD1ZIut+g= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.49.0/go.mod h1:l2fIqmwB+FKSfvn3bAD/0i+AXAxhIZjTK2svT/mgUXs= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 h1:GYUJLfvd++4DMuMhCFLgLXvFwofIxh/qOwoGuS/LTew= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0/go.mod h1:wRbFgBQUVm1YXrvWKofAEmq9HNJTDphbAaJSSX01KUI= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o= @@ -46,50 +46,50 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/appleboy/go-fcm v1.2.1 h1:NhpACabtRuAplYg6bTNfSr3LBwsSuutP55HsphzLU/g= github.com/appleboy/go-fcm v1.2.1/go.mod h1:5FzMN+9J2sxnkoys9h3y48GQH8HnI637Q/ro/uP2Qsk= -github.com/aws/aws-sdk-go-v2 v1.32.4 h1:S13INUiTxgrPueTmrm5DZ+MiAo99zYzHEFh1UNkOxNE= -github.com/aws/aws-sdk-go-v2 v1.32.4/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 h1:pT3hpW0cOHRJx8Y0DfJUEQuqPild8jRGmSFmBgvydr0= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6/go.mod h1:j/I2++U0xX+cr44QjHay4Cvxj6FUbnxrgmqN3H1jTZA= -github.com/aws/aws-sdk-go-v2/config v1.28.3 h1:kL5uAptPcPKaJ4q0sDUjUIdueO18Q7JDzl64GpVwdOM= -github.com/aws/aws-sdk-go-v2/config v1.28.3/go.mod h1:SPEn1KA8YbgQnwiJ/OISU4fz7+F6Fe309Jf0QTsRCl4= -github.com/aws/aws-sdk-go-v2/credentials v1.17.44 h1:qqfs5kulLUHUEXlHEZXLJkgGoF3kkUeFUTVA585cFpU= -github.com/aws/aws-sdk-go-v2/credentials v1.17.44/go.mod h1:0Lm2YJ8etJdEdw23s+q/9wTpOeo2HhNE97XcRa7T8MA= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.15 h1:2HXPu4MCUKVA/hU0g2DWtYgXjVPsj7Ujd+xif/Yl2fc= -github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.15/go.mod h1:fqQI+CG2FX4yVDJORf6QAKLRw16yO+JcB6io1iubcm0= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.19 h1:woXadbf0c7enQ2UGCi8gW/WuKmE0xIzxBF/eD94jMKQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.19/go.mod h1:zminj5ucw7w0r65bP6nhyOd3xL6veAUMc3ElGMoLVb4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.23 h1:A2w6m6Tmr+BNXjDsr7M90zkWjsu4JXHwrzPg235STs4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.23/go.mod h1:35EVp9wyeANdujZruvHiQUAo9E3vbhnIO1mTCAxMlY0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.23 h1:pgYW9FCabt2M25MoHYCfMrVY2ghiiBKYWUVXfwZs+sU= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.23/go.mod h1:c48kLgzO19wAu3CPkDWC28JbaJ+hfQlsdl7I2+oqIbk= +github.com/aws/aws-sdk-go-v2 v1.32.5 h1:U8vdWJuY7ruAkzaOdD7guwJjD06YSKmnKCJs7s3IkIo= +github.com/aws/aws-sdk-go-v2 v1.32.5/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= +github.com/aws/aws-sdk-go-v2/config v1.28.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0= +github.com/aws/aws-sdk-go-v2/config v1.28.5/go.mod h1:4VsPbHP8JdcdUDmbTVgNL/8w9SqOkM5jyY8ljIxLO3o= +github.com/aws/aws-sdk-go-v2/credentials v1.17.46 h1:AU7RcriIo2lXjUfHFnFKYsLCwgbz1E7Mm95ieIRDNUg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.46/go.mod h1:1FmYyLGL08KQXQ6mcTlifyFXfJVCNJTVGuQP4m0d/UA= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.17 h1:36xxDfD/hD9cMBjANIBSr+kZ0/+IYKHql4KPGN/DvM4= +github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.17/go.mod h1:A4XQVRy4yJ70Sk5Qz2tuCQX6J5kXcRa53nGP6wtgntM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 h1:sDSXIrlsFSFJtWKLQS4PUWRvrT580rrnuLydJrCQ/yA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20/go.mod h1:WZ/c+w0ofps+/OUqMwWgnfrgzZH1DZO1RIkktICsqnY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 h1:4usbeaes3yJnCFC7kfeyhkdkPtoRYPa/hTmCqMpKpLI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24/go.mod h1:5CI1JemjVwde8m2WG3cz23qHKPOxbpkq0HaoreEgLIY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 h1:N1zsICrQglfzaBnrfM0Ys00860C+QFwu6u/5+LomP+o= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24/go.mod h1:dCn9HbJ8+K31i8IQ8EWmWj0EiIk0+vKiHNMxTTYveAg= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.23 h1:1SZBDiRzzs3sNhOMVApyWPduWYGAX0imGy06XiBnCAM= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.23/go.mod h1:i9TkxgbZmHVh2S0La6CAXtnyFhlCX/pJ0JsOvBAS6Mk= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.5 h1:VWun/99wjelZZ+d0DGeSrffiCBJhC481geypGc6rfn0= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.5/go.mod h1:P+1rrWglInpWvnBpN0pH8jIIhkLkBaolkRVG4X9Kous= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.5 h1:pc8+YeYe6bBe8D3QeBz9/S5kUZ9k9yoBMbljGIBMNK4= -github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.5/go.mod h1:R09/8/9eLYHJ50PQ8FlIGjZb3XA2t2XhcI5E5332eCI= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.4 h1:aaPpoG15S2qHkWm4KlEyF01zovK1nW4BBbyXuHNSE90= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.4/go.mod h1:eD9gS2EARTKgGr/W5xwgY/ik9z/zqpW+m/xOQbVxrMk= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.4 h1:rWKH6IiWDRIxmsTJUB/wEY+EIPp+P3C78Vidl+HXp6w= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.4/go.mod h1:MzOAfuiNZ6asjVrA+dNvXl5lI2nmzXakSpDFLOcOyJ4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.4 h1:tHxQi/XHPK0ctd/wdOw0t7Xrc2OxcRCnVzv8lwWPu0c= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.4/go.mod h1:4GQbF1vJzG60poZqWatZlhP31y8PGCCVTvIGPdaaYJ0= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.4 h1:E5ZAVOmI2apR8ADb72Q63KqwwwdW1XcMeXIlrZ1Psjg= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.4/go.mod h1:wezzqVUOVVdk+2Z/JzQT4NxAU0NbhRe5W8pIE72jsWI= -github.com/aws/aws-sdk-go-v2/service/s3 v1.66.3 h1:neNOYJl72bHrz9ikAEED4VqWyND/Po0DnEx64RW6YM4= -github.com/aws/aws-sdk-go-v2/service/s3 v1.66.3/go.mod h1:TMhLIyRIyoGVlaEMAt+ITMbwskSTpcGsCPDq91/ihY0= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.5 h1:HJwZwRt2Z2Tdec+m+fPjvdmkq2s9Ra+VR0hjF7V2o40= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.5/go.mod h1:wrMCEwjFPms+V86TCQQeOxQF/If4vT44FGIOFiMC2ck= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.4 h1:zcx9LiGWZ6i6pjdcoE9oXAB6mUdeyC36Ia/QEiIvYdg= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.4/go.mod h1:Tp/ly1cTjRLGBBmNccFumbZ8oqpZlpdhFf80SrRh4is= -github.com/aws/aws-sdk-go-v2/service/sts v1.32.4 h1:yDxvkz3/uOKfxnv8YhzOi9m+2OGIxF+on3KOISbK5IU= -github.com/aws/aws-sdk-go-v2/service/sts v1.32.4/go.mod h1:9XEUty5v5UAsMiFOBJrNibZgwCeOma73jgGwwhgffa8= -github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= -github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 h1:JX70yGKLj25+lMC5Yyh8wBtvB01GDilyRuJvXJ4piD0= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24/go.mod h1:+Ln60j9SUTD0LEwnhEB0Xhg61DHqplBrbZpLgyjoEHg= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.37.1 h1:vucMirlM6D+RDU8ncKaSZ/5dGrXNajozVwpmWNPn2gQ= +github.com/aws/aws-sdk-go-v2/service/dynamodb v1.37.1/go.mod h1:fceORfs010mNxZbQhfqUjUeHlTwANmIT4mvHamuUaUg= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.6 h1:hIl7Z1zcfdzsl5SiV32acFj4gY/cZ5Xr9wd6PpoNYGE= +github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.6/go.mod h1:VswWf/9ztSHHnMP3SMtGqrFOooVXI6NTDNjTcyLQ2HY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 h1:gvZOjQKPxFXy1ft3QnEyXmT+IqneM9QAUWlM3r0mfqw= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5/go.mod h1:DLWnfvIcm9IET/mmjdxeXbBKmTCm0ZB8p1za9BVteM8= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.5 h1:3Y457U2eGukmjYjeHG6kanZpDzJADa2m0ADqnuePYVQ= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.5/go.mod h1:CfwEHGkTjYZpkQ/5PvcbEtT7AJlG68KkEvmtwU8z3/U= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 h1:wtpJ4zcwrSbwhECWQoI/g6WM9zqCcSpHDJIWSbMLOu4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5/go.mod h1:qu/W9HXQbbQ4+1+JcZp0ZNPV31ym537ZJN+fiS7Ti8E= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 h1:P1doBzv5VEg1ONxnJss1Kh5ZG/ewoIE4MQtKKc6Crgg= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5/go.mod h1:NOP+euMW7W3Ukt28tAxPuoWao4rhhqJD3QEBk7oCg7w= +github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1 h1:LXLnDfjT/P6SPIaCE86xCOjJROPn4FNB2EdN68vMK5c= +github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1/go.mod h1:ralv4XawHjEMaHOWnTFushl0WRqim/gQWesAMF6hTow= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 h1:3zu537oLmsPfDMyjnUS2g+F2vITgy5pB74tHI+JBNoM= +github.com/aws/aws-sdk-go-v2/service/sso v1.24.6/go.mod h1:WJSZH2ZvepM6t6jwu4w/Z45Eoi75lPN7DcydSRtJg6Y= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 h1:K0OQAsDywb0ltlFrZm0JHPY3yZp/S9OaoLU33S7vPS8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5/go.mod h1:ORITg+fyuMoeiQFiVGoqB3OydVTLkClw/ljbblMq6Cc= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 h1:6SZUVRQNvExYlMLbHdlKB48x0fLbc2iVROyaNEwBHbU= +github.com/aws/aws-sdk-go-v2/service/sts v1.33.1/go.mod h1:GqWyYCwLXnlUB1lOAXQyNSPqPLQJvmo8J0DWBzp9mtg= +github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= +github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU= github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= @@ -108,13 +108,13 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/elastic/elastic-transport-go/v8 v8.6.0 h1:Y2S/FBjx1LlCv5m6pWAF2kDJAHoSjSRSJCApolgfthA= github.com/elastic/elastic-transport-go/v8 v8.6.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk= -github.com/elastic/go-elasticsearch/v8 v8.15.0 h1:IZyJhe7t7WI3NEFdcHnf6IJXqpRf+8S8QWLtZYYyBYk= -github.com/elastic/go-elasticsearch/v8 v8.15.0/go.mod h1:HCON3zj4btpqs2N1jjsAy4a/fiAul+YBP00mBH4xik8= +github.com/elastic/go-elasticsearch/v8 v8.16.0 h1:f7bR+iBz8GTAVhwyFO3hm4ixsz2eMaEy0QroYnXV3jE= +github.com/elastic/go-elasticsearch/v8 v8.16.0/go.mod h1:lGMlgKIbYoRvay3xWBeKahAiJOgmFDsjZC39nmO3H64= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.13.0 h1:HzkeUz1Knt+3bK+8LG1bxOO/jzWZmdxpwC51i202les= -github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnvMg4d7nvT/wl9WgVXn3Q8= +github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE= +github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= @@ -141,15 +141,15 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= -github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= +github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -187,8 +187,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= -github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= -github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o= +github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= @@ -232,8 +232,8 @@ github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw github.com/nyaruka/null/v2 v2.0.3/go.mod h1:OCVeCkCXwrg5/qE6RU0c1oUVZBy+ZDrT+xYg1XSaIWA= github.com/nyaruka/null/v3 v3.0.0 h1:JvOiNuKmRBFHxzZFt4sWii+ewmMkCQ1vO7X0clTNn6E= github.com/nyaruka/null/v3 v3.0.0/go.mod h1:Sus286RmC8P0VihFuQDDQPib/xJQ7++TsaPLdRuwgVc= -github.com/nyaruka/phonenumbers v1.4.1 h1:dNsiYGirahC2lMRz3p2dxmmyLbzD3arCgmj/hPEVRPY= -github.com/nyaruka/phonenumbers v1.4.1/go.mod h1:gv+CtldaFz+G3vHHnasBSirAi3O2XLqZzVWz4V1pl2E= +github.com/nyaruka/phonenumbers v1.4.2 h1:V791/B74Sb5i/X7Od2AVKA0BkvcNaInf7DWykPS2YSU= +github.com/nyaruka/phonenumbers v1.4.2/go.mod h1:gv+CtldaFz+G3vHHnasBSirAi3O2XLqZzVWz4V1pl2E= github.com/nyaruka/redisx v0.8.1 h1:d9Hc8nfSKTSEU+bx+YrB13d6bzAgiiHygk4jg/Q4nb4= github.com/nyaruka/redisx v0.8.1/go.mod h1:2TUmkDvprPInnmInR5AEbCm0zRRewkvSDVLsO+Do6iI= github.com/nyaruka/rp-indexer/v9 v9.2.1 h1:gQa0QHiU+LjhmgpToHpoGRKRC8oI1EdW4dDaN9inhSk= @@ -277,22 +277,24 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/detectors/gcp v1.29.0 h1:TiaiXB4DpGD3sdzNlYQxruQngn5Apwzi1X0DRhuGvDQ= -go.opentelemetry.io/contrib/detectors/gcp v1.29.0/go.mod h1:GW2aWZNwR2ZxDLdv8OyC2G8zkRoQBuURgV7RPQgcPoU= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 h1:yMkBS9yViCc7U7yeLzJPM2XizlfdVvBRSmsQDWu6qc0= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0/go.mod h1:n8MR6/liuGB5EmTETUBeU5ZgqMOlqKRxUaqPQBOANZ8= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= -go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= -go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= -go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= -go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= -go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= -go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= -go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.opentelemetry.io/contrib/detectors/gcp v1.32.0 h1:P78qWqkLSShicHmAzfECaTgvslqHxblNE9j62Ws1NK8= +go.opentelemetry.io/contrib/detectors/gcp v1.32.0/go.mod h1:TVqo0Sda4Cv8gCIixd7LuLwW4EylumVWfhjZJjDD4DU= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0 h1:qtFISDHKolvIxzSs0gIaiPUPR0Cucb0F2coHC7ZLdps= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0/go.mod h1:Y+Pop1Q6hCOnETWTW4NROK/q1hv50hM7yDaUTjG8lp8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -317,8 +319,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= +golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -343,8 +345,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -354,8 +356,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.205.0 h1:LFaxkAIpDb/GsrWV20dMMo5MR0h8UARTbn24LmD+0Pg= -google.golang.org/api v0.205.0/go.mod h1:NrK1EMqO8Xk6l6QwRAmrXXg2v6dzukhlOyvkYtnvUuc= +google.golang.org/api v0.206.0 h1:A27GClesCSheW5P2BymVHjpEeQ2XHH8DI8Srs2HI2L8= +google.golang.org/api v0.206.0/go.mod h1:BtB8bfjTYIrai3d8UyvPmV9REGgox7coh+ZRwm0b+W8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw= @@ -363,21 +365,21 @@ google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOskjbcSMcF2jiYMNiQYZ0c2KEJLKKU= -google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4= -google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U= -google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= +google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 h1:pgr/4QbFyktUv9CtQ/Fq4gzEE6/Xs7iCXbktaGzLHbQ= +google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a h1:UIpYSuWdWHSzjwcAFRLjKcPXFZVVLXGEM23W+NWqipw= -google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a/go.mod h1:9i1T9n4ZinTUZGgzENMi8MDDgbGC5mqTS75JAv6xN3A= +google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3 h1:hUfOButuEtpc0UvYiaYRbNwxVYr0mQQOWq6X8beJ9Gc= +google.golang.org/grpc/stats/opentelemetry v0.0.0-20241028142157-ada6787961b3/go.mod h1:jzYlkSMbKypzuu6xoAEijsNVo9ZeDF1u/zCfFgsx7jg= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -389,8 +391,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From c4bf51884033bf383db16112f79f018a158d84c7 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 20 Nov 2024 10:35:18 -0500 Subject: [PATCH 151/216] Update CHANGELOG.md for v9.3.49 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1a7ba365..750dbe04d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.49 (2024-11-20) +------------------------- + * Update deps + * Add task to handle throttled expirations + v9.3.48 (2024-11-19) ------------------------- * Add new task to handle bulk session timeouts From 5d84f13ef6947ed719bcbcc703cfe4a1e0c8214c Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 20 Nov 2024 14:49:43 -0500 Subject: [PATCH 152/216] Only set start_id on first run of a session --- core/handlers/base_test.go | 4 +-- core/ivr/ivr.go | 2 +- core/models/runs.go | 1 + core/models/sessions.go | 15 +++++--- core/models/sessions_test.go | 14 +++++--- core/runner/runner.go | 39 +++++---------------- core/runner/runner_test.go | 4 +-- core/tasks/campaigns/fire_campaign_event.go | 2 +- core/tasks/handler/ctasks/channel_event.go | 2 +- core/tasks/handler/ctasks/msg_event.go | 2 +- core/tasks/handler/ctasks/ticket_closed.go | 2 +- 11 files changed, 39 insertions(+), 48 deletions(-) diff --git a/core/handlers/base_test.go b/core/handlers/base_test.go index 67eea177c..e5817fe48 100644 --- a/core/handlers/base_test.go +++ b/core/handlers/base_test.go @@ -203,7 +203,7 @@ func RunTestCases(t *testing.T, ctx context.Context, rt *runtime.Runtime, tcs [] } for _, c := range []*testdata.Contact{testdata.Cathy, testdata.Bob, testdata.George, testdata.Alexandria} { - _, err := runner.StartFlow(ctx, rt, oa, flow.(*models.Flow), []models.ContactID{c.ID}, options) + _, err := runner.StartFlow(ctx, rt, oa, flow.(*models.Flow), []models.ContactID{c.ID}, options, models.NilStartID) require.NoError(t, err) } @@ -278,7 +278,7 @@ func RunFlowAndApplyEvents(t *testing.T, ctx context.Context, rt *runtime.Runtim tx, err := rt.DB.BeginTxx(ctx, nil) require.NoError(t, err) - session, err := models.NewSession(ctx, tx, oa, fs, sprint) + session, err := models.NewSession(ctx, tx, oa, fs, sprint, models.NilStartID) require.NoError(t, err) err = tx.Commit() diff --git a/core/ivr/ivr.go b/core/ivr/ivr.go index 25970b579..2e3a232b0 100644 --- a/core/ivr/ivr.go +++ b/core/ivr/ivr.go @@ -381,7 +381,7 @@ func StartIVRFlow( } // start our flow - sessions, err := runner.StartFlowForContacts(ctx, rt, oa, flow, []*models.Contact{c}, []flows.Trigger{trigger}, hook, true) + sessions, err := runner.StartFlowForContacts(ctx, rt, oa, flow, []*models.Contact{c}, []flows.Trigger{trigger}, hook, true, models.NilStartID) if err != nil { return fmt.Errorf("error starting flow: %w", err) } diff --git a/core/models/runs.go b/core/models/runs.go index b9b025793..87b65dc31 100644 --- a/core/models/runs.go +++ b/core/models/runs.go @@ -64,6 +64,7 @@ type FlowRun struct { func (r *FlowRun) SetSessionID(sessionID SessionID) { r.r.SessionID = sessionID } func (r *FlowRun) SetStartID(startID StartID) { r.r.StartID = startID } +func (r *FlowRun) StartID() StartID { return r.r.StartID } func (r *FlowRun) UUID() flows.RunUUID { return r.r.UUID } func (r *FlowRun) ModifiedOn() time.Time { return r.r.ModifiedOn } diff --git a/core/models/sessions.go b/core/models/sessions.go index 227f2ead8..77f6b906f 100644 --- a/core/models/sessions.go +++ b/core/models/sessions.go @@ -490,7 +490,7 @@ func (s *Session) UnmarshalJSON(b []byte) error { // NewSession a session objects from the passed in flow session. It does NOT // commit said session to the database. -func NewSession(ctx context.Context, tx *sqlx.Tx, oa *OrgAssets, fs flows.Session, sprint flows.Sprint) (*Session, error) { +func NewSession(ctx context.Context, tx *sqlx.Tx, oa *OrgAssets, fs flows.Session, sprint flows.Sprint, startID StartID) (*Session, error) { output, err := json.Marshal(fs) if err != nil { return nil, fmt.Errorf("error marshalling flow session: %w", err) @@ -542,12 +542,17 @@ func NewSession(ctx context.Context, tx *sqlx.Tx, oa *OrgAssets, fs flows.Sessio session.findStep = fs.FindStep // now build up our runs - for _, r := range fs.Runs() { + for i, r := range fs.Runs() { run, err := newRun(ctx, tx, oa, session, r) if err != nil { return nil, fmt.Errorf("error creating run: %s: %w", r.UUID(), err) } + // set start id if first run of session + if i == 0 && startID != NilStartID { + run.SetStartID(startID) + } + // save the run to our session session.runs = append(session.runs, run) @@ -593,7 +598,7 @@ RETURNING id` // InsertSessions writes the passed in session to our database, writes any runs that need to be created // as well as appying any events created in the session -func InsertSessions(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, oa *OrgAssets, ss []flows.Session, sprints []flows.Sprint, contacts []*Contact, hook SessionCommitHook) ([]*Session, error) { +func InsertSessions(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, oa *OrgAssets, ss []flows.Session, sprints []flows.Sprint, contacts []*Contact, hook SessionCommitHook, startID StartID) ([]*Session, error) { if len(ss) == 0 { return nil, nil } @@ -605,7 +610,7 @@ func InsertSessions(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, oa *O completedCallIDs := make([]CallID, 0, 1) for i, s := range ss { - session, err := NewSession(ctx, tx, oa, s, sprints[i]) + session, err := NewSession(ctx, tx, oa, s, sprints[i], startID) if err != nil { return nil, fmt.Errorf("error creating session objects: %w", err) } @@ -716,7 +721,7 @@ func InsertSessions(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, oa *O // gather all our pre commit events, group them by hook err = ApplyEventPreCommitHooks(ctx, rt, tx, oa, scenes) if err != nil { - return nil, fmt.Errorf("error applying session pre commit hook: %T: %w", hook, err) + return nil, fmt.Errorf("error applying session pre commit hooks: %w", err) } // return our session diff --git a/core/models/sessions_test.go b/core/models/sessions_test.go index 71dcd2f80..a9626f924 100644 --- a/core/models/sessions_test.go +++ b/core/models/sessions_test.go @@ -42,7 +42,7 @@ func TestSessionCreationAndUpdating(t *testing.T) { return nil } - modelSessions, err := models.InsertSessions(ctx, rt, tx, oa, []flows.Session{flowSession}, []flows.Sprint{sprint1}, []*models.Contact{modelContact}, hook) + modelSessions, err := models.InsertSessions(ctx, rt, tx, oa, []flows.Session{flowSession}, []flows.Sprint{sprint1}, []*models.Contact{modelContact}, hook, models.NilStartID) require.NoError(t, err) assert.Equal(t, 1, hookCalls) @@ -153,7 +153,7 @@ func TestSingleSprintSession(t *testing.T) { return nil } - modelSessions, err := models.InsertSessions(ctx, rt, tx, oa, []flows.Session{flowSession}, []flows.Sprint{sprint1}, []*models.Contact{modelContact}, hook) + modelSessions, err := models.InsertSessions(ctx, rt, tx, oa, []flows.Session{flowSession}, []flows.Sprint{sprint1}, []*models.Contact{modelContact}, hook, models.NilStartID) require.NoError(t, err) assert.Equal(t, 1, hookCalls) @@ -193,6 +193,8 @@ func TestSessionWithSubflows(t *testing.T) { sa, flowSession, sprint1 := test.NewSessionBuilder().WithAssets(oa.SessionAssets()).WithFlow(parent.UUID). WithContact(testdata.Cathy.UUID, flows.ContactID(testdata.Cathy.ID), "Cathy", "eng", "").MustBuild() + startID := testdata.InsertFlowStart(rt, testdata.Org1, testdata.Admin, parent, []*testdata.Contact{testdata.Cathy}) + tx := rt.DB.MustBegin() hookCalls := 0 @@ -201,7 +203,7 @@ func TestSessionWithSubflows(t *testing.T) { return nil } - modelSessions, err := models.InsertSessions(ctx, rt, tx, oa, []flows.Session{flowSession}, []flows.Sprint{sprint1}, []*models.Contact{modelContact}, hook) + modelSessions, err := models.InsertSessions(ctx, rt, tx, oa, []flows.Session{flowSession}, []flows.Sprint{sprint1}, []*models.Contact{modelContact}, hook, startID) require.NoError(t, err) assert.Equal(t, 1, hookCalls) @@ -221,6 +223,10 @@ func TestSessionWithSubflows(t *testing.T) { assert.True(t, session.WaitResumeOnExpire()) // because we have a parent assert.Nil(t, session.Timeout()) + require.Len(t, session.Runs(), 2) + assert.Equal(t, startID, session.Runs()[0].StartID()) + assert.Equal(t, models.NilStartID, session.Runs()[1].StartID()) + // check that matches what is in the db assertdb.Query(t, rt.DB, `SELECT status, session_type, current_flow_id, responded, ended_on, wait_resume_on_expire FROM flows_flowsession`). Columns(map[string]any{ @@ -274,7 +280,7 @@ func TestSessionFailedStart(t *testing.T) { return nil } - modelSessions, err := models.InsertSessions(ctx, rt, tx, oa, []flows.Session{flowSession}, []flows.Sprint{sprint1}, []*models.Contact{modelContact}, hook) + modelSessions, err := models.InsertSessions(ctx, rt, tx, oa, []flows.Session{flowSession}, []flows.Sprint{sprint1}, []*models.Contact{modelContact}, hook, models.NilStartID) require.NoError(t, err) assert.Equal(t, 1, hookCalls) diff --git a/core/runner/runner.go b/core/runner/runner.go index 0d9113a12..7e9215d0d 100644 --- a/core/runner/runner.go +++ b/core/runner/runner.go @@ -9,9 +9,6 @@ import ( "slices" "time" - "github.com/gomodule/redigo/redis" - "github.com/jmoiron/sqlx" - "github.com/nyaruka/gocommon/analytics" "github.com/nyaruka/goflow/excellent/types" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/flows/triggers" @@ -182,27 +179,12 @@ func StartFlowBatch(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAsse return tb.WithUser(flowUser).WithOrigin(startTypeToOrigin[start.StartType]).Build() } - // if we have a persisted start, we need to set its id on runs before they are saved - var commitHook models.SessionCommitHook - if start.ID != models.NilStartID { - commitHook = func(ctx context.Context, tx *sqlx.Tx, rp *redis.Pool, oa *models.OrgAssets, sessions []*models.Session) error { - // for each run in our sessions, set the start id - for _, s := range sessions { - for _, r := range s.Runs() { - r.SetStartID(batch.StartID) - } - } - return nil - } - } - options := &StartOptions{ Interrupt: flow.FlowType().Interrupts(), TriggerBuilder: triggerBuilder, - CommitHook: commitHook, } - sessions, err := StartFlow(ctx, rt, oa, flow, batch.ContactIDs, options) + sessions, err := StartFlow(ctx, rt, oa, flow, batch.ContactIDs, options, batch.StartID) if err != nil { return nil, fmt.Errorf("error starting flow batch: %w", err) } @@ -211,7 +193,7 @@ func StartFlowBatch(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAsse } // StartFlow runs the passed in flow for the passed in contacts -func StartFlow(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets, flow *models.Flow, contactIDs []models.ContactID, options *StartOptions) ([]*models.Session, error) { +func StartFlow(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets, flow *models.Flow, contactIDs []models.ContactID, options *StartOptions, startID models.StartID) ([]*models.Session, error) { if len(contactIDs) == 0 { return nil, nil } @@ -228,7 +210,7 @@ func StartFlow(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets, f return sessions, ctx.Err() } - ss, skipped, err := tryToStartWithLock(ctx, rt, oa, flow, remaining, options) + ss, skipped, err := tryToStartWithLock(ctx, rt, oa, flow, remaining, options, startID) if err != nil { return nil, err } @@ -246,7 +228,7 @@ func StartFlow(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets, f // tries to start the given contacts, returning sessions for those we could, and the ids that were skipped because we // couldn't get their locks -func tryToStartWithLock(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets, flow *models.Flow, ids []models.ContactID, options *StartOptions) ([]*models.Session, []models.ContactID, error) { +func tryToStartWithLock(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets, flow *models.Flow, ids []models.ContactID, options *StartOptions, startID models.StartID) ([]*models.Session, []models.ContactID, error) { // try to get locks for these contacts, waiting for up to a second for each contact locks, skipped, err := models.LockContacts(ctx, rt, oa.OrgID(), ids, time.Second) if err != nil { @@ -274,7 +256,7 @@ func tryToStartWithLock(ctx context.Context, rt *runtime.Runtime, oa *models.Org triggers = append(triggers, trigger) } - ss, err := StartFlowForContacts(ctx, rt, oa, flow, contacts, triggers, options.CommitHook, options.Interrupt) + ss, err := StartFlowForContacts(ctx, rt, oa, flow, contacts, triggers, options.CommitHook, options.Interrupt, startID) if err != nil { return nil, nil, fmt.Errorf("error starting flow for contacts: %w", err) } @@ -285,7 +267,7 @@ func tryToStartWithLock(ctx context.Context, rt *runtime.Runtime, oa *models.Org // StartFlowForContacts runs the passed in flow for the passed in contact func StartFlowForContacts( ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets, - flow *models.Flow, contacts []*models.Contact, triggers []flows.Trigger, hook models.SessionCommitHook, interrupt bool) ([]*models.Session, error) { + flow *models.Flow, contacts []*models.Contact, triggers []flows.Trigger, hook models.SessionCommitHook, interrupt bool, startID models.StartID) ([]*models.Session, error) { sa := oa.SessionAssets() // no triggers? nothing to do @@ -301,17 +283,14 @@ func StartFlowForContacts( sprints := make([]flows.Sprint, 0, len(triggers)) for _, trigger := range triggers { - // start our flow session log := log.With("contact_uuid", trigger.Contact().UUID()) - start := time.Now() session, sprint, err := goflow.Engine(rt).NewSession(sa, trigger) if err != nil { log.Error("error starting flow", "error", err) continue } - log.Info("flow engine start", "elapsed", time.Since(start)) - analytics.Gauge("mr.flow_start_elapsed", float64(time.Since(start))) + log.Debug("new flow session") sessions = append(sessions, session) sprints = append(sprints, sprint) @@ -346,7 +325,7 @@ func StartFlowForContacts( } // write our session to the db - dbSessions, err := models.InsertSessions(txCTX, rt, tx, oa, sessions, sprints, contacts, hook) + dbSessions, err := models.InsertSessions(txCTX, rt, tx, oa, sessions, sprints, contacts, hook, startID) if err == nil { // commit it at once commitStart := time.Now() @@ -387,7 +366,7 @@ func StartFlowForContacts( } } - dbSession, err := models.InsertSessions(txCTX, rt, tx, oa, []flows.Session{session}, []flows.Sprint{sprint}, []*models.Contact{contact}, hook) + dbSession, err := models.InsertSessions(txCTX, rt, tx, oa, []flows.Session{session}, []flows.Sprint{sprint}, []*models.Contact{contact}, hook, startID) if err != nil { tx.Rollback() log.Error("error writing session to db", "error", err, "contact_uuid", session.Contact().UUID()) diff --git a/core/runner/runner_test.go b/core/runner/runner_test.go index 5e6afacb4..850a654d4 100644 --- a/core/runner/runner_test.go +++ b/core/runner/runner_test.go @@ -89,7 +89,7 @@ func TestResume(t *testing.T) { modelContact, flowContact, _ := testdata.Cathy.Load(rt, oa) trigger := triggers.NewBuilder(oa.Env(), flow.Reference(), flowContact).Manual().Build() - sessions, err := runner.StartFlowForContacts(ctx, rt, oa, flow, []*models.Contact{modelContact}, []flows.Trigger{trigger}, nil, true) + sessions, err := runner.StartFlowForContacts(ctx, rt, oa, flow, []*models.Contact{modelContact}, []flows.Trigger{trigger}, nil, true, models.NilStartID) assert.NoError(t, err) assert.NotNil(t, sessions) @@ -178,7 +178,7 @@ func TestStartFlowConcurrency(t *testing.T) { // start each contact in the flow at the same time... test.RunConcurrently(len(contacts), func(i int) { - sessions, err := runner.StartFlow(ctx, rt, oa, dbFlow, []models.ContactID{contacts[i].ID}, options) + sessions, err := runner.StartFlow(ctx, rt, oa, dbFlow, []models.ContactID{contacts[i].ID}, options, models.NilStartID) assert.NoError(t, err) assert.Equal(t, 1, len(sessions)) }) diff --git a/core/tasks/campaigns/fire_campaign_event.go b/core/tasks/campaigns/fire_campaign_event.go index 7716690f5..da1ff365d 100644 --- a/core/tasks/campaigns/fire_campaign_event.go +++ b/core/tasks/campaigns/fire_campaign_event.go @@ -227,7 +227,7 @@ func FireCampaignEvents(ctx context.Context, rt *runtime.Runtime, oa *models.Org CommitHook: markFired, } - _, err = runner.StartFlow(ctx, rt, oa, dbFlow, slices.Collect(maps.Keys(firesToFire)), options) + _, err = runner.StartFlow(ctx, rt, oa, dbFlow, slices.Collect(maps.Keys(firesToFire)), options, models.NilStartID) if err != nil { slog.Error("error starting flow for campaign event", "error", err, "event", eventUUID) } diff --git a/core/tasks/handler/ctasks/channel_event.go b/core/tasks/handler/ctasks/channel_event.go index 60ae8f22c..500c241d8 100644 --- a/core/tasks/handler/ctasks/channel_event.go +++ b/core/tasks/handler/ctasks/channel_event.go @@ -192,7 +192,7 @@ func (t *ChannelEventTask) handle(ctx context.Context, rt *runtime.Runtime, oa * } } - sessions, err := runner.StartFlowForContacts(ctx, rt, oa, flow, []*models.Contact{contact}, []flows.Trigger{trig}, hook, flow.FlowType().Interrupts()) + sessions, err := runner.StartFlowForContacts(ctx, rt, oa, flow, []*models.Contact{contact}, []flows.Trigger{trig}, hook, flow.FlowType().Interrupts(), models.NilStartID) if err != nil { return nil, fmt.Errorf("error starting flow for contact: %w", err) } diff --git a/core/tasks/handler/ctasks/msg_event.go b/core/tasks/handler/ctasks/msg_event.go index a60a019e3..151d04c72 100644 --- a/core/tasks/handler/ctasks/msg_event.go +++ b/core/tasks/handler/ctasks/msg_event.go @@ -198,7 +198,7 @@ func (t *MsgEventTask) Perform(ctx context.Context, rt *runtime.Runtime, oa *mod // otherwise build the trigger and start the flow directly trigger := tb.Build() - _, err = runner.StartFlowForContacts(ctx, rt, oa, flow, []*models.Contact{contact}, []flows.Trigger{trigger}, flowMsgHook, flow.FlowType().Interrupts()) + _, err = runner.StartFlowForContacts(ctx, rt, oa, flow, []*models.Contact{contact}, []flows.Trigger{trigger}, flowMsgHook, flow.FlowType().Interrupts(), models.NilStartID) if err != nil { return fmt.Errorf("error starting flow for contact: %w", err) } diff --git a/core/tasks/handler/ctasks/ticket_closed.go b/core/tasks/handler/ctasks/ticket_closed.go index 63b07fcac..c15c4609f 100644 --- a/core/tasks/handler/ctasks/ticket_closed.go +++ b/core/tasks/handler/ctasks/ticket_closed.go @@ -87,7 +87,7 @@ func (t *TicketClosedTask) Perform(ctx context.Context, rt *runtime.Runtime, oa Ticket(ticket, triggers.TicketEventTypeClosed). Build() - _, err = runner.StartFlowForContacts(ctx, rt, oa, flow, []*models.Contact{contact}, []flows.Trigger{flowTrigger}, nil, flow.FlowType().Interrupts()) + _, err = runner.StartFlowForContacts(ctx, rt, oa, flow, []*models.Contact{contact}, []flows.Trigger{flowTrigger}, nil, flow.FlowType().Interrupts(), models.NilStartID) if err != nil { return fmt.Errorf("error starting flow for contact: %w", err) } From 68082708f6ba6fadbe6ee1ecc27c2895543aab00 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 20 Nov 2024 15:06:35 -0500 Subject: [PATCH 153/216] Simplify FlowRun struct --- core/models/runs.go | 83 ++++++++++++++---------------------- core/models/sessions.go | 24 +++++------ core/models/sessions_test.go | 4 +- 3 files changed, 46 insertions(+), 65 deletions(-) diff --git a/core/models/runs.go b/core/models/runs.go index 87b65dc31..df3d33707 100644 --- a/core/models/runs.go +++ b/core/models/runs.go @@ -3,7 +3,6 @@ package models import ( "context" "database/sql" - "encoding/json" "fmt" "time" @@ -40,44 +39,26 @@ var runStatusMap = map[flows.RunStatus]RunStatus{ // FlowRun is the mailroom type for a FlowRun type FlowRun struct { - r struct { - ID FlowRunID `db:"id"` - UUID flows.RunUUID `db:"uuid"` - Status RunStatus `db:"status"` - CreatedOn time.Time `db:"created_on"` - ModifiedOn time.Time `db:"modified_on"` - ExitedOn *time.Time `db:"exited_on"` - Responded bool `db:"responded"` - Results string `db:"results"` - Path string `db:"path"` - CurrentNodeUUID null.String `db:"current_node_uuid"` - ContactID flows.ContactID `db:"contact_id"` - FlowID FlowID `db:"flow_id"` - OrgID OrgID `db:"org_id"` - SessionID SessionID `db:"session_id"` - StartID StartID `db:"start_id"` - } + ID FlowRunID `db:"id"` + UUID flows.RunUUID `db:"uuid"` + Status RunStatus `db:"status"` + CreatedOn time.Time `db:"created_on"` + ModifiedOn time.Time `db:"modified_on"` + ExitedOn *time.Time `db:"exited_on"` + Responded bool `db:"responded"` + Results string `db:"results"` + Path string `db:"path"` + CurrentNodeUUID null.String `db:"current_node_uuid"` + ContactID flows.ContactID `db:"contact_id"` + FlowID FlowID `db:"flow_id"` + OrgID OrgID `db:"org_id"` + SessionID SessionID `db:"session_id"` + StartID StartID `db:"start_id"` // we keep a reference to the engine's run run flows.Run } -func (r *FlowRun) SetSessionID(sessionID SessionID) { r.r.SessionID = sessionID } -func (r *FlowRun) SetStartID(startID StartID) { r.r.StartID = startID } -func (r *FlowRun) StartID() StartID { return r.r.StartID } -func (r *FlowRun) UUID() flows.RunUUID { return r.r.UUID } -func (r *FlowRun) ModifiedOn() time.Time { return r.r.ModifiedOn } - -// MarshalJSON is our custom marshaller so that our inner struct get output -func (r *FlowRun) MarshalJSON() ([]byte, error) { - return json.Marshal(r.r) -} - -// UnmarshalJSON is our custom marshaller so that our inner struct get output -func (r *FlowRun) UnmarshalJSON(b []byte) error { - return json.Unmarshal(b, &r.r) -} - // Step represents a single step in a run, this struct is used for serialization to the steps type Step struct { UUID flows.StepUUID `json:"uuid"` @@ -112,26 +93,26 @@ func newRun(ctx context.Context, tx *sqlx.Tx, oa *OrgAssets, session *Session, f return nil, fmt.Errorf("unable to load flow with uuid: %s: %w", fr.FlowReference().UUID, err) } - // create our run - run := &FlowRun{} - r := &run.r - r.UUID = fr.UUID() - r.Status = runStatusMap[fr.Status()] - r.CreatedOn = fr.CreatedOn() - r.ExitedOn = fr.ExitedOn() - r.ModifiedOn = fr.ModifiedOn() - r.ContactID = fr.Contact().ID() - r.FlowID = flowID - r.SessionID = session.ID() - r.StartID = NilStartID - r.OrgID = oa.OrgID() - r.Path = string(jsonx.MustMarshal(path)) - r.Results = string(jsonx.MustMarshal(fr.Results())) + r := &FlowRun{ + UUID: fr.UUID(), + Status: runStatusMap[fr.Status()], + CreatedOn: fr.CreatedOn(), + ExitedOn: fr.ExitedOn(), + ModifiedOn: fr.ModifiedOn(), + ContactID: fr.Contact().ID(), + FlowID: flowID, + OrgID: oa.OrgID(), + SessionID: session.ID(), + StartID: NilStartID, + Path: string(jsonx.MustMarshal(path)), + Results: string(jsonx.MustMarshal(fr.Results())), + + run: fr, + } if len(path) > 0 { r.CurrentNodeUUID = null.String(path[len(path)-1].NodeUUID) } - run.run = fr // mark ourselves as responded if we received a message for _, e := range fr.Events() { @@ -141,7 +122,7 @@ func newRun(ctx context.Context, tx *sqlx.Tx, oa *OrgAssets, session *Session, f } } - return run, nil + return r, nil } // GetContactIDsAtNode returns the ids of contacts currently waiting or active at the given flow node diff --git a/core/models/sessions.go b/core/models/sessions.go index 77f6b906f..122dcbef2 100644 --- a/core/models/sessions.go +++ b/core/models/sessions.go @@ -399,17 +399,18 @@ func (s *Session) Update(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, } // figure out which runs are new and which are updated - updatedRuns := make([]any, 0, 1) - newRuns := make([]any, 0) + updatedRuns := make([]*FlowRun, 0, 1) + newRuns := make([]*FlowRun, 0) + for _, r := range s.Runs() { - modified, found := s.seenRuns[r.UUID()] + modified, found := s.seenRuns[r.UUID] if !found { - newRuns = append(newRuns, &r.r) + newRuns = append(newRuns, r) continue } - if r.ModifiedOn().After(modified) { - updatedRuns = append(updatedRuns, &r.r) + if r.ModifiedOn.After(modified) { + updatedRuns = append(updatedRuns, r) continue } } @@ -550,7 +551,7 @@ func NewSession(ctx context.Context, tx *sqlx.Tx, oa *OrgAssets, fs flows.Sessio // set start id if first run of session if i == 0 && startID != NilStartID { - run.SetStartID(startID) + run.StartID = startID } // save the run to our session @@ -677,14 +678,13 @@ func InsertSessions(ctx context.Context, rt *runtime.Runtime, tx *sqlx.Tx, oa *O return nil, fmt.Errorf("error inserting waiting sessions: %w", err) } - // for each session associate our run with each - runs := make([]any, 0, len(sessions)) + // gather all runs across all sessions + runs := make([]*FlowRun, 0, len(sessions)) for _, s := range sessions { for _, r := range s.runs { - runs = append(runs, &r.r) + r.SessionID = s.ID() // set our session id now that it is written - // set our session id now that it is written - r.SetSessionID(s.ID()) + runs = append(runs, r) } } diff --git a/core/models/sessions_test.go b/core/models/sessions_test.go index a9626f924..5824d530f 100644 --- a/core/models/sessions_test.go +++ b/core/models/sessions_test.go @@ -224,8 +224,8 @@ func TestSessionWithSubflows(t *testing.T) { assert.Nil(t, session.Timeout()) require.Len(t, session.Runs(), 2) - assert.Equal(t, startID, session.Runs()[0].StartID()) - assert.Equal(t, models.NilStartID, session.Runs()[1].StartID()) + assert.Equal(t, startID, session.Runs()[0].StartID) + assert.Equal(t, models.NilStartID, session.Runs()[1].StartID) // check that matches what is in the db assertdb.Query(t, rt.DB, `SELECT status, session_type, current_flow_id, responded, ended_on, wait_resume_on_expire FROM flows_flowsession`). From 2985e50182c0921c7f3278d84fc0e3b645118a57 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 20 Nov 2024 15:24:03 -0500 Subject: [PATCH 154/216] Update CHANGELOG.md for v9.3.50 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 750dbe04d..5725502f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v9.3.50 (2024-11-20) +------------------------- + * Simplify FlowRun struct + * Only set start_id on first run of a session + v9.3.49 (2024-11-20) ------------------------- * Update deps From a922853b2953c0d3e5b55ede39253c3e9c1c9582 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 21 Nov 2024 10:55:05 -0500 Subject: [PATCH 155/216] Update test database --- testsuite/testfiles/postgres.dump | Bin 1769897 -> 1773208 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/testsuite/testfiles/postgres.dump b/testsuite/testfiles/postgres.dump index 549e1ef1a26fef9499e799354ea140f507eaef55..977b82b208aed2d4c5430d9c4f692ab55d2aa138 100644 GIT binary patch delta 86021 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 delta 83921 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%0D5utNIV%0D3 zgi;t}I66P?;UZmY?7N;P10xFpm^3r1ut#5r)_XS9RIzDjKc|zXn3S?zA?3i@7 z#wyUZx!JYzeDRxfr|~EL#Hso0->hbOQsi$!?K~X<4Qd)iuzJo&E-gZJa9=7#Zuqq< z3z2h^7_fAkjPX^DI>b)!noOD)r%3CnXm+00)*QbEsCAs$O+V6Hd^1#Wg|vZO#x)60rFx zcToG8>m79pN`KKNu;z@!j^yzTu4N0R_NT+HR@;D+#Ol%-Kq`P=lY}Umvu+wHmi^h# zy2FX1Q9k#l4*UjX;%g?VzMUBMH%^tFzh^@=eh(F& zP?0a*<*7N?Fy5)PGBEh5q_STPatk!Xe}oo~f2k{}z2LYIz?v`iibs}fYzJaL12VYv z5%pX_)KvFf6r!Lo7y_A+3x^vlc!CftUoN;sfvp?sP^?-KlLb~IBs}XW?Ha9NPy+lJ z!Yk%onvCsjEQbSxa~axh3iJx^k4%$Z1de-{!c;Z*f3mx(trEF@_%?ft^X-&npX~-5k7Q>%Nf>d zKlyW))H@mj0acd#5dQko8lz6|6~8?ZDl0gfArx6=ElkkKcGLW*C@-%* z@Ueye@t~1r9O0*l{O13(R{5W7cg5{HPhTcer`5 zkH6D8kUiCU`m6d>fP2oVVOn22iiy~*`yjUyw=`LV>D5)l$1lD95Vv$ZX&0F-}E%|vHv*ut33XevX9S8#^$5(fM6U~A6k|R2hToZPX01p>K=Rv zbK~TJUS>;WZc|PXWWyoXpJE1fRvAssBZRG`m1Ays&JE*EivdEc>C6;FSiNgpvzm z2b4BIkC@k@AAYG0M)kXW5If1Iw2GOGLEKrFyF?c1Xokh!COWH^o5G8#6f5~wiQxrm zAbp^OV~XWU=YhrQ#egOEvO0hM>4(^QB%YOdLZk3e6!#=aQ__d?1#TQ8HO}ce!p#E% z?K8efX$Ak@K!OMk!RsQ$B3pO$l&iVc?1Vxfcx<`hSqAB;>1o1gS1uisfuyXxT#lN&=A11uY>=2>eV3zF~Ex)?Nhg_SNjaZFm5T8^@CFtOBIQp zZhdoW-Jzw};J!{V2nwfA&IsL<)Qz5iDlt?sr7iZ|xh7cl#3#I~_Zj(4BL(8v#=s?~ z8>F4dOse0~wU?5#wn;4U9BfJA=Di*<<57kI6TB$^D`V2>Te`?WLH5*akCIcP{;(C% z9g>lCCY^+Ik9yUDwMSS-%~|(9LHb*AVQ~2Xfb)Na0LK4gg;)Pw_YAMxAOkYQ_8RBV zqs6b6JB|i?3je^w;aFqKB88-GE6qYo!0w0Sc0s`V5Cl4G8^jPNDu;(oX+b+$O&oVE8}U+4+C% z*bOjzJ)T$jM4tkODI$wYb@HG_*F<}iXCN)TK7T(b<*~IQdxSw1X6B#;g=CSez^y}^ z4H*kw|GI_ET3mdoiWD!+#x?z(fcz(~+^g@tv&;2^++slk+~ zjlp=Cg@uK|gl+S9=ron%3sXB0aLM4ShB12n8RTn}*MGGMc>nL~^FQ61!+%j Date: Tue, 26 Nov 2024 16:46:31 -0500 Subject: [PATCH 156/216] Update test database --- testsuite/testfiles/postgres.dump | Bin 1773208 -> 1772923 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/testsuite/testfiles/postgres.dump b/testsuite/testfiles/postgres.dump index 977b82b208aed2d4c5430d9c4f692ab55d2aa138..deb33d2cf76bbdaaee55108d15e57d4c44ff3084 100644 GIT binary patch delta 62959 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 Xrp&SN|91~q&`)FnLJ$C%ApZXVqV%z` delta 64020 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