From e0875c9dca0608c800ecc8f2994861a5acc5c6b6 Mon Sep 17 00:00:00 2001 From: Guillem Bonet Date: Tue, 28 Dec 2021 10:13:46 +0100 Subject: [PATCH] updated client and support no used Id Signed-off-by: Guillem Bonet --- .gitignore | 1 + client/api.go | 91 ++++++++++++++++++++++++ feedback/api.go | 7 +- feedback/intercom.go | 164 ++++++++++++++++++++++++++----------------- 4 files changed, 193 insertions(+), 70 deletions(-) diff --git a/.gitignore b/.gitignore index a8dca73..8926059 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ build/ vendor/ .env +.vscode/ diff --git a/client/api.go b/client/api.go index 256de68..1bfa660 100644 --- a/client/api.go +++ b/client/api.go @@ -58,6 +58,21 @@ func (f *FeedbackAPI) CreateGithubIssue(request CreateGithubIssueRequest) (respo return parseCreateGithubIssueResult(resp) } +// CreateIntercomIssue creates a intercom issue +func (f *FeedbackAPI) CreateIntercomIssue(request CreateIntercomIssueRequest) (response *CreateIntercomIssueResult, err error) { + multipartReq, err := newCreateIntercomIssueRequest(f.apiURL("/intercom"), request) + if err != nil { + // For now not using fmt.Errorf with %w for compatibility with go <1.13 + return nil, errors.Wrap(err, "could not create multipart request") + } + + resp, err := f.http.Do(multipartReq) + if err != nil { + return nil, errors.Wrap(err, "failed request to feedback service") + } + return parseCreateIntercomIssueResult(resp) +} + func newCreateGithubIssueRequest(uri string, req CreateGithubIssueRequest) (multipartReq *http.Request, err error) { fileContent, err := ioutil.ReadFile(req.Filepath) if err != nil { @@ -88,6 +103,40 @@ func newCreateGithubIssueRequest(uri string, req CreateGithubIssueRequest) (mult return request, err } +func newCreateIntercomIssueRequest(uri string, req CreateIntercomIssueRequest) (multipartReq *http.Request, err error) { + fileContent, err := ioutil.ReadFile(req.Filepath) + if err != nil { + return nil, errors.Wrap(err, "could not read input file") + } + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("file", req.Filepath) + if err != nil { + return nil, errors.Wrap(err, "could not add file to request") + } + _, err = part.Write(fileContent) + if err != nil { + return nil, errors.Wrap(err, "could not write file part") + } + + _ = writer.WriteField("userId", req.UserId) + _ = writer.WriteField("description", req.Description) + _ = writer.WriteField("email", req.Email) + _ = writer.WriteField("nodeIdentity", req.UserId) + _ = writer.WriteField("userType", req.Description) + _ = writer.WriteField("ipType", req.Email) + _ = writer.WriteField("ip", req.Email) + _ = writer.Close() + + request, err := http.NewRequest("POST", uri, body) + if err != nil { + return nil, errors.Wrap(err, "could not create request") + } + + request.Header.Set("Content-Type", writer.FormDataContentType()) + return request, err +} + // CreateGithubIssueRequest create github issue request type CreateGithubIssueRequest struct { UserId string `json:"userId"` @@ -96,6 +145,19 @@ type CreateGithubIssueRequest struct { Filepath string `json:"file"` } +// CreateIntercomIssueRequest create intercom issue request +type CreateIntercomIssueRequest struct { + UserId string `json:"userId"` + Description string `json:"description"` + Email string `json:"email"` + Filepath string `json:"file"` + NodeIdentity string `json:"nodeIdentity"` + UserType string `json:"userType"` + NodeCountry string `json:"nodeCountry"` + IpType string `json:"ipType"` + Ip string `json:"ip"` +} + // CreateGithubIssueResult represents create github issue response (either successful or error) type CreateGithubIssueResult struct { Response *GithubIssueCreated @@ -104,6 +166,13 @@ type CreateGithubIssueResult struct { Success bool } +// CreateIntercomIssueResult represents create intercom issue response (either successful or error) +type CreateIntercomIssueResult struct { + Errors *ErrorResponse + HTTPResponse *http.Response + Success bool +} + // GithubIssueCreated represents successful response (issue created) type GithubIssueCreated struct { IssueId string `json:"issueId"` @@ -137,3 +206,25 @@ func parseCreateGithubIssueResult(httpRes *http.Response) (*CreateGithubIssueRes } return res, nil } + +func parseCreateIntercomIssueResult(httpRes *http.Response) (*CreateIntercomIssueResult, error) { + res := &CreateIntercomIssueResult{HTTPResponse: httpRes} + resJSON, err := ioutil.ReadAll(httpRes.Body) + if err != nil { + res.Success = false + res.Errors = singleErrorResponse("could not parse feedback response: " + err.Error()) + return res, err + } + if res.HTTPResponse.StatusCode >= 400 { + res.Success = false + res.Errors = &ErrorResponse{[]Error{}} + err = json.Unmarshal(resJSON, res.Errors) + if err != nil { + res.Errors = singleErrorResponse("could not parse feedback response: " + err.Error()) + return res, err + } + } else { + res.Success = true + } + return res, nil +} diff --git a/feedback/api.go b/feedback/api.go index 88cbb06..7f3d623 100644 --- a/feedback/api.go +++ b/feedback/api.go @@ -186,6 +186,7 @@ type CreateIntercomIssueRequest struct { // required: true Description string `json:"description"` // in: formData + // required: false Email string `json:"email"` // in: formData // required: true @@ -293,7 +294,7 @@ func (e *Endpoint) CreateIntercomIssue(c *gin.Context) { // return // } - issueId, err := e.intercomReporter.ReportIssue(&IntercomReport{ + err = e.intercomReporter.ReportIssue(&IntercomReport{ UserId: form.UserId, NodeIdentity: form.NodeIdentity, UserType: form.UserType, @@ -312,9 +313,7 @@ func (e *Endpoint) CreateIntercomIssue(c *gin.Context) { } log.Infof("Created intercom conversation from request %+v", form) - c.JSON(http.StatusOK, &CreateGithubIssueResponse{ - IssueId: issueId, - }) + c.Status(http.StatusCreated) } // RegisterRoutes registers feedback API routes diff --git a/feedback/intercom.go b/feedback/intercom.go index bd0bf3a..48ed5ed 100644 --- a/feedback/intercom.go +++ b/feedback/intercom.go @@ -23,7 +23,6 @@ import ( "fmt" "net/http" "net/url" - "strconv" "strings" "text/template" "time" @@ -34,6 +33,11 @@ import ( const ( INTERCOM_BASE_API = "https://api.intercom.io" + USER_ROLE_KEY = "user_role" + NODE_IDENTITY_KEY = "node_identity" + NODE_COUNTRY_KEY = "node_country" + IP_TYPE_KEY = "ip_type" + IP_KEY = "ip" ) // IntercomReporter reports issues to Intercom @@ -86,7 +90,7 @@ Logs: ` // ReportIssue creates a issue message for the user in intercom -func (rep *IntercomReporter) ReportIssue(report *IntercomReport) (issueId string, err error) { +func (rep *IntercomReporter) ReportIssue(report *IntercomReport) error { templateOpts := struct { Description, Timestamp, @@ -97,91 +101,118 @@ func (rep *IntercomReporter) ReportIssue(report *IntercomReport) (issueId string LogURL: report.LogURL.String(), } var body bytes.Buffer - err = rep.messageTemplate.Execute(&body, templateOpts) + err := rep.messageTemplate.Execute(&body, templateOpts) if err != nil { - return "", fmt.Errorf("could not generate message body with report (%+v): %w", templateOpts, err) + return fmt.Errorf("could not generate message body with report (%+v): %w", templateOpts, err) } - // try update visitor - err = rep.updateVisitor(report.UserId, &updateVisitorRequest{ - Email: report.Email, - CustomAttributes: updateVisitorRequestCustomAttributes{ - NodeIdentity: report.NodeIdentity, - IsConsumer: (strings.ToLower(report.UserType) == "consumer"), - NodeCountry: report.NodeCountry, - IpType: report.IpType, - Ip: report.Ip, - }, - }) - if err != nil { - log.Warn().Msg("could not update visitor") - } - visitorUpdated := (err == nil) - - // try update contact - contactUpdated := false - if !visitorUpdated { - contact, err := rep.client.Contacts.FindByUserID(report.UserId) + if report.UserId != "" { + // try update visitor (will become lead) + err = rep.updateVisitor(report.UserId, &updateVisitorRequest{ + Email: report.Email, + CustomAttributes: updateVisitorRequestCustomAttributes{ + NodeIdentity: report.NodeIdentity, + IsConsumer: (strings.ToLower(report.UserType) == "consumer"), + NodeCountry: report.NodeCountry, + IpType: report.IpType, + Ip: report.Ip, + }, + }) if err != nil { - log.Warn().Msg("could not update contact") + log.Warn().Msgf("could not update visitor %s\n", report.UserId) } - if err == nil { - contact.Email = report.Email - contact.CustomAttributes["node_identity"] = report.NodeIdentity - contact.CustomAttributes["node_country"] = report.NodeCountry - contact.CustomAttributes["is_consumer"] = (strings.ToLower(report.UserType) == "consumer") - contact.CustomAttributes["ip_type"] = report.IpType - contact.CustomAttributes["ip"] = report.Ip - _, err := rep.client.Contacts.Update(&contact) + visitorUpdated := (err == nil) + + // try update contact + contactUpdated := false + if !visitorUpdated { + contact, err := rep.client.Contacts.FindByUserID(report.UserId) if err != nil { - return "", fmt.Errorf("could not update contact (%s): %w", contact.ID, err) + log.Warn().Msgf("could not update contact %s\n", report.UserId) + } + if err == nil { + contact.Email = report.Email + contact.CustomAttributes[NODE_IDENTITY_KEY] = report.NodeIdentity + contact.CustomAttributes[NODE_COUNTRY_KEY] = report.NodeCountry + contact.CustomAttributes[USER_ROLE_KEY] = report.UserType + contact.CustomAttributes[IP_TYPE_KEY] = report.IpType + contact.CustomAttributes[IP_KEY] = report.Ip + _, err := rep.client.Contacts.Update(&contact) + if err != nil { + return fmt.Errorf("could not update contact (%s): %w", contact.ID, err) + } + contactUpdated = true } - contactUpdated = true - } - } - // try update user - userUpdated := false - if !visitorUpdated && !contactUpdated { - user, err := rep.client.Users.FindByUserID(report.UserId) - if err != nil { - log.Warn().Msg("could not update user") } - if err == nil { - user.Email = report.Email - user.CustomAttributes["node_identity"] = report.NodeIdentity - user.CustomAttributes["node_country"] = report.NodeCountry - user.CustomAttributes["is_consumer"] = (strings.ToLower(report.UserType) == "consumer") - user.CustomAttributes["ip_type"] = report.IpType - user.CustomAttributes["ip"] = report.Ip - _, err := rep.client.Users.Save(&user) + // try update user + userUpdated := false + if !visitorUpdated && !contactUpdated { + user, err := rep.client.Users.FindByUserID(report.UserId) if err != nil { - return "", fmt.Errorf("could not update user (%s): %w", user.ID, err) + log.Warn().Msgf("could not update user %s\n", report.UserId) + } + if err == nil { + user.Email = report.Email + user.CustomAttributes[NODE_IDENTITY_KEY] = report.NodeIdentity + user.CustomAttributes[NODE_COUNTRY_KEY] = report.NodeCountry + user.CustomAttributes[USER_ROLE_KEY] = report.UserType + user.CustomAttributes[IP_TYPE_KEY] = report.IpType + user.CustomAttributes[IP_KEY] = report.Ip + _, err := rep.client.Users.Save(&user) + if err != nil { + return fmt.Errorf("could not update user (%s): %w", user.ID, err) + } + userUpdated = true } - userUpdated = true } - } - if !visitorUpdated && !contactUpdated && !userUpdated { - return "", fmt.Errorf("could not update visitor, contact or user (%s): %w", report.UserId, err) + if !visitorUpdated && !contactUpdated && !userUpdated { + return fmt.Errorf("could not update visitor, contact or user (%s): %w", report.UserId, err) + } + + userType := "contact" + if userUpdated { + userType = "user" + } + + err = rep.createConversation(&createConversationRequest{ + From: createConversationRequestFrom{ + UserType: userType, + UserId: &report.UserId, + }, + Body: body.String(), + }) + if err != nil { + return fmt.Errorf("could not create conversation for user (%s): %w", report.UserId, err) + } + return nil } - userType := "contact" - if userUpdated { - userType = "user" + contact, err := rep.client.Contacts.Create(&intercom.Contact{ + Email: report.Email, + CustomAttributes: map[string]interface{}{ + NODE_IDENTITY_KEY: report.NodeIdentity, + NODE_COUNTRY_KEY: report.NodeCountry, + USER_ROLE_KEY: report.UserType, + IP_TYPE_KEY: report.IpType, + IP_KEY: report.Ip, + }, + }) + if err != nil { + return fmt.Errorf("could not create contact: %w", err) } err = rep.createConversation(&createConversationRequest{ From: createConversationRequestFrom{ - UserType: userType, - UserId: report.UserId, + UserType: "contact", + Id: &contact.ID, }, Body: body.String(), }) if err != nil { - return "", fmt.Errorf("could not create conversation for user (%s): %w", report.UserId, err) + return fmt.Errorf("could not create conversation for user with id (%s): %w", contact.ID, err) } - - return strconv.Itoa(123), nil + return nil } type updateVisitorRequestCustomAttributes struct { @@ -221,8 +252,9 @@ func (rep *IntercomReporter) updateVisitor(userId string, updateVisitorRequest * } type createConversationRequestFrom struct { - UserType string `json:"type"` - UserId string `json:"user_id"` + UserType string `json:"type"` + UserId *string `json:"user_id"` + Id *string `json:"id"` } type createConversationRequest struct {