This is an automated email from the ASF dual-hosted git repository.

klesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new d5bdc15c0 feat(dora): filter pull requests by source branch pattern in 
lead time for changes (#9229)
d5bdc15c0 is described below

commit d5bdc15c018ec6a53bff292a8db4d61ada72972c
Author: AwEi <[email protected]>
AuthorDate: Sun Oct 18 13:03:16 2026 +0800

    feat(dora): filter pull requests by source branch pattern in lead time for 
changes (#9229)
    
    * feat(dora): filter pull requests by source branch pattern in lead time 
for changes
    
    Add a project-level prHeadRefPattern option (Go RE2 regex) to the DORA
    metric plugin. When set, calculateChangeLeadTime only writes
    project_pr_metrics rows for merged pull requests whose head_ref matches
    the pattern; the refs/heads/ prefix is ignored. A blank pattern keeps
    the current behavior. Invalid patterns fail plan generation with a
    BadInput error.
    
    Refs #9222
    
    * feat(config-ui): add DORA source branch pattern to project settings
    
    Refs #9222
    
    * feat(config-ui): clarify that the DORA source branch pattern is optional
    
    Refs #9222
---
 .../dora/e2e/calculate_change_lead_time_test.go    | 33 ++++++++++++
 .../project_pr_metrics_pr_head_ref_pattern.csv     |  4 ++
 .../dora/e2e/change_lead_time/pull_requests.csv    | 18 +++----
 backend/plugins/dora/impl/impl.go                  | 24 +++++++--
 backend/plugins/dora/impl/impl_test.go             | 56 +++++++++++++++++++
 .../dora/tasks/change_lead_time_calculator.go      | 16 +++++-
 backend/plugins/dora/tasks/task_data.go            | 30 +++++++++++
 backend/plugins/dora/tasks/task_data_test.go       | 62 ++++++++++++++++++++++
 .../src/routes/project/detail/settings-panel.tsx   | 38 +++++++++++--
 9 files changed, 261 insertions(+), 20 deletions(-)

diff --git a/backend/plugins/dora/e2e/calculate_change_lead_time_test.go 
b/backend/plugins/dora/e2e/calculate_change_lead_time_test.go
index 626929c78..86f7dfbdd 100644
--- a/backend/plugins/dora/e2e/calculate_change_lead_time_test.go
+++ b/backend/plugins/dora/e2e/calculate_change_lead_time_test.go
@@ -18,6 +18,7 @@ limitations under the License.
 package e2e
 
 import (
+       "regexp"
        "testing"
 
        "github.com/apache/devlake/core/models/common"
@@ -61,3 +62,35 @@ func TestCalculateCLTimeDataFlow(t *testing.T) {
                IgnoreTypes: []interface{}{common.NoPKModel{}},
        })
 }
+
+func TestCalculateCLTimeWithPrHeadRefPatternDataFlow(t *testing.T) {
+       var plugin impl.Dora
+       dataflowTester := e2ehelper.NewDataFlowTester(t, "dora", plugin)
+
+       taskData := &tasks.DoraTaskData{
+               Options: &tasks.DoraOptions{
+                       ProjectName:      "project1",
+                       PrHeadRefPattern: "^(feat|fix)/",
+               },
+               PrHeadRefRegexp: regexp.MustCompile("^(feat|fix)/"),
+       }
+
+       dataflowTester.FlushTabler(&code.PullRequest{})
+
+       
dataflowTester.ImportCsvIntoTabler("./change_lead_time/project_mapping.csv", 
&crossdomain.ProjectMapping{})
+       dataflowTester.ImportCsvIntoTabler("./change_lead_time/repos.csv", 
&code.Repo{})
+       
dataflowTester.ImportCsvIntoTabler("./change_lead_time/cicd_scopes.csv", 
&devops.CicdScope{})
+       
dataflowTester.ImportCsvIntoTabler("./change_lead_time/pull_requests.csv", 
&code.PullRequest{})
+       
dataflowTester.ImportCsvIntoTabler("./change_lead_time/cicd_deployment_commits.csv",
 &devops.CicdDeploymentCommit{})
+       
dataflowTester.ImportNullableCsvIntoTabler("./change_lead_time/commits_diffs.csv",
 &code.CommitsDiff{})
+       
dataflowTester.ImportCsvIntoTabler("./change_lead_time/pull_request_comments.csv",
 &code.PullRequestComment{})
+       
dataflowTester.ImportCsvIntoTabler("./change_lead_time/pull_request_commits.csv",
 &code.PullRequestCommit{})
+
+       // only pr0 (feat/a), pr1 (fix/b) and pr4 (refs/heads/feat/d) match the 
pattern
+       dataflowTester.FlushTabler(&crossdomain.ProjectPrMetric{})
+       dataflowTester.Subtask(tasks.CalculateChangeLeadTimeMeta, taskData)
+       dataflowTester.VerifyTableWithOptions(&crossdomain.ProjectPrMetric{}, 
e2ehelper.TableOptions{
+               CSVRelPath:  
"./change_lead_time/project_pr_metrics_pr_head_ref_pattern.csv",
+               IgnoreTypes: []interface{}{common.NoPKModel{}},
+       })
+}
diff --git 
a/backend/plugins/dora/e2e/change_lead_time/project_pr_metrics_pr_head_ref_pattern.csv
 
b/backend/plugins/dora/e2e/change_lead_time/project_pr_metrics_pr_head_ref_pattern.csv
new file mode 100644
index 000000000..b41c2ada6
--- /dev/null
+++ 
b/backend/plugins/dora/e2e/change_lead_time/project_pr_metrics_pr_head_ref_pattern.csv
@@ -0,0 +1,4 @@
+id,project_name,first_commit_sha,pr_coding_time,first_review_id,pr_pickup_time,pr_review_time,deployment_commit_id,pr_deploy_time,pr_cycle_time,first_commit_authored_date,first_comment_date,pr_created_date,pr_merged_date,pr_deployed_date
+pr0,project1,pr0_commit0,1440,,,,,,44640,2022-01-10T04:51:47.000+00:00,,2022-01-11T04:51:47.000+00:00,2022-02-10T04:51:47.000+00:00,
+pr1,project1,08d2f2b6de0fa8de4d0e2b55b4b9a2e244214029,1440,comment02,5,55,5,2978,4478,2023-04-10T04:51:47.000+00:00,2023-04-11T04:56:47.000+00:00,2023-04-11T04:51:47.000+00:00,2023-04-11T05:51:47.000+00:00,2023-04-13T07:29:14.000+00:00
+pr4,project1,5ad0c09c447c19338f1dfbb65d89a3728962b3b7,11704,comment10,1500,,,,11764,2023-04-05T04:51:47.000+00:00,2023-04-14T08:55:01.000+00:00,2023-04-13T07:55:01.000+00:00,2023-04-13T08:55:01.000+00:00,
diff --git a/backend/plugins/dora/e2e/change_lead_time/pull_requests.csv 
b/backend/plugins/dora/e2e/change_lead_time/pull_requests.csv
index 3a3ec7ae9..a83370ab0 100644
--- a/backend/plugins/dora/e2e/change_lead_time/pull_requests.csv
+++ b/backend/plugins/dora/e2e/change_lead_time/pull_requests.csv
@@ -1,9 +1,9 @@
-id,base_repo_id,author_id,merge_commit_sha,created_date,merged_date,_raw_data_remark,base_commit_sha,head_commit_sha
-pr0,repo1,a,pr_merge_commit0,2022-1-11 4:51:47,2022-2-10 
4:51:47,deployment_commit 0,,
-pr1,repo1,a,pr_merge_commit1,2023-4-11 4:51:47,2023-4-11 
5:51:47,deployment_commit 5,,
-pr2,repo1,a,pr_merge_commit2,2023-4-12 4:51:47,2023-4-12 
5:51:47,deployment_commit 5,,
-pr3,repo1,a,pr_merge_commit3,2023-4-11 6:53:51,2023-4-14 
6:53:51,deployment_commit 6,,
-pr4,repo1,,pr_merge_commit4,2023-4-13 7:55:01,2023-4-13 8:55:01,,,
-pr5,repo1,,pr_merge_commit5,2023-4-13 7:55:01,2023-4-13 8:55:01,,,
-pr6,repo1,,pr_merge_commit6,2023-4-13 7:55:01,,,,
-pr7,repo1,a,commit9,2023-4-12 7:00:00,2023-4-12 8:00:00,,,
+id,base_repo_id,author_id,merge_commit_sha,created_date,merged_date,_raw_data_remark,base_commit_sha,head_commit_sha,head_ref
+pr0,repo1,a,pr_merge_commit0,2022-1-11 4:51:47,2022-2-10 
4:51:47,deployment_commit 0,,,feat/a
+pr1,repo1,a,pr_merge_commit1,2023-4-11 4:51:47,2023-4-11 
5:51:47,deployment_commit 5,,,fix/b
+pr2,repo1,a,pr_merge_commit2,2023-4-12 4:51:47,2023-4-12 
5:51:47,deployment_commit 5,,,chore/c
+pr3,repo1,a,pr_merge_commit3,2023-4-11 6:53:51,2023-4-14 
6:53:51,deployment_commit 6,,,release-uat
+pr4,repo1,,pr_merge_commit4,2023-4-13 7:55:01,2023-4-13 
8:55:01,,,,refs/heads/feat/d
+pr5,repo1,,pr_merge_commit5,2023-4-13 7:55:01,2023-4-13 8:55:01,,,,
+pr6,repo1,,pr_merge_commit6,2023-4-13 7:55:01,,,,,feat/e
+pr7,repo1,a,commit9,2023-4-12 7:00:00,2023-4-12 8:00:00,,,,hotfix/g
diff --git a/backend/plugins/dora/impl/impl.go 
b/backend/plugins/dora/impl/impl.go
index 92802a1bd..2b7315d16 100644
--- a/backend/plugins/dora/impl/impl.go
+++ b/backend/plugins/dora/impl/impl.go
@@ -104,8 +104,13 @@ func (p Dora) PrepareTaskData(taskCtx plugin.TaskContext, 
options map[string]int
        if err != nil {
                return nil, err
        }
+       prHeadRefRegexp, err := 
tasks.CompilePrHeadRefPattern(op.PrHeadRefPattern)
+       if err != nil {
+               return nil, err
+       }
        return &tasks.DoraTaskData{
-               Options: op,
+               Options:         op,
+               PrHeadRefRegexp: prHeadRefRegexp,
        }, nil
 }
 
@@ -126,6 +131,17 @@ func (p Dora) MakeMetricPluginPipelinePlanV200(projectName 
string, options json.
                        return nil, errors.Default.WrapRaw(err)
                }
        }
