Repository: preslavmihaylov/todocheck
Pull request #81 (2020-10-24 08:51:37)

refactored issue tracker component, extracting specific issue tracker code into separate components
part 1 of #82

I've made this refactoring to enable a more easier integration of new issue trackers.

The current implementation is quite rigid as it is based on various `switch-cases`, scattered throughout the whole project.
One should have a very deep understanding of the project to be able to create a new integration with an issue tracker.

With this change, all issue tracker-related functions are encapsulated in separate packages, which are independent from the rest of the system and the higher-level interface.

This is part 1 of the refactoring. There are still other things to do, but I will do them with a separate PR:
 * Add the `issuetracker.HealthcheckURL` and related functions into the `issutracker.IssueTracker` interface
 * Extract the authentication middleware functions into the new package as well

## Test plan
I've tested the tool works without issues with all supported issue trackers (github, gitlab, redmine, pivotal, jira).

mehdy: Just checking in to say this is a big one :)) So It may take a little longer than usual.
preslavmihaylov: Yeah, I agree. Please take your time. Let's not break anything. :)
mehdy: @preslavmihaylov I think this is ready to be merged if you're done with it.
preslavmihaylov: Yeah, I'm done with it. I've also described the follow-up tasks [here](https://github.com/preslavmihaylov/todocheck/issues/82#issuecomment-720043278)

Let me know if I missed something

Patch:
diff --git a/fetcher/fetcher.go b/fetcher/fetcher.go
index d6254ca..e0ebebd 100644
--- a/fetcher/fetcher.go
+++ b/fetcher/fetcher.go
@@ -14,19 +14,18 @@ import (
 
 // Fetcher for task statuses by contacting task management web apps' rest api
 type Fetcher struct {
-	origin         string
-	tracker        config.IssueTracker
+	issuetracker.IssueTracker
 	authMiddleware authmiddleware.Func
 }
 
 // NewFetcher instance
-func NewFetcher(origin string, tracker config.IssueTracker, authMw authmiddleware.Func) *Fetcher {
-	return &Fetcher{origin, tracker, authMw}
+func NewFetcher(issueTracker issuetracker.IssueTracker, authMw authmiddleware.Func) *Fetcher {
+	return &Fetcher{issueTracker, authMw}
 }
 
 // Fetch a task's status based on task ID
 func (f *Fetcher) Fetch(taskID string) (taskstatus.TaskStatus, error) {
-	req, err := http.NewRequest("GET", f.origin+issuetracker.TaskURLSuffixFor(taskID, f.tracker), nil)
+	req, err := http.NewRequest("GET", f.IssueTracker.IssueAPIOrigin()+f.IssueTracker.TaskURLFrom(taskID), nil)
 	if err != nil {
 		return taskstatus.None, fmt.Errorf("failed creating new GET request: %w", err)
 	}
@@ -49,7 +48,7 @@ func (f *Fetcher) Fetch(taskID string) (taskstatus.TaskStatus, error) {
 		return taskstatus.None, fmt.Errorf("bad status code upon fetching task: %d - %s", resp.StatusCode, string(body))
 	}
 
-	task := issuetracker.TaskFor(f.tracker)
+	task := f.IssueTracker.TaskModel()
 	err = json.Unmarshal(body, &task)
 	if err != nil {
 		return taskstatus.None, fmt.Errorf("couldn't unmarshal response task JSON: %w", err)
diff --git a/issuetracker/factory/factory.go b/issuetracker/factory/factory.go
new file mode 100644
index 0000000..e1a562d
--- /dev/null
+++ b/issuetracker/factory/factory.go
@@ -0,0 +1,32 @@
+package factory
+
+import (
+	"errors"
+
+	"github.com/preslavmihaylov/todocheck/config"
+	"github.com/preslavmihaylov/todocheck/issuetracker"
+	"github.com/preslavmihaylov/todocheck/issuetracker/github"
+	"github.com/preslavmihaylov/todocheck/issuetracker/gitlab"
+	"github.com/preslavmihaylov/todocheck/issuetracker/jira"
+	"github.com/preslavmihaylov/todocheck/issuetracker/pivotaltracker"
+	"github.com/preslavmihaylov/todocheck/issuetracker/redmine"
+)
+
+// NewIssueTrackerFrom is a static factory method for creating an issuetracker.IssueTracker instance based on the chosen issue tracker type
+// in the configuration
+func NewIssueTrackerFrom(issueTrackerType config.IssueTracker, origin string) (issuetracker.IssueTracker, error) {
+	switch issueTrackerType {
+	case config.IssueTrackerGithub:
+		return &github.IssueTracker{Origin: origin}, nil
+	case config.IssueTrackerJira:
+		return &jira.IssueTracker{Origin: origin}, nil
+	case config.IssueTrackerGitlab:
+		return &gitlab.IssueTracker{Origin: origin}, nil
+	case config.IssueTrackerRedmine:
+		return &redmine.IssueTracker{Origin: origin}, nil
+	case config.IssueTrackerPivotal:
+		return &pivotaltracker.IssueTracker{Origin: origin}, nil
+	}
+
+	return nil, errors.New("unknown issue tracker " + string(issueTrackerType))
+}
diff --git a/issuetracker/github/github.go b/issuetracker/github/github.go
new file mode 100644
index 0000000..b27fcda
--- /dev/null
+++ b/issuetracker/github/github.go
@@ -0,0 +1,39 @@
+package github
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/preslavmihaylov/todocheck/common"
+	"github.com/preslavmihaylov/todocheck/issuetracker"
+)
+
+// IssueTracker implementation for integrating with public & private github issue trackers
+type IssueTracker struct {
+	Origin string
+}
+
+// TaskModel returns the model representing a deserialized github task
+func (it *IssueTracker) TaskModel() issuetracker.Task {
+	return &Task{}
+}
+
+// TaskURLFrom taskID returns the url for the target github task ID to fetch
+func (it *IssueTracker) TaskURLFrom(taskID string) string {
+	if len(taskID) > 0 && taskID[0] == '#' {
+		return taskID[1:]
+	}
+
+	return taskID
+}
+
+// IssueAPIOrigin returns the URL for github's issue-fetching API
+func (it *IssueTracker) IssueAPIOrigin() string {
+	tokens := common.RemoveEmptyTokens(strings.Split(it.Origin, "/"))
+	if !strings.HasPrefix(tokens[0], "http") {
+		tokens = append([]string{"https:"}, tokens...)
+	}
+
+	scheme, owner, repo := tokens[0], tokens[2], tokens[3]
+	return fmt.Sprintf("%s//api.github.com/repos/%s/%s/issues/", scheme, owner, repo)
+}
diff --git a/issuetracker/models/github.go b/issuetracker/github/task.go
similarity index 54%
rename from issuetracker/models/github.go
rename to issuetracker/github/task.go
index d0285d0..5a5e1c8 100644
--- a/issuetracker/models/github.go
+++ b/issuetracker/github/task.go
@@ -1,14 +1,14 @@
-package models
+package github
 
 import "github.com/preslavmihaylov/todocheck/issuetracker/taskstatus"
 
-// GithubTask model
-type GithubTask struct {
+// Task model
+type Task struct {
 	State string `json:"state"`
 }
 
-// GetStatus of jira task, based on underlying structure
-func (t *GithubTask) GetStatus() taskstatus.TaskStatus {
+// GetStatus of github task, based on underlying structure
+func (t *Task) GetStatus() taskstatus.TaskStatus {
 	switch t.State {
 	case "closed":
 		return taskstatus.Closed
diff --git a/issuetracker/gitlab/gitlab.go b/issuetracker/gitlab/gitlab.go
new file mode 100644
index 0000000..e66f03e
--- /dev/null
+++ b/issuetracker/gitlab/gitlab.go
@@ -0,0 +1,37 @@
+package gitlab
+
+import (
+	"fmt"
+	"net/url"
+	"strings"
+
+	"github.com/preslavmihaylov/todocheck/common"
+	"github.com/preslavmihaylov/todocheck/issuetracker"
+)
+
+// IssueTracker is an issue tracker implementation for integrating with public & private gitlab issue trackers
+type IssueTracker struct {
+	Origin string
+}
+
+// TaskModel returns the model representing a deserialized gitlab task
+func (it *IssueTracker) TaskModel() issuetracker.Task {
+	return &Task{}
+}
+
+// TaskURLFrom taskID returns the url for the target gitlab task ID to fetch
+func (it *IssueTracker) TaskURLFrom(taskID string) string {
+	return taskID
+}
+
+// IssueAPIOrigin returns the URL for github's issue-fetching API
+func (it *IssueTracker) IssueAPIOrigin() string {
+	tokens := common.RemoveEmptyTokens(strings.Split(it.Origin, "/"))
+	if tokens[0] != "http:" && tokens[0] != "https:" {
+		tokens = append([]string{"https:"}, tokens...)
+	}
+
+	scheme, host, owner, repo := tokens[0], tokens[1], tokens[2], tokens[3]
+	urlEncodedProject := url.QueryEscape(fmt.Sprintf("%s/%s", owner, repo))
+	return fmt.Sprintf("%s//%s/api/v4/projects/%s/issues/", scheme, host, urlEncodedProject)
+}
diff --git a/issuetracker/models/gitlab.go b/issuetracker/gitlab/task.go
similarity index 69%
rename from issuetracker/models/gitlab.go
rename to issuetracker/gitlab/task.go
index 6b590fd..a34a5e1 100644
--- a/issuetracker/models/gitlab.go
+++ b/issuetracker/gitlab/task.go
@@ -1,14 +1,14 @@
-package models
+package gitlab
 
 import "github.com/preslavmihaylov/todocheck/issuetracker/taskstatus"
 
-// GitlabTask model
-type GitlabTask struct {
+// Task model for gitlab tasks
+type Task struct {
 	State string `json:"state"`
 }
 
 // GetStatus of gitlab task, based on underlying structure
-func (t *GitlabTask) GetStatus() taskstatus.TaskStatus {
+func (t *Task) GetStatus() taskstatus.TaskStatus {
 	switch t.State {
 	case "closed":
 		return taskstatus.Closed
diff --git a/issuetracker/issuetracker.go b/issuetracker/issuetracker.go
index 52963f6..7b5245e 100644
--- a/issuetracker/issuetracker.go
+++ b/issuetracker/issuetracker.go
@@ -3,87 +3,36 @@ package issuetracker
 import (
 	"errors"
 	"fmt"
-	"net/url"
 	"strings"
 
 	"github.com/preslavmihaylov/todocheck/common"
 	"github.com/preslavmihaylov/todocheck/config"
-	"github.com/preslavmihaylov/todocheck/issuetracker/models"
+	"github.com/preslavmihaylov/todocheck/issuetracker/taskstatus"
 )
 
 // ErrUnsupportedHealthCheck is returned when the health check doesn't support the given issue tracker
 var ErrUnsupportedHealthCheck = errors.New("unsupported issue tracker for health check")
 
-// TaskFor gets the corresponding task model, based on the issue tracker type
-func TaskFor(issueTracker config.IssueTracker) models.Task {
-	switch issueTracker {
-	case config.IssueTrackerJira:
-		return &models.JiraTask{}
-	case config.IssueTrackerGithub:
-		return &models.GithubTask{}
-	case config.IssueTrackerGitlab:
-		return &models.GitlabTask{}
-	case config.IssueTrackerPivotal:
-		return &models.PivotalTrackerTask{}
-	case config.IssueTrackerRedmine:
-		return &models.RedmineTask{}
-	default:
-		return nil
-	}
-}
-
-// TaskURLSuffixFor the given issue tracker. Returns the appropriate task ID to append to rest api request.
-// In most use-cases, the taskID is returned as-is as it is simply appended to the issue tracker URL origin (e.g. issuetracker.com/issues/{taskID}.
-// However, some issue trackers might expect the taskID to be appended in a special format
-func TaskURLSuffixFor(taskID string, issueTracker config.IssueTracker) string {
-	switch issueTracker {
-	case config.IssueTrackerRedmine:
-		return taskID + ".json"
-	case config.IssueTrackerPivotal, config.IssueTrackerGithub:
-		if len(taskID) > 0 && taskID[0] == '#' {
-			return taskID[1:]
-		}
-
-		return taskID
-	default:
-		return taskID
-	}
+// Task is an interface for generic task operations, decoupled from the specific platform's task structure
+type Task interface {
+	GetStatus() taskstatus.TaskStatus
 }
 
-// BaseURLFor returns the task-fetching base url given the issue tracker type and the site origin
-func BaseURLFor(issueTracker config.IssueTracker, origin string) (string, error) {
-	switch issueTracker {
-	case config.IssueTrackerJira:
-		return fmt.Sprintf("%s/rest/api/latest/issue/", origin), nil
-	case config.IssueTrackerGithub:
-		scheme, owner, repo := parseGithubDetails(origin)
-		return fmt.Sprintf("%s//api.github.com/repos/%s/%s/issues/", scheme, owner, repo), nil
-	case config.IssueTrackerGitlab:
-		tokens := common.RemoveEmptyTokens(strings.Split(origin, "/"))
-		if tokens[0] != "http:" && tokens[0] != "https:" {
-			tokens = append([]string{"https:"}, tokens...)
-		}
-
-		scheme, host, owner, repo := tokens[0], tokens[1], tokens[2], tokens[3]
-		urlEncodedProject := url.QueryEscape(fmt.Sprintf("%s/%s", owner, repo))
-		return fmt.Sprintf("%s//%s/api/v4/projects/%s/issues/", scheme, host, urlEncodedProject), nil
-	case config.IssueTrackerPivotal:
-		tokens := common.RemoveEmptyTokens(strings.Split(origin, "/"))
-		if tokens[0] == "pivotaltracker.com" {
-			tokens = append([]string{"https:"}, tokens...)
-		}
-
-		scheme, project := tokens[0], tokens[3]
-		if tokens[2] == "n" {
-			project = tokens[4]
-		}
-
-		return fmt.Sprintf("%s//www.pivotaltracker.com/services/v5/projects/%s/stories/", scheme, project), nil
-	case config.IssueTrackerRedmine:
-		return fmt.Sprintf("%s/issues/", origin), nil
-	default:
-		return "", errors.New("unknown issue tracker " + string(issueTracker))
-	}
+// IssueTracker is an interface, which all issue tracker integration components adhere to in order to
+// detach the specific issue trackers from the high-level rules for using issue trackers in the system
+type IssueTracker interface {
+	// returns a Task model, specific to the given issue tracker, which can be unmarshaled from JSON
+	TaskModel() Task
+
+	// TaskURLFrom the given task ID. Returns the appropriate task ID to append to rest api request.
+	// In most use-cases, the taskID is returned as-is as it is simply appended to the issue tracker URL origin
+	// (e.g. issuetracker.com/issues/{taskID}.
+	//
+	// However, some issue trackers might expect the taskID to be appended in a special format
+	TaskURLFrom(taskID string) string
+
+	// IssueAPIOrigin returns the task-fetching base url for the given issue tracker
+	IssueAPIOrigin() string
 }
 
 // HealthcheckURL returns the health check base url given the issue tracker type and the site origin
diff --git a/issuetracker/jira/jira.go b/issuetracker/jira/jira.go
new file mode 100644
index 0000000..13171aa
--- /dev/null
+++ b/issuetracker/jira/jira.go
@@ -0,0 +1,27 @@
+package jira
+
+import (
+	"fmt"
+
+	"github.com/preslavmihaylov/todocheck/issuetracker"
+)
+
+// IssueTracker is an issue tracker implementation for integrating with private Jira servers
+type IssueTracker struct {
+	Origin string
+}
+
+// TaskModel returns the model representing a deserialized Jira task
+func (it *IssueTracker) TaskModel() issuetracker.Task {
+	return &Task{}
+}
+
+// TaskURLFrom taskID returns the url for the target Jira task ID to fetch
+func (it *IssueTracker) TaskURLFrom(taskID string) string {
+	return taskID
+}
+
+// IssueAPIOrigin returns the URL for Jira's issue-fetching API
+func (it *IssueTracker) IssueAPIOrigin() string {
+	return fmt.Sprintf("%s/rest/api/latest/issue/", it.Origin)
+}
diff --git a/issuetracker/models/jira.go b/issuetracker/jira/task.go
similarity index 76%
rename from issuetracker/models/jira.go
rename to issuetracker/jira/task.go
index 3455053..ac825eb 100644
--- a/issuetracker/models/jira.go
+++ b/issuetracker/jira/task.go
@@ -1,4 +1,4 @@
-package models
+package jira
 
 import "github.com/preslavmihaylov/todocheck/issuetracker/taskstatus"
 
@@ -12,13 +12,13 @@ type Fields struct {
 	Status `json:"status"`
 }
 
-// JiraTask JSON model as returned by Rest API
-type JiraTask struct {
+// Task JSON model as returned by the Jira Rest API
+type Task struct {
 	Fields `json:"fields"`
 }
 
 // GetStatus of jira task, based on underlying structure
-func (t *JiraTask) GetStatus() taskstatus.TaskStatus {
+func (t *Task) GetStatus() taskstatus.TaskStatus {
 	switch t.Fields.Status.Name {
 	case "Done":
 		fallthrough
diff --git a/issuetracker/models/models.go b/issuetracker/models/models.go
deleted file mode 100644
index af6cba1..0000000
--- a/issuetracker/models/models.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package models
-
-import "github.com/preslavmihaylov/todocheck/issuetracker/taskstatus"
-
-// Task is an interface for generic task operations, decoupled from the specific platform's task structure
-type Task interface {
-	GetStatus() taskstatus.TaskStatus
-}
diff --git a/issuetracker/pivotaltracker/pivotaltracker.go b/issuetracker/pivotaltracker/pivotaltracker.go
new file mode 100644
index 0000000..42abfa7
--- /dev/null
+++ b/issuetracker/pivotaltracker/pivotaltracker.go
@@ -0,0 +1,43 @@
+package pivotaltracker
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/preslavmihaylov/todocheck/common"
+	"github.com/preslavmihaylov/todocheck/issuetracker"
+)
+
+// IssueTracker implementation for integrating with public pivotaltracker issue trackers
+type IssueTracker struct {
+	Origin string
+}
+
+// TaskModel returns the model representing a deserialized pivotaltracker task
+func (it *IssueTracker) TaskModel() issuetracker.Task {
+	return &Task{}
+}
+
+// TaskURLFrom taskID returns the url for the target pivotaltracker task ID to fetch
+func (it *IssueTracker) TaskURLFrom(taskID string) string {
+	if len(taskID) > 0 && taskID[0] == '#' {
+		return taskID[1:]
+	}
+
+	return taskID
+}
+
+// IssueAPIOrigin returns the URL for pivotaltracker's issue-fetching API
+func (it *IssueTracker) IssueAPIOrigin() string {
+	tokens := common.RemoveEmptyTokens(strings.Split(it.Origin, "/"))
+	if tokens[0] == "pivotaltracker.com" {
+		tokens = append([]string{"https:"}, tokens...)
+	}
+
+	scheme, project := tokens[0], tokens[3]
+	if tokens[2] == "n" {
+		project = tokens[4]
+	}
+
+	return fmt.Sprintf("%s//www.pivotaltracker.com/services/v5/projects/%s/stories/", scheme, project)
+}
diff --git a/issuetracker/models/pivotaltracker.go b/issuetracker/pivotaltracker/task.go
similarity index 73%
rename from issuetracker/models/pivotaltracker.go
rename to issuetracker/pivotaltracker/task.go
index 51a5494..8447574 100644
--- a/issuetracker/models/pivotaltracker.go
+++ b/issuetracker/pivotaltracker/task.go
@@ -1,14 +1,14 @@
-package models
+package pivotaltracker
 
 import "github.com/preslavmihaylov/todocheck/issuetracker/taskstatus"
 
-// PivotalTrackerTask model
-type PivotalTrackerTask struct {
+// Task model
+type Task struct {
 	CurrentState string `json:"current_state"`
 }
 
 // GetStatus of pivotal tracker task, based on underlying structure
-func (t *PivotalTrackerTask) GetStatus() taskstatus.TaskStatus {
+func (t *Task) GetStatus() taskstatus.TaskStatus {
 	switch t.CurrentState {
 	case "finished":
 		fallthrough
diff --git a/issuetracker/redmine/redmine.go b/issuetracker/redmine/redmine.go
new file mode 100644
index 0000000..e39f8b3
--- /dev/null
+++ b/issuetracker/redmine/redmine.go
@@ -0,0 +1,27 @@
+package redmine
+
+import (
+	"fmt"
+
+	"github.com/preslavmihaylov/todocheck/issuetracker"
+)
+
+// IssueTracker implementation for integrating with public & private redmine issue trackers
+type IssueTracker struct {
+	Origin string
+}
+
+// TaskModel returns the model representing a deserialized redmine task
+func (it *IssueTracker) TaskModel() issuetracker.Task {
+	return &Task{}
+}
+
+// TaskURLFrom taskID returns the url for the target redmine task ID to fetch
+func (it *IssueTracker) TaskURLFrom(taskID string) string {
+	return taskID + ".json"
+}
+
+// IssueAPIOrigin returns the URL for redmine's issue-fetching API
+func (it *IssueTracker) IssueAPIOrigin() string {
+	return fmt.Sprintf("%s/issues/", it.Origin)
+}
diff --git a/issuetracker/models/redmine.go b/issuetracker/redmine/task.go
similarity index 67%
rename from issuetracker/models/redmine.go
rename to issuetracker/redmine/task.go
index a42a223..3fa3c70 100644
--- a/issuetracker/models/redmine.go
+++ b/issuetracker/redmine/task.go
@@ -1,9 +1,9 @@
-package models
+package redmine
 
 import "github.com/preslavmihaylov/todocheck/issuetracker/taskstatus"
 
-// RedmineTask model
-type RedmineTask struct {
+// Task model
+type Task struct {
 	Issue struct {
 		Status struct {
 			Name string `json:"name"`
@@ -11,8 +11,8 @@ type RedmineTask struct {
 	} `json:"issue"`
 }
 
-// GetStatus of pivotal tracker task, based on underlying structure
-func (t *RedmineTask) GetStatus() taskstatus.TaskStatus {
+// GetStatus of redmine task, based on underlying structure
+func (t *Task) GetStatus() taskstatus.TaskStatus {
 	switch t.Issue.Status.Name {
 	case "Resolved":
 		fallthrough
diff --git a/main.go b/main.go
index 59e3e60..d3f87a5 100644
--- a/main.go
+++ b/main.go
@@ -13,7 +13,7 @@ import (
 	todocheckerrors "github.com/preslavmihaylov/todocheck/checker/errors"
 	"github.com/preslavmihaylov/todocheck/config"
 	"github.com/preslavmihaylov/todocheck/fetcher"
-	"github.com/preslavmihaylov/todocheck/issuetracker"
+	"github.com/preslavmihaylov/todocheck/issuetracker/factory"
 	"github.com/preslavmihaylov/todocheck/traverser/todoerrs"
 	"github.com/preslavmihaylov/todocheck/validation"
 )
@@ -55,14 +55,14 @@ func main() {
 		os.Exit(1)
 	}
 
-	baseURL, err := issuetracker.BaseURLFor(localCfg.IssueTracker, localCfg.Origin)
+	tracker, err := factory.NewIssueTrackerFrom(localCfg.IssueTracker, localCfg.Origin)
 	if err != nil {
-		log.Fatalf("couldn't get base url for origin %s & issue tracker %s: %s\n",
-			localCfg.Origin, localCfg.IssueTracker, err)
+		log.Fatalf("couldn't create new issue tracker: %s\n", err)
 	}
 
+	f := fetcher.NewFetcher(tracker, authmiddleware.For(localCfg))
+
 	todoErrs := []*todocheckerrors.TODO{}
-	f := fetcher.NewFetcher(baseURL, localCfg.IssueTracker, authmiddleware.For(localCfg))
 	traverser := todoerrs.NewTraverser(f, localCfg.IgnoredPaths, func(todoErr *todocheckerrors.TODO) error {
 		todoErrs = append(todoErrs, todoErr)
 		return nil


Test patch:
diff --git a/testing/scenariobuilder/issuetracker/issuetracker.go b/testing/scenariobuilder/issuetracker/issuetracker.go
index a0b5e01..7de814c 100644
--- a/testing/scenariobuilder/issuetracker/issuetracker.go
+++ b/testing/scenariobuilder/issuetracker/issuetracker.go
@@ -3,7 +3,7 @@ package issuetracker
 import (
 	"encoding/json"
 
-	"github.com/preslavmihaylov/todocheck/issuetracker/models"
+	"github.com/preslavmihaylov/todocheck/issuetracker/jira"
 )
 
 // Type is an enum specifying the target mock issue tracker to use
@@ -41,9 +41,9 @@ func IssueURLFrom(t Type, issue string) string {
 func BuildResponseFor(t Type, issue string, status Status) []byte {
 	switch t {
 	case Jira:
-		return must(json.Marshal(&models.JiraTask{
-			Fields: models.Fields{
-				Status: models.Status{
+		return must(json.Marshal(&jira.Task{
+			Fields: jira.Fields{
+				Status: jira.Status{
 					Name: string(status),
 				},
 			},