diff --git a/core/models/ticket_events.go b/core/models/ticket_events.go index 4ef24d6b7..d3cd876f0 100644 --- a/core/models/ticket_events.go +++ b/core/models/ticket_events.go @@ -52,8 +52,8 @@ func NewTicketTopicChangedEvent(t *Ticket, userID UserID, topicID TopicID) *Tick return newTicketEvent(t, userID, TicketEventTypeTopicChanged, "", topicID, NilUserID) } -func NewTicketClosedEvent(t *Ticket, userID UserID) *TicketEvent { - return newTicketEvent(t, userID, TicketEventTypeClosed, "", NilTopicID, NilUserID) +func NewTicketClosedEvent(t *Ticket, userID UserID, note string) *TicketEvent { + return newTicketEvent(t, userID, TicketEventTypeClosed, note, NilTopicID, NilUserID) } func NewTicketReopenedEvent(t *Ticket, userID UserID) *TicketEvent { diff --git a/core/models/ticket_events_test.go b/core/models/ticket_events_test.go index 891caa004..ea1cff98b 100644 --- a/core/models/ticket_events_test.go +++ b/core/models/ticket_events_test.go @@ -38,7 +38,7 @@ func TestTicketEvents(t *testing.T) { assert.Equal(t, null.String("please handle"), e3.Note()) assert.Equal(t, testdata.Agent.ID, e3.CreatedByID()) - e4 := models.NewTicketClosedEvent(modelTicket, testdata.Agent.ID) + e4 := models.NewTicketClosedEvent(modelTicket, testdata.Agent.ID, "") assert.Equal(t, models.TicketEventTypeClosed, e4.EventType()) assert.Equal(t, testdata.Agent.ID, e4.CreatedByID()) diff --git a/core/models/tickets.go b/core/models/tickets.go index c62c4bd65..bf6dbbafe 100644 --- a/core/models/tickets.go +++ b/core/models/tickets.go @@ -518,7 +518,7 @@ WHERE ` // CloseTickets closes the passed in tickets -func CloseTickets(ctx context.Context, rt *runtime.Runtime, oa *OrgAssets, userID UserID, tickets []*Ticket, externally, force bool, logger *HTTPLogger) (map[*Ticket]*TicketEvent, error) { +func CloseTickets(ctx context.Context, rt *runtime.Runtime, oa *OrgAssets, userID UserID, tickets []*Ticket, externally, force bool, logger *HTTPLogger, request string) (map[*Ticket]*TicketEvent, error) { byTicketer := make(map[TicketerID][]*Ticket) ids := make([]TicketID, 0, len(tickets)) events := make([]*TicketEvent, 0, len(tickets)) @@ -527,7 +527,7 @@ func CloseTickets(ctx context.Context, rt *runtime.Runtime, oa *OrgAssets, userI now := dates.Now() for _, ticket := range tickets { - if ticket.Status() != TicketStatusClosed { + if ticket.Status() != TicketStatusClosed || request != "" { byTicketer[ticket.TicketerID()] = append(byTicketer[ticket.TicketerID()], ticket) ids = append(ids, ticket.ID()) t := &ticket.t @@ -535,8 +535,7 @@ func CloseTickets(ctx context.Context, rt *runtime.Runtime, oa *OrgAssets, userI t.ModifiedOn = now t.ClosedOn = &now t.LastActivityOn = now - - e := NewTicketClosedEvent(ticket, userID) + e := NewTicketClosedEvent(ticket, userID, request) events = append(events, e) eventsByTicket[ticket] = e contactIDs[ticket.ContactID()] = true diff --git a/core/models/tickets_test.go b/core/models/tickets_test.go index 0d5fad535..678205901 100644 --- a/core/models/tickets_test.go +++ b/core/models/tickets_test.go @@ -301,7 +301,7 @@ func TestCloseTickets(t *testing.T) { assert.Equal(t, "Tickets", cathy.Groups().All()[1].Name()) logger := &models.HTTPLogger{} - evts, err := models.CloseTickets(ctx, rt, oa, testdata.Admin.ID, []*models.Ticket{modelTicket1, modelTicket2}, true, false, logger) + evts, err := models.CloseTickets(ctx, rt, oa, testdata.Admin.ID, []*models.Ticket{modelTicket1, modelTicket2}, true, false, logger, "") require.NoError(t, err) assert.Equal(t, 1, len(evts)) assert.Equal(t, models.TicketEventTypeClosed, evts[modelTicket1].EventType()) @@ -330,7 +330,7 @@ func TestCloseTickets(t *testing.T) { ticket3 := testdata.InsertOpenTicket(db, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Where my shoes", "123", nil) modelTicket3 := ticket3.Load(db) - evts, err = models.CloseTickets(ctx, rt, oa, models.NilUserID, []*models.Ticket{modelTicket3}, false, false, logger) + evts, err = models.CloseTickets(ctx, rt, oa, models.NilUserID, []*models.Ticket{modelTicket3}, false, false, logger, "") require.NoError(t, err) assert.Equal(t, 1, len(evts)) assert.Equal(t, models.TicketEventTypeClosed, evts[modelTicket3].EventType()) diff --git a/core/tasks/handler/handler_test.go b/core/tasks/handler/handler_test.go index e7ac6af4a..a9ae080aa 100644 --- a/core/tasks/handler/handler_test.go +++ b/core/tasks/handler/handler_test.go @@ -318,7 +318,7 @@ func TestTicketEvents(t *testing.T) { ticket := testdata.InsertClosedTicket(rt.DB, testdata.Org1, testdata.Cathy, testdata.Mailgun, testdata.DefaultTopic, "Where are my shoes?", "", nil) modelTicket := ticket.Load(db) - event := models.NewTicketClosedEvent(modelTicket, testdata.Admin.ID) + event := models.NewTicketClosedEvent(modelTicket, testdata.Admin.ID, "") err := handler.QueueTicketEvent(rc, testdata.Cathy.ID, event) require.NoError(t, err) diff --git a/core/tasks/handler/worker.go b/core/tasks/handler/worker.go index e5e1248e4..f4da8fbb2 100644 --- a/core/tasks/handler/worker.go +++ b/core/tasks/handler/worker.go @@ -6,6 +6,7 @@ import ( "fmt" "time" + "github.com/apex/log" "github.com/gomodule/redigo/redis" "github.com/jmoiron/sqlx" "github.com/nyaruka/gocommon/urns" @@ -729,13 +730,34 @@ func handleTicketEvent(ctx context.Context, rt *runtime.Runtime, event *models.T return errors.Wrapf(err, "error creating flow contact") } + var params *types.XObject + if event.Note() != "" { + note := struct { + Event string `json:"event"` + ID int `json:"id"` + Status string `json:"status"` + }{} + err := json.Unmarshal([]byte(event.Note()), ¬e) + if err != nil { + log.WithError(err).Error("unable to unmarshal note from ticket event") + } + asJSON, err := json.Marshal(note) + if err != nil { + log.WithError(err).Error("unable to marshal note from ticket event") + } + params, err = types.ReadXObject(asJSON) + if err != nil { + log.WithError(err).Error("unable to marshal note from ticket event") + } + } + // build our flow trigger var flowTrigger flows.Trigger switch event.EventType() { case models.TicketEventTypeClosed: flowTrigger = triggers.NewBuilder(oa.Env(), flow.FlowReference(), contact). - Ticket(ticket, triggers.TicketEventTypeClosed). + Ticket(ticket, triggers.TicketEventTypeClosed).WithParams(params). Build() default: return errors.Errorf("unknown ticket event type: %s", event.EventType()) diff --git a/go.mod b/go.mod index dcf222718..281f61055 100644 --- a/go.mod +++ b/go.mod @@ -70,4 +70,4 @@ go 1.17 replace github.com/nyaruka/gocommon => github.com/Ilhasoft/gocommon v1.16.2-weni -replace github.com/nyaruka/goflow => github.com/Ilhasoft/goflow v0.0.1-goflow-0.144.3-stg-2 +replace github.com/nyaruka/goflow => github.com/Ilhasoft/goflow v0.0.3-goflow-0.144.3 diff --git a/go.sum b/go.sum index d9a825203..5b3aa9021 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20O github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/Ilhasoft/gocommon v1.16.2-weni h1:IDDxPVNIVDMwSErQmTrAiziLMvEi6rbeRb3GG8D+XmA= github.com/Ilhasoft/gocommon v1.16.2-weni/go.mod h1:pk8L9T79VoKO8OWTiZbtUutFPI3sGGKB5u8nNWDKuGE= -github.com/Ilhasoft/goflow v0.0.1-goflow-0.144.3-stg-2 h1:2p26eo5Ybc8xjfKg/BJucoVuerk5x8zaaiCmSq75ANg= -github.com/Ilhasoft/goflow v0.0.1-goflow-0.144.3-stg-2/go.mod h1:o0xaVWP9qNcauBSlcNLa79Fm2oCPV+BDpheFRa/D40c= +github.com/Ilhasoft/goflow v0.0.3-goflow-0.144.3 h1:L+HZFwumaV2ms0Wvi8fMbx6KnVjK5uRhR8Df/td05bc= +github.com/Ilhasoft/goflow v0.0.3-goflow-0.144.3/go.mod h1:o0xaVWP9qNcauBSlcNLa79Fm2oCPV+BDpheFRa/D40c= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= diff --git a/services/tickets/mailgun/web.go b/services/tickets/mailgun/web.go index 8f3735bf1..dd8981c4e 100644 --- a/services/tickets/mailgun/web.go +++ b/services/tickets/mailgun/web.go @@ -111,7 +111,7 @@ func handleReceive(ctx context.Context, rt *runtime.Runtime, r *http.Request, l // check if reply is actually a command if strings.ToLower(strings.TrimSpace(request.StrippedText)) == "close" { - err = tickets.Close(ctx, rt, oa, ticket, true, l) + err = tickets.Close(ctx, rt, oa, ticket, true, l, "") if err != nil { return errors.Wrapf(err, "error closing ticket: %s", ticket.UUID()), http.StatusInternalServerError, nil } diff --git a/services/tickets/rocketchat/web.go b/services/tickets/rocketchat/web.go index 286de0cf2..042f9f1aa 100644 --- a/services/tickets/rocketchat/web.go +++ b/services/tickets/rocketchat/web.go @@ -98,7 +98,7 @@ func handleEventCallback(ctx context.Context, rt *runtime.Runtime, r *http.Reque _, err = tickets.SendReply(ctx, rt, ticket, data.Text, files) case "close-room": - err = tickets.Close(ctx, rt, oa, ticket, false, l) + err = tickets.Close(ctx, rt, oa, ticket, false, l, "") default: err = errors.New("invalid event type") diff --git a/services/tickets/twilioflex/web.go b/services/tickets/twilioflex/web.go index c5da9a193..4192a009b 100644 --- a/services/tickets/twilioflex/web.go +++ b/services/tickets/twilioflex/web.go @@ -114,7 +114,7 @@ func handleEventCallback(ctx context.Context, rt *runtime.Runtime, r *http.Reque return err, http.StatusBadRequest, nil } if jsonMap["status"] == "INACTIVE" { - err = tickets.Close(ctx, rt, oa, ticket, false, nil) + err = tickets.Close(ctx, rt, oa, ticket, false, nil, "") if err != nil { return err, http.StatusBadRequest, nil } diff --git a/services/tickets/utils.go b/services/tickets/utils.go index 47c2ef5a9..4988b6d62 100644 --- a/services/tickets/utils.go +++ b/services/tickets/utils.go @@ -155,8 +155,8 @@ func FetchFileWithMaxSize(url string, headers map[string]string, maxBodyBytes in } // Close closes the given ticket, and creates and queues a closed event -func Close(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets, ticket *models.Ticket, externally bool, l *models.HTTPLogger) error { - events, err := models.CloseTickets(ctx, rt, oa, models.NilUserID, []*models.Ticket{ticket}, externally, false, l) +func Close(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAssets, ticket *models.Ticket, externally bool, l *models.HTTPLogger, request string) error { + events, err := models.CloseTickets(ctx, rt, oa, models.NilUserID, []*models.Ticket{ticket}, externally, false, l, request) if err != nil { return errors.Wrap(err, "error closing ticket") } diff --git a/services/tickets/utils_test.go b/services/tickets/utils_test.go index 74453cca4..ccf8ec5cf 100644 --- a/services/tickets/utils_test.go +++ b/services/tickets/utils_test.go @@ -184,7 +184,7 @@ func TestCloseTicket(t *testing.T) { logger := &models.HTTPLogger{} - err = tickets.Close(ctx, rt, oa, ticket1, true, logger) + err = tickets.Close(ctx, rt, oa, ticket1, true, logger, "") require.NoError(t, err) testsuite.AssertContactTasks(t, 1, testdata.Cathy.ID, diff --git a/services/tickets/wenichats/web.go b/services/tickets/wenichats/web.go index c6ff6ab69..7d05719b7 100644 --- a/services/tickets/wenichats/web.go +++ b/services/tickets/wenichats/web.go @@ -115,7 +115,7 @@ func handleEventCallback(ctx context.Context, rt *runtime.Runtime, r *http.Reque } } case "room.update": - err = tickets.Close(ctx, rt, oa, ticket, false, nil) + err = tickets.Close(ctx, rt, oa, ticket, false, nil, "") if err != nil { return errors.Wrapf(err, "error on close ticket"), http.StatusInternalServerError, nil } diff --git a/services/tickets/zendesk/web.go b/services/tickets/zendesk/web.go index a98228bea..466da2e32 100644 --- a/services/tickets/zendesk/web.go +++ b/services/tickets/zendesk/web.go @@ -285,10 +285,11 @@ func handleTicketerWebhook(ctx context.Context, rt *runtime.Runtime, r *http.Req return err, http.StatusBadRequest, nil } + requestJSON, err := json.Marshal(request) if request.Event == "status_changed" { switch strings.ToLower(request.Status) { case statusSolved, statusClosed, "resuelto", "cerrado", "resolvido": - err = tickets.Close(ctx, rt, oa, ticket, false, l) + err = tickets.Close(ctx, rt, oa, ticket, false, l, string(requestJSON)) case statusOpen, "abierto", "aberto": err = tickets.Reopen(ctx, rt, oa, ticket, false, l) } diff --git a/web/ticket/close.go b/web/ticket/close.go index 5e1e1d996..f9dc30981 100644 --- a/web/ticket/close.go +++ b/web/ticket/close.go @@ -19,13 +19,12 @@ func init() { // Closes any open tickets with the given ids. If force=true then even if tickets can't be closed on external service, // they are still closed locally. This is used in case of deleting a ticketing service which may no longer be functioning. // -// { -// "org_id": 123, -// "user_id": 234, -// "ticket_ids": [1234, 2345], -// "force": false -// } -// +// { +// "org_id": 123, +// "user_id": 234, +// "ticket_ids": [1234, 2345], +// "force": false +// } func handleClose(ctx context.Context, rt *runtime.Runtime, r *http.Request, l *models.HTTPLogger) (interface{}, int, error) { request := &bulkTicketRequest{} if err := utils.UnmarshalAndValidateWithLimit(r.Body, request, web.MaxRequestBytes); err != nil { @@ -43,7 +42,7 @@ func handleClose(ctx context.Context, rt *runtime.Runtime, r *http.Request, l *m return nil, http.StatusBadRequest, errors.Wrapf(err, "error loading tickets for org: %d", request.OrgID) } - evts, err := models.CloseTickets(ctx, rt, oa, request.UserID, tickets, true, request.Force, l) + evts, err := models.CloseTickets(ctx, rt, oa, request.UserID, tickets, true, request.Force, l, "") if err != nil { return nil, http.StatusInternalServerError, errors.Wrap(err, "error closing tickets") }