Skip to content

Commit c9422f6

Browse files
committed
feat: add branch filtering for Jenkins jobs
1 parent 7674a49 commit c9422f6

File tree

6 files changed

+128
-9
lines changed

6 files changed

+128
-9
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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 addBranchFilterPattern struct{}
27+
28+
type JenkinsScopeConfig20251002 struct {
29+
BranchFilterPattern string `gorm:"type:varchar(255)"`
30+
}
31+
32+
func (JenkinsScopeConfig20251002) TableName() string {
33+
return "_tool_jenkins_scope_configs"
34+
}
35+
36+
func (u *addBranchFilterPattern) Up(baseRes context.BasicRes) errors.Error {
37+
return migrationhelper.AutoMigrateTables(baseRes, &JenkinsScopeConfig20251002{})
38+
}
39+
40+
func (*addBranchFilterPattern) Version() uint64 {
41+
return 20251002100000
42+
}
43+
44+
func (*addBranchFilterPattern) Name() string {
45+
return "add branch filter pattern to jenkins scope config"
46+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@ func All() []plugin.MigrationScript {
3636
new(renameTr2ScopeConfig),
3737
new(addRawParamTableForScope),
3838
new(addNumberToJenkinsBuildCommit),
39+
new(addBranchFilterPattern),
3940
}
4041
}

backend/plugins/jenkins/models/scope_config.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ import (
2222
)
2323

2424
type JenkinsScopeConfig struct {
25-
common.ScopeConfig `mapstructure:",squash" json:",inline" gorm:"embedded"`
26-
DeploymentPattern string `gorm:"type:varchar(255)" mapstructure:"deploymentPattern,omitempty" json:"deploymentPattern"`
27-
ProductionPattern string `gorm:"type:varchar(255)" mapstructure:"productionPattern,omitempty" json:"productionPattern"`
25+
common.ScopeConfig `mapstructure:",squash" json:",inline" gorm:"embedded"`
26+
DeploymentPattern string `mapstructure:"deploymentPattern,omitempty" json:"deploymentPattern" gorm:"type:varchar(255)"`
27+
ProductionPattern string `mapstructure:"productionPattern,omitempty" json:"productionPattern" gorm:"type:varchar(255)"`
28+
BranchFilterPattern string `mapstructure:"branchFilterPattern,omitempty" json:"branchFilterPattern" gorm:"type:varchar(255)"`
2829
}
2930

