Merge pull request #1062 from merico-dev/fix1061
Fix Github commits data lost
diff --git a/models/domainlayer/ticket/board.go b/models/domainlayer/ticket/board.go
index 07ddf4b..9399c40 100644
--- a/models/domainlayer/ticket/board.go
+++ b/models/domainlayer/ticket/board.go
@@ -1,11 +1,20 @@
package ticket
import (
+ "time"
+
"github.com/merico-dev/lake/models/domainlayer"
)
type Board struct {
domainlayer.DomainEntity
- Name string
- Url string
+ Name string
+ Description string
+ Url string
+ CreatedDate *time.Time
+}
+
+type BoardSprint struct {
+ BoardId string `gorm:"primaryKey"`
+ SprintId string `gorm:"primaryKey"`
}
diff --git a/models/domainlayer/ticket/issue.go b/models/domainlayer/ticket/issue.go
index 479f971..9fcbf99 100644
--- a/models/domainlayer/ticket/issue.go
+++ b/models/domainlayer/ticket/issue.go
@@ -8,27 +8,23 @@
type Issue struct {
domainlayer.DomainEntity
-
- // collected fields
- Url string
- Key string
- Title string
- Summary string
- EpicKey string
- Type string
- Status string
- StoryPoint uint
- OriginalEstimateMinutes int64 // user input?
- AggregateEstimateMinutes int64 // sum up of all subtasks?
- RemainingEstimateMinutes int64 // could it be negative value?
- CreatorId string
- AssigneeId string
- ResolutionDate *time.Time
- Priority string // not sure how to deal with it yet, copy the name for now
- ParentId string
- SprintId string
- CreatedDate time.Time
- UpdatedDate time.Time
- SpentMinutes int64
- LeadTimeMinutes uint
+ Url string
+ Key string
+ Title string
+ Summary string
+ EpicKey string
+ Type string
+ Status string
+ StoryPoint uint
+ ResolutionDate *time.Time
+ CreatedDate *time.Time
+ UpdatedDate *time.Time
+ LeadTimeMinutes uint
+ ParentIssueId string
+ Priority string
+ OriginalEstimateMinutes int64
+ TimeRemainingMinutes int64
+ CreatorId string
+ AssigneeId string
+ OwnerId string
}
diff --git a/models/domainlayer/ticket/issue_history.go b/models/domainlayer/ticket/issue_history.go
new file mode 100644
index 0000000..2b66e27
--- /dev/null
+++ b/models/domainlayer/ticket/issue_history.go
@@ -0,0 +1,43 @@
+package ticket
+
+import (
+ "time"
+
+ "github.com/merico-dev/lake/models/common"
+)
+
+type IssueStatusHistory struct {
+ common.NoPKModel
+ IssueId string `gorm:"primaryKey"`
+ Status string `gorm:"primaryKey"`
+ StartDate time.Time `gorm:"primaryKey"`
+ EndDate *time.Time
+}
+
+func (IssueStatusHistory) TableName() string {
+ return "issue_status_history"
+}
+
+type IssueAssigneeHistory struct {
+ common.NoPKModel
+ IssueId string `gorm:"primaryKey"`
+ Assignee string `gorm:"primaryKey"`
+ StartDate time.Time `gorm:"primaryKey"`
+ EndDate *time.Time
+}
+
+func (IssueAssigneeHistory) TableName() string {
+ return "issue_assignee_history"
+}
+
+type IssueSprintsHistory struct {
+ common.NoPKModel
+ IssueId string `gorm:"primaryKey"`
+ SprintId string `gorm:"primaryKey"`
+ StartDate time.Time `gorm:"primaryKey"`
+ EndDate *time.Time
+}
+
+func (IssueSprintsHistory) TableName() string {
+ return "issue_sprints_history"
+}
diff --git a/models/domainlayer/ticket/sprint.go b/models/domainlayer/ticket/sprint.go
index 88e985c..db42653 100644
--- a/models/domainlayer/ticket/sprint.go
+++ b/models/domainlayer/ticket/sprint.go
@@ -6,57 +6,29 @@
"github.com/merico-dev/lake/models/domainlayer"
)
+const (
+ BeforeSprint = "BEFORE_SPRINT"
+ DuringSprint = "DURING_SPRINT"
+ AfterSprint = "AFTER_SPRINT"
+)
+
type Sprint struct {
domainlayer.DomainEntity
-
- // collected fields
- BoardId string `gorm:"index"`
- Url string
- State string
- Name string
- StartDate *time.Time
- EndDate *time.Time
- CompleteDate *time.Time
+ Name string
+ Url string
+ Status string
+ Title string
+ StartedDate *time.Time
+ EndedDate *time.Time
+ CompletedDate *time.Time
}
type SprintIssue struct {
- SprintId string `gorm:"primaryKey"`
- IssueId string `gorm:"primaryKey"`
- AddedAt *time.Time
- RemovedAt *time.Time
-}
-
-type SprintIssueBurndown struct {
- SprintId string `gorm:"primaryKey"`
- EndedHour int `gorm:"primaryKey"`
- StartedAt time.Time
- EndedAt time.Time
-
- Added int
- Removed int
- Remaining int
-
- AddedRequirements int
- RemovedRequirements int
- RemainingRequirements int
-
- AddedBugs int
- RemovedBugs int
- RemainingBugs int
-
- AddedIncidents int
- RemovedIncidents int
- RemainingIncidents int
-
- AddedOtherIssues int
- RemovedOtherIssues int
- RemainingOtherIssues int
-
- AddedStoryPoints int
- RemovedStoryPoints int
- RemainingStoryPoints int
-}
-
-func (SprintIssueBurndown) TableName() string {
- return "sprint_issue_burndown"
-}
+ SprintId string `gorm:"primaryKey"`
+ IssueId string `gorm:"primaryKey"`
+ IsRemoved bool
+ AddedDate *time.Time
+ RemovedDate *time.Time
+ AddedStage string
+ ResolvedStage string
+}
\ No newline at end of file
diff --git a/models/domainlayer/ticket/worklog.go b/models/domainlayer/ticket/worklog.go
index 1aab868..31b3a77 100644
--- a/models/domainlayer/ticket/worklog.go
+++ b/models/domainlayer/ticket/worklog.go
@@ -8,12 +8,10 @@
type Worklog struct {
domainlayer.DomainEntity
- IssueId string `gorm:"index"`
- BoardId string `gorm:"index"`
AuthorId string
- UpdateAuthorId string
- TimeSpent string
- TimeSpentSeconds int
- Updated time.Time
- Started time.Time
+ Comment string
+ TimeSpentMinutes int
+ LoggedDate *time.Time
+ StartedDate *time.Time
+ IssueId string `gorm:"index"`
}
diff --git a/models/init.go b/models/init.go
index 543dad6..a6fbb92 100644
--- a/models/init.go
+++ b/models/init.go
@@ -60,10 +60,13 @@
&ticket.Board{},
&ticket.Issue{},
&ticket.BoardIssue{},
+ &ticket.BoardSprint{},
&ticket.Changelog{},
&ticket.Sprint{},
&ticket.SprintIssue{},
- &ticket.SprintIssueBurndown{},
+ &ticket.IssueStatusHistory{},
+ &ticket.IssueSprintsHistory{},
+ &ticket.IssueAssigneeHistory{},
&devops.Job{},
&devops.Build{},
&ticket.Worklog{},
diff --git a/models/test/changelog_model_test.go b/models/test/changelog_model_test.go
index 93cec9a..18bca39 100644
--- a/models/test/changelog_model_test.go
+++ b/models/test/changelog_model_test.go
@@ -9,11 +9,11 @@
)
func TestInsertChangelog(t *testing.T) {
- board, err := factory.CreateBoard()
+ _, err := factory.CreateBoard()
assert.Nil(t, err)
- issue, err := factory.CreateIssue(board.DomainEntity.Id)
+ issue, err := factory.CreateIssue()
assert.Nil(t, err)
- changelog, err := factory.CreateChangelog(issue.DomainEntity.Id)
+ changelog, err := factory.CreateChangelog(issue.Id)
assert.Nil(t, err)
tx := models.Db.Create(&changelog)
assert.Nil(t, tx.Error)
diff --git a/models/test/factory/issue_factory.go b/models/test/factory/issue_factory.go
index d3df9ae..fee08ab 100644
--- a/models/test/factory/issue_factory.go
+++ b/models/test/factory/issue_factory.go
@@ -7,7 +7,8 @@
"github.com/merico-dev/lake/models/domainlayer/ticket"
)
-func CreateIssue(boardId string) (*ticket.Issue, error) {
+func CreateIssue() (*ticket.Issue, error) {
+ now := time.Now()
issue := &ticket.Issue{
DomainEntity: domainlayer.DomainEntity{
Id: RandIntString(),
@@ -21,17 +22,13 @@
Status: "",
StoryPoint: 1,
OriginalEstimateMinutes: 1, // user input?
- AggregateEstimateMinutes: 1, // sum up of all subtasks?
- RemainingEstimateMinutes: 1, // could it be negative value?
CreatorId: "",
AssigneeId: "",
ResolutionDate: nil,
Priority: "", // not sure how to deal with it yet, copy the name for now
- ParentId: "",
- SprintId: "",
- CreatedDate: time.Now(),
- UpdatedDate: time.Now(),
- SpentMinutes: 1,
+ ParentIssueId: "",
+ CreatedDate: &now,
+ UpdatedDate: &now,
LeadTimeMinutes: 1,
}
return issue, nil
diff --git a/models/test/factory/sprint_factory.go b/models/test/factory/sprint_factory.go
index 2a8ec4a..b860034 100644
--- a/models/test/factory/sprint_factory.go
+++ b/models/test/factory/sprint_factory.go
@@ -10,13 +10,12 @@
DomainEntity: domainlayer.DomainEntity{
Id: RandIntString(),
},
- BoardId: boardId, // ref to board
- Url: "",
- State: "",
- Name: "",
- StartDate: nil,
- EndDate: nil,
- CompleteDate: nil,
+ Url: "",
+ Status: "",
+ Name: "",
+ StartedDate: nil,
+ EndedDate: nil,
+ CompletedDate: nil,
}
return sprint, nil
}
diff --git a/models/test/factory/worklog_factory.go b/models/test/factory/worklog_factory.go
index 161a0e3..ca5ea6e 100644
--- a/models/test/factory/worklog_factory.go
+++ b/models/test/factory/worklog_factory.go
@@ -1,8 +1,6 @@
package factory
import (
- "time"
-
"github.com/merico-dev/lake/models/domainlayer"
"github.com/merico-dev/lake/models/domainlayer/ticket"
)
@@ -12,14 +10,8 @@
DomainEntity: domainlayer.DomainEntity{
Id: RandIntString(),
},
- IssueId: issueId, // ref to issue
- BoardId: boardId, // ref to board
- AuthorId: "",
- UpdateAuthorId: "",
- TimeSpent: "",
- TimeSpentSeconds: RandInt(),
- Updated: time.Now(),
- Started: time.Now(),
+ IssueId: issueId, // ref to issue
+ AuthorId: "",
}
return worklog, nil
}
diff --git a/models/test/issue_model_test.go b/models/test/issue_model_test.go
index 2eaf58c..c22ff5f 100644
--- a/models/test/issue_model_test.go
+++ b/models/test/issue_model_test.go
@@ -9,9 +9,7 @@
)
func TestInsertIssue(t *testing.T) {
- board, err := factory.CreateBoard()
- assert.Nil(t, err)
- issue, err := factory.CreateIssue(board.DomainEntity.Id)
+ issue, err := factory.CreateIssue()
assert.Nil(t, err)
tx := models.Db.Create(&issue)
assert.Nil(t, tx.Error)
diff --git a/models/test/worklog_model_test.go b/models/test/worklog_model_test.go
index da8c36d..bc9f848 100644
--- a/models/test/worklog_model_test.go
+++ b/models/test/worklog_model_test.go
@@ -11,9 +11,9 @@
func TestInsertWorklog(t *testing.T) {
board, err := factory.CreateBoard()
assert.Nil(t, err)
- issue, err := factory.CreateIssue(board.DomainEntity.Id)
+ issue, err := factory.CreateIssue()
assert.Nil(t, err)
- worklog, err := factory.CreateWorklog(board.DomainEntity.Id, issue.DomainEntity.Id)
+ worklog, err := factory.CreateWorklog(board.Id, issue.Id)
assert.Nil(t, err)
tx := models.Db.Create(&worklog)
assert.Nil(t, tx.Error)
diff --git a/plugins/github/tasks/github_issue_converter.go b/plugins/github/tasks/github_issue_converter.go
index bbe2bfc..22e9e4d 100644
--- a/plugins/github/tasks/github_issue_converter.go
+++ b/plugins/github/tasks/github_issue_converter.go
@@ -2,9 +2,9 @@
import (
"fmt"
+ "github.com/merico-dev/lake/models/domainlayer"
lakeModels "github.com/merico-dev/lake/models"
- "github.com/merico-dev/lake/models/domainlayer"
"github.com/merico-dev/lake/models/domainlayer/didgen"
"github.com/merico-dev/lake/models/domainlayer/ticket"
githubModels "github.com/merico-dev/lake/plugins/github/models"
@@ -38,9 +38,7 @@
func convertToIssueModel(issue *githubModels.GithubIssue) *ticket.Issue {
domainIssue := &ticket.Issue{
- DomainEntity: domainlayer.DomainEntity{
- Id: didgen.NewDomainIdGenerator(issue).Generate(issue.GithubId),
- },
+ DomainEntity:domainlayer.DomainEntity{Id: didgen.NewDomainIdGenerator(issue).Generate(issue.GithubId)},
Key: fmt.Sprint(issue.GithubId),
Title: issue.Title,
Summary: issue.Body,
@@ -49,8 +47,8 @@
Type: issue.Type,
AssigneeId: issue.Assignee,
LeadTimeMinutes: issue.LeadTimeMinutes,
- CreatedDate: issue.GithubCreatedAt,
- UpdatedDate: issue.GithubUpdatedAt,
+ CreatedDate: &issue.GithubCreatedAt,
+ UpdatedDate: &issue.GithubUpdatedAt,
ResolutionDate: issue.ClosedAt,
}
return domainIssue
diff --git a/plugins/jira/tasks/jira_board_converter.go b/plugins/jira/tasks/jira_board_converter.go
index 867e685..be29825 100644
--- a/plugins/jira/tasks/jira_board_converter.go
+++ b/plugins/jira/tasks/jira_board_converter.go
@@ -18,9 +18,7 @@
}
board := &ticket.Board{
- DomainEntity: domainlayer.DomainEntity{
- Id: didgen.NewDomainIdGenerator(jiraBoard).Generate(jiraBoard.SourceId, boardId),
- },
+ DomainEntity:domainlayer.DomainEntity{Id: didgen.NewDomainIdGenerator(jiraBoard).Generate(jiraBoard.SourceId, boardId)},
Name: jiraBoard.Name,
Url: jiraBoard.Self,
}
diff --git a/plugins/jira/tasks/jira_changelog_converter.go b/plugins/jira/tasks/jira_changelog_converter.go
index 0e49b31..c4813fa 100644
--- a/plugins/jira/tasks/jira_changelog_converter.go
+++ b/plugins/jira/tasks/jira_changelog_converter.go
@@ -65,7 +65,7 @@
logger.Info("convert changelog", fmt.Sprintf("%s .. %s", batch[0].Id, batch[i-1].Id))
return nil
}
- burndownConverter := NewSprintIssueBurndownConverter()
+ sprintIssueConverter := NewSprintIssueConverter()
row := &ChangelogItemResult{}
// iterate all rows
for cursor.Next() {
@@ -93,7 +93,7 @@
changelog.To = row.ToString
changelog.CreatedDate = row.Created
i++
- burndownConverter.FeedIn(sourceId, *row)
+ sprintIssueConverter.FeedIn(sourceId, *row)
}
if i > 0 {
err = saveBatch()
@@ -101,11 +101,7 @@
return err
}
}
- err = burndownConverter.Save()
- if err != nil {
- logger.Error("save error:", err)
- }
- err = burndownConverter.UpdateSprintIssue()
+ err = sprintIssueConverter.UpdateSprintIssue()
if err != nil {
logger.Error("update sprint issue error:", err)
}
diff --git a/plugins/jira/tasks/jira_issue_converter.go b/plugins/jira/tasks/jira_issue_converter.go
index 73d0c41..bdc606c 100644
--- a/plugins/jira/tasks/jira_issue_converter.go
+++ b/plugins/jira/tasks/jira_issue_converter.go
@@ -25,7 +25,6 @@
issueIdGen := didgen.NewDomainIdGenerator(&jiraModels.JiraIssue{})
userIdGen := didgen.NewDomainIdGenerator(&jiraModels.JiraUser{})
- sprintIdGen := didgen.NewDomainIdGenerator(&jiraModels.JiraSprint{})
boardIssue := &ticket.BoardIssue{
BoardId: didgen.NewDomainIdGenerator(&jiraModels.JiraBoard{}).Generate(sourceId, boardId),
@@ -46,32 +45,26 @@
DomainEntity: domainlayer.DomainEntity{
Id: issueIdGen.Generate(jiraIssue.SourceId, jiraIssue.IssueId),
},
- Url: jiraIssue.Self,
- Key: jiraIssue.Key,
- Summary: jiraIssue.Summary,
- EpicKey: jiraIssue.EpicKey,
- Type: jiraIssue.StdType,
- Status: jiraIssue.StdStatus,
- StoryPoint: jiraIssue.StdStoryPoint,
- OriginalEstimateMinutes: jiraIssue.OriginalEstimateMinutes,
- AggregateEstimateMinutes: jiraIssue.AggregateEstimateMinutes,
- RemainingEstimateMinutes: jiraIssue.RemainingEstimateMinutes,
- CreatorId: userIdGen.Generate(sourceId, jiraIssue.CreatorAccountId),
- ResolutionDate: jiraIssue.ResolutionDate,
- Priority: jiraIssue.PriorityName,
- CreatedDate: jiraIssue.Created,
- UpdatedDate: jiraIssue.Updated,
- LeadTimeMinutes: jiraIssue.LeadTimeMinutes,
- SpentMinutes: jiraIssue.SpentMinutes,
+ Url: jiraIssue.Self,
+ Key: jiraIssue.Key,
+ Summary: jiraIssue.Summary,
+ EpicKey: jiraIssue.EpicKey,
+ Type: jiraIssue.StdType,
+ Status: jiraIssue.StdStatus,
+ StoryPoint: jiraIssue.StdStoryPoint,
+ OriginalEstimateMinutes: jiraIssue.OriginalEstimateMinutes,
+ CreatorId: userIdGen.Generate(sourceId, jiraIssue.CreatorAccountId),
+ ResolutionDate: jiraIssue.ResolutionDate,
+ Priority: jiraIssue.PriorityName,
+ CreatedDate: &jiraIssue.Created,
+ UpdatedDate: &jiraIssue.Updated,
+ LeadTimeMinutes: jiraIssue.LeadTimeMinutes,
}
if jiraIssue.AssigneeAccountId != "" {
issue.AssigneeId = userIdGen.Generate(sourceId, jiraIssue.AssigneeAccountId)
}
if jiraIssue.ParentId != 0 {
- issue.ParentId = issueIdGen.Generate(sourceId, jiraIssue.ParentId)
- }
- if jiraIssue.SprintId != 0 {
- issue.SprintId = sprintIdGen.Generate(sourceId, jiraIssue.SprintId)
+ issue.ParentIssueId = issueIdGen.Generate(sourceId, jiraIssue.ParentId)
}
err = lakeModels.Db.Clauses(clause.OnConflict{UpdateAll: true}).Create(issue).Error
diff --git a/plugins/jira/tasks/jira_sprint_converter.go b/plugins/jira/tasks/jira_sprint_converter.go
index e826ef5..be398ce 100644
--- a/plugins/jira/tasks/jira_sprint_converter.go
+++ b/plugins/jira/tasks/jira_sprint_converter.go
@@ -22,7 +22,7 @@
}
defer cursor.Close()
- boardIdGen := didgen.NewDomainIdGenerator(&jiraModels.JiraBoard{}).Generate(sourceId, boardId)
+ domainBoardId := didgen.NewDomainIdGenerator(&jiraModels.JiraBoard{}).Generate(sourceId, boardId)
sprintIdGen := didgen.NewDomainIdGenerator(&jiraModels.JiraSprint{})
issueIdGen := didgen.NewDomainIdGenerator(&jiraModels.JiraIssue{})
// iterate all rows
@@ -33,16 +33,13 @@
return err
}
sprint := &ticket.Sprint{
- DomainEntity: domainlayer.DomainEntity{
- Id: sprintIdGen.Generate(jiraSprint.SourceId, jiraSprint.SprintId),
- },
- BoardId: boardIdGen,
- Url: jiraSprint.Self,
- State: jiraSprint.State,
- Name: jiraSprint.Name,
- StartDate: jiraSprint.StartDate,
- EndDate: jiraSprint.EndDate,
- CompleteDate: jiraSprint.CompleteDate,
+ DomainEntity:domainlayer.DomainEntity{Id: sprintIdGen.Generate(jiraSprint.SourceId, jiraSprint.SprintId)},
+ Url: jiraSprint.Self,
+ Status: jiraSprint.State,
+ Name: jiraSprint.Name,
+ StartedDate: jiraSprint.StartDate,
+ EndedDate: jiraSprint.EndDate,
+ CompletedDate: jiraSprint.CompleteDate,
}
err = lakeModels.Db.Clauses(clause.OnConflict{UpdateAll: true}).Create(sprint).Error
if err != nil {
@@ -65,6 +62,14 @@
if err != nil {
return err
}
+ boardSprint := &ticket.BoardSprint{
+ BoardId: domainBoardId,
+ SprintId: sprint.Id,
+ }
+ err = lakeModels.Db.Clauses(clause.OnConflict{DoNothing: true}).Create(boardSprint).Error
+ if err != nil {
+ return err
+ }
}
return nil
}
diff --git a/plugins/jira/tasks/jira_sprint_issue_burndown_converter.go b/plugins/jira/tasks/jira_sprint_issue_burndown_converter.go
deleted file mode 100644
index e65bc77..0000000
--- a/plugins/jira/tasks/jira_sprint_issue_burndown_converter.go
+++ /dev/null
@@ -1,349 +0,0 @@
-package tasks
-
-import (
- "fmt"
- "github.com/merico-dev/lake/logger"
- "gorm.io/gorm"
- "gorm.io/gorm/clause"
- "math"
- "strconv"
- "strings"
- "time"
-
- lakeModels "github.com/merico-dev/lake/models"
- "github.com/merico-dev/lake/models/domainlayer/didgen"
- "github.com/merico-dev/lake/models/domainlayer/ticket"
- "github.com/merico-dev/lake/plugins/jira/models"
-)
-
-// issue types
-const (
- Bug = "Bug"
- Story = "Story"
- Incident = "Incident"
- Task = "Task"
-)
-
-const (
- BashSize = 100
-)
-
-var (
- UTCLocation, _ = time.LoadLocation("UTC")
-)
-
-type SprintIssueBurndownConverter struct {
- cache map[string]map[int]*ticket.SprintIssueBurndown
- sprintIdGen *didgen.DomainIdGenerator
- issueIdGen *didgen.DomainIdGenerator
- sprints map[string]*models.JiraSprint
- sprintIssue map[string]*ticket.SprintIssue
-}
-
-func NewSprintIssueBurndownConverter() *SprintIssueBurndownConverter {
- return &SprintIssueBurndownConverter{
- cache: make(map[string]map[int]*ticket.SprintIssueBurndown),
- sprintIdGen: didgen.NewDomainIdGenerator(&models.JiraSprint{}),
- issueIdGen: didgen.NewDomainIdGenerator(&models.JiraIssue{}),
- sprints: make(map[string]*models.JiraSprint),
- sprintIssue: make(map[string]*ticket.SprintIssue),
- }
-}
-
-func (c *SprintIssueBurndownConverter) FeedIn(sourceId uint64, cl ChangelogItemResult) {
- if cl.Field != "Sprint" {
- return
- }
- from, to, err := c.parseFromTo(cl.From, cl.To)
- if err != nil {
- return
- }
- for sprintId := range from {
- err = c.handleFrom(sourceId, sprintId, cl)
- if err != nil {
- logger.Error("handle from error:", err)
- return
- }
- }
- for sprintId := range to {
- err = c.handleTo(sourceId, sprintId, cl)
- if err != nil {
- logger.Error("handle to error:", err)
- return
- }
- }
-}
-
-func (c *SprintIssueBurndownConverter) UpdateSprintIssue() error {
- var err error
- var flag bool
- var list []*ticket.SprintIssue
- for _, fresh := range c.sprintIssue {
- var old ticket.SprintIssue
- err = lakeModels.Db.First(&old, "sprint_id = ? AND issue_id = ?", fresh.SprintId, fresh.IssueId).Error
- if err != nil && err != gorm.ErrRecordNotFound {
- logger.Error("UpdateSprintIssue error:", err)
- return err
- }
-
- if old.AddedAt == nil && fresh.AddedAt != nil || old.RemovedAt == nil && fresh.RemovedAt != nil {
- flag = true
- }
- if old.AddedAt != nil && fresh.AddedAt != nil && old.AddedAt.Before(*fresh.AddedAt) {
- fresh.AddedAt = old.AddedAt
- flag = true
- }
- if old.RemovedAt != nil && fresh.RemovedAt != nil && old.RemovedAt.After(*fresh.RemovedAt) {
- fresh.RemovedAt = old.RemovedAt
- flag = true
- }
- if flag {
- list = append(list, fresh)
- }
- }
- return lakeModels.Db.Clauses(clause.OnConflict{
- UpdateAll: true,
- }).CreateInBatches(list, BatchSize).Error
-}
-func (c *SprintIssueBurndownConverter) Save() error {
- var err error
- for sprintId, sprint := range c.cache {
- err = lakeModels.Db.Clauses(clause.OnConflict{
- UpdateAll: true,
- }).CreateInBatches(c.fill(sprintId, sprint), BatchSize).Error
- if err != nil {
- logger.Error("save sprint issue burndwon error:", err)
- return err
- }
- }
- return nil
-}
-
-func (c *SprintIssueBurndownConverter) fill(sprintId string, m map[int]*ticket.SprintIssueBurndown) []*ticket.SprintIssueBurndown {
- var result []*ticket.SprintIssueBurndown
- var max, min int
- min = math.MaxInt32
- for k := range m {
- if k > max {
- max = k
- }
- if k < min {
- min = k
- }
- }
- for dateHour := min; dateHour <= max; dateHour = c.nextDateHour(dateHour) {
- if item, ok := m[dateHour]; ok {
- result = append(result, item)
- } else {
- result = append(result, c.newSprintIssueBurndown(sprintId, dateHour))
- }
- }
-
- // fill remaining
- var remain, remainBugs, remainRequirements, remainIncidents, remainStoryPoints int
- for _, item := range result {
- remain += item.Added
- remain -= item.Removed
- remainBugs += item.AddedBugs
- remainBugs -= item.RemovedBugs
- remainRequirements += item.AddedRequirements
- remainRequirements -= item.RemovedRequirements
- remainIncidents += item.AddedIncidents
- remainIncidents -= item.RemovedIncidents
- remainStoryPoints += item.AddedStoryPoints
- remainStoryPoints -= item.RemovedStoryPoints
- item.Remaining = remain
- item.RemainingBugs = remainBugs
- item.RemainingRequirements = remainRequirements
- item.RemainingIncidents = remainIncidents
- item.RemainingStoryPoints = remainStoryPoints
- item.RemainingOtherIssues = remain - remainBugs - remainIncidents - remainRequirements - remainStoryPoints
- }
- for p := len(result) - 1; p > -1; p-- {
- for i := 1; i < 24 && p-i > -1; i++ {
- result[p].Added += result[p-i].Added
- result[p].Removed += result[p-i].Removed
- result[p].AddedBugs += result[p-i].AddedBugs
- result[p].RemovedBugs += result[p-i].RemovedBugs
- result[p].AddedRequirements += result[p-i].AddedRequirements
- result[p].RemovedRequirements += result[p-i].RemovedRequirements
- result[p].AddedIncidents += result[p-i].AddedIncidents
- result[p].RemovedIncidents += result[p-i].RemovedIncidents
- result[p].AddedStoryPoints += result[p-i].AddedStoryPoints
- result[p].RemovedStoryPoints += result[p-i].RemovedStoryPoints
- result[p].AddedOtherIssues += result[p-i].AddedOtherIssues
- result[p].RemovedOtherIssues += result[p-i].RemovedOtherIssues
- }
- }
- return result
-}
-func (c *SprintIssueBurndownConverter) getJiraIssue(sourceId, issueId uint64) (*models.JiraIssue, error) {
- var issue models.JiraIssue
- err := lakeModels.Db.First(&issue, "issue_id = ? AND source_id = ?", issueId, sourceId).Error
- if err != nil {
- logger.Error("getJiraIssue error:", err)
- return nil, err
- }
- return &issue, err
-}
-
-
-
-func (c *SprintIssueBurndownConverter) parseFromTo(from, to string) (map[uint64]struct{}, map[uint64]struct{}, error) {
- fromInts := make(map[uint64]struct{})
- toInts := make(map[uint64]struct{})
- var n uint64
- var err error
- for _, item := range strings.Split(from, ",") {
- s := strings.TrimSpace(item)
- if s == ""{
- continue
- }
- n, err = strconv.ParseUint(s, 10, 64)
- if err != nil {
- return nil, nil, err
- }
- fromInts[n] = struct{}{}
- }
- for _, item := range strings.Split(to, ",") {
- s := strings.TrimSpace(item)
- if s == ""{
- continue
- }
- n, err = strconv.ParseUint(s, 10, 64)
- if err != nil {
- return nil, nil, err
- }
- toInts[n] = struct{}{}
- }
- inter := make(map[uint64]struct{})
- for k := range fromInts {
- if _, ok := toInts[k]; ok {
- inter[k] = struct{}{}
- delete(toInts, k)
- }
- }
- for k := range inter {
- delete(fromInts, k)
- }
- return fromInts, toInts, nil
-}
-
-func (c *SprintIssueBurndownConverter) getDateHour(t time.Time) int {
- t = t.UTC().Add(time.Hour)
- y, m, d := t.Date()
- return y*1000000 + int(m)*10000 + d*100 + t.Hour()
-}
-
-func (c *SprintIssueBurndownConverter) dateHourEndPoints(dateHour int) (time.Time, time.Time) {
- y := dateHour / 1000000
- m := (dateHour / 10000) % 100
- d := (dateHour / 100) % 100
- h := dateHour % 100
- end := time.Date(y, time.Month(m), d, h, 0, 0, 0, UTCLocation)
- return end.Add(-24 * time.Hour), end
-}
-
-func (c *SprintIssueBurndownConverter) nextDateHour(dateHour int) int {
- y := dateHour / 1000000
- m := (dateHour / 10000) % 100
- d := (dateHour / 100) % 100
- h := dateHour % 100
- t := time.Date(y, time.Month(m), d, h, 0, 0, 0, UTCLocation).Add(time.Hour).UTC()
- y1, m1, d1 := t.Date()
- return y1*1000000 + int(m1)*10000 + d1*100 + t.Hour()
-}
-
-func (c *SprintIssueBurndownConverter) handleFrom(sourceId, sprintId uint64, cl ChangelogItemResult) error {
- sprint := c.sprintIdGen.Generate(sourceId, sprintId)
- key := fmt.Sprintf("%d:%d:%d", sourceId, sprintId, cl.IssueId)
- if item, ok := c.sprintIssue[key]; ok {
- if item != nil && (item.RemovedAt == nil || item.RemovedAt != nil && item.RemovedAt.Before(cl.Created)) {
- item.RemovedAt = &cl.Created
- }
- } else {
- c.sprintIssue[key] = &ticket.SprintIssue{
- SprintId: sprint,
- IssueId: c.issueIdGen.Generate(sourceId, cl.IssueId),
- AddedAt: nil,
- RemovedAt: &cl.Created,
- }
- }
- dateHour := c.getDateHour(cl.Created)
- if _, ok := c.cache[sprint]; !ok {
- c.cache[sprint] = make(map[int]*ticket.SprintIssueBurndown)
- }
- if c.cache[sprint][dateHour] == nil {
- c.cache[sprint][dateHour] = c.newSprintIssueBurndown(sprint, dateHour)
- }
- c.cache[sprint][dateHour].Removed++
- jiraIssue, err := c.getJiraIssue(sourceId, cl.IssueId)
- if err != nil {
- return err
- }
- switch jiraIssue.StdType {
- case Bug:
- c.cache[sprint][dateHour].RemovedBugs++
- case Incident:
- c.cache[sprint][dateHour].RemovedIncidents++
- case Task:
- c.cache[sprint][dateHour].RemovedRequirements++
- case Story:
- c.cache[sprint][dateHour].RemovedStoryPoints++
- default:
- c.cache[sprint][dateHour].RemovedOtherIssues++
- }
- return nil
-}
-
-func (c *SprintIssueBurndownConverter) handleTo(sourceId, sprintId uint64, cl ChangelogItemResult) error {
- sprint := c.sprintIdGen.Generate(sourceId, sprintId)
- key := fmt.Sprintf("%d:%d:%d", sourceId, sprintId, cl.IssueId)
- if item, ok := c.sprintIssue[key]; ok {
- if item != nil && (item.AddedAt == nil || item.AddedAt != nil && item.AddedAt.After(cl.Created)) {
- item.AddedAt = &cl.Created
- }
- } else {
- c.sprintIssue[key] = &ticket.SprintIssue{
- SprintId: sprint,
- IssueId: c.issueIdGen.Generate(sourceId, cl.IssueId),
- AddedAt: &cl.Created,
- RemovedAt: nil,
- }
- }
- dateHour := c.getDateHour(cl.Created)
- if _, ok := c.cache[sprint]; !ok {
- c.cache[sprint] = make(map[int]*ticket.SprintIssueBurndown)
- }
- if c.cache[sprint][dateHour] == nil {
- c.cache[sprint][dateHour] = c.newSprintIssueBurndown(sprint, dateHour)
- }
- c.cache[sprint][dateHour].Added++
- jiraIssue, err := c.getJiraIssue(sourceId, cl.IssueId)
- if err != nil {
- return err
- }
- switch jiraIssue.StdType {
- case Bug:
- c.cache[sprint][dateHour].AddedBugs++
- case Incident:
- c.cache[sprint][dateHour].AddedIncidents++
- case Task:
- c.cache[sprint][dateHour].AddedRequirements++
- case Story:
- c.cache[sprint][dateHour].AddedStoryPoints++
- default:
- c.cache[sprint][dateHour].AddedOtherIssues++
- }
- return nil
-}
-
-func (c *SprintIssueBurndownConverter) newSprintIssueBurndown(sprintId string, dateHour int) *ticket.SprintIssueBurndown {
- startedAt, endedAt := c.dateHourEndPoints(dateHour)
- return &ticket.SprintIssueBurndown{
- SprintId: sprintId,
- StartedAt: startedAt,
- EndedAt: endedAt,
- EndedHour: dateHour,
- }
-}
diff --git a/plugins/jira/tasks/jira_sprint_issues_converter.go b/plugins/jira/tasks/jira_sprint_issues_converter.go
new file mode 100644
index 0000000..f65cabd
--- /dev/null
+++ b/plugins/jira/tasks/jira_sprint_issues_converter.go
@@ -0,0 +1,276 @@
+package tasks
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/merico-dev/lake/logger"
+ lakeModels "github.com/merico-dev/lake/models"
+ "github.com/merico-dev/lake/models/domainlayer/didgen"
+ "github.com/merico-dev/lake/models/domainlayer/ticket"
+ "github.com/merico-dev/lake/plugins/jira/models"
+ "gorm.io/gorm"
+ "gorm.io/gorm/clause"
+)
+
+type SprintIssuesConverter struct {
+ sprintIdGen *didgen.DomainIdGenerator
+ issueIdGen *didgen.DomainIdGenerator
+ sprints map[string]*models.JiraSprint
+ sprintIssue map[string]*ticket.SprintIssue
+ status map[string]*ticket.IssueStatusHistory
+ assignee map[string]*ticket.IssueAssigneeHistory
+ sprintsHistory map[string]*ticket.IssueSprintsHistory
+}
+
+func NewSprintIssueConverter() *SprintIssuesConverter {
+ return &SprintIssuesConverter{
+ sprintIdGen: didgen.NewDomainIdGenerator(&models.JiraSprint{}),
+ issueIdGen: didgen.NewDomainIdGenerator(&models.JiraIssue{}),
+ sprints: make(map[string]*models.JiraSprint),
+ sprintIssue: make(map[string]*ticket.SprintIssue),
+ status: make(map[string]*ticket.IssueStatusHistory),
+ assignee: make(map[string]*ticket.IssueAssigneeHistory),
+ sprintsHistory: make(map[string]*ticket.IssueSprintsHistory),
+ }
+}
+
+func (c *SprintIssuesConverter) FeedIn(sourceId uint64, cl ChangelogItemResult) {
+ if cl.Field == "status" {
+ err := c.handleStatus(sourceId, cl)
+ if err != nil {
+ return
+ }
+ }
+ if cl.Field == "assignee" {
+ err := c.handleAssignee(sourceId, cl)
+ if err != nil {
+ return
+ }
+ }
+ if cl.Field != "Sprint" {
+ return
+ }
+ from, to, err := c.parseFromTo(cl.From, cl.To)
+ if err != nil {
+ return
+ }
+ for sprintId := range from {
+ err = c.handleFrom(sourceId, sprintId, cl)
+ if err != nil {
+ logger.Error("handle from error:", err)
+ return
+ }
+ }
+ for sprintId := range to {
+ err = c.handleTo(sourceId, sprintId, cl)
+ if err != nil {
+ logger.Error("handle to error:", err)
+ return
+ }
+ }
+}
+
+func (c *SprintIssuesConverter) UpdateSprintIssue() error {
+ var err error
+ var flag bool
+ var list []*ticket.SprintIssue
+ for _, fresh := range c.sprintIssue {
+ var old ticket.SprintIssue
+ err = lakeModels.Db.First(&old, "sprint_id = ? AND issue_id = ?", fresh.SprintId, fresh.IssueId).Error
+ if err != nil && err != gorm.ErrRecordNotFound {
+ logger.Error("UpdateSprintIssue error:", err)
+ return err
+ }
+
+ if old.AddedDate == nil && fresh.AddedDate != nil || old.RemovedDate == nil && fresh.RemovedDate != nil {
+ flag = true
+ }
+ if old.AddedDate != nil && fresh.AddedDate != nil && old.AddedDate.Before(*fresh.AddedDate) {
+ fresh.AddedDate = old.AddedDate
+ flag = true
+ }
+ if old.RemovedDate != nil && fresh.RemovedDate != nil && old.RemovedDate.After(*fresh.RemovedDate) {
+ fresh.RemovedDate = old.RemovedDate
+ flag = true
+ }
+ if fresh.AddedDate != nil && fresh.RemovedDate != nil {
+ fresh.IsRemoved = fresh.AddedDate.Before(*fresh.RemovedDate)
+ if fresh.IsRemoved != old.IsRemoved {
+ flag = true
+ }
+ }
+ if flag {
+ list = append(list, fresh)
+ }
+ }
+ return lakeModels.Db.Clauses(clause.OnConflict{
+ UpdateAll: true,
+ }).CreateInBatches(list, BatchSize).Error
+}
+
+func (c *SprintIssuesConverter) parseFromTo(from, to string) (map[uint64]struct{}, map[uint64]struct{}, error) {
+ fromInts := make(map[uint64]struct{})
+ toInts := make(map[uint64]struct{})
+ var n uint64
+ var err error
+ for _, item := range strings.Split(from, ",") {
+ s := strings.TrimSpace(item)
+ if s == "" {
+ continue
+ }
+ n, err = strconv.ParseUint(s, 10, 64)
+ if err != nil {
+ return nil, nil, err
+ }
+ fromInts[n] = struct{}{}
+ }
+ for _, item := range strings.Split(to, ",") {
+ s := strings.TrimSpace(item)
+ if s == "" {
+ continue
+ }
+ n, err = strconv.ParseUint(s, 10, 64)
+ if err != nil {
+ return nil, nil, err
+ }
+ toInts[n] = struct{}{}
+ }
+ inter := make(map[uint64]struct{})
+ for k := range fromInts {
+ if _, ok := toInts[k]; ok {
+ inter[k] = struct{}{}
+ delete(toInts, k)
+ }
+ }
+ for k := range inter {
+ delete(fromInts, k)
+ }
+ return fromInts, toInts, nil
+}
+
+func (c *SprintIssuesConverter) handleFrom(sourceId, sprintId uint64, cl ChangelogItemResult) error {
+ domainSprintId := c.sprintIdGen.Generate(sourceId, sprintId)
+ key := fmt.Sprintf("%d:%d:%d", sourceId, sprintId, cl.IssueId)
+ if item, ok := c.sprintIssue[key]; ok {
+ if item != nil && (item.RemovedDate == nil || item.RemovedDate != nil && item.RemovedDate.Before(cl.Created)) {
+ item.RemovedDate = &cl.Created
+ }
+ } else {
+ c.sprintIssue[key] = &ticket.SprintIssue{
+ SprintId: domainSprintId,
+ IssueId: c.issueIdGen.Generate(sourceId, cl.IssueId),
+ AddedDate: nil,
+ RemovedDate: &cl.Created,
+ }
+ }
+ k := fmt.Sprintf("%d:%d", sprintId, cl.IssueId)
+ if item := c.sprintsHistory[k]; item != nil {
+ item.EndDate = &cl.Created
+ err := lakeModels.Db.Create(item).Error
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (c *SprintIssuesConverter) handleTo(sourceId, sprintId uint64, cl ChangelogItemResult) error {
+ domainSprintId := c.sprintIdGen.Generate(sourceId, sprintId)
+ key := fmt.Sprintf("%d:%d:%d", sourceId, sprintId, cl.IssueId)
+ if item, ok := c.sprintIssue[key]; ok {
+ if item != nil && (item.AddedDate == nil || item.AddedDate != nil && item.AddedDate.After(cl.Created)) {
+ item.AddedDate = &cl.Created
+ item.AddedStage, _ = c.getStage(cl.Created, sourceId, sprintId)
+ }
+ } else {
+ c.sprintIssue[key] = &ticket.SprintIssue{
+ SprintId: domainSprintId,
+ IssueId: c.issueIdGen.Generate(sourceId, cl.IssueId),
+ AddedDate: &cl.Created,
+ RemovedDate: nil,
+ }
+ c.sprintIssue[key].AddedStage, _ = c.getStage(cl.Created, sourceId, sprintId)
+ }
+ k := fmt.Sprintf("%d:%d", sprintId, cl.IssueId)
+ c.sprintsHistory[k] = &ticket.IssueSprintsHistory{
+ IssueId: c.issueIdGen.Generate(sourceId, cl.IssueId),
+ SprintId: domainSprintId,
+ StartDate: cl.Created,
+ EndDate: nil,
+ }
+ return nil
+}
+
+func (c *SprintIssuesConverter) getSprint(sourceId, sprintId uint64) (*models.JiraSprint, error) {
+ id := c.sprintIdGen.Generate(sourceId, sprintId)
+ if value, ok := c.sprints[id]; ok {
+ return value, nil
+ }
+ var sprint models.JiraSprint
+ err := lakeModels.Db.First(&sprint, "source_id = ? AND sprint_id = ?", sourceId, sprintId).Error
+ if err != nil {
+ c.sprints[id] = &sprint
+ }
+ return &sprint, err
+}
+
+func (c *SprintIssuesConverter) getStage(t time.Time, sourceId, sprintId uint64) (string, error) {
+ sprint, err := c.getSprint(sourceId, sprintId)
+ if err != nil {
+ return "", err
+ }
+ if sprint.StartDate != nil {
+ if sprint.StartDate.After(t) {
+ return ticket.BeforeSprint, nil
+ }
+ if sprint.StartDate.Equal(t) || (sprint.CompleteDate != nil && sprint.CompleteDate.Equal(t)) {
+ return ticket.DuringSprint, nil
+ }
+ if sprint.CompleteDate != nil && sprint.StartDate.Before(t) && sprint.CompleteDate.After(t) {
+ return ticket.DuringSprint, nil
+ }
+ }
+ if sprint.CompleteDate != nil && sprint.CompleteDate.Before(t) {
+ return ticket.AfterSprint, nil
+ }
+ return "", nil
+}
+
+func (c *SprintIssuesConverter) handleStatus(sourceId uint64, cl ChangelogItemResult) error {
+ issueId := c.issueIdGen.Generate(sourceId, cl.IssueId)
+ if statusHistory := c.status[issueId]; statusHistory != nil {
+ statusHistory.EndDate = &cl.Created
+ err := lakeModels.Db.Create(statusHistory).Error
+ if err != nil {
+ return err
+ }
+ }
+ c.status[issueId] = &ticket.IssueStatusHistory{
+ IssueId: issueId,
+ Status: cl.ToString,
+ StartDate: cl.Created,
+ EndDate: nil,
+ }
+ return nil
+}
+
+func (c *SprintIssuesConverter) handleAssignee(sourceId uint64, cl ChangelogItemResult) error {
+ issueId := c.issueIdGen.Generate(sourceId, cl.IssueId)
+ if assigneeHistory := c.assignee[issueId]; assigneeHistory != nil {
+ assigneeHistory.EndDate = &cl.Created
+ err := lakeModels.Db.Create(assigneeHistory).Error
+ if err != nil {
+ return err
+ }
+ }
+ c.assignee[issueId] = &ticket.IssueAssigneeHistory{
+ IssueId: issueId,
+ Assignee: cl.To,
+ StartDate: cl.Created,
+ EndDate: nil,
+ }
+ return nil
+}
diff --git a/plugins/jira/tasks/jira_user_converter.go b/plugins/jira/tasks/jira_user_converter.go
index bd019dd..f463d58 100644
--- a/plugins/jira/tasks/jira_user_converter.go
+++ b/plugins/jira/tasks/jira_user_converter.go
@@ -21,7 +21,7 @@
userIdGen := didgen.NewDomainIdGenerator(&jiraModels.JiraUser{})
for _, jiraUser := range jiraUserRows {
- user := &user.User{
+ u := &user.User{
DomainEntity: domainlayer.DomainEntity{
Id: userIdGen.Generate(jiraUser.SourceId, jiraUser.AccountId),
},
@@ -31,7 +31,7 @@
Timezone: jiraUser.Timezone,
}
- err = lakeModels.Db.Clauses(clause.OnConflict{UpdateAll: true}).Create(user).Error
+ err = lakeModels.Db.Clauses(clause.OnConflict{UpdateAll: true}).Create(u).Error
if err != nil {
return err
}
diff --git a/plugins/jira/tasks/jira_worklog_converter.go b/plugins/jira/tasks/jira_worklog_converter.go
index ac88141..8280b2b 100644
--- a/plugins/jira/tasks/jira_worklog_converter.go
+++ b/plugins/jira/tasks/jira_worklog_converter.go
@@ -24,7 +24,6 @@
}
defer cursor.Close()
- boardIdGen := didgen.NewDomainIdGenerator(&jiraModels.JiraBoard{}).Generate(sourceId, boardId)
worklogIdGen := didgen.NewDomainIdGenerator(&jiraModels.JiraWorklog{})
userIdGen := didgen.NewDomainIdGenerator(&jiraModels.JiraUser{})
issueIdGen := didgen.NewDomainIdGenerator(&jiraModels.JiraIssue{})
@@ -35,22 +34,13 @@
return err
}
worklog := &ticket.Worklog{
- DomainEntity: domainlayer.DomainEntity{
- Id: worklogIdGen.Generate(jiraWorklog.SourceId, jiraWorklog.IssueId, jiraWorklog.WorklogId),
- },
+ DomainEntity:domainlayer.DomainEntity{Id: worklogIdGen.Generate(jiraWorklog.SourceId, jiraWorklog.IssueId, jiraWorklog.WorklogId)},
IssueId: issueIdGen.Generate(jiraWorklog.SourceId, jiraWorklog.IssueId),
- BoardId: boardIdGen,
- TimeSpent: jiraWorklog.TimeSpent,
- TimeSpentSeconds: jiraWorklog.TimeSpentSeconds,
- Updated: jiraWorklog.Updated,
- Started: jiraWorklog.Started,
+ TimeSpentMinutes: jiraWorklog.TimeSpentSeconds / 60,
}
if jiraWorklog.AuthorId != "" {
worklog.AuthorId = userIdGen.Generate(sourceId, jiraWorklog.AuthorId)
}
- if jiraWorklog.UpdateAuthorId != "" {
- worklog.UpdateAuthorId = userIdGen.Generate(sourceId, jiraWorklog.UpdateAuthorId)
- }
err = lakeModels.Db.Clauses(clause.OnConflict{UpdateAll: true}).Create(worklog).Error
if err != nil {