+       // validate early so that an invalid pattern fails when the plan is 
generated
+       prHeadRefRegexp, err := 
tasks.CompilePrHeadRefPattern(op.PrHeadRefPattern)
+       if err != nil {
+               return nil, err
+       }
+       changeLeadTimeOptions := map[string]interface{}{
+               "projectName": projectName,
+       }
+       if prHeadRefRegexp != nil {
+               changeLeadTimeOptions["prHeadRefPattern"] = 
prHeadRefRegexp.String()
+       }
 
        plan := coreModels.PipelinePlan{
                {
@@ -154,10 +170,8 @@ func (p Dora) MakeMetricPluginPipelinePlanV200(projectName 
string, options json.
                },
                {
                        {
-                               Plugin: "dora",
-                               Options: map[string]interface{}{
-                                       "projectName": projectName,
-                               },
+                               Plugin:  "dora",
+                               Options: changeLeadTimeOptions,
                                Subtasks: []string{
                                        "calculateChangeLeadTime",
                                        tasks.IssuesToIncidentsMeta.Name,
diff --git a/backend/plugins/dora/impl/impl_test.go 
b/backend/plugins/dora/impl/impl_test.go
index 3fcdc58e4..6ca36abe7 100644
--- a/backend/plugins/dora/impl/impl_test.go
+++ b/backend/plugins/dora/impl/impl_test.go
@@ -22,6 +22,7 @@ import (
        "github.com/apache/devlake/plugins/dora/tasks"
        "testing"
 
+       "github.com/apache/devlake/core/errors"
        coreModels "github.com/apache/devlake/core/models"
        "github.com/stretchr/testify/assert"
 )
@@ -70,3 +71,58 @@ func TestMakeMetricPluginPipelinePlanV200(t *testing.T) {
        }
        assert.Equal(t, doraOutputPlan, plan)
 }
+
+func TestMakeMetricPluginPipelinePlanV200WithPrHeadRefPattern(t *testing.T) {
+       var dora Dora
+       const projectName = "TestMakePlanV200-project"
+       optionJson, err := json.Marshal(map[string]interface{}{
+               "prHeadRefPattern": " ^(feat|fix)/ ",
+       })
+       assert.Nil(t, err)
+       plan, err := dora.MakeMetricPluginPipelinePlanV200(projectName, 
optionJson)
+       assert.Nil(t, err)
+       assert.Len(t, plan, 3)
+       assert.Equal(t, map[string]interface{}{"projectName": projectName}, 
plan[0][0].Options)
+       assert.Equal(t, map[string]interface{}{"projectName": projectName}, 
plan[1][0].Options)
+       assert.Equal(t, map[string]interface{}{
+               "projectName":      projectName,
+               "prHeadRefPattern": "^(feat|fix)/",
+       }, plan[2][0].Options)
+}
+
+func TestMakeMetricPluginPipelinePlanV200WithBlankPrHeadRefPattern(t 
*testing.T) {
+       var dora Dora
+       const projectName = "TestMakePlanV200-project"
+       plan, err := dora.MakeMetricPluginPipelinePlanV200(projectName, 
json.RawMessage(`{"prHeadRefPattern":"  "}`))
+       assert.Nil(t, err)
+       assert.Equal(t, map[string]interface{}{"projectName": projectName}, 
plan[2][0].Options)
+}
+
+func TestPrepareTaskDataPrHeadRefPattern(t *testing.T) {
+       var dora Dora
+
+       data, err := dora.PrepareTaskData(nil, 
map[string]interface{}{"projectName": "p", "prHeadRefPattern": "^feat/"})
+       assert.Nil(t, err)
+       if re := data.(*tasks.DoraTaskData).PrHeadRefRegexp; assert.NotNil(t, 
re) {
+               assert.Equal(t, "^feat/", re.String())
+       }
+
+       data, err = dora.PrepareTaskData(nil, 
map[string]interface{}{"projectName": "p"})
+       assert.Nil(t, err)
+       assert.Nil(t, data.(*tasks.DoraTaskData).PrHeadRefRegexp)
+
+       data, err = dora.PrepareTaskData(nil, 
map[string]interface{}{"projectName": "p", "prHeadRefPattern": "^(feat"})
+       assert.Nil(t, data)
+       if assert.NotNil(t, err) {
+               assert.Equal(t, errors.BadInput, err.GetType())
+       }
+}
+
+func TestMakeMetricPluginPipelinePlanV200WithInvalidPrHeadRefPattern(t 
*testing.T) {
+       var dora Dora
+       plan, err := dora.MakeMetricPluginPipelinePlanV200("p", 
json.RawMessage(`{"prHeadRefPattern":"^(feat"}`))
+       assert.Nil(t, plan)
+       if assert.NotNil(t, err) {
+               assert.Equal(t, errors.BadInput, err.GetType())
+       }
+}
diff --git a/backend/plugins/dora/tasks/change_lead_time_calculator.go 
b/backend/plugins/dora/tasks/change_lead_time_calculator.go
index 9a86c6ed1..fdcf628b5 100644
--- a/backend/plugins/dora/tasks/change_lead_time_calculator.go
+++ b/backend/plugins/dora/tasks/change_lead_time_calculator.go
@@ -79,7 +79,7 @@ func CalculateChangeLeadTime(taskCtx plugin.SubTaskContext) 
errors.Error {
 
        // Get pull requests by repo project_name
        var clauses = []dal.Clause{
-               dal.Select("pr.id, pr.pull_request_key, pr.author_id, 
pr.merge_commit_sha, pr.created_date, pr.merged_date"),
+               dal.Select("pr.id, pr.pull_request_key, pr.author_id, 
pr.merge_commit_sha, pr.created_date, pr.merged_date, pr.head_ref"),
                dal.From("pull_requests pr"),
                dal.Join(`LEFT JOIN project_mapping pm ON (pm.row_id = 
pr.base_repo_id)`),
                dal.Where("pr.merged_date IS NOT NULL AND pm.project_name = ? 
AND pm.table = 'repos'", data.Options.ProjectName),
@@ -90,6 +90,7 @@ func CalculateChangeLeadTime(taskCtx plugin.SubTaskContext) 
errors.Error {
        }
        defer cursor.Close()
 
+       var total, excluded int
        converter, err := api.NewDataConverter(api.DataConverterArgs{
                RawDataSubTaskArgs: api.RawDataSubTaskArgs{
                        Ctx: taskCtx,
@@ -104,6 +105,11 @@ func CalculateChangeLeadTime(taskCtx 
plugin.SubTaskContext) errors.Error {
                Input:        cursor,
                Convert: func(inputRow interface{}) ([]interface{}, 
errors.Error) {
                        pr := inputRow.(*code.PullRequest)
+                       total++
+                       if !MatchPrHeadRef(data.PrHeadRefRegexp, pr.HeadRef) {
+                               excluded++
+                               return nil, nil
+                       }
                        // Initialize a new ProjectPrMetric
                        projectPrMetric := &crossdomain.ProjectPrMetric{}
                        projectPrMetric.Id = pr.Id
@@ -165,7 +171,13 @@ func CalculateChangeLeadTime(taskCtx 
plugin.SubTaskContext) errors.Error {
                return err
        }
        // Execute the data converter
-       return converter.Execute()
+       if err := converter.Execute(); err != nil {
+               return err
+       }
+       if data.PrHeadRefRegexp != nil {
+               logger.Info("prHeadRefPattern %q excluded %d of %d merged pull 
requests", data.PrHeadRefRegexp.String(), excluded, total)
+       }
+       return nil
 }
 
 func computeTimeSpan(start, end *time.Time) *int64 {
diff --git a/backend/plugins/dora/tasks/task_data.go 
b/backend/plugins/dora/tasks/task_data.go
index 83fadc496..55a05629f 100644
--- a/backend/plugins/dora/tasks/task_data.go
+++ b/backend/plugins/dora/tasks/task_data.go
@@ -18,6 +18,9 @@ limitations under the License.
 package tasks
 
 import (
+       "regexp"
+       "strings"
+
        "github.com/apache/devlake/core/errors"
        helper "github.com/apache/devlake/helpers/pluginhelper/api"
 )
@@ -31,11 +34,15 @@ type DoraOptions struct {
        Since       string
        ProjectName string  `json:"projectName"`
        ScopeId     *string `json:"scopeId,omitempty"`
+       // PrHeadRefPattern is a Go RE2 regex; when set, only merged pull 
requests whose
+       // source branch (head_ref) matches it are counted by 
calculateChangeLeadTime.
+       PrHeadRefPattern string `json:"prHeadRefPattern,omitempty"`
 }
 
 type DoraTaskData struct {
        Options                         *DoraOptions
        DisableIssueToIncidentGenerator bool
+       PrHeadRefRegexp                 *regexp.Regexp
 }
 
 func DecodeAndValidateTaskOptions(options map[string]interface{}) 
(*DoraOptions, errors.Error) {
@@ -47,3 +54,26 @@ func DecodeAndValidateTaskOptions(options 
map[string]interface{}) (*DoraOptions,
 
        return &op, nil
 }
+
+// CompilePrHeadRefPattern compiles the prHeadRefPattern option.
+// A blank pattern means no filtering and yields a nil regexp.
+func CompilePrHeadRefPattern(pattern string) (*regexp.Regexp, errors.Error) {
+       pattern = strings.TrimSpace(pattern)
+       if pattern == "" {
+               return nil, nil
+       }
+       re, err := regexp.Compile(pattern)
+       if err != nil {
+               return nil, errors.BadInput.Wrap(err, "invalid 
prHeadRefPattern")
+       }
+       return re, nil
+}
+
+// MatchPrHeadRef reports whether a pull request source branch matches the 
pattern.
+// A nil regexp matches everything. The "refs/heads/" prefix is ignored.
+func MatchPrHeadRef(re *regexp.Regexp, headRef string) bool {
+       if re == nil {
+               return true
+       }
+       return re.MatchString(strings.TrimPrefix(headRef, "refs/heads/"))
+}
diff --git a/backend/plugins/dora/tasks/task_data_test.go 
b/backend/plugins/dora/tasks/task_data_test.go
new file mode 100644
index 000000000..10245bdd2
--- /dev/null
+++ b/backend/plugins/dora/tasks/task_data_test.go
@@ -0,0 +1,62 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+       "testing"
+
+       "github.com/apache/devlake/core/errors"
+       "github.com/stretchr/testify/assert"
+)
+
+func TestCompilePrHeadRefPattern(t *testing.T) {
+       for _, pattern := range []string{"", "   "} {
+               re, err := CompilePrHeadRefPattern(pattern)
+               assert.Nil(t, err)
+               assert.Nil(t, re)
+       }
+
+       re, err := CompilePrHeadRefPattern("^(feat")
+       assert.Nil(t, re)
+       if assert.NotNil(t, err) {
+               assert.Equal(t, errors.BadInput, err.GetType())
+       }
+
+       re, err = CompilePrHeadRefPattern(" ^(feat|fix)/ ")
+       assert.Nil(t, err)
+       if assert.NotNil(t, re) {
+               assert.Equal(t, "^(feat|fix)/", re.String())
+       }
+}
+
+func TestMatchPrHeadRef(t *testing.T) {
+       assert.True(t, MatchPrHeadRef(nil, ""))
+       assert.True(t, MatchPrHeadRef(nil, "chore/a"))
+
+       re, err := CompilePrHeadRefPattern("^(feat|fix)/")
+       assert.Nil(t, err)
+       assert.True(t, MatchPrHeadRef(re, "feat/a"))
+       assert.True(t, MatchPrHeadRef(re, "refs/heads/fix/b"))
+       assert.False(t, MatchPrHeadRef(re, "chore/a"))
+       assert.False(t, MatchPrHeadRef(re, ""))
+       assert.False(t, MatchPrHeadRef(re, "FEAT/x"))
+
+       re, err = CompilePrHeadRefPattern("(?i)^feat/")
+       assert.Nil(t, err)
+       assert.True(t, MatchPrHeadRef(re, "FEAT/x"))
+}
diff --git a/config-ui/src/routes/project/detail/settings-panel.tsx 
b/config-ui/src/routes/project/detail/settings-panel.tsx
index 39832ca36..7b62c620e 100644
--- a/config-ui/src/routes/project/detail/settings-panel.tsx
+++ b/config-ui/src/routes/project/detail/settings-panel.tsx
@@ -55,6 +55,7 @@ export const SettingsPanel = ({ project, onRefresh }: Props) 
=> {
   const [name, setName] = useState('');
   const [dora, setDora] = useState({
     enable: false,
+    prHeadRefPattern: '',
   });
   const [linker, setLinker] = useState({
     enable: false,
@@ -81,6 +82,7 @@ export const SettingsPanel = ({ project, onRefresh }: Props) 
=> {
     setName(project.name);
     setDora({
       enable: dora?.enable ?? false,
+      prHeadRefPattern: dora?.pluginOption?.prHeadRefPattern ?? '',
     });
     setLinker({
       enable: linker?.enable ?? false,
@@ -132,7 +134,9 @@ export const SettingsPanel = ({ project, onRefresh }: 
Props) => {
           metrics: [
             {
               pluginName: 'dora',
-              pluginOption: {},
+              pluginOption: {
+                prHeadRefPattern: dora.prHeadRefPattern.trim(),
+              },
               enable: dora.enable,
             },
             {
@@ -208,12 +212,38 @@ export const SettingsPanel = ({ project, onRefresh }: 
Props) => {
           </Block>
           <Block
             title={
-              <Checkbox checked={dora.enable} onChange={(e) => setDora({ 
enable: e.target.checked })}>
+              <Checkbox checked={dora.enable} onChange={(e) => setDora({ 
...dora, enable: e.target.checked })}>
                 Enable DORA Metrics
               </Checkbox>
             }
-            description="DORA metrics are four widely-adopted metrics for 
measuring software delivery performance."
-          />
+            description={
+              <span>
+                DORA metrics are four widely-adopted metrics for measuring 
software delivery performance. Optionally,
+                only count pull requests whose source branch matches this 
regex as changes for Lead Time for Changes and
+                the PR cycle time metrics. Leave empty to count all merged 
pull requests.
+                <HelpTooltip
+                  overlayInnerStyle={{ width: 500 }}
+                  content={
+                    <>
+                      <div>Example 1 - Only feature and fix branches: 
^(feat|fix)/</div>
+                      <div>Example 2 - Same, case-insensitive, plus hotfix: 
(?i)^(feat|fix|hotfix)/</div>
+                      <div>Go RE2 syntax; lookahead such as (?!...) is not 
supported.</div>
+                      <div>The refs/heads/ prefix (Azure DevOps) is ignored 
when matching.</div>
+                    </>
+                  }
+                />
+              </span>
+            }
+          >
+            {dora.enable && (
+              <Input
+                style={{ width: 600 }}
+                placeholder="^(feat|fix)/"
+                value={dora.prHeadRefPattern}
+                onChange={(e) => setDora({ ...dora, prHeadRefPattern: 
e.target.value })}
+              />
+            )}
+          </Block>
           <Block
             title={
               <Checkbox checked={linker.enable} onChange={(e) => setLinker({ 
...linker, enable: e.target.checked })}>

Reply via email to