From 40696393baf7c4081163884d511b5be0bff97180 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 9 Jun 2021 16:00:51 -0500 Subject: [PATCH 1/2] Get rid of ticket references --- flows/actions/open_ticket.go | 2 +- flows/contact.go | 29 +++- flows/contact_test.go | 2 +- flows/events/base_test.go | 7 +- flows/events/ticket_opened.go | 19 ++- flows/tickets.go | 157 +++++++++--------- flows/tickets_test.go | 76 ++++----- flows/triggers/base_test.go | 4 +- .../TestTriggerMarshaling_ticket_closed.snap | 2 +- flows/triggers/testdata/ticket.json | 2 +- flows/triggers/ticket.go | 44 +++-- test/engine.go | 4 +- 12 files changed, 197 insertions(+), 151 deletions(-) diff --git a/flows/actions/open_ticket.go b/flows/actions/open_ticket.go index f6abce6f1..dc0da0c22 100644 --- a/flows/actions/open_ticket.go +++ b/flows/actions/open_ticket.go @@ -65,7 +65,7 @@ func (a *OpenTicketAction) Execute(run flows.FlowRun, step flows.Step, logModifi ticket := a.open(run, step, ticketer, evaluatedSubject, evaluatedBody, logEvent) if ticket != nil { - a.saveResult(run, step, a.ResultName, string(ticket.UUID), CategorySuccess, "", "", nil, logEvent) + a.saveResult(run, step, a.ResultName, string(ticket.UUID()), CategorySuccess, "", "", nil, logEvent) } else { a.saveResult(run, step, a.ResultName, "", CategoryFailure, "", "", nil, logEvent) } diff --git a/flows/contact.go b/flows/contact.go index 8a724a101..61af16ec6 100644 --- a/flows/contact.go +++ b/flows/contact.go @@ -76,7 +76,7 @@ func NewContact( urns []urns.URN, groups []*assets.GroupReference, fields map[string]*Value, - tickets []*TicketReference, + tickets []*Ticket, missing assets.MissingCallback) (*Contact, error) { urnList, err := ReadURNList(sa, urns, missing) @@ -86,7 +86,7 @@ func NewContact( groupList := NewGroupList(sa, groups, missing) fieldValues := NewFieldValues(sa, fields, missing) - ticketList := NewTicketList(sa, tickets, missing) + ticketList := NewTicketList(tickets) return &Contact{ uuid: uuid, @@ -118,7 +118,7 @@ func NewEmptyContact(sa SessionAssets, name string, language envs.Language, time urns: URNList{}, groups: NewGroupList(sa, nil, assets.IgnoreMissing), fields: make(FieldValues), - tickets: NewTicketList(sa, nil, assets.IgnoreMissing), + tickets: NewTicketList([]*Ticket{}), assets: sa, } } @@ -582,7 +582,7 @@ type contactEnvelope struct { URNs []urns.URN `json:"urns,omitempty" validate:"dive,urn"` Groups []*assets.GroupReference `json:"groups,omitempty" validate:"dive"` Fields map[string]*Value `json:"fields,omitempty"` - Tickets []*TicketReference `json:"tickets,omitempty" validate:"dive"` + Tickets []json.RawMessage `json:"tickets,omitempty"` } // ReadContact decodes a contact from the passed in JSON @@ -626,13 +626,30 @@ func ReadContact(sa SessionAssets, data json.RawMessage, missing assets.MissingC c.groups = NewGroupList(sa, envelope.Groups, missing) c.fields = NewFieldValues(sa, envelope.Fields, missing) - c.tickets = NewTicketList(sa, envelope.Tickets, missing) + + tickets := make([]*Ticket, len(envelope.Tickets)) + for i := range envelope.Tickets { + tickets[i], err = ReadTicket(sa, envelope.Tickets[i], missing) + if err != nil { + return nil, errors.Wrap(err, "unable to read ticket") + } + } + c.tickets = NewTicketList(tickets) return c, nil } // MarshalJSON marshals this contact into JSON func (c *Contact) MarshalJSON() ([]byte, error) { + var err error + tickets := make([]json.RawMessage, len(c.tickets.tickets)) + for i, ticket := range c.tickets.tickets { + tickets[i], err = jsonx.Marshal(ticket) + if err != nil { + return nil, err + } + } + ce := &contactEnvelope{ Name: c.name, UUID: c.uuid, @@ -643,7 +660,7 @@ func (c *Contact) MarshalJSON() ([]byte, error) { LastSeenOn: c.lastSeenOn, URNs: c.urns.RawURNs(), Groups: c.groups.references(), - Tickets: c.tickets.references(), + Tickets: tickets, } if c.timezone != nil { diff --git a/flows/contact_test.go b/flows/contact_test.go index 33711a6fd..2166ee631 100644 --- a/flows/contact_test.go +++ b/flows/contact_test.go @@ -128,7 +128,7 @@ func TestContact(t *testing.T) { assert.Equal(t, 0, contact.Tickets().Count()) - ticket := flows.NewTicket(sa.Ticketers().Get("19dc6346-9623-4fe4-be80-538d493ecdf5"), "New ticket", "I have issues") + ticket := flows.OpenTicket(sa.Ticketers().Get("19dc6346-9623-4fe4-be80-538d493ecdf5"), "New ticket", "I have issues") contact.Tickets().Add(ticket) assert.Equal(t, 1, contact.Tickets().Count()) diff --git a/flows/events/base_test.go b/flows/events/base_test.go index f57bae63f..b3053766a 100644 --- a/flows/events/base_test.go +++ b/flows/events/base_test.go @@ -42,8 +42,7 @@ func TestEventMarshaling(t *testing.T) { timeout := 500 gender := session.Assets().Fields().Get("gender") mailgun := session.Assets().Ticketers().Get("19dc6346-9623-4fe4-be80-538d493ecdf5") - ticket := flows.NewTicket(mailgun, "Need help", "Where are my cookies?") - ticket.ExternalID = "1243252" + ticket := flows.NewTicket("7481888c-07dd-47dc-bf22-ef7448696ffe", mailgun, "Need help", "Where are my cookies?", "1243252") eventTests := []struct { event flows.Event @@ -424,7 +423,7 @@ func TestEventMarshaling(t *testing.T) { }, "text": "Hi there", "urn": "tel:+12345678900", - "uuid": "04e910a5-d2e3-448b-958a-630e35c62431" + "uuid": "20cc4181-48cf-4344-9751-99419796decd" }, "type": "ivr_created" }`, @@ -518,7 +517,7 @@ func TestEventMarshaling(t *testing.T) { "type": "ticket_opened", "created_on": "2018-10-18T14:20:30.000123456Z", "ticket": { - "uuid": "20cc4181-48cf-4344-9751-99419796decd", + "uuid": "7481888c-07dd-47dc-bf22-ef7448696ffe", "ticketer": { "uuid": "19dc6346-9623-4fe4-be80-538d493ecdf5", "name": "Support Tickets" diff --git a/flows/events/ticket_opened.go b/flows/events/ticket_opened.go index fc0b37f8f..7c44765a4 100644 --- a/flows/events/ticket_opened.go +++ b/flows/events/ticket_opened.go @@ -1,6 +1,7 @@ package events import ( + "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/flows" ) @@ -11,6 +12,14 @@ func init() { // TypeTicketOpened is the type for our ticket opened events const TypeTicketOpened string = "ticket_opened" +type Ticket struct { + UUID flows.TicketUUID `json:"uuid" validate:"required,uuid4"` + Ticketer *assets.TicketerReference `json:"ticketer" validate:"required,dive"` + Subject string `json:"subject"` + Body string `json:"body"` + ExternalID string `json:"external_id,omitempty"` +} + // TicketOpenedEvent events are created when a new ticket is opened. // // { @@ -32,13 +41,19 @@ const TypeTicketOpened string = "ticket_opened" type TicketOpenedEvent struct { baseEvent - Ticket *flows.TicketReference `json:"ticket"` + Ticket *Ticket `json:"ticket"` } // NewTicketOpened returns a new ticket opened event func NewTicketOpened(ticket *flows.Ticket) *TicketOpenedEvent { return &TicketOpenedEvent{ baseEvent: newBaseEvent(TypeTicketOpened), - Ticket: ticket.Reference(), + Ticket: &Ticket{ + UUID: ticket.UUID(), + Ticketer: ticket.Ticketer().Reference(), + Subject: ticket.Subject(), + Body: ticket.Body(), + ExternalID: ticket.ExternalID(), + }, } } diff --git a/flows/tickets.go b/flows/tickets.go index 80044a20a..d2ca09055 100644 --- a/flows/tickets.go +++ b/flows/tickets.go @@ -1,73 +1,50 @@ package flows import ( + "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/goflow/assets" "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/excellent/types" + "github.com/nyaruka/goflow/utils" + "github.com/pkg/errors" ) // TicketUUID is the UUID of a ticket type TicketUUID uuids.UUID -type baseTicket struct { - UUID TicketUUID `json:"uuid"` - Subject string `json:"subject"` - Body string `json:"body"` - ExternalID string `json:"external_id,omitempty"` -} - -// TicketReference is a ticket with a reference to the ticketer -type TicketReference struct { - Ticketer *assets.TicketerReference `json:"ticketer"` - baseTicket -} - -// NewTicketReference creates a new ticket with a reference to the ticketer -func NewTicketReference(uuid TicketUUID, ticketer *assets.TicketerReference, subject, body, externalID string) *TicketReference { - return &TicketReference{ - baseTicket: baseTicket{ - UUID: uuid, - Subject: subject, - Body: body, - ExternalID: externalID, - }, - Ticketer: ticketer, - } -} - // Ticket is a ticket in a ticketing system type Ticket struct { - Ticketer *Ticketer - baseTicket + uuid TicketUUID `json:"uuid"` + ticketer *Ticketer + subject string `json:"subject"` + body string `json:"body"` + externalID string `json:"external_id,omitempty"` } -// NewTicket creates a new ticket. Used by ticketing services to open a new ticket. -func NewTicket(ticketer *Ticketer, subject, body string) *Ticket { - return newTicket(TicketUUID(uuids.New()), ticketer, subject, body, "") -} - -// creates a new ticket -func newTicket(uuid TicketUUID, ticketer *Ticketer, subject, body, externalID string) *Ticket { +// NewTicketcreates a new ticket with a reference to the ticketer +func NewTicket(uuid TicketUUID, ticketer *Ticketer, subject, body, externalID string) *Ticket { return &Ticket{ - baseTicket: baseTicket{ - UUID: uuid, - Subject: subject, - Body: body, - ExternalID: externalID, - }, - Ticketer: ticketer, + uuid: uuid, + ticketer: ticketer, + subject: subject, + body: body, + externalID: externalID, } } -// Reference converts this ticket to a ticket reference suitable for marshaling -func (t *Ticket) Reference() *TicketReference { - return &TicketReference{ - baseTicket: t.baseTicket, - Ticketer: t.Ticketer.Reference(), - } +// OpenTicket creates a new ticket. Used by ticketing services to open a new ticket. +func OpenTicket(ticketer *Ticketer, subject, body string) *Ticket { + return NewTicket(TicketUUID(uuids.New()), ticketer, subject, body, "") } +func (t *Ticket) UUID() TicketUUID { return t.uuid } +func (t *Ticket) Ticketer() *Ticketer { return t.ticketer } +func (t *Ticket) Subject() string { return t.subject } +func (t *Ticket) Body() string { return t.body } +func (t *Ticket) ExternalID() string { return t.externalID } +func (t *Ticket) SetExternalID(id string) { t.externalID = id } + // Context returns the properties available in expressions // // uuid:text -> the UUID of the ticket @@ -77,35 +54,70 @@ func (t *Ticket) Reference() *TicketReference { // @context ticket func (t *Ticket) Context(env envs.Environment) map[string]types.XValue { return map[string]types.XValue{ - "uuid": types.NewXText(string(t.UUID)), - "subject": types.NewXText(t.Subject), - "body": types.NewXText(t.Body), + "uuid": types.NewXText(string(t.uuid)), + "subject": types.NewXText(t.subject), + "body": types.NewXText(t.body), } } +//------------------------------------------------------------------------------------------ +// JSON Encoding / Decoding +//------------------------------------------------------------------------------------------ + +type ticketEnvelope struct { + UUID TicketUUID `json:"uuid" validate:"required,uuid4"` + Ticketer *assets.TicketerReference `json:"ticketer" validate:"required,dive"` + Subject string `json:"subject"` + Body string `json:"body"` + ExternalID string `json:"external_id,omitempty"` +} + +// ReadTicket ecodes a contact from the passed in JSON. If the ticketer can't be found in the assets, +// we return report the missing asset and return ticket with nil ticketer. +func ReadTicket(sa SessionAssets, data []byte, missing assets.MissingCallback) (*Ticket, error) { + e := &ticketEnvelope{} + + if err := utils.UnmarshalAndValidate(data, e); err != nil { + return nil, errors.Wrap(err, "unable to read ticket") + } + + ticketer := sa.Ticketers().Get(e.Ticketer.UUID) + if ticketer == nil { + missing(e.Ticketer, nil) + } + + return &Ticket{ + uuid: e.UUID, + ticketer: ticketer, + subject: e.Subject, + body: e.Body, + externalID: e.ExternalID, + }, nil +} + +// MarshalJSON marshals this ticket into JSON +func (t *Ticket) MarshalJSON() ([]byte, error) { + var ticketerRef *assets.TicketerReference + if t.ticketer != nil { + ticketerRef = t.ticketer.Reference() + } + + return jsonx.Marshal(&ticketEnvelope{ + UUID: t.uuid, + Ticketer: ticketerRef, + Subject: t.subject, + Body: t.body, + ExternalID: t.externalID, + }) +} + // TicketList defines a contact's list of tickets type TicketList struct { tickets []*Ticket } -// NewTicketFromReference creates a new ticket from a ticket reference -func NewTicketFromReference(sa SessionAssets, ref *TicketReference) *Ticket { - ticketer := sa.Ticketers().Get(ref.Ticketer.UUID) - return newTicket(ref.UUID, ticketer, ref.Subject, ref.Body, ref.ExternalID) -} - // NewTicketList creates a new ticket list -func NewTicketList(sa SessionAssets, refs []*TicketReference, missing assets.MissingCallback) *TicketList { - tickets := make([]*Ticket, 0, len(refs)) - - for _, ref := range refs { - ticket := NewTicketFromReference(sa, ref) - if ticket.Ticketer != nil { - tickets = append(tickets, ticket) - } else { - missing(ref.Ticketer, nil) - } - } +func NewTicketList(tickets []*Ticket) *TicketList { return &TicketList{tickets: tickets} } @@ -116,15 +128,6 @@ func (l *TicketList) clone() *TicketList { return &TicketList{tickets: tickets} } -// returns this ticket list as a slice of ticket references -func (l *TicketList) references() []*TicketReference { - refs := make([]*TicketReference, len(l.tickets)) - for i, ticket := range l.tickets { - refs[i] = ticket.Reference() - } - return refs -} - // Adds adds the given ticket to this ticket list func (l *TicketList) Add(ticket *Ticket) { l.tickets = append(l.tickets, ticket) diff --git a/flows/tickets_test.go b/flows/tickets_test.go index 149af7518..c1073a111 100644 --- a/flows/tickets_test.go +++ b/flows/tickets_test.go @@ -1,7 +1,6 @@ package flows_test import ( - "encoding/json" "testing" "github.com/nyaruka/gocommon/uuids" @@ -51,52 +50,47 @@ func TestTickets(t *testing.T) { missingRefs = append(missingRefs, ref) } - ticketsJSON := `[ - { - "uuid": "349c851f-3f8e-4353-8bf2-8e90b6d73530", - "ticketer": {"uuid": "0a0b5ce4-35c9-47b7-b124-40258f0a5b53", "name": "Deleted"}, - "subject": "Very Old ticket", - "body": "Ticketer gone!" - }, - { - "uuid": "5a4af021-d2c2-47fc-9abc-abbb8635d8c0", - "ticketer": {"uuid": "d605bb96-258d-4097-ad0a-080937db2212", "name": "Support Tickets"}, - "subject": "Old ticket", - "body": "Where are my shoes?" - } - ]` - var ticketRefs []*flows.TicketReference - err = json.Unmarshal([]byte(ticketsJSON), &ticketRefs) + ticket1, err := flows.ReadTicket(sa, []byte(`{ + "uuid": "349c851f-3f8e-4353-8bf2-8e90b6d73530", + "ticketer": {"uuid": "0a0b5ce4-35c9-47b7-b124-40258f0a5b53", "name": "Deleted"}, + "subject": "Very Old Ticket", + "body": "Ticketer gone!", + "external_id": "7654" + }`), missing) require.NoError(t, err) - tickets := flows.NewTicketList(sa, ticketRefs, missing) - assert.Equal(t, 1, tickets.Count()) - assert.Equal(t, "Old ticket", tickets.All()[0].Subject) + assert.Equal(t, flows.TicketUUID("349c851f-3f8e-4353-8bf2-8e90b6d73530"), ticket1.UUID()) + assert.Nil(t, ticket1.Ticketer()) + assert.Equal(t, "Very Old Ticket", ticket1.Subject()) + assert.Equal(t, "Ticketer gone!", ticket1.Body()) + assert.Equal(t, "7654", ticket1.ExternalID()) - // check that ticket with missing ticketer is logged as a missing dependency + // check that missing ticketer is logged as a missing dependency assert.Equal(t, 1, len(missingRefs)) assert.Equal(t, "0a0b5ce4-35c9-47b7-b124-40258f0a5b53", missingRefs[0].Identity()) - ticket := flows.NewTicket(mailgun, "New ticket", "Where are my pants?") - ticket.ExternalID = "24567" - tickets.Add(ticket) + ticket2, err := flows.ReadTicket(sa, []byte(`{ + "uuid": "5a4af021-d2c2-47fc-9abc-abbb8635d8c0", + "ticketer": {"uuid": "d605bb96-258d-4097-ad0a-080937db2212", "name": "Support Tickets"}, + "subject": "Old Ticket", + "body": "Where are my shoes?" + }`), missing) + require.NoError(t, err) + + tickets := flows.NewTicketList([]*flows.Ticket{ticket1, ticket2}) assert.Equal(t, 2, tickets.Count()) + assert.Equal(t, "Very Old Ticket", tickets.All()[0].Subject()) + assert.Equal(t, "Old Ticket", tickets.All()[1].Subject()) + + ticket3 := flows.OpenTicket(mailgun, "New Ticket", "Where are my pants?") + ticket3.SetExternalID("24567") + + assert.Equal(t, flows.TicketUUID("1ae96956-4b34-433e-8d1a-f05fe6923d6d"), ticket3.UUID()) + assert.Equal(t, mailgun, ticket3.Ticketer()) + assert.Equal(t, "New Ticket", ticket3.Subject()) + assert.Equal(t, "Where are my pants?", ticket3.Body()) + assert.Equal(t, "24567", ticket3.ExternalID()) - ticketRef := ticket.Reference() - assert.Equal(t, flows.TicketUUID("1ae96956-4b34-433e-8d1a-f05fe6923d6d"), ticketRef.UUID) - assert.Equal(t, assets.TicketerUUID("5885ed52-8d3e-4fd3-be49-57eebe5d4d59"), ticketRef.Ticketer.UUID) - assert.Equal(t, "Email Tickets", ticketRef.Ticketer.Name) - assert.Equal(t, "New ticket", ticketRef.Subject) - assert.Equal(t, "Where are my pants?", ticketRef.Body) - assert.Equal(t, "24567", ticketRef.ExternalID) - - // can also create same ticket ref explicitly - ticketRef2 := flows.NewTicketReference( - "1ae96956-4b34-433e-8d1a-f05fe6923d6d", - assets.NewTicketerReference("5885ed52-8d3e-4fd3-be49-57eebe5d4d59", "Email Tickets"), - "New ticket", - "Where are my pants?", - "24567", - ) - assert.Equal(t, ticketRef, ticketRef2) + tickets.Add(ticket3) + assert.Equal(t, 3, tickets.Count()) } diff --git a/flows/triggers/base_test.go b/flows/triggers/base_test.go index a27b46b40..2022e5d2e 100644 --- a/flows/triggers/base_test.go +++ b/flows/triggers/base_test.go @@ -177,8 +177,8 @@ func TestTriggerMarshaling(t *testing.T) { flow := assets.NewFlowReference("7c37d7e5-6468-4b31-8109-ced2ef8b5ddc", "Registration") channel := assets.NewChannelReference("3a05eaf5-cb1b-4246-bef1-f277419c83a7", "Nexmo") - ticketer := assets.NewTicketerReference("19dc6346-9623-4fe4-be80-538d493ecdf5", "Support Tickets") - ticket := flows.NewTicketReference("276c2e43-d6f9-4c36-8e54-b5af5039acf6", ticketer, "Problem", "Where are my shoes?", "123456") + ticketer := sa.Ticketers().Get("19dc6346-9623-4fe4-be80-538d493ecdf5") + ticket := flows.NewTicket("276c2e43-d6f9-4c36-8e54-b5af5039acf6", ticketer, "Problem", "Where are my shoes?", "123456") contact := flows.NewEmptyContact(sa, "Bob", envs.Language("eng"), nil) contact.AddURN(urns.URN("tel:+12065551212"), nil) diff --git a/flows/triggers/testdata/TestTriggerMarshaling_ticket_closed.snap b/flows/triggers/testdata/TestTriggerMarshaling_ticket_closed.snap index 398dd5174..e19178c30 100644 --- a/flows/triggers/testdata/TestTriggerMarshaling_ticket_closed.snap +++ b/flows/triggers/testdata/TestTriggerMarshaling_ticket_closed.snap @@ -29,11 +29,11 @@ "event": { "type": "closed", "ticket": { + "uuid": "276c2e43-d6f9-4c36-8e54-b5af5039acf6", "ticketer": { "uuid": "19dc6346-9623-4fe4-be80-538d493ecdf5", "name": "Support Tickets" }, - "uuid": "276c2e43-d6f9-4c36-8e54-b5af5039acf6", "subject": "Problem", "body": "Where are my shoes?", "external_id": "123456" diff --git a/flows/triggers/testdata/ticket.json b/flows/triggers/testdata/ticket.json index da4ce91e9..3c1a9cab3 100644 --- a/flows/triggers/testdata/ticket.json +++ b/flows/triggers/testdata/ticket.json @@ -15,7 +15,7 @@ }, "triggered_on": "2000-01-01T00:00:00Z" }, - "read_error": "field 'event' is required" + "read_error": "field 'event.type' is required, field 'event.ticket' is required" }, { "description": "with all required fields", diff --git a/flows/triggers/ticket.go b/flows/triggers/ticket.go index ef2bdb01f..e57c967f6 100644 --- a/flows/triggers/ticket.go +++ b/flows/triggers/ticket.go @@ -9,6 +9,7 @@ import ( "github.com/nyaruka/goflow/excellent/types" "github.com/nyaruka/goflow/flows" "github.com/nyaruka/goflow/utils" + "github.com/pkg/errors" ) func init() { @@ -28,8 +29,8 @@ const ( // TicketEvent describes the specific event on the ticket that triggered the session type TicketEvent struct { - Type TicketEventType `json:"type" validate:"required"` - Ticket *flows.TicketReference `json:"ticket" validate:"required,dive"` + type_ TicketEventType + ticket *flows.Ticket } // TicketTrigger is used when a session was triggered by a ticket event @@ -58,14 +59,12 @@ type TicketEvent struct { type TicketTrigger struct { baseTrigger event *TicketEvent - - ticket *flows.Ticket } // Context for ticket triggers includes the ticket func (t *TicketTrigger) Context(env envs.Environment) map[string]types.XValue { c := t.context() - c.ticket = flows.Context(env, t.ticket) + c.ticket = flows.Context(env, t.event.ticket) return c.asMap() } @@ -81,11 +80,11 @@ type TicketBuilder struct { } // Ticket returns a ticket trigger builder -func (b *Builder) Ticket(ticket *flows.TicketReference, eventType TicketEventType) *TicketBuilder { +func (b *Builder) Ticket(ticket *flows.Ticket, eventType TicketEventType) *TicketBuilder { return &TicketBuilder{ t: &TicketTrigger{ baseTrigger: newBaseTrigger(TypeTicket, b.environment, b.flow, b.contact, nil, false, nil), - event: &TicketEvent{Type: eventType, Ticket: ticket}, + event: &TicketEvent{type_: eventType, ticket: ticket}, }, } } @@ -99,9 +98,14 @@ func (b *TicketBuilder) Build() *TicketTrigger { // JSON Encoding / Decoding //------------------------------------------------------------------------------------------ +type ticketEventEnvelope struct { + Type TicketEventType `json:"type" validate:"required"` + Ticket json.RawMessage `json:"ticket" validate:"required"` +} + type ticketTriggerEnvelope struct { baseTriggerEnvelope - Event *TicketEvent `json:"event" validate:"required,dive"` + Event ticketEventEnvelope `json:"event" validate:"required,dive"` } func readTicketTrigger(sa flows.SessionAssets, data json.RawMessage, missing assets.MissingCallback) (flows.Trigger, error) { @@ -111,22 +115,36 @@ func readTicketTrigger(sa flows.SessionAssets, data json.RawMessage, missing ass } t := &TicketTrigger{ - event: e.Event, + event: &TicketEvent{ + type_: e.Event.Type, + }, + } + + var err error + t.event.ticket, err = flows.ReadTicket(sa, e.Event.Ticket, missing) + if err != nil { + return nil, errors.Wrap(err, "unable to read ticket") } + if err := t.unmarshal(sa, &e.baseTriggerEnvelope, missing); err != nil { return nil, err } - // convert to real ticket in case we need to use it in the context - t.ticket = flows.NewTicketFromReference(sa, t.event.Ticket) - return t, nil } // MarshalJSON marshals this trigger into JSON func (t *TicketTrigger) MarshalJSON() ([]byte, error) { + ticket, err := jsonx.Marshal(t.event.ticket) + if err != nil { + return nil, err + } + e := &ticketTriggerEnvelope{ - Event: t.event, + Event: ticketEventEnvelope{ + Type: t.event.type_, + Ticket: ticket, + }, } if err := t.marshal(&e.baseTriggerEnvelope); err != nil { diff --git a/test/engine.go b/test/engine.go index 53dc83777..28565f634 100644 --- a/test/engine.go +++ b/test/engine.go @@ -120,8 +120,8 @@ func (s *ticketService) Open(session flows.Session, subject, body string, logHTT ElapsedMS: 1, }) - ticket := flows.NewTicket(s.ticketer, subject, body) - ticket.ExternalID = "123456" + ticket := flows.OpenTicket(s.ticketer, subject, body) + ticket.SetExternalID("123456") return ticket, nil } From e55ba079a3b34a07cc61534e1b781b8a3dca4694 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Wed, 9 Jun 2021 16:13:44 -0500 Subject: [PATCH 2/2] Coverage --- flows/contact_test.go | 20 +++++++++++++++++++- flows/tickets.go | 5 ++--- flows/tickets_test.go | 3 +++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/flows/contact_test.go b/flows/contact_test.go index 2166ee631..95c287959 100644 --- a/flows/contact_test.go +++ b/flows/contact_test.go @@ -2,6 +2,7 @@ package flows_test import ( "encoding/json" + "fmt" "io/ioutil" "testing" "time" @@ -34,6 +35,13 @@ func TestContact(t *testing.T) { "roles": ["send", "receive"], "country": "US" } + ], + "ticketers": [ + { + "uuid": "d605bb96-258d-4097-ad0a-080937db2212", + "name": "Support Tickets", + "type": "mailgun" + } ] }`)) require.NoError(t, err) @@ -128,7 +136,7 @@ func TestContact(t *testing.T) { assert.Equal(t, 0, contact.Tickets().Count()) - ticket := flows.OpenTicket(sa.Ticketers().Get("19dc6346-9623-4fe4-be80-538d493ecdf5"), "New ticket", "I have issues") + ticket := flows.OpenTicket(sa.Ticketers().Get("d605bb96-258d-4097-ad0a-080937db2212"), "New ticket", "I have issues") contact.Tickets().Add(ticket) assert.Equal(t, 1, contact.Tickets().Count()) @@ -166,6 +174,16 @@ func TestContact(t *testing.T) { assert.True(t, contact.ClearURNs()) // did have URNs assert.False(t, contact.ClearURNs()) assert.Equal(t, flows.URNList{}, contact.URNs()) + + marshaled, err := jsonx.Marshal(contact) + require.NoError(t, err) + + fmt.Println(string(marshaled)) + + unmarshaled, err := flows.ReadContact(sa, marshaled, assets.PanicOnMissing) + require.NoError(t, err) + + assert.True(t, contact.Equal(unmarshaled)) } func TestReadContact(t *testing.T) { diff --git a/flows/tickets.go b/flows/tickets.go index d2ca09055..4f803d1ae 100644 --- a/flows/tickets.go +++ b/flows/tickets.go @@ -7,7 +7,6 @@ import ( "github.com/nyaruka/goflow/envs" "github.com/nyaruka/goflow/excellent/types" "github.com/nyaruka/goflow/utils" - "github.com/pkg/errors" ) // TicketUUID is the UUID of a ticket @@ -66,7 +65,7 @@ func (t *Ticket) Context(env envs.Environment) map[string]types.XValue { type ticketEnvelope struct { UUID TicketUUID `json:"uuid" validate:"required,uuid4"` - Ticketer *assets.TicketerReference `json:"ticketer" validate:"required,dive"` + Ticketer *assets.TicketerReference `json:"ticketer" validate:"omitempty,dive"` Subject string `json:"subject"` Body string `json:"body"` ExternalID string `json:"external_id,omitempty"` @@ -78,7 +77,7 @@ func ReadTicket(sa SessionAssets, data []byte, missing assets.MissingCallback) ( e := &ticketEnvelope{} if err := utils.UnmarshalAndValidate(data, e); err != nil { - return nil, errors.Wrap(err, "unable to read ticket") + return nil, err } ticketer := sa.Ticketers().Get(e.Ticketer.UUID) diff --git a/flows/tickets_test.go b/flows/tickets_test.go index c1073a111..c33e7076b 100644 --- a/flows/tickets_test.go +++ b/flows/tickets_test.go @@ -50,6 +50,9 @@ func TestTickets(t *testing.T) { missingRefs = append(missingRefs, ref) } + _, err = flows.ReadTicket(sa, []byte(`{}`), missing) + assert.EqualError(t, err, "field 'uuid' is required") + ticket1, err := flows.ReadTicket(sa, []byte(`{ "uuid": "349c851f-3f8e-4353-8bf2-8e90b6d73530", "ticketer": {"uuid": "0a0b5ce4-35c9-47b7-b124-40258f0a5b53", "name": "Deleted"},