You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by ly...@apache.org on 2024/06/21 09:04:34 UTC

(incubator-devlake) branch dev0620 created (now 636d3870a)

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

lynwee pushed a change to branch dev0620
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


      at 636d3870a feat(dora): add incident related tables

This branch includes the following new commits:

     new 636d3870a feat(dora): add incident related tables

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(incubator-devlake) 01/01: feat(dora): add incident related tables

Posted by ly...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

lynwee pushed a commit to branch dev0620
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git

commit 636d3870a6287b8577216d00fc012b1de758bb0b
Author: d4x1 <15...@qq.com>
AuthorDate: Fri Jun 21 17:04:22 2024 +0800

    feat(dora): add incident related tables
---
 .../core/models/domainlayer/code/pull_request.go   |  53 ++++++++
 ...=> project_incident_deployment_relationship.go} |   6 +-
 .../models/domainlayer/domaininfo/domaininfo.go    |   2 +-
 .../domainlayer/ticket/{issue.go => incident.go}   |  70 ++--------
 .../incident_assginee.go}                          |  20 +--
 backend/core/models/domainlayer/ticket/issue.go    |  51 ++++++-
 ...40621_init_incidents_and_incident_assignees.go} |  27 ++--
 .../20240621_rename_project_issue_metrics.go}      |  23 ++--
 .../archived/incident.go}                          |  71 ++--------
 .../archived/incident_assignee.go}                 |  18 ++-
 backend/core/models/migrationscripts/register.go   |   2 +
 .../dora/e2e/connect_incident_to_deploy_test.go    |   6 +-
 ... project_incident_deployment_relationships.csv} |   0
 backend/plugins/dora/impl/impl.go                  |   4 +
 .../dora/tasks/incident_deploy_connector.go        |   8 +-
 .../dora/tasks/incident_from_issue_generator.go    | 151 +++++++++++++++++++++
 .../tasks/incident_from_pull_request_generator.go  |  65 +++++++++
 backend/plugins/starrocks/utils/utils.go           |   2 +-
 .../pydevlake/domain_layer/crossdomain.py          |   4 +-
 backend/server/services/project.go                 |   6 +-
 grafana/dashboards/DORA.json                       |  10 +-
 grafana/dashboards/DORAByTeam.json                 |  10 +-
 grafana/dashboards/DORADebug.json                  |  12 +-
 .../dashboards/DORADetails-ChangeFailureRate.json  |   6 +-
 .../DORADetails-FailedDeploymentRecoveryTime.json  |   6 +-
 25 files changed, 433 insertions(+), 200 deletions(-)

