Skip to content

Commit 2f49d1e

Browse files
committed
Merge remote-tracking branch 'origin/main' into versionUpgrade
# Conflicts: # backend/impls/dalgorm/dalgorm.go
2 parents 9626294 + 965fd28 commit 2f49d1e

23 files changed

Lines changed: 1122 additions & 30 deletions

File tree

backend/core/models/domainlayer/ticket/sprint.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ limitations under the License.
1818
package ticket
1919

2020
import (
21+
"time"
22+
2123
"github.com/apache/incubator-devlake/core/models/common"
2224
"github.com/apache/incubator-devlake/core/models/domainlayer"
23-
"time"
2425
)
2526

2627
var (
@@ -31,13 +32,15 @@ var (
3132

3233
type Sprint struct {
3334
domainlayer.DomainEntity
34-
Name string `gorm:"type:varchar(255)"`
35-
Url string `gorm:"type:varchar(255)"`
36-
Status string `gorm:"type:varchar(100)"`
37-
StartedDate *time.Time
38-
EndedDate *time.Time
39-
CompletedDate *time.Time
40-
OriginalBoardID string `gorm:"type:varchar(255)"`
35+
Name string `gorm:"type:varchar(255)"`
36+
Url string `gorm:"type:varchar(255)"`
37+
Status string `gorm:"type:varchar(100)"`
38+
StartedDate *time.Time
39+
EndedDate *time.Time
40+
CompletedDate *time.Time
41+
OriginalBoardID string `gorm:"type:varchar(255)"`
42+
CommittedStoryPoint *float64
43+
CompletedStoryPoint *float64
4144
}
4245

4346
func (Sprint) TableName() string {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package migrationscripts
19+
20+
import (
21+
"github.com/apache/incubator-devlake/core/context"
22+
"github.com/apache/incubator-devlake/core/errors"
23+
"github.com/apache/incubator-devlake/core/plugin"
24+
)
25+
26+
var _ plugin.MigrationScript = (*addSprintVelocityFields)(nil)
27+
28+
type sprint20260722 struct {
29+
CommittedStoryPoint *float64
30+
CompletedStoryPoint *float64
31+
}
32+
33+
func (sprint20260722) TableName() string {
34+
return "sprints"
35+
}
36+
37+
type addSprintVelocityFields struct{}
38+
39+
func (script *addSprintVelocityFields) Up(basicRes context.BasicRes) errors.Error {
40+
return basicRes.GetDal().AutoMigrate(new(sprint20260722))
41+
}
42+
43+
func (*addSprintVelocityFields) Version() uint64 {
44+
return 20260722100000
45+
}
46+
47+
func (*addSprintVelocityFields) Name() string {
48+
return "add committed_story_point/completed_story_point to sprints"
49+
}

backend/core/models/migrationscripts/register.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,5 +148,6 @@ func All() []plugin.MigrationScript {
148148
new(changeIssueComponentToText),
149149
new(changeCqIssueCodeBlocksComponentToText),
150150
new(addCqProjectMetricsHistory),
151+
new(addSprintVelocityFields),
151152
}
152153
}

backend/plugins/bitbucket/api/remote_api.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ func listBitbucketWorkspaces(
107107
FullName: r.GroupName(),
108108
})
109109
}
110+
if resBody.Next != "" {
111+
nextPage = &BitbucketRemotePagination{
112+
Page: page.Page + 1,
113+
PageLen: page.PageLen,
114+
}
115+
}
110116
return
111117
}
112118

