@@ -12,6 +12,7 @@ import (
12
12
jiraModels "github.com/apache/incubator-devlake/plugins/jira/models"
13
13
)
14
14
15
+ // CalculateIssueLeadTimeMeta contains metadata for the CalculateIssueLeadTime subtask.
15
16
var CalculateIssueLeadTimeMeta = plugin.SubTaskMeta {
16
17
Name : "calculateIssueLeadTime" ,
17
18
EntryPoint : CalculateIssueLeadTime ,
@@ -20,118 +21,125 @@ var CalculateIssueLeadTimeMeta = plugin.SubTaskMeta{
20
21
DomainTypes : []string {plugin .DOMAIN_TYPE_TICKET },
21
22
}
22
23
24
+ // CalculateIssueLeadTime calculates the lead time for issues from first 'In Progress' status to 'Done' status.
23
25
func CalculateIssueLeadTime (taskCtx plugin.SubTaskContext ) errors.Error {
24
26
db := taskCtx .GetDal ()
27
+ logger := taskCtx .GetLogger ()
25
28
data := taskCtx .GetData ().(* DoraTaskData )
26
29
27
- logger := taskCtx .GetLogger ()
28
- logger .Info (fmt .Sprintf ("Starting calculateIssueLeadTime task for project %s" , data .Options .ProjectName ))
30
+ logger .Info ("Starting calculateIssueLeadTime task for project %s" , data .Options .ProjectName )
29
31
30
- // 1) delete any old metrics for this project
31
- if err := db .Delete (
32
+ // Delete any old metrics for this project
33
+ err := db .Delete (
32
34
& models.IssueLeadTimeMetric {},
33
35
dal .Where ("project_name = ?" , data .Options .ProjectName ),
34
- ); err != nil {
35
- return errors .Default .Wrap (err , "deleting old issue lead time metrics" )
36
+ )
37
+ if err != nil {
38
+ return errors .Default .Wrap (err , "failed to delete old issue lead time metrics" )
36
39
}
37
- logger .Info (fmt . Sprintf ( "Deleted old issue lead time metrics for project %s" , data .Options .ProjectName ) )
40
+ logger .Info ("Deleted old issue lead time metrics for project %s" , data .Options .ProjectName )
38
41
39
- // 2) get the actual _tool_jira_* table names
40
- rawItems := jiraModels.JiraIssueChangelogItems {}.TableName () // "_tool_jira_issue_changelog_items"
41
- rawChgs := jiraModels.JiraIssueChangelogs {}.TableName () // "_tool_jira_issue_changelogs"
42
- rawIss := jiraModels.JiraIssue {}.TableName () // "_tool_jira_issues"
42
+ // Get the actual _tool_jira_* table names
43
+ rawItems := jiraModels.JiraIssueChangelogItems {}.TableName ()
44
+ rawChgs := jiraModels.JiraIssueChangelogs {}.TableName ()
45
+ rawIss := jiraModels.JiraIssue {}.TableName ()
43
46
44
- // 3) build the SQL query, filter out null timestamps and use only latest resolution per issue
45
- query := `
47
+ // Build the SQL query, filter out null timestamps and use only latest resolution per issue
48
+ query := fmt . Sprintf ( `
46
49
SELECT
47
- c.issue_id AS issue_id,
48
- MIN(CASE WHEN UPPER(TRIM(i.to_string)) IN ('IN PROGRESS', 'INPROGRESS') THEN c.created END) AS in_progress_timestamp,
49
- u.resolution_date AS done_timestamp
50
- FROM ` + rawItems + ` i
51
- JOIN ` + rawChgs + ` c
52
- ON i.connection_id = c.connection_id
53
- AND i.changelog_id = c.changelog_id
54
- JOIN ` + rawIss + ` u
55
- ON c.connection_id = u.connection_id
56
- AND c.issue_id = u.issue_id
50
+ c.issue_id AS issue_id,
51
+ MIN(CASE WHEN UPPER(TRIM(i.to_string)) IN ('IN PROGRESS', 'INPROGRESS') THEN c.created END) AS in_progress_timestamp,
52
+ u.resolution_date AS done_timestamp
53
+ FROM %s i
54
+ JOIN %s c
55
+ ON i.connection_id = c.connection_id
56
+ AND i.changelog_id = c.changelog_id
57
+ JOIN %s u
58
+ ON c.connection_id = u.connection_id
59
+ AND c.issue_id = u.issue_id
57
60
JOIN _tool_jira_board_issues bi
58
- ON u.connection_id = bi.connection_id
59
- AND u.issue_id = bi.issue_id
61
+ ON u.connection_id = bi.connection_id
62
+ AND u.issue_id = bi.issue_id
60
63
JOIN project_mapping pm
61
- ON pm.row_id = CONCAT('jira:JiraBoard:', bi.connection_id, ':', bi.board_id)
62
- AND pm.table = 'boards'
64
+ ON pm.row_id = CONCAT('jira:JiraBoard:', bi.connection_id, ':', bi.board_id)
65
+ AND pm.table = 'boards'
63
66
WHERE i.field = 'status'
64
- AND pm.project_name = ?
65
- AND u.resolution_date IS NOT NULL
66
- AND u.resolution_date = (
67
- SELECT MAX(u2.resolution_date)
68
- FROM ` + rawIss + ` u2
69
- WHERE u2.connection_id = u.connection_id
70
- AND u2.issue_id = u.issue_id
71
- )
67
+ AND pm.project_name = ?
68
+ AND u.resolution_date IS NOT NULL
69
+ AND u.resolution_date = (
70
+ SELECT MAX(u2.resolution_date)
71
+ FROM %s u2
72
+ WHERE u2.connection_id = u.connection_id
73
+ AND u2.issue_id = u.issue_id
74
+ )
72
75
GROUP BY c.issue_id, u.resolution_date
73
76
HAVING in_progress_timestamp IS NOT NULL
74
- `
75
- logger .Info (fmt .Sprintf ("Executing SQL query for DevLake project: %s" , data .Options .ProjectName ))
77
+ ` , rawItems , rawChgs , rawIss , rawIss )
76
78
77
- // 4) execute & stream
79
+ logger .Info ("Executing SQL query for DevLake project: %s" , data .Options .ProjectName )
80
+
81
+ // Execute query and stream results
78
82
rows , err := db .RawCursor (query , data .Options .ProjectName )
79
83
if err != nil {
80
- logger .Error (err , "" )
81
- return errors .Default .Wrap (err , "running lead time aggregation query" )
84
+ return errors .Default .Wrap (err , "failed to run lead time aggregation query" )
82
85
}
83
86
defer rows .Close ()
84
87
85
88
rowCount := 0
86
89
logger .Info ("Starting to process SQL query results..." )
90
+
87
91
for rows .Next () {
88
92
var (
89
93
rawIssueID uint64
90
94
rawInProgress sql.NullTime
91
95
rawDone sql.NullTime
92
96
)
97
+
93
98
if scanErr := rows .Scan (& rawIssueID , & rawInProgress , & rawDone ); scanErr != nil {
94
- logger .Error (scanErr , "" )
95
- return errors .Default .Wrap (scanErr , "scanning lead time row" )
99
+ return errors .Default .Wrap (scanErr , "failed to scan lead time row" )
96
100
}
97
- logger .Info (fmt .Sprintf ("Scanned row: issueID=%d, inProgress=%v, done=%v" , rawIssueID , rawInProgress , rawDone ))
98
- // skip if null
101
+
102
+ logger .Debug ("Scanned row: issueID=%d, inProgress=%v, done=%v" , rawIssueID , rawInProgress , rawDone )
103
+
104
+ // Skip if null timestamps
99
105
if ! rawInProgress .Valid || ! rawDone .Valid {
100
- logger .Debug (fmt . Sprintf ( "Skipping row with null timestamp: issueID=%d" , rawIssueID ) )
106
+ logger .Debug ("Skipping row with null timestamp: issueID=%d" , rawIssueID )
101
107
continue
102
108
}
109
+
103
110
start := rawInProgress .Time
104
111
end := rawDone .Time
105
112
mins := int64 (end .Sub (start ).Minutes ())
113
+
114
+ // Skip negative lead times
106
115
if mins < 0 {
107
- logger .Info ( fmt . Sprintf ( "Skipping row with negative lead time: issueID=%d" , rawIssueID ) )
116
+ logger .Debug ( "Skipping row with negative lead time: issueID=%d" , rawIssueID )
108
117
continue
109
118
}
110
119
111
- // 5) upsert
120
+ // Create and save the metric
112
121
metric := & models.IssueLeadTimeMetric {
113
122
ProjectName : data .Options .ProjectName ,
114
123
IssueId : strconv .FormatUint (rawIssueID , 10 ),
115
124
InProgressDate : & start ,
116
125
DoneDate : & end ,
117
126
InProgressToDoneMinutes : & mins ,
118
127
}
119
- logger .Info (fmt .Sprintf ("About to upsert metric: projectName=%s, issueId=%s, minutes=%d" ,
120
- metric .ProjectName , metric .IssueId , * metric .InProgressToDoneMinutes ))
128
+
129
+ logger .Debug ("Upserting metric: projectName=%s, issueId=%s, minutes=%d" ,
130
+ metric .ProjectName , metric .IssueId , * metric .InProgressToDoneMinutes )
121
131
122
132
if upsertErr := db .CreateOrUpdate (metric ); upsertErr != nil {
123
- logger .Error (upsertErr , fmt .Sprintf ("Failed to upsert metric for issueId=%s" , metric .IssueId ))
124
- return errors .Default .Wrap (upsertErr , "upserting issue lead time metric" )
133
+ return errors .Default .Wrap (upsertErr , "failed to upsert issue lead time metric" )
125
134
}
126
- logger . Info ( fmt . Sprintf ( "Successfully upserted metric for issueId=%s" , metric . IssueId ))
135
+
127
136
rowCount ++
128
137
}
129
138
130
- logger .Info (fmt . Sprintf ( "Completed calculateIssueLeadTime task: processed %d records" , rowCount ) )
139
+ logger .Info ("Completed calculateIssueLeadTime task: processed %d records" , rowCount )
131
140
132
141
if err := rows .Err (); err != nil && err != sql .ErrNoRows {
133
- logger .Error (err , "" )
134
- return errors .Default .Wrap (err , "iterating lead time rows" )
142
+ return errors .Default .Wrap (err , "error iterating lead time rows" )
135
143
}
136
144
137
145
return nil
0 commit comments