diff --git a/backend/core/models/domainlayer/code/pull_request.go b/backend/core/models/domainlayer/code/pull_request.go
index a300ae544..0c3be7f8d 100644
--- a/backend/core/models/domainlayer/code/pull_request.go
+++ b/backend/core/models/domainlayer/code/pull_request.go
@@ -18,6 +18,8 @@ limitations under the License.
 package code
 
 import (
+	"fmt"
+	"github.com/apache/incubator-devlake/core/models/domainlayer/ticket"
 	"time"
 
 	"github.com/apache/incubator-devlake/core/models/domainlayer"
@@ -58,3 +60,54 @@ type PullRequest struct {
 func (PullRequest) TableName() string {
 	return "pull_requests"
 }
+
+func (pr PullRequest) ConvertStatusToIncidentStatus() string {
+	switch pr.Status {
+	case OPEN:
+		return ticket.TODO
+	case CLOSED:
+		return ticket.OTHER
+	case MERGED:
+		return ticket.DONE
+	default:
+		return ticket.OTHER
+	}
+}
+
+func (pr PullRequest) ToIncident() (*ticket.Incident, error) {
+	incident := &ticket.Incident{
+		DomainEntity:            pr.DomainEntity,
+		Url:                     pr.Url,
+		IncidentKey:             fmt.Sprintf("%d", pr.PullRequestKey),
+		Title:                   pr.Title,
+		Description:             pr.Description,
+		Status:                  pr.ConvertStatusToIncidentStatus(),
+		OriginalStatus:          pr.OriginalStatus,
+		ResolutionDate:          pr.MergedDate,
+		CreatedDate:             &pr.CreatedDate,
+		OriginalEstimateMinutes: nil,
+		TimeSpentMinutes:        nil,
+		TimeRemainingMinutes:    nil,
+		CreatorId:               pr.AuthorId,
+		CreatorName:             pr.AuthorName,
+		ParentIncidentId:        pr.ParentPrId,
+		Priority:                "",
+		Severity:                "",
+		Urgency:                 "",
+		Component:               pr.Component,
+		OriginalProject:         "",
+	}
+
+	if pr.MergedDate != nil {
+		incident.UpdatedDate = pr.MergedDate
+	}
+	if incident.UpdatedDate == nil {
+		incident.UpdatedDate = pr.ClosedDate
+	}
+
+	if pr.MergedDate != nil {
+		temp := uint(pr.MergedDate.Sub(pr.CreatedDate).Minutes())
+		incident.LeadTimeMinutes = &temp
+	}
+	return incident, nil
+}
diff --git a/backend/core/models/domainlayer/crossdomain/project_issue_metric.go b/backend/core/models/domainlayer/crossdomain/project_incident_deployment_relationship.go
similarity index 85%
copy from backend/core/models/domainlayer/crossdomain/project_issue_metric.go
copy to backend/core/models/domainlayer/crossdomain/project_incident_deployment_relationship.go
index 5c2a3cf51..755741c1b 100644
--- a/backend/core/models/domainlayer/crossdomain/project_issue_metric.go
+++ b/backend/core/models/domainlayer/crossdomain/project_incident_deployment_relationship.go
@@ -21,12 +21,12 @@ import (
 	"github.com/apache/incubator-devlake/core/models/domainlayer"
 )
 
-type ProjectIssueMetric struct {
+type ProjectIncidentDeploymentRelationship struct {
 	domainlayer.DomainEntity
 	ProjectName  string `gorm:"primaryKey;type:varchar(100)"`
 	DeploymentId string
 }
 
-func (ProjectIssueMetric) TableName() string {
-	return "project_issue_metrics"
+func (ProjectIncidentDeploymentRelationship) TableName() string {
+	return "project_incident_deployment_relationships"
 }
diff --git a/backend/core/models/domainlayer/domaininfo/domaininfo.go b/backend/core/models/domainlayer/domaininfo/domaininfo.go
index 422f537ab..12f7a2e02 100644
--- a/backend/core/models/domainlayer/domaininfo/domaininfo.go
+++ b/backend/core/models/domainlayer/domaininfo/domaininfo.go
@@ -60,7 +60,7 @@ func GetDomainTablesInfo() []dal.Tabler {
 		&crossdomain.IssueCommit{},
 		&crossdomain.IssueRepoCommit{},
 		&crossdomain.ProjectMapping{},
-		&crossdomain.ProjectIssueMetric{},
+		&crossdomain.ProjectIncidentDeploymentRelationship{},
 		&crossdomain.ProjectPrMetric{},
 		&crossdomain.PullRequestIssue{},
 		&crossdomain.RefsIssuesDiffs{},
diff --git a/backend/core/models/domainlayer/ticket/issue.go b/backend/core/models/domainlayer/ticket/incident.go
similarity index 55%
copy from backend/core/models/domainlayer/ticket/issue.go
copy to backend/core/models/domainlayer/ticket/incident.go
index 7f29404d8..b565cd3b9 100644
--- a/backend/core/models/domainlayer/ticket/issue.go
+++ b/backend/core/models/domainlayer/ticket/incident.go
@@ -18,24 +18,21 @@ limitations under the License.
 package ticket
 
 import (
-	"time"
-
 	"github.com/apache/incubator-devlake/core/models/domainlayer"
+	"time"
 )
 
-type Issue struct {
+// Incident comes from two tables: issues and pull_requests, it won't be produced from tool layer tables so far.
+// 1.From issues, all fields are the same with issues' record, include id field.
+// 2.From pull_requests, id field is pull_requests.id, other fields should be transformed or calculated.
+type Incident struct {
 	domainlayer.DomainEntity
 	Url                     string `gorm:"type:varchar(255)"`
-	IconURL                 string `gorm:"type:varchar(255);column:icon_url"`
-	IssueKey                string `gorm:"type:varchar(255)"`
+	IncidentKey             string `gorm:"type:varchar(255)"` // issue_key/pull_request_key
 	Title                   string
 	Description             string
-	EpicKey                 string `gorm:"type:varchar(255)"`
-	Type                    string `gorm:"type:varchar(100)"`
-	OriginalType            string `gorm:"type:varchar(500)"`
 	Status                  string `gorm:"type:varchar(100)"`
 	OriginalStatus          string `gorm:"type:varchar(100)"`
-	StoryPoint              *float64
 	ResolutionDate          *time.Time
 	CreatedDate             *time.Time
 	UpdatedDate             *time.Time
@@ -45,9 +42,7 @@ type Issue struct {
 	TimeRemainingMinutes    *int64
 	CreatorId               string `gorm:"type:varchar(255)"`
 	CreatorName             string `gorm:"type:varchar(255)"`
-	AssigneeId              string `gorm:"type:varchar(255)"`
-	AssigneeName            string `gorm:"type:varchar(255)"`
-	ParentIssueId           string `gorm:"type:varchar(255)"`
+	ParentIncidentId        string `gorm:"type:varchar(255)"`
 	Priority                string `gorm:"type:varchar(255)"`
 	Severity                string `gorm:"type:varchar(255)"`
 	Urgency                 string `gorm:"type:varchar(255)"`
@@ -55,53 +50,6 @@ type Issue struct {
 	OriginalProject         string `gorm:"type:varchar(255)"`
 }
 
-func (Issue) TableName() string {
-	return "issues"
-}
-
-const (
-	BUG         = "BUG"
-	REQUIREMENT = "REQUIREMENT"
-	INCIDENT    = "INCIDENT"
-	TASK        = "TASK"
-	SUBTASK     = "SUBTASK"
-
-	// status
-	TODO        = "TODO"
-	DONE        = "DONE"
-	IN_PROGRESS = "IN_PROGRESS"
-	OTHER       = "OTHER"
-)
-
-type StatusRule struct {
-	InProgress []string
-	Todo       []string
-	Done       []string
-	Other      []string
-	Default    string
-}
-
-// GetStatus compare the input with rule for return the enmu value of status
-func GetStatus(rule *StatusRule, input interface{}) string {
-	for _, inp := range rule.InProgress {
-		if inp == input {
-			return IN_PROGRESS
-		}
-	}
-	for _, Todo := range rule.Todo {
-		if Todo == input {
-			return TODO
-		}
-	}
-	for _, done := range rule.Done {
-		if done == input {
-			return DONE
-		}
-	}
-	for _, Other := range rule.Other {
-		if Other == input {
-			return OTHER
-		}
-	}
-	return rule.Default
+func (Incident) TableName() string {
+	return "incidents"
 }
diff --git a/backend/core/models/domainlayer/crossdomain/project_issue_metric.go b/backend/core/models/domainlayer/ticket/incident_assginee.go
similarity index 67%
copy from backend/core/models/domainlayer/crossdomain/project_issue_metric.go
copy to backend/core/models/domainlayer/ticket/incident_assginee.go
index 5c2a3cf51..d4e16c993 100644
--- a/backend/core/models/domainlayer/crossdomain/project_issue_metric.go
+++ b/backend/core/models/domainlayer/ticket/incident_assginee.go
@@ -15,18 +15,18 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package crossdomain
+package ticket
 
-import (
-	"github.com/apache/incubator-devlake/core/models/domainlayer"
-)
+import "github.com/apache/incubator-devlake/core/models/common"
 
-type ProjectIssueMetric struct {
-	domainlayer.DomainEntity
-	ProjectName  string `gorm:"primaryKey;type:varchar(100)"`
-	DeploymentId string
+type IncidentAssignee struct {
+	IncidentId   string `gorm:"primaryKey;type:varchar(255)"`
+	AssigneeId   string `gorm:"primaryKey;type:varchar(255)"`
+	AssigneeName string `gorm:"type:varchar(255)"`
+
+	common.NoPKModel
 }
 
-func (ProjectIssueMetric) TableName() string {
-	return "project_issue_metrics"
+func (IncidentAssignee) TableName() string {
+	return "incident_assignees"
 }
diff --git a/backend/core/models/domainlayer/ticket/issue.go b/backend/core/models/domainlayer/ticket/issue.go
index 7f29404d8..ec88fdb13 100644
--- a/backend/core/models/domainlayer/ticket/issue.go
+++ b/backend/core/models/domainlayer/ticket/issue.go
@@ -18,9 +18,9 @@ limitations under the License.
 package ticket
 
 import (
-	"time"
-
+	"errors"
 	"github.com/apache/incubator-devlake/core/models/domainlayer"
+	"time"
 )
 
 type Issue struct {
@@ -105,3 +105,50 @@ func GetStatus(rule *StatusRule, input interface{}) string {
 	}
 	return rule.Default
 }
+
+func (issue Issue) IsIncident() bool {
+	return issue.Type != INCIDENT
+}
+
+func (issue Issue) ToIncidentAssignee() (*IncidentAssignee, error) {
+	if !issue.IsIncident() {
+		return nil, errors.New("issue type is not INCIDENT, cannot generate incident_assignee")
+	}
+	return &IncidentAssignee{
+		IncidentId:   issue.Id,
+		AssigneeId:   issue.AssigneeId,
+		AssigneeName: issue.AssigneeName,
+		NoPKModel:    issue.DomainEntity.NoPKModel,
+	}, nil
+}
+
+func (issue Issue) ToIncident() (*Incident, error) {
+	if !issue.IsIncident() {
+		return nil, errors.New("issue type is not INCIDENT, cannot generate incident")
+	}
+	incident := &Incident{
+		DomainEntity:            issue.DomainEntity,
+		Url:                     issue.Url,
+		IncidentKey:             issue.IssueKey,
+		Title:                   issue.Title,
+		Description:             issue.Description,
+		Status:                  issue.Status,
+		OriginalStatus:          issue.OriginalStatus,
+		ResolutionDate:          issue.ResolutionDate,
+		CreatedDate:             issue.CreatedDate,
+		UpdatedDate:             issue.UpdatedDate,
+		LeadTimeMinutes:         issue.LeadTimeMinutes,
+		OriginalEstimateMinutes: issue.OriginalEstimateMinutes,
+		TimeSpentMinutes:        issue.TimeSpentMinutes,
+		TimeRemainingMinutes:    issue.TimeRemainingMinutes,
+		CreatorId:               issue.CreatorId,
+		CreatorName:             issue.CreatorName,
+		ParentIncidentId:        issue.ParentIssueId,
+		Priority:                issue.Priority,
+		Severity:                issue.Severity,
+		Urgency:                 issue.Urgency,
+		Component:               issue.Component,
+		OriginalProject:         issue.OriginalProject,
+	}
+	return incident, nil
+}
diff --git a/backend/core/models/domainlayer/crossdomain/project_issue_metric.go b/backend/core/models/migrationscripts/20240621_init_incidents_and_incident_assignees.go
similarity index 53%
copy from backend/core/models/domainlayer/crossdomain/project_issue_metric.go
copy to backend/core/models/migrationscripts/20240621_init_incidents_and_incident_assignees.go
index 5c2a3cf51..d98275162 100644
--- a/backend/core/models/domainlayer/crossdomain/project_issue_metric.go
+++ b/backend/core/models/migrationscripts/20240621_init_incidents_and_incident_assignees.go
@@ -15,18 +15,29 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package crossdomain
+package migrationscripts
 
 import (
-	"github.com/apache/incubator-devlake/core/models/domainlayer"
+	"github.com/apache/incubator-devlake/core/context"
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/models/migrationscripts/archived"
+	"github.com/apache/incubator-devlake/helpers/migrationhelper"
 )
 
-type ProjectIssueMetric struct {
-	domainlayer.DomainEntity
-	ProjectName  string `gorm:"primaryKey;type:varchar(100)"`
-	DeploymentId string
+type initIncidentRelatedTables struct{}
+
+func (u *initIncidentRelatedTables) Up(basicRes context.BasicRes) errors.Error {
+	return migrationhelper.AutoMigrateTables(
+		basicRes,
+		&archived.IncidentAssignee{},
+		&archived.Incident{},
+	)
+}
+
+func (*initIncidentRelatedTables) Version() uint64 {
+	return 20240621152500
 }
 
-func (ProjectIssueMetric) TableName() string {
-	return "project_issue_metrics"
+func (*initIncidentRelatedTables) Name() string {
+	return "init tables: incidents and incident_assignees"
 }
diff --git a/backend/core/models/domainlayer/crossdomain/project_issue_metric.go b/backend/core/models/migrationscripts/20240621_rename_project_issue_metrics.go
similarity index 53%
copy from backend/core/models/domainlayer/crossdomain/project_issue_metric.go
copy to backend/core/models/migrationscripts/20240621_rename_project_issue_metrics.go
index 5c2a3cf51..76fe3db48 100644
--- a/backend/core/models/domainlayer/crossdomain/project_issue_metric.go
+++ b/backend/core/models/migrationscripts/20240621_rename_project_issue_metrics.go
@@ -15,18 +15,25 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package crossdomain
+package migrationscripts
 
 import (
-	"github.com/apache/incubator-devlake/core/models/domainlayer"
+	"github.com/apache/incubator-devlake/core/context"
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/models/migrationscripts/archived"
 )
 
-type ProjectIssueMetric struct {
-	domainlayer.DomainEntity
-	ProjectName  string `gorm:"primaryKey;type:varchar(100)"`
-	DeploymentId string
+type renameProjectIssueMetrics struct{}
+
+func (u *renameProjectIssueMetrics) Up(basicRes context.BasicRes) errors.Error {
+	db := basicRes.GetDal()
+	return db.RenameTable(archived.ProjectIssueMetric{}.TableName(), "project_incident_deployment_relationships")
+}
+
+func (*renameProjectIssueMetrics) Version() uint64 {
+	return 20240621162000
 }
 
-func (ProjectIssueMetric) TableName() string {
-	return "project_issue_metrics"
+func (*renameProjectIssueMetrics) Name() string {
+	return "rename project_issue_metrics to project_incident_deployment_relationships"
 }
diff --git a/backend/core/models/domainlayer/ticket/issue.go b/backend/core/models/migrationscripts/archived/incident.go
similarity index 50%
copy from backend/core/models/domainlayer/ticket/issue.go
copy to backend/core/models/migrationscripts/archived/incident.go
index 7f29404d8..8c00af341 100644
--- a/backend/core/models/domainlayer/ticket/issue.go
+++ b/backend/core/models/migrationscripts/archived/incident.go
@@ -15,27 +15,20 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package ticket
+package archived
 
 import (
 	"time"
-
-	"github.com/apache/incubator-devlake/core/models/domainlayer"
 )
 
-type Issue struct {
-	domainlayer.DomainEntity
+type Incident struct {
+	DomainEntity
 	Url                     string `gorm:"type:varchar(255)"`
-	IconURL                 string `gorm:"type:varchar(255);column:icon_url"`
-	IssueKey                string `gorm:"type:varchar(255)"`
+	IncidentKey             string `gorm:"type:varchar(255)"` // issue_key/pull_request_key
 	Title                   string
 	Description             string
-	EpicKey                 string `gorm:"type:varchar(255)"`
-	Type                    string `gorm:"type:varchar(100)"`
-	OriginalType            string `gorm:"type:varchar(500)"`
 	Status                  string `gorm:"type:varchar(100)"`
 	OriginalStatus          string `gorm:"type:varchar(100)"`
-	StoryPoint              *float64
 	ResolutionDate          *time.Time
 	CreatedDate             *time.Time
 	UpdatedDate             *time.Time
@@ -45,63 +38,15 @@ type Issue struct {
 	TimeRemainingMinutes    *int64
 	CreatorId               string `gorm:"type:varchar(255)"`
 	CreatorName             string `gorm:"type:varchar(255)"`
-	AssigneeId              string `gorm:"type:varchar(255)"`
-	AssigneeName            string `gorm:"type:varchar(255)"`
-	ParentIssueId           string `gorm:"type:varchar(255)"`
+	ParentIncidentId        string `gorm:"type:varchar(255)"`
 	Priority                string `gorm:"type:varchar(255)"`
 	Severity                string `gorm:"type:varchar(255)"`
 	Urgency                 string `gorm:"type:varchar(255)"`
 	Component               string `gorm:"type:varchar(255)"`
-	OriginalProject         string `gorm:"type:varchar(255)"`
-}
-
-func (Issue) TableName() string {
-	return "issues"
-}
-
-const (
-	BUG         = "BUG"
-	REQUIREMENT = "REQUIREMENT"
-	INCIDENT    = "INCIDENT"
-	TASK        = "TASK"
-	SUBTASK     = "SUBTASK"
-
-	// status
-	TODO        = "TODO"
-	DONE        = "DONE"
-	IN_PROGRESS = "IN_PROGRESS"
-	OTHER       = "OTHER"
-)
 
-type StatusRule struct {
-	InProgress []string
-	Todo       []string
-	Done       []string
-	Other      []string
-	Default    string
+	OriginalProject string `gorm:"type:varchar(255)"`
 }
 
-// GetStatus compare the input with rule for return the enmu value of status
-func GetStatus(rule *StatusRule, input interface{}) string {
-	for _, inp := range rule.InProgress {
-		if inp == input {
-			return IN_PROGRESS
-		}
-	}
-	for _, Todo := range rule.Todo {
-		if Todo == input {
-			return TODO
-		}
-	}
-	for _, done := range rule.Done {
-		if done == input {
-			return DONE
-		}
-	}
-	for _, Other := range rule.Other {
-		if Other == input {
-			return OTHER
-		}
-	}
-	return rule.Default
+func (Incident) TableName() string {
+	return "incidents"
 }
diff --git a/backend/core/models/domainlayer/crossdomain/project_issue_metric.go b/backend/core/models/migrationscripts/archived/incident_assignee.go
similarity index 71%
rename from backend/core/models/domainlayer/crossdomain/project_issue_metric.go
rename to backend/core/models/migrationscripts/archived/incident_assignee.go
index 5c2a3cf51..4207e2fd3 100644
--- a/backend/core/models/domainlayer/crossdomain/project_issue_metric.go
+++ b/backend/core/models/migrationscripts/archived/incident_assignee.go
@@ -15,18 +15,16 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-package crossdomain
+package archived
 
-import (
-	"github.com/apache/incubator-devlake/core/models/domainlayer"
-)
+type IncidentAssignee struct {
+	IncidentId   string `gorm:"primaryKey;type:varchar(255)"`
+	AssigneeId   string `gorm:"primaryKey;type:varchar(255)"`
+	AssigneeName string `gorm:"type:varchar(255)"`
 
-type ProjectIssueMetric struct {
-	domainlayer.DomainEntity
-	ProjectName  string `gorm:"primaryKey;type:varchar(100)"`
-	DeploymentId string
+	NoPKModel
 }
 
-func (ProjectIssueMetric) TableName() string {
-	return "project_issue_metrics"
+func (IncidentAssignee) TableName() string {
+	return "incident_assignees"
 }
diff --git a/backend/core/models/migrationscripts/register.go b/backend/core/models/migrationscripts/register.go
index 057a2a327..6caa89677 100644
--- a/backend/core/models/migrationscripts/register.go
+++ b/backend/core/models/migrationscripts/register.go
@@ -123,5 +123,7 @@ func All() []plugin.MigrationScript {
 		new(modifyPrAssigneeAndReviewerId),
 		new(addPullRequestIdIndexToPullRequestCommits),
 		new(addPullRequestIdIndexToPullRequestComments),
+		new(initIncidentRelatedTables),
+		new(renameProjectIssueMetrics),
 	}
 }
diff --git a/backend/plugins/dora/e2e/connect_incident_to_deploy_test.go b/backend/plugins/dora/e2e/connect_incident_to_deploy_test.go
index ee340d078..ed625063d 100644
--- a/backend/plugins/dora/e2e/connect_incident_to_deploy_test.go
+++ b/backend/plugins/dora/e2e/connect_incident_to_deploy_test.go
@@ -45,10 +45,10 @@ func TestConnectIncidentToDeploymentDataFlow(t *testing.T) {
 	dataflowTester.ImportCsvIntoTabler("./raw_tables/issues.csv", &ticket.Issue{})
 
 	// verify converter
-	dataflowTester.FlushTabler(&crossdomain.ProjectIssueMetric{})
+	dataflowTester.FlushTabler(&crossdomain.ProjectIncidentDeploymentRelationship{})
 	dataflowTester.Subtask(tasks.ConnectIncidentToDeploymentMeta, taskData)
-	dataflowTester.VerifyTableWithOptions(&crossdomain.ProjectIssueMetric{}, e2ehelper.TableOptions{
-		CSVRelPath:  "./snapshot_tables/project_issue_metrics.csv",
+	dataflowTester.VerifyTableWithOptions(&crossdomain.ProjectIncidentDeploymentRelationship{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/project_incident_deployment_relationships.csv",
 		IgnoreTypes: []interface{}{common.NoPKModel{}},
 	})
 }
diff --git a/backend/plugins/dora/e2e/snapshot_tables/project_issue_metrics.csv b/backend/plugins/dora/e2e/snapshot_tables/project_incident_deployment_relationships.csv
similarity index 100%
rename from backend/plugins/dora/e2e/snapshot_tables/project_issue_metrics.csv
rename to backend/plugins/dora/e2e/snapshot_tables/project_incident_deployment_relationships.csv
diff --git a/backend/plugins/dora/impl/impl.go b/backend/plugins/dora/impl/impl.go
index f96a04b5b..530a9b486 100644
--- a/backend/plugins/dora/impl/impl.go
+++ b/backend/plugins/dora/impl/impl.go
@@ -95,6 +95,8 @@ func (p Dora) SubTaskMetas() []plugin.SubTaskMeta {
 		tasks.EnrichTaskEnvMeta,
 		tasks.CalculateChangeLeadTimeMeta,
 		tasks.ConnectIncidentToDeploymentMeta,
+		tasks.IssuesToIncidentsMeta,
+		tasks.PullRequestToIncidentsMeta,
 	}
 }
 
@@ -160,6 +162,8 @@ func (p Dora) MakeMetricPluginPipelinePlanV200(projectName string, options json.
 				Subtasks: []string{
 					"calculateChangeLeadTime",
 					"ConnectIncidentToDeployment",
+					tasks.IssuesToIncidentsMeta.Name,
+					tasks.PullRequestToIncidentsMeta.Name,
 				},
 			},
 		},
diff --git a/backend/plugins/dora/tasks/incident_deploy_connector.go b/backend/plugins/dora/tasks/incident_deploy_connector.go
index 6d9a1365d..d91bca5eb 100644
--- a/backend/plugins/dora/tasks/incident_deploy_connector.go
+++ b/backend/plugins/dora/tasks/incident_deploy_connector.go
@@ -44,13 +44,15 @@ type simpleCicdDeploymentCommit struct {
 	FinishedDate *time.Time
 }
 
+// ConnectIncidentToDeployment will generate data to crossdomain.ProjectIncidentDeploymentRelationship.
+// FIXME: it should be generated from Incident.
 func ConnectIncidentToDeployment(taskCtx plugin.SubTaskContext) errors.Error {
 	db := taskCtx.GetDal()
 	data := taskCtx.GetData().(*DoraTaskData)
 	// Clear previous results from the project
-	err := db.Exec("DELETE FROM project_issue_metrics WHERE project_name = ?", data.Options.ProjectName)
+	err := db.Exec("DELETE FROM ? WHERE project_name = ?", crossdomain.ProjectIncidentDeploymentRelationship{}.TableName(), data.Options.ProjectName)
 	if err != nil {
-		return errors.Default.Wrap(err, "error deleting previous project_issue_metrics")
+		return errors.Default.Wrap(err, "error deleting previous project_incident_deployment_relationships")
 	}
 	// select all issues belongs to the board
 	clauses := []dal.Clause{
@@ -80,7 +82,7 @@ func ConnectIncidentToDeployment(taskCtx plugin.SubTaskContext) errors.Error {
 		Input:        cursor,
 		Convert: func(inputRow interface{}) ([]interface{}, errors.Error) {
 			issue := inputRow.(*ticket.Issue)
-			projectIssueMetric := &crossdomain.ProjectIssueMetric{
+			projectIssueMetric := &crossdomain.ProjectIncidentDeploymentRelationship{
 				DomainEntity: domainlayer.DomainEntity{
 					Id: issue.Id,
 				},
diff --git a/backend/plugins/dora/tasks/incident_from_issue_generator.go b/backend/plugins/dora/tasks/incident_from_issue_generator.go
new file mode 100644
index 000000000..21ffbd364
--- /dev/null
+++ b/backend/plugins/dora/tasks/incident_from_issue_generator.go
@@ -0,0 +1,151 @@
+/*
+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 (
+	goerrors "errors"
+	"github.com/apache/incubator-devlake/core/dal"
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/log"
+	"github.com/apache/incubator-devlake/core/models/common"
+	"github.com/apache/incubator-devlake/core/models/domainlayer/ticket"
+	"github.com/apache/incubator-devlake/core/plugin"
+	"github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	"reflect"
+)
+
+var IssuesToIncidentsMeta = plugin.SubTaskMeta{
+	Name:             "ConvertIssuesToIncidents",
+	EntryPoint:       ConvertIssuesToIncidents,
+	EnabledByDefault: true,
+	Description:      "Connect issue to incident",
+	DomainTypes:      []string{plugin.DOMAIN_TYPE_TICKET},
+}
+
+func ConvertIssuesToIncidents(taskCtx plugin.SubTaskContext) errors.Error {
+	db := taskCtx.GetDal()
+	data := taskCtx.GetData().(*DoraTaskData)
+	// clear previous incidents and incident_assignees from the project
+	deleteIncidentsSql := `
+		DELETE
+		FROM incidents
+		WHERE id IN
+			(SELECT i.id
+			 FROM issues i
+			 LEFT JOIN board_issues bi ON bi.issue_id = i.id
+			 LEFT JOIN project_mapping pm ON pm.row_id = bi.board_id
+			 WHERE i.type = "INCIDENT"
+			   AND pm.project_name = ?
+		       AND pm.table = "boards")
+	`
+	if err := db.Exec(deleteIncidentsSql, data.Options.ProjectName); err != nil {
+		return errors.Default.Wrap(err, "error deleting previous incidents")
+
+	}
+	deleteIncidentAssigneesSql := `
+		DELETE
+		FROM incident_assignees
+		WHERE incident_id IN
+			(SELECT i.id
+			 FROM issues i
+			 LEFT JOIN board_issues bi ON bi.issue_id = i.id
+			 LEFT JOIN project_mapping pm ON pm.row_id = bi.board_id
+			 WHERE i.type = "INCIDENT"
+			   AND pm.project_name = ?
+		       AND pm.table = "boards")
+	`
+	if err := db.Exec(deleteIncidentAssigneesSql, data.Options.ProjectName); err != nil {
+		return errors.Default.Wrap(err, "error deleting previous incident_assignees")
+	}
+
+	// select all issues belongs to the board
+	clauses := []dal.Clause{
+		dal.Select("i.*"),
+		dal.From(`issues i`),
+		dal.Join(`left join board_issues bi on bi.issue_id = i.id`),
+		dal.Join(`left join project_mapping pm on pm.row_id = bi.board_id`),
+		dal.Where(
+			"i.type = ? and pm.project_name = ? and pm.table = ?",
+			"INCIDENT", data.Options.ProjectName, "boards",
+		),
+	}
+	cursor, err := db.Cursor(clauses...)
+	if err != nil {
+		return err
+	}
+	defer cursor.Close()
+
+	enricher, err := api.NewDataConverter(api.DataConverterArgs{
+		RawDataSubTaskArgs: api.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: DoraApiParams{
+				ProjectName: data.Options.ProjectName,
+			},
+			Table: ticket.Issue{}.TableName(),
+		},
+		InputRowType: reflect.TypeOf(ticket.Issue{}),
+		Input:        cursor,
+		Convert: func(inputRow interface{}) ([]interface{}, errors.Error) {
+			issue := inputRow.(*ticket.Issue)
+			incident, err := issue.ToIncident()
+			if err != nil {
+				return nil, errors.Convert(err)
+			}
+			incidentAssignees, err := generateIncidentAssigneeFromIssue(db, taskCtx.GetLogger(), issue)
+			if err != nil {
+				return nil, errors.Convert(err)
+			}
+			ret := []interface{}{incident}
+			for _, assignee := range incidentAssignees {
+				ret = append(ret, assignee)
+			}
+			return ret, nil
+		},
+	})
+	if err != nil {
+		return err
+	}
+	return enricher.Execute()
+}
+
+func generateIncidentAssigneeFromIssue(db dal.Dal, logger log.Logger, issue *ticket.Issue) ([]*ticket.IncidentAssignee, error) {
+	if issue == nil {
+		return nil, goerrors.New("issue is nil")
+	}
+	var issueAssignees []*ticket.IssueAssignee
+	if err := db.All(&issueAssignees, dal.Where("issue_id = ?", issue.Id)); err != nil {
+		logger.Error(err, "Failed to fetch issue assignees")
+		return nil, err
+	}
+	incidentAssignee, err := issue.ToIncidentAssignee()
+	if err != nil {
+		return nil, err
+	}
+	incidentAssignees := []*ticket.IncidentAssignee{
+		incidentAssignee,
+	}
+	for _, issueAssignee := range issueAssignees {
+		incidentAssignees = append(incidentAssignees, &ticket.IncidentAssignee{
+			IncidentId:   issueAssignee.IssueId,
+			AssigneeId:   issueAssignee.AssigneeId,
+			AssigneeName: issueAssignee.AssigneeName,
+			NoPKModel:    common.NewNoPKModel(),
+		})
+	}
+	return incidentAssignees, nil
+}
diff --git a/backend/plugins/dora/tasks/incident_from_pull_request_generator.go b/backend/plugins/dora/tasks/incident_from_pull_request_generator.go
new file mode 100644
index 000000000..021def330
--- /dev/null
+++ b/backend/plugins/dora/tasks/incident_from_pull_request_generator.go
@@ -0,0 +1,65 @@
+/*
+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 (
+	goerrors "errors"
+	"github.com/apache/incubator-devlake/core/dal"
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/log"
+	"github.com/apache/incubator-devlake/core/models/common"
+	"github.com/apache/incubator-devlake/core/models/domainlayer/code"
+	"github.com/apache/incubator-devlake/core/models/domainlayer/ticket"
+	"github.com/apache/incubator-devlake/core/plugin"
+)
+
+var PullRequestToIncidentsMeta = plugin.SubTaskMeta{
+	Name:             "ConvertPullRequestToIncidents",
+	EntryPoint:       ConvertPullRequestToIncidents,
+	EnabledByDefault: true,
+	Description:      "Connect pull_request to incident",
+	DomainTypes:      []string{plugin.DOMAIN_TYPE_TICKET, plugin.DOMAIN_TYPE_CICD, plugin.DOMAIN_TYPE_CROSS},
+}
+
+func ConvertPullRequestToIncidents(taskCtx plugin.SubTaskContext) errors.Error {
+	// TODO
+	return nil
+}
+
+// lint:ignore U1000
+func generateIncidentAssigneeFromPullRequest(db dal.Dal, logger log.Logger, pullRequest *code.PullRequest) ([]*ticket.IncidentAssignee, error) {
+	if pullRequest == nil {
+		return nil, goerrors.New("pull request is nil")
+	}
+	var pullRequestAssignees []*code.PullRequestAssignee
+	if err := db.All(&pullRequestAssignees, dal.Where("pull_request_id = ?", pullRequest.Id)); err != nil {
+		logger.Error(err, "Failed to fetch pull request assignees")
+		return nil, err
+	}
+
+	var incidentAssignees []*ticket.IncidentAssignee
+	for _, pullRequestAssignee := range pullRequestAssignees {
+		incidentAssignees = append(incidentAssignees, &ticket.IncidentAssignee{
+			IncidentId:   pullRequestAssignee.PullRequestId,
+			AssigneeId:   pullRequestAssignee.AssigneeId,
+			AssigneeName: pullRequestAssignee.Name,
+			NoPKModel:    common.NewNoPKModel(),
+		})
+	}
+	return incidentAssignees, nil
+}
diff --git a/backend/plugins/starrocks/utils/utils.go b/backend/plugins/starrocks/utils/utils.go
index 010e7bded..36ba3813e 100644
--- a/backend/plugins/starrocks/utils/utils.go
+++ b/backend/plugins/starrocks/utils/utils.go
@@ -59,7 +59,7 @@ func GetTablesByDomainLayer(domainLayer string) []string {
 			"board_repos",
 			"issue_commits",
 			"issue_repo_commits",
-			"project_issue_metrics",
+			"project_incident_deployment_relationships",
 			"project_mapping",
 			"project_pr_metrics",
 			"pull_request_issues",
diff --git a/backend/python/pydevlake/pydevlake/domain_layer/crossdomain.py b/backend/python/pydevlake/pydevlake/domain_layer/crossdomain.py
index 3d7d83e7a..b82ee1f02 100644
--- a/backend/python/pydevlake/pydevlake/domain_layer/crossdomain.py
+++ b/backend/python/pydevlake/pydevlake/domain_layer/crossdomain.py
@@ -50,8 +50,8 @@ class IssueRepoCommit(NoPKModel, table=True):
     commit_sha: str = Field(primary_key=True)
 
 
-class ProjectIssueMetric(NoPKModel, table=True):
-    __tablename__ = "project_issue_metrics"
+class ProjectIncidentDeploymentRelationship(NoPKModel, table=True):
+    __tablename__ = "project_incident_deployment_relationships"
     project_name: str = Field(primary_key=True)
     deployment_id: str
 
diff --git a/backend/server/services/project.go b/backend/server/services/project.go
index e119979ab..5c274e24f 100644
--- a/backend/server/services/project.go
+++ b/backend/server/services/project.go
@@ -227,9 +227,9 @@ func PatchProject(name string, body map[string]interface{}) (*models.ApiOutputPr
 			return nil, err
 		}
 
-		// ProjectIssueMetric
+		// ProjectIncidentDeploymentRelationship
 		err = tx.UpdateColumn(
-			&crossdomain.ProjectIssueMetric{},
+			&crossdomain.ProjectIncidentDeploymentRelationship{},
 			"project_name", project.Name,
 			dal.Where("project_name = ?", name),
 		)
@@ -338,7 +338,7 @@ func DeleteProject(name string) errors.Error {
 	if err != nil {
 		return errors.Default.Wrap(err, "error deleting project PR metric")
 	}
-	err = tx.Delete(&crossdomain.ProjectIssueMetric{}, dal.Where("project_name = ?", name))
+	err = tx.Delete(&crossdomain.ProjectIncidentDeploymentRelationship{}, dal.Where("project_name = ?", name))
 	if err != nil {
 		return errors.Default.Wrap(err, "error deleting project Issue metric")
 	}
diff --git a/grafana/dashboards/DORA.json b/grafana/dashboards/DORA.json
index 0bf338685..5d4191978 100644
--- a/grafana/dashboards/DORA.json
+++ b/grafana/dashboards/DORA.json
@@ -235,7 +235,7 @@
           "metricColumn": "none",
           "queryType": "randomWalk",
           "rawQuery": true,
-          "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT  10 UNION ALL SELECT  20 UNION ALL SELECT  30\n\t\t\tUNION ALL SELECT  40 UNION ALL SELECT   [...]
+          "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT  10 UNION ALL SELECT  20 UNION ALL SELECT  30\n\t\t\tUNION ALL SELECT  40 UNION ALL SELECT   [...]
           "refId": "A",
           "select": [
             [
@@ -669,7 +669,7 @@
           "metricColumn": "none",
           "queryType": "randomWalk",
           "rawQuery": true,
-          "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n\tSELECT\n\t\tcdc.cicd_deployment_id as deployment_id,\n\t\tmax(cdc.finished_date) as deployment_finished_date\n\tFROM \n\t\tcicd_deployment_commits cdc\n\t\tJOIN proje [...]
+          "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n\tSELECT\n\t\tcdc.cicd_deployment_id as deployment_id,\n\t\tmax(cdc.finished_date) as deployment_finished_date\n\tFROM \n\t\tcicd_deployment_commits cdc\n\t\tJOIN proje [...]
           "refId": "A",
           "select": [
             [
@@ -821,7 +821,7 @@
           "metricColumn": "none",
           "queryType": "randomWalk",
           "rawQuery": true,
-          "rawSql": "--  ***** 2023 report ***** --\n--  Metric 4: Failed deployment recovery time\nwith _deployments as (\n    SELECT\n        cdc.cicd_deployment_id as deployment_id,\n        max(cdc.finished_date) as deployment_finished_date\n    FROM \n        cicd_deployment_commits cdc\n        JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n    WHERE\n        pm.project_name in ($project)\n        and cdc.result = 'SUCCESS'\n        and cdc [...]
+          "rawSql": "--  ***** 2023 report ***** --\n--  Metric 4: Failed deployment recovery time\nwith _deployments as (\n    SELECT\n        cdc.cicd_deployment_id as deployment_id,\n        max(cdc.finished_date) as deployment_finished_date\n    FROM \n        cicd_deployment_commits cdc\n        JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n    WHERE\n        pm.project_name in ($project)\n        and cdc.result = 'SUCCESS'\n        and cdc [...]
           "refId": "A",
           "select": [
             [
@@ -1237,7 +1237,7 @@
           "hide": false,
           "metricColumn": "none",
           "rawQuery": true,
-          "rawSql": "-- Metric 3: change failure rate per month\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n\tSELECT\n\t\tcdc.cicd_deployment_id as deployment_id,\n\t\tmax(cdc.finished_date) as deployment_finished_date\n\tFROM \n\t\tcicd_deployment_commits cdc\n\t\t [...]
+          "rawSql": "-- Metric 3: change failure rate per month\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n\tSELECT\n\t\tcdc.cicd_deployment_id as deployment_id,\n\t\tmax(cdc.finished_date) as deployment_finished_date\n\tFROM \n\t\tcicd_deployment_commits cdc\n\t\t [...]
           "refId": "A",
           "select": [
             [
@@ -1393,7 +1393,7 @@
           "hide": false,
           "metricColumn": "none",
           "rawQuery": true,
-          "rawSql": "--  ***** 2023 report ***** --\n--  Metric 4: Failed deployment recovery time\nwith _deployments as (\n    SELECT\n        cdc.cicd_deployment_id as deployment_id,\n        max(cdc.finished_date) as deployment_finished_date\n    FROM \n        cicd_deployment_commits cdc\n        JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n    WHERE\n        pm.project_name in ($project)\n        and cdc.result = 'SUCCESS'\n        and cdc [...]
+          "rawSql": "--  ***** 2023 report ***** --\n--  Metric 4: Failed deployment recovery time\nwith _deployments as (\n    SELECT\n        cdc.cicd_deployment_id as deployment_id,\n        max(cdc.finished_date) as deployment_finished_date\n    FROM \n        cicd_deployment_commits cdc\n        JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n    WHERE\n        pm.project_name in ($project)\n        and cdc.result = 'SUCCESS'\n        and cdc [...]
           "refId": "A",
           "select": [
             [
diff --git a/grafana/dashboards/DORAByTeam.json b/grafana/dashboards/DORAByTeam.json
index 5dcda9980..c1901c7bd 100644
--- a/grafana/dashboards/DORAByTeam.json
+++ b/grafana/dashboards/DORAByTeam.json
@@ -208,7 +208,7 @@
           "format": "table",
           "hide": false,
           "rawQuery": true,
-          "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT  10 UNION ALL SELECT  20 UNION ALL SELECT  30\n\t\t\tUNION ALL SELECT  40 UNION ALL SELECT   [...]
+          "rawSql": "-- Metric 1: Deployment Frequency\nwith last_few_calendar_months as(\n-- construct the last few calendar months within the selected time period in the top-right corner\n\tSELECT CAST(($__timeTo()-INTERVAL (H+T+U) DAY) AS date) day\n\tFROM ( SELECT 0 H\n\t\t\tUNION ALL SELECT 100 UNION ALL SELECT 200 UNION ALL SELECT 300\n\t\t) H CROSS JOIN ( SELECT 0 T\n\t\t\tUNION ALL SELECT  10 UNION ALL SELECT  20 UNION ALL SELECT  30\n\t\t\tUNION ALL SELECT  40 UNION ALL SELECT   [...]
           "refId": "A",
           "sql": {
             "columns": [
@@ -578,7 +578,7 @@
           "format": "table",
           "hide": false,
           "rawQuery": true,
-          "rawSql": "-- Metric 4: change failure rate\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n\tSELECT\n\t\tcdc.cicd_deployment_id as deployment_id,\n\t\tmax(cdc.finished_date) as deployment_finished_date\n\tFROM \n\t\tcicd_deployment_commits cdc\n\t    JOIN com [...]
+          "rawSql": "-- Metric 4: change failure rate\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n\tSELECT\n\t\tcdc.cicd_deployment_id as deployment_id,\n\t\tmax(cdc.finished_date) as deployment_finished_date\n\tFROM \n\t\tcicd_deployment_commits cdc\n\t    JOIN com [...]
           "refId": "A",
           "sql": {
             "columns": [
@@ -699,7 +699,7 @@
           "format": "table",
           "hide": false,
           "rawQuery": true,
-          "rawSql": "--  ***** 2023 report ***** --\n--  Metric 4: Failed deployment recovery time\nwith _deployments as (\n    SELECT\n        cdc.cicd_deployment_id as deployment_id,\n        max(cdc.finished_date) as deployment_finished_date\n    FROM \n        cicd_deployment_commits cdc\n\t\tJOIN commits c on cdc.commit_sha = c.sha\n        join user_accounts ua on c.author_id = ua.account_id\n        join users u on ua.user_id = u.id\n        join team_users tu on u.id = tu.user_id [...]
+          "rawSql": "--  ***** 2023 report ***** --\n--  Metric 4: Failed deployment recovery time\nwith _deployments as (\n    SELECT\n        cdc.cicd_deployment_id as deployment_id,\n        max(cdc.finished_date) as deployment_finished_date\n    FROM \n        cicd_deployment_commits cdc\n\t\tJOIN commits c on cdc.commit_sha = c.sha\n        join user_accounts ua on c.author_id = ua.account_id\n        join users u on ua.user_id = u.id\n        join team_users tu on u.id = tu.user_id [...]
           "refId": "A",
           "sql": {
             "columns": [
@@ -1032,7 +1032,7 @@
           "format": "table",
           "hide": false,
           "rawQuery": true,
-          "rawSql": "-- Metric 4: change failure rate per month\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n\tSELECT\n\t\tcdc.cicd_deployment_id as deployment_id,\n\t\tmax(cdc.finished_date) as deployment_finished_date\n\tFROM \n\t\tcicd_deployment_commits cdc\n\t\t [...]
+          "rawSql": "-- Metric 4: change failure rate per month\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n\tSELECT\n\t\tcdc.cicd_deployment_id as deployment_id,\n\t\tmax(cdc.finished_date) as deployment_finished_date\n\tFROM \n\t\tcicd_deployment_commits cdc\n\t\t [...]
           "refId": "A",
           "sql": {
             "columns": [
@@ -1141,7 +1141,7 @@
           "format": "table",
           "hide": false,
           "rawQuery": true,
-          "rawSql": "--  ***** 2023 report ***** --\n--  Metric 4: Failed deployment recovery time\nwith _deployments as (\n    SELECT\n        cdc.cicd_deployment_id as deployment_id,\n        max(cdc.finished_date) as deployment_finished_date\n    FROM \n        cicd_deployment_commits cdc\n\t\tJOIN commits c on cdc.commit_sha = c.sha\n\tjoin user_accounts ua on c.author_id = ua.account_id\n    join users u on ua.user_id = u.id\n    join team_users tu on u.id = tu.user_id\n    join tea [...]
+          "rawSql": "--  ***** 2023 report ***** --\n--  Metric 4: Failed deployment recovery time\nwith _deployments as (\n    SELECT\n        cdc.cicd_deployment_id as deployment_id,\n        max(cdc.finished_date) as deployment_finished_date\n    FROM \n        cicd_deployment_commits cdc\n\t\tJOIN commits c on cdc.commit_sha = c.sha\n\tjoin user_accounts ua on c.author_id = ua.account_id\n    join users u on ua.user_id = u.id\n    join team_users tu on u.id = tu.user_id\n    join tea [...]
           "refId": "A",
           "sql": {
             "columns": [
diff --git a/grafana/dashboards/DORADebug.json b/grafana/dashboards/DORADebug.json
index ddc6f4caf..2b3408b01 100644
--- a/grafana/dashboards/DORADebug.json
+++ b/grafana/dashboards/DORADebug.json
@@ -2847,7 +2847,7 @@
           ]
         }
       ],
-      "title": "Step 1. All types of issues in table.issues (rows with 2 green columns will be used to construct project_issue_metrics)",
+      "title": "Step 1. All types of issues in table.issues (rows with 2 green columns will be used to construct project_incident_deployment_relationships)",
       "type": "table"
     },
     {
@@ -2948,7 +2948,7 @@
           "metricColumn": "none",
           "queryType": "randomWalk",
           "rawQuery": true,
-          "rawSql": "--  ***** 2023 report ***** --\n--  Metric 4: Failed deployment recovery time\nwith _deployments as (\n    SELECT\n        cdc.cicd_deployment_id as deployment_id,\n        max(cdc.finished_date) as deployment_finished_date\n    FROM \n        cicd_deployment_commits cdc\n        JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n    WHERE\n        pm.project_name in ($project)\n        and cdc.result = 'SUCCESS'\n        and cdc [...]
+          "rawSql": "--  ***** 2023 report ***** --\n--  Metric 4: Failed deployment recovery time\nwith _deployments as (\n    SELECT\n        cdc.cicd_deployment_id as deployment_id,\n        max(cdc.finished_date) as deployment_finished_date\n    FROM \n        cicd_deployment_commits cdc\n        JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n    WHERE\n        pm.project_name in ($project)\n        and cdc.result = 'SUCCESS'\n        and cdc [...]
           "refId": "A",
           "select": [
             [
@@ -3231,7 +3231,7 @@
           "hide": false,
           "metricColumn": "none",
           "rawQuery": true,
-          "rawSql": "--  ***** 2023 report ***** --\n--  Metric 4: Failed deployment recovery time\nwith _deployments as (\n    SELECT\n        cdc.cicd_deployment_id as deployment_id,\n        max(cdc.finished_date) as deployment_finished_date\n    FROM \n        cicd_deployment_commits cdc\n        JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n    WHERE\n        pm.project_name in ($project)\n        and cdc.result = 'SUCCESS'\n        and cdc [...]
+          "rawSql": "--  ***** 2023 report ***** --\n--  Metric 4: Failed deployment recovery time\nwith _deployments as (\n    SELECT\n        cdc.cicd_deployment_id as deployment_id,\n        max(cdc.finished_date) as deployment_finished_date\n    FROM \n        cicd_deployment_commits cdc\n        JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n    WHERE\n        pm.project_name in ($project)\n        and cdc.result = 'SUCCESS'\n        and cdc [...]
           "refId": "A",
           "select": [
             [
@@ -3369,7 +3369,7 @@
           "metricColumn": "none",
           "queryType": "randomWalk",
           "rawQuery": true,
-          "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n\tSELECT\n\t\tcdc.cicd_deployment_id as deployment_id,\n\t\tmax(cdc.finished_date) as deployment_finished_date\n\tFROM \n\t\tcicd_deployment_commits cdc\n\t\tJOIN proje [...]
+          "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n\tSELECT\n\t\tcdc.cicd_deployment_id as deployment_id,\n\t\tmax(cdc.finished_date) as deployment_finished_date\n\tFROM \n\t\tcicd_deployment_commits cdc\n\t\tJOIN proje [...]
           "refId": "A",
           "select": [
             [
@@ -3589,7 +3589,7 @@
           "metricColumn": "none",
           "queryType": "randomWalk",
           "rawQuery": true,
-          "rawSql": "select \n  -- in CFR we use deployment_commit_id as the deployment_id in a specific repo\n  cdc.cicd_deployment_id as deployment_id,\n  cdc.finished_date,\n  pim.id as incident_id,\n  if (pim.id is not null, 'TRUE', 'FALSE') as has_failure\nfrom \n  cicd_deployment_commits cdc\n  left join project_issue_metrics pim on cdc.cicd_deployment_id = pim.deployment_id\n  left join project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\nwhere \n  p [...]
+          "rawSql": "select \n  -- in CFR we use deployment_commit_id as the deployment_id in a specific repo\n  cdc.cicd_deployment_id as deployment_id,\n  cdc.finished_date,\n  pim.id as incident_id,\n  if (pim.id is not null, 'TRUE', 'FALSE') as has_failure\nfrom \n  cicd_deployment_commits cdc\n  left join project_incident_deployment_relationships pim on cdc.cicd_deployment_id = pim.deployment_id\n  left join project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_ [...]
           "refId": "A",
           "select": [
             [
@@ -3736,7 +3736,7 @@
           "hide": false,
           "metricColumn": "none",
           "rawQuery": true,
-          "rawSql": "-- Metric 3: change failure rate per month\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n\tSELECT\n\t\tcdc.cicd_deployment_id as deployment_id,\n\t\tmax(cdc.finished_date) as deployment_finished_date\n\tFROM \n\t\tcicd_deployment_commits cdc\n\t\t [...]
+          "rawSql": "-- Metric 3: change failure rate per month\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n\tSELECT\n\t\tcdc.cicd_deployment_id as deployment_id,\n\t\tmax(cdc.finished_date) as deployment_finished_date\n\tFROM \n\t\tcicd_deployment_commits cdc\n\t\t [...]
           "refId": "A",
           "select": [
             [
diff --git a/grafana/dashboards/DORADetails-ChangeFailureRate.json b/grafana/dashboards/DORADetails-ChangeFailureRate.json
index 3322f683a..e9d165ce7 100644
--- a/grafana/dashboards/DORADetails-ChangeFailureRate.json
+++ b/grafana/dashboards/DORADetails-ChangeFailureRate.json
@@ -164,7 +164,7 @@
           "metricColumn": "none",
           "queryType": "randomWalk",
           "rawQuery": true,
-          "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n\tSELECT\n\t\tcdc.cicd_deployment_id as deployment_id,\n\t\tmax(cdc.finished_date) as deployment_finished_date\n\tFROM \n\t\tcicd_deployment_commits cdc\n\t\tJOIN proje [...]
+          "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n\tSELECT\n\t\tcdc.cicd_deployment_id as deployment_id,\n\t\tmax(cdc.finished_date) as deployment_finished_date\n\tFROM \n\t\tcicd_deployment_commits cdc\n\t\tJOIN proje [...]
           "refId": "A",
           "select": [
             [
@@ -365,7 +365,7 @@
           "metricColumn": "none",
           "queryType": "randomWalk",
           "rawQuery": true,
-          "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n\tSELECT\n\t\tcdc.cicd_deployment_id as deployment_id,\n\t\tmax(cdc.finished_date) as deployment_finished_date\n\tFROM \n\t\tcicd_deployment_commits cdc\n\t\tJOIN proje [...]
+          "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n\tSELECT\n\t\tcdc.cicd_deployment_id as deployment_id,\n\t\tmax(cdc.finished_date) as deployment_finished_date\n\tFROM \n\t\tcicd_deployment_commits cdc\n\t\tJOIN proje [...]
           "refId": "A",
           "select": [
             [
@@ -502,7 +502,7 @@
           "metricColumn": "none",
           "queryType": "randomWalk",
           "rawQuery": true,
-          "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n\tSELECT\n\t\tcdc.cicd_deployment_id as deployment_id,\n\t\tmax(cdc.finished_date) as deployment_finished_date\n\tFROM \n\t\tcicd_deployment_commits cdc\n\t\tJOIN proje [...]
+          "rawSql": "-- Metric 3: change failure rate\nwith _deployments as (\n-- When deploying multiple commits in one pipeline, GitLab and BitBucket may generate more than one deployment. However, DevLake consider these deployments as ONE production deployment and use the last one's finished_date as the finished date.\n\tSELECT\n\t\tcdc.cicd_deployment_id as deployment_id,\n\t\tmax(cdc.finished_date) as deployment_finished_date\n\tFROM \n\t\tcicd_deployment_commits cdc\n\t\tJOIN proje [...]
           "refId": "A",
           "select": [
             [
diff --git a/grafana/dashboards/DORADetails-FailedDeploymentRecoveryTime.json b/grafana/dashboards/DORADetails-FailedDeploymentRecoveryTime.json
index 056e40c3d..edc06606d 100644
--- a/grafana/dashboards/DORADetails-FailedDeploymentRecoveryTime.json
+++ b/grafana/dashboards/DORADetails-FailedDeploymentRecoveryTime.json
@@ -121,7 +121,7 @@
           "format": "table",
           "hide": false,
           "rawQuery": true,
-          "rawSql": "--  ***** 2023 report ***** --\n--  Metric 4: Failed deployment recovery time\nwith _deployments as (\n    SELECT\n        cdc.cicd_deployment_id as deployment_id,\n        max(cdc.finished_date) as deployment_finished_date\n    FROM \n        cicd_deployment_commits cdc\n        JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n    WHERE\n        pm.project_name in ($project)\n        and cdc.result = 'SUCCESS'\n        and cdc [...]
+          "rawSql": "--  ***** 2023 report ***** --\n--  Metric 4: Failed deployment recovery time\nwith _deployments as (\n    SELECT\n        cdc.cicd_deployment_id as deployment_id,\n        max(cdc.finished_date) as deployment_finished_date\n    FROM \n        cicd_deployment_commits cdc\n        JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n    WHERE\n        pm.project_name in ($project)\n        and cdc.result = 'SUCCESS'\n        and cdc [...]
           "refId": "D",
           "sql": {
             "columns": [
@@ -315,7 +315,7 @@
           "metricColumn": "none",
           "queryType": "randomWalk",
           "rawQuery": true,
-          "rawSql": "--  ***** 2023 report ***** --\n--  Metric 4: Failed deployment recovery time\nwith _deployments as (\n    SELECT\n        cdc.cicd_deployment_id as deployment_id,\n        max(cdc.finished_date) as deployment_finished_date\n    FROM \n        cicd_deployment_commits cdc\n        JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n    WHERE\n        pm.project_name in ($project)\n        and cdc.result = 'SUCCESS'\n        and cdc [...]
+          "rawSql": "--  ***** 2023 report ***** --\n--  Metric 4: Failed deployment recovery time\nwith _deployments as (\n    SELECT\n        cdc.cicd_deployment_id as deployment_id,\n        max(cdc.finished_date) as deployment_finished_date\n    FROM \n        cicd_deployment_commits cdc\n        JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n    WHERE\n        pm.project_name in ($project)\n        and cdc.result = 'SUCCESS'\n        and cdc [...]
           "refId": "A",
           "select": [
             [
@@ -409,7 +409,7 @@
           "format": "table",
           "hide": false,
           "rawQuery": true,
-          "rawSql": "--  ***** 2023 report ***** --\n--  Metric 4: Failed deployment recovery time\nwith _deployments as (\n    SELECT\n        cdc.cicd_deployment_id as deployment_id,\n        max(cdc.finished_date) as deployment_finished_date\n    FROM \n        cicd_deployment_commits cdc\n        JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n    WHERE\n        pm.project_name in ($project)\n        and cdc.result = 'SUCCESS'\n        and cdc [...]
+          "rawSql": "--  ***** 2023 report ***** --\n--  Metric 4: Failed deployment recovery time\nwith _deployments as (\n    SELECT\n        cdc.cicd_deployment_id as deployment_id,\n        max(cdc.finished_date) as deployment_finished_date\n    FROM \n        cicd_deployment_commits cdc\n        JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n    WHERE\n        pm.project_name in ($project)\n        and cdc.result = 'SUCCESS'\n        and cdc [...]
           "refId": "D",
           "sql": {
             "columns": [