@@ -123,7 +129,7 @@ func listBitbucketRepos(
123129
var res *http.Response
124130
// list projects part
125131
res, err = apiClient.Get(fmt.Sprintf("/repositories/%s", workspace), url.Values{
126-
"fields": {"values.name,values.full_name,values.language,values.description,values.owner.display_name,values.created_on,values.updated_on,values.links.clone,values.links.html,pagelen,page,size"},
132+
"fields": {"values.name,values.full_name,values.language,values.description,values.owner.display_name,values.created_on,values.updated_on,values.links.clone,values.links.html,pagelen,page,size,next"},
127133
"page": {fmt.Sprintf("%v", page.Page)},
128134
"pagelen": {fmt.Sprintf("%v", page.PageLen)},
129135
}, nil)
@@ -153,6 +159,12 @@ func listBitbucketRepos(
153159
Data: r.ConvertApiScope(),
154160
})
155161
}
162+
if resBody.Next != "" {
163+
nextPage = &BitbucketRemotePagination{
164+
Page: page.Page + 1,
165+
PageLen: page.PageLen,
166+
}
167+
}
156168
return
157169
}
158170

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package api
19+
20+
import (
21+
"io"
22+
"net/http"
23+
"net/url"
24+
"strings"
25+
"testing"
26+
27+
"github.com/stretchr/testify/assert"
28+
29+
"github.com/apache/incubator-devlake/core/errors"
30+
"github.com/apache/incubator-devlake/core/plugin"
31+
)
32+
33+
type fakeApiClient struct{ body string }
34+
35+
func (f *fakeApiClient) SetData(name string, data interface{}) {}
36+
func (f *fakeApiClient) GetData(name string) interface{} { return nil }
37+
func (f *fakeApiClient) SetHeaders(headers map[string]string) {}
38+
func (f *fakeApiClient) SetBeforeFunction(callback plugin.ApiClientBeforeRequest) {}
39+
func (f *fakeApiClient) GetBeforeFunction() plugin.ApiClientBeforeRequest { return nil }
40+
func (f *fakeApiClient) SetAfterFunction(callback plugin.ApiClientAfterResponse) {}
41+
func (f *fakeApiClient) GetAfterFunction() plugin.ApiClientAfterResponse { return nil }
42+
43+
func (f *fakeApiClient) Get(path string, query url.Values, headers http.Header) (*http.Response, errors.Error) {
44+
return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(f.body))}, nil
45+
}
46+
func (f *fakeApiClient) Post(path string, query url.Values, body interface{}, headers http.Header) (*http.Response, errors.Error) {
47+
return nil, nil
48+
}
49+
50+
func TestListBitbucketRepos_ReturnsNextPage(t *testing.T) {
51+
client := &fakeApiClient{body: `{"pagelen":2,"page":1,"size":4,
52+
"next":"https://api.bitbucket.org/2.0/repositories/myworkspace?page=2",
53+
"values":[{"name":"repo-a","full_name":"myworkspace/repo-a"},
54+
{"name":"repo-b","full_name":"myworkspace/repo-b"}]}`}
55+
children, nextPage, err := listBitbucketRepos(client, "myworkspace", BitbucketRemotePagination{Page: 1, PageLen: 2})
56+
assert.Nil(t, err)
57+
assert.Len(t, children, 2)
58+
if assert.NotNil(t, nextPage) {
59+
assert.Equal(t, 2, nextPage.Page)
60+
}
61+
}
62+
63+
func TestListBitbucketRepos_LastPageHasNoNextPage(t *testing.T) {
64+
client := &fakeApiClient{body: `{"pagelen":2,"page":2,"size":4,
65+
"values":[{"name":"repo-c","full_name":"myworkspace/repo-c"},
66+
{"name":"repo-d","full_name":"myworkspace/repo-d"}]}`}
67+
children, nextPage, err := listBitbucketRepos(client, "myworkspace", BitbucketRemotePagination{Page: 2, PageLen: 2})
68+
assert.Nil(t, err)
69+
assert.Len(t, children, 2)
70+
assert.Nil(t, nextPage)
71+
}
72+
73+
func TestListBitbucketWorkspaces_ReturnsNextPage(t *testing.T) {
74+
client := &fakeApiClient{body: `{"pagelen":1,"page":1,"size":2,
75+
"next":"https://api.bitbucket.org/2.0/user/workspaces?page=2",
76+
"values":[{"workspace":{"slug":"ws-a","name":"Workspace A"}}]}`}
77+
children, nextPage, err := listBitbucketWorkspaces(client, BitbucketRemotePagination{Page: 1, PageLen: 1})
78+
assert.Nil(t, err)
79+
assert.Len(t, children, 1)
80+
if assert.NotNil(t, nextPage) {
81+
assert.Equal(t, 2, nextPage.Page)
82+
}
83+
}

backend/plugins/bitbucket/models/repo.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ type WorkspaceResponse struct {
114114
Pagelen int `json:"pagelen"`
115115
Page int `json:"page"`
116116
Size int `json:"size"`
117+
Next string `json:"next"`
117118
Values []GroupResponse `json:"values"`
118119
}
119120

@@ -142,6 +143,7 @@ type ReposResponse struct {
142143
Pagelen int `json:"pagelen"`
143144
Page int `json:"page"`
144145
Size int `json:"size"`
146+
Next string `json:"next"`
145147
Values []BitbucketApiRepo `json:"values"`
146148
}
147149