3031
func (t JenkinsScopeConfig) TableName() string {

backend/plugins/jenkins/tasks/job_collector.go

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ import (
2222
"fmt"
2323
"net/http"
2424
"net/url"
25+
"regexp"
2526
"time"
2627

2728
"github.com/apache/incubator-devlake/core/errors"
29+
"github.com/apache/incubator-devlake/core/log"
2830
"github.com/apache/incubator-devlake/core/plugin"
2931
helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
3032
)
@@ -66,16 +68,26 @@ func CollectApiJobs(taskCtx plugin.SubTaskContext) errors.Error {
6668
return query, nil
6769
},
6870
ResponseParser: func(res *http.Response) ([]json.RawMessage, errors.Error) {
69-
var data struct {
71+
var resData struct {
7072
Jobs []json.RawMessage `json:"jobs"`
7173
}
72-
err := helper.UnmarshalResponse(res, &data)
74+
err := helper.UnmarshalResponse(res, &resData)
7375
if err != nil {
7476
return nil, err
7577
}
7678

77-
jobs := make([]json.RawMessage, 0, len(data.Jobs))
78-
for _, job := range data.Jobs {
79+
// Compile branch filter pattern once for this batch
80+
var branchPattern *regexp.Regexp
81+
if data.Options.ScopeConfig != nil && data.Options.ScopeConfig.BranchFilterPattern != "" {
82+
var compileErr error
83+
branchPattern, compileErr = regexp.Compile(data.Options.ScopeConfig.BranchFilterPattern)
84+
if compileErr != nil {
85+
logger.Warn(nil, "Invalid branch filter pattern: %s, will include all jobs", data.Options.ScopeConfig.BranchFilterPattern)
86+
}
87+
}
88+
89+
jobs := make([]json.RawMessage, 0, len(resData.Jobs))
90+
for _, job := range resData.Jobs {
7991
var jobObj map[string]interface{}
8092
err := json.Unmarshal(job, &jobObj)
8193
if err != nil {
@@ -84,7 +96,10 @@ func CollectApiJobs(taskCtx plugin.SubTaskContext) errors.Error {
8496

8597
logger.Debug("%v", jobObj)
8698
if jobObj["color"] != "notbuilt" && jobObj["color"] != "nobuilt_anime" {
87-
jobs = append(jobs, job)
99+
// Apply branch filter pattern if configured
100+
if shouldIncludeJob(jobObj, branchPattern, logger) {
101+
jobs = append(jobs, job)
102+
}
88103
}
89104
}
90105

@@ -100,3 +115,26 @@ func CollectApiJobs(taskCtx plugin.SubTaskContext) errors.Error {
100115

101116
return collector.Execute()
102117
}
118+
119+
// shouldIncludeJob determines whether a job should be included based on the branch filter pattern
120+
func shouldIncludeJob(jobObj map[string]interface{}, branchPattern *regexp.Regexp, logger log.Logger) bool {
121+
// If no branch filter pattern is configured, include all jobs
122+
if branchPattern == nil {
123+
return true
124+
}
125+
126+
// Get the job name for pattern matching
127+
jobName, ok := jobObj["name"].(string)
128+
if !ok {
129+
// If we can't get the job name, include it by default
130+
logger.Warn(nil, "Could not extract job name for filtering, including job by default")
131+
return true
132+
}
133+
134+
// Match the job name against the pattern
135+
matched := branchPattern.MatchString(jobName)
136+
logger.Debug("Job '%s' %s branch filter pattern", jobName,
137+
map[bool]string{true: "matches", false: "does not match"}[matched])
138+
139+
return matched
140+
}

config-ui/src/plugins/register/jenkins/config.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export const JenkinsConfig: IPluginConfig = {
5959
transformation: {
6060
deploymentPattern: '(deploy|push-image)',
6161
productionPattern: 'prod(.*)',
62+
branchFilterPattern: '',
6263
},
6364
},
6465
};

config-ui/src/plugins/register/jenkins/transformation.tsx

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,38 @@ const renderCollapseItems = ({
7474
children: (
7575
<>
7676
<h3 style={{ marginBottom: 16 }}>
77+
<span>Branch Filtering</span>
78+
<Tag style={{ marginLeft: 4 }} color="green">
79+
NEW
80+
</Tag>
81+
</h3>
82+
<p style={{ marginBottom: 16 }}>
83+
Use Regular Expression to filter Jenkins jobs by branch name. This helps exclude temporary branch/PR builds from collection.{' '}
84+
<ExternalLink link={DOC_URL.PLUGIN.JENKINS.TRANSFORMATION}>Learn more</ExternalLink>
85+
</p>
86+
<div style={{ marginTop: 16 }}>Only collect Jenkins jobs when: </div>
87+
<div style={{ margin: '8px 0', paddingLeft: 28 }}>
88+
<span>
89+
The <strong>job name</strong> matches
90+
</span>
91+
<Input
92+
style={{ width: 200, margin: '0 8px' }}
93+
placeholder="^(main|master|develop).*"
94+
value={transformation.branchFilterPattern ?? ''}
95+
onChange={(e) =>
96+
onChangeTransformation({
97+
...transformation,
98+
branchFilterPattern: e.target.value,
99+
})
100+
}
101+
/>
102+
<HelpTooltip content="Regular expression to match job names. Leave empty to collect all jobs. Example: '^(main|master|develop).*' to collect only main branches" />
103+
</div>
104+
<div style={{ margin: '8px 0', paddingLeft: 28, fontSize: '12px', color: '#666' }}>
105+
<span>Examples: <code>^(main|master|develop).*</code> (main branches only), <code>.*production.*</code> (production jobs)</span>
106+
</div>
107+
108+
<h3 style={{ marginBottom: 16, marginTop: 32 }}>
77109
<span>Deployment</span>
78110
<Tag style={{ marginLeft: 4 }} color="blue">
79111
DORA
@@ -117,7 +149,7 @@ const renderCollapseItems = ({
117149
})
118150
}
119151
/>
120-
<span>, this Deployment is a Production Deployment</span>
152+
<span>, this Deployment is a 'Production Deployment'</span>
121153
<HelpTooltip content="If you leave this field empty, all DevLake Deployments will be tagged as in the Production environment. " />
122154
</div>
123155
</>

0 commit comments

Comments
 (0)