Skip to content

Commit

Permalink
add job api
Browse files Browse the repository at this point in the history
  • Loading branch information
josler committed Sep 15, 2015
1 parent ba82547 commit 30be252
Show file tree
Hide file tree
Showing 7 changed files with 310 additions and 7 deletions.
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ companyList, err := ic.Companies.ListByTag("42", intercom.PageParams{})

```go
event := intercom.Event{
UserId: "27",
UserID: "27",
EventName: "bought_item",
CreatedAt: int32(time.Now().Unix()),
Metadata: map[string]interface{}{"item_name": "PocketWatch"},
Expand Down Expand Up @@ -439,32 +439,36 @@ convo, err := intercom.Conversations.Assign("1234", &assignerAdmin, &assigneeAdm

### Bulk

Bulk operations are supported through the gem, see [the documentation](https://doc.intercom.io/api/#bulk-apis) for details.
Bulk operations are supported through this package, see [the documentation](https://doc.intercom.io/api/#bulk-apis) for details.

New user bulk job, posts a user and deletes another:

```go
jobResponse := intercom.Jobs.NewUserJob(intercom.NewUserJobItem(user, intercom.JOB_POST), intercom.NewUserJobItem(userTwo, intercom.JOB_DELETE))
jobResponse := ic.Jobs.NewUserJob(intercom.NewUserJobItem(&user, intercom.JOB_POST), intercom.NewUserJobItem(&userTwo, intercom.JOB_DELETE))
```

Append to an existing user job:

```go
jobResponse := intercom.Jobs.Append("job_5ca1ab1eca11ab1e", intercom.NewUserJobItem(user, intercom.JOB_POST))
jobResponse := ic.Jobs.AppendUsers("job_5ca1ab1eca11ab1e", intercom.NewUserJobItem(&user, intercom.JOB_POST))
```

New event bulk job:

```go
jobResponse := intercom.Jobs.NewEventJob(intercom.NewEventJobItem(event), intercom.NewEventJobItem(eventTwo))
jobResponse := ic.Jobs.NewEventJob(intercom.NewEventJobItem(&event), intercom.NewEventJobItem(&eventTwo))
```

Appending works the same way.
Append to an existing event job:

```go
jobResponse := ic.Jobs.AppendEvents("job_5ca1ab1eca11ab1e", intercom.NewEventJobItem(&eventTwo))
```

Find a Job:

```go
jobResponse := intercom.Jobs.Find("job_5ca1ab1eca11ab1e")
jobResponse := ic.Jobs.Find("job_5ca1ab1eca11ab1e")
```

### Errors
Expand Down
13 changes: 13 additions & 0 deletions fixtures/job.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"id": "job_5ca1ab1eca11ab1e",
"app_id": "pi3243fa",
"name": "api bulk job",
"state": "running",
"updated_at": 1438944983,
"created_at": 1438944983,
"completed_at": null,
"links": {
"error": "https://api.intercom.io/jobs/job_5ca1ab1eca11ab1e/error",
"self": "https://api.intercom.io/jobs/job_5ca1ab1eca11ab1e"
}
}
88 changes: 88 additions & 0 deletions job.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package intercom

import "fmt"

// JobService builds jobs to process
type JobService struct {
Repository JobRepository
}

// The state of a Job
type JobState int

const (
PENDING JobState = iota
RUNNING
COMPLETED
FAILED
)

var jobStates = [...]string{
"pending",
"running",
"completed",
"failed",
}

// A JobRequest represents a new job to be sent to Intercom
type JobRequest struct {
JobData *JobData `json:"job,omitempty"`
Items []*JobItem `json:"items,omitempty"`

bulkType string
}

// A JobResponse represents a job enqueud on Intercom
type JobResponse struct {
ID string `json:"id,omitempty"`
AppID string `json:"app_id,omitempty"`
UpdatedAt int64 `json:"updated_at,omitempty"`
CreatedAt int64 `json:"created_at,omitempty"`
CompletedAt int64 `json:"completed_at,omitempty"`
ClosingAt int64 `json:"closing_at,omitempty"`
Name string `json:"name,omitempty"`
State string `json:"job_state,omitempty"`
Links map[string]string `json:"links,omitempty"`
}

// JobData is a payload that can be used to identify an existing Job to append to.
type JobData struct {
ID string `json:"id,omitempty"`
}

// NewUserJob creates a new Job for processing Users.
func (js *JobService) NewUserJob(items ...*JobItem) (JobResponse, error) {
job := JobRequest{Items: items, bulkType: "users"}
return js.Repository.save(&job)
}

// NewEventJob creates a new Job for processing Events.
func (js *JobService) NewEventJob(items ...*JobItem) (JobResponse, error) {
job := JobRequest{Items: items, bulkType: "events"}
return js.Repository.save(&job)
}

// Append User items to existing Job
func (js *JobService) AppendUsers(id string, items ...*JobItem) (JobResponse, error) {
job := JobRequest{JobData: &JobData{ID: id}, Items: items, bulkType: "users"}
return js.Repository.save(&job)
}

// Append Event items to existing Job
func (js *JobService) AppendEvents(id string, items ...*JobItem) (JobResponse, error) {
job := JobRequest{JobData: &JobData{ID: id}, Items: items, bulkType: "events"}
return js.Repository.save(&job)
}

// Find existing Job
func (js *JobService) Find(id string) (JobResponse, error) {
return js.Repository.find(id)
}

func (j JobResponse) String() string {
return fmt.Sprintf("[intercom] job { id: %s, name: %s}", j.ID, j.Name)
}

func (state JobState) String() string {
return jobStates[state]
}
47 changes: 47 additions & 0 deletions job_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package intercom

import (
"encoding/json"
"fmt"

"github.com/intercom/intercom-go/interfaces"
)

// JobRepository defines the interface for working with Jobs.
type JobRepository interface {
save(job *JobRequest) (JobResponse, error)
find(id string) (JobResponse, error)
}

// JobAPI implements TagRepository
type JobAPI struct {
httpClient interfaces.HTTPClient
}

func (api JobAPI) save(job *JobRequest) (JobResponse, error) {
for i := range job.Items {
obj := job.Items[i].Data
switch obj.(type) {
case *User:
user := obj.(*User)
job.Items[i].Data = RequestUserMapper{}.ConvertUser(user)
}
}
savedJob := JobResponse{}
data, err := api.httpClient.Post(fmt.Sprintf("/bulk/%s", job.bulkType), job)
if err != nil {
return savedJob, err
}
err = json.Unmarshal(data, &savedJob)
return savedJob, err
}

func (api JobAPI) find(id string) (JobResponse, error) {
fetchedJob := JobResponse{}
data, err := api.httpClient.Get(fmt.Sprintf("/jobs/%s", id), nil)
if err != nil {
return fetchedJob, err
}
err = json.Unmarshal(data, &fetchedJob)
return fetchedJob, err
}
62 changes: 62 additions & 0 deletions job_api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package intercom

import (
"io/ioutil"
"testing"
)

func TestJobAPISaveUser(t *testing.T) {
http := TestJobHTTPClient{t: t, expectedURI: "/bulk/users", fixtureFilename: "fixtures/job.json"}
api := JobAPI{httpClient: &http}
user := User{UserID: "1234"}
job := JobRequest{Items: []*JobItem{NewUserJobItem(&user, JOB_POST)}, bulkType: "users"}
http.f = func(job *JobRequest) {
if job.Items[0].DataType != "user" {
t.Errorf("job item was of wrong data type, expected %s, was %s", "user", job.Items[0].DataType)
}
if job.Items[0].Data.(requestUser).UserID != "1234" {
t.Errorf("wrong user id sent")
}
}
savedJob, _ := api.save(&job)
if savedJob.ID != "job_5ca1ab1eca11ab1e" {
t.Errorf("Did not respond with correct job")
}
}

func TestJobAPISaveEvent(t *testing.T) {
http := TestJobHTTPClient{t: t, expectedURI: "/bulk/events", fixtureFilename: "fixtures/job.json"}
api := JobAPI{httpClient: &http}
event := Event{UserID: "1234"}
job := JobRequest{Items: []*JobItem{NewEventJobItem(&event)}, bulkType: "events"}
http.f = func(job *JobRequest) {
if job.Items[0].DataType != "event" {
t.Errorf("job item was of wrong data type, expected %s, was %s", "event", job.Items[0].DataType)
}
if job.Items[0].Data.(*Event).UserID != "1234" {
t.Errorf("wrong user id sent")
}
}
savedJob, _ := api.save(&job)
if savedJob.ID != "job_5ca1ab1eca11ab1e" {
t.Errorf("Did not respond with correct job")
}
}

type TestJobHTTPClient struct {
TestHTTPClient
t *testing.T
f func(job *JobRequest)
fixtureFilename string
expectedURI string
}

func (t *TestJobHTTPClient) Post(uri string, body interface{}) ([]byte, error) {
if t.expectedURI != uri {
t.t.Errorf("Wrong endpoint called")
}
if t.f != nil {
t.f(body.(*JobRequest))
}
return ioutil.ReadFile(t.fixtureFilename)
}
35 changes: 35 additions & 0 deletions job_item.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package intercom

// A JobItem is an item to be processed as part of a bulk Job
type JobItem struct {
Method string `json:"method"`
DataType string `json:"data_type"`
Data interface{} `json:"data"`
}

// NewUserJobItem creates a JobItem that holds an User.
// It can take either a JOB_POST (for updates) or JOB_DELETE (for deletes) method.
func NewUserJobItem(user *User, method JobItemMethod) *JobItem {
return &JobItem{Method: method.String(), DataType: "user", Data: user}
}

// NewEventJobItem creates a JobItem that holds an Event.
func NewEventJobItem(event *Event) *JobItem {
return &JobItem{Method: JOB_POST.String(), DataType: "event", Data: event}
}

type JobItemMethod int

const (
JOB_POST JobItemMethod = iota
JOB_DELETE
)

var jobItemMethods = [...]string{
"post",
"delete",
}

func (state JobItemMethod) String() string {
return jobItemMethods[state]
}
54 changes: 54 additions & 0 deletions job_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package intercom

import "testing"

func TestNewJob(t *testing.T) {
repo := &TestJobRepository{t: t}
repo.f = func(job *JobRequest) {
if job.Items[0].Method != JOB_POST.String() {
repo.t.Errorf("Wrong job method")
}
u := job.Items[0].Data.(*User)
if u.Email != "foo@bar.com" {
repo.t.Errorf("Wrong user email")
}
}
user := User{Email: "foo@bar.com"}
js := JobService{Repository: repo}
js.NewUserJob(NewUserJobItem(&user, JOB_POST))
}

func TestAppendJob(t *testing.T) {
repo := &TestJobRepository{t: t}
js := JobService{Repository: repo}
newJob, _ := js.NewUserJob()

repo.f = func(job *JobRequest) {
if job.Items[0].Method != JOB_POST.String() {
repo.t.Errorf("Wrong job method")
}
u := job.Items[0].Data.(*User)
if u.Email != "foo@bar.com" {
repo.t.Errorf("Wrong user email")
}
}
user := User{Email: "foo@bar.com"}

js.AppendUsers(newJob.ID, NewUserJobItem(&user, JOB_POST))
}

type TestJobRepository struct {
t *testing.T
f func(job *JobRequest)
}

func (api *TestJobRepository) save(job *JobRequest) (JobResponse, error) {
if api.f != nil {
api.f(job)
}
return JobResponse{}, nil
}

func (api *TestJobRepository) find(id string) (JobResponse, error) {
return JobResponse{}, nil
}

0 comments on commit 30be252

Please sign in to comment.