backend/plugins/jira/impl/impl.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ func (p Jira) GetTablesInfo() []dal.Tabler {
8585
&models.JiraServerInfo{},
8686
&models.JiraSprint{},
8787
&models.JiraSprintIssue{},
88+
&models.JiraSprintReport{},
8889
&models.JiraStatus{},
8990
&models.JiraWorklog{},
9091
&models.JiraIssueComment{},
@@ -138,6 +139,9 @@ func (p Jira) SubTaskMetas() []plugin.SubTaskMeta {
138139
tasks.CollectSprintsMeta,
139140
tasks.ExtractSprintsMeta,
140141

142+
tasks.CollectSprintReportMeta,
143+
tasks.ExtractSprintReportMeta,
144+
141145
tasks.CollectEpicsMeta,
142146
tasks.ExtractEpicsMeta,
143147

@@ -153,6 +157,7 @@ func (p Jira) SubTaskMetas() []plugin.SubTaskMeta {
153157

154158
tasks.ConvertSprintsMeta,
155159
tasks.ConvertSprintIssuesMeta,
160+
tasks.ConvertSprintReportMeta,
156161

157162
tasks.CollectDevelopmentPanelMeta,
158163
tasks.ExtractDevelopmentPanelMeta,
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package migrationscripts
19+
20+
import (
21+
"github.com/apache/incubator-devlake/core/context"
22+
"github.com/apache/incubator-devlake/core/errors"
23+
"github.com/apache/incubator-devlake/helpers/migrationhelper"
24+
)
25+
26+
type jiraSprintReport20260722 struct {
27+
ConnectionId uint64 `gorm:"primaryKey"`
28+
BoardId uint64 `gorm:"primaryKey"`
29+
SprintId uint64 `gorm:"primaryKey"`
30+
IssueId uint64 `gorm:"primaryKey"`
31+
32+
IssueKey string `gorm:"type:varchar(255)"`
33+
Bucket string `gorm:"type:varchar(32);index"`
34+
Done bool
35+
StoryPointsAtSprintStart *float64
36+
StoryPointsAtSprintEnd *float64
37+
}
38+
39+
func (jiraSprintReport20260722) TableName() string {
40+
return "_tool_jira_sprint_reports"
41+
}
42+
43+
type addSprintReportTable struct{}
44+
45+
func (script *addSprintReportTable) Up(basicRes context.BasicRes) errors.Error {
46+
return migrationhelper.AutoMigrateTables(basicRes, &jiraSprintReport20260722{})
47+
}
48+
49+
func (*addSprintReportTable) Version() uint64 {
50+
return 20260722000000
51+
}
52+
53+
func (*addSprintReportTable) Name() string {
54+
return "add _tool_jira_sprint_reports table to persist Jira's frozen Sprint Report snapshot"
55+
}

backend/plugins/jira/models/migrationscripts/register.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,6 @@ func All() []plugin.MigrationScript {
5858
new(addSubQueryToBoards),
5959
new(changeFixVersionsToText20260707),
6060
new(addExtraJQLToScopeConfig),
61+
new(addSprintReportTable),
6162
}
6263
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package models
19+
20+
import (
21+
"github.com/apache/incubator-devlake/core/models/common"
22+
)
23+
24+
// Sprint Report bucket values, mirroring the four buckets returned by
25+
// GET rest/greenhopper/1.0/rapid/charts/sprintreport. Jira computes these
26+
// once, at sprint close, so persisting them (rather than reconstructing
27+
// from resolution_date) is what makes committed/completed velocity exact.
28+
const (
29+
SprintReportBucketCompleted = "completed"
30+
SprintReportBucketNotCompleted = "notCompleted"
31+
SprintReportBucketPunted = "punted"
32+
SprintReportBucketCompletedInOtherSprint = "completedInOtherSprint"
33+
)
34+
35+
// JiraSprintReport is a frozen, per-(board, sprint, issue) snapshot taken
36+
// from Jira's Sprint Report at sprint close. Unlike JiraSprintIssue (which
37+
// is derived from each issue's live resolution_date and therefore
38+
// mis-attributes carryover issues), this table stores Jira's own
39+
// point-in-time bucketing, so it doesn't drift.
40+
type JiraSprintReport struct {
41+
common.NoPKModel
42+
ConnectionId uint64 `gorm:"primaryKey"`
43+
BoardId uint64 `gorm:"primaryKey"`
44+
SprintId uint64 `gorm:"primaryKey"`
45+
IssueId uint64 `gorm:"primaryKey"`
46+
47+
IssueKey string `gorm:"type:varchar(255)"`
48+
// Bucket is one of the SprintReportBucket* constants above.
49+
Bucket string `gorm:"type:varchar(32);index"`
50+
Done bool
51+
52+
// StoryPointsAtSprintStart is estimateStatistic.statFieldValue.value in
53+
// Jira's response ("BOS points") — the estimate as it stood when the
54+
// sprint began, i.e. what should be summed for *committed* velocity.
55+
StoryPointsAtSprintStart *float64
56+
// StoryPointsAtSprintEnd is currentEstimateStatistic.statFieldValue.value
57+
// ("EOS points") — the estimate as of sprint close, i.e. what should be
58+
// summed (for Bucket == completed) for *completed* velocity.
59+
StoryPointsAtSprintEnd *float64
60+
}
61+
62+
func (JiraSprintReport) TableName() string {
63+
return "_tool_jira_sprint_reports"
64+
}

0 commit comments

Comments
 (0)