diff --git a/backend/core/models/domainlayer/code/pull_request.go b/backend/core/models/domainlayer/code/pull_request.go index a300ae54427..0c3be7f8d77 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% rename from backend/core/models/domainlayer/crossdomain/project_issue_metric.go rename to backend/core/models/domainlayer/crossdomain/project_incident_deployment_relationship.go index 5c2a3cf515e..755741c1b13 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 422f537abd9..6837126f895 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{}, @@ -91,5 +91,7 @@ func GetDomainTablesInfo() []dal.Tabler { &ticket.IssueAssignee{}, &ticket.IssueRelationship{}, &ticket.IssueCustomArrayField{}, + &ticket.Incident{}, + &ticket.IncidentAssignee{}, } } diff --git a/backend/core/models/domainlayer/ticket/incident.go b/backend/core/models/domainlayer/ticket/incident.go new file mode 100644 index 00000000000..b565cd3b995 --- /dev/null +++ b/backend/core/models/domainlayer/ticket/incident.go @@ -0,0 +1,55 @@ +/* +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 ticket + +import ( + "github.com/apache/incubator-devlake/core/models/domainlayer" + "time" +) + +// 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)"` + IncidentKey string `gorm:"type:varchar(255)"` // issue_key/pull_request_key + Title string + Description string + Status string `gorm:"type:varchar(100)"` + OriginalStatus string `gorm:"type:varchar(100)"` + ResolutionDate *time.Time + CreatedDate *time.Time + UpdatedDate *time.Time + LeadTimeMinutes *uint + OriginalEstimateMinutes *int64 + TimeSpentMinutes *int64 + TimeRemainingMinutes *int64 + CreatorId string `gorm:"type:varchar(255)"` + CreatorName 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 (Incident) TableName() string { + return "incidents" +} diff --git a/backend/core/models/domainlayer/ticket/incident_assginee.go b/backend/core/models/domainlayer/ticket/incident_assginee.go new file mode 100644 index 00000000000..d4e16c993fb --- /dev/null +++ b/backend/core/models/domainlayer/ticket/incident_assginee.go @@ -0,0 +1,32 @@ +/* +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 ticket + +import "github.com/apache/incubator-devlake/core/models/common" + +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 (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 7f29404d803..816571a4a47 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/migrationscripts/20240621_init_incidents_and_incident_assignees.go b/backend/core/models/migrationscripts/20240621_init_incidents_and_incident_assignees.go new file mode 100644 index 00000000000..d9827516275 --- /dev/null +++ b/backend/core/models/migrationscripts/20240621_init_incidents_and_incident_assignees.go @@ -0,0 +1,43 @@ +/* +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 migrationscripts + +import ( + "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 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 (*initIncidentRelatedTables) Name() string { + return "init tables: incidents and incident_assignees" +} diff --git a/backend/core/models/migrationscripts/20240621_rename_project_issue_metrics.go b/backend/core/models/migrationscripts/20240621_rename_project_issue_metrics.go new file mode 100644 index 00000000000..76fe3db4850 --- /dev/null +++ b/backend/core/models/migrationscripts/20240621_rename_project_issue_metrics.go @@ -0,0 +1,39 @@ +/* +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 migrationscripts + +import ( + "github.com/apache/incubator-devlake/core/context" + "github.com/apache/incubator-devlake/core/errors" + "github.com/apache/incubator-devlake/core/models/migrationscripts/archived" +) + +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 (*renameProjectIssueMetrics) Name() string { + return "rename project_issue_metrics to project_incident_deployment_relationships" +} diff --git a/backend/core/models/migrationscripts/archived/incident.go b/backend/core/models/migrationscripts/archived/incident.go new file mode 100644 index 00000000000..8c00af3418d --- /dev/null +++ b/backend/core/models/migrationscripts/archived/incident.go @@ -0,0 +1,52 @@ +/* +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 archived + +import ( + "time" +) + +type Incident struct { + DomainEntity + Url string `gorm:"type:varchar(255)"` + IncidentKey string `gorm:"type:varchar(255)"` // issue_key/pull_request_key + Title string + Description string + Status string `gorm:"type:varchar(100)"` + OriginalStatus string `gorm:"type:varchar(100)"` + ResolutionDate *time.Time + CreatedDate *time.Time + UpdatedDate *time.Time + LeadTimeMinutes *uint + OriginalEstimateMinutes *int64 + TimeSpentMinutes *int64 + TimeRemainingMinutes *int64 + CreatorId string `gorm:"type:varchar(255)"` + CreatorName 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 (Incident) TableName() string { + return "incidents" +} diff --git a/backend/core/models/migrationscripts/archived/incident_assignee.go b/backend/core/models/migrationscripts/archived/incident_assignee.go new file mode 100644 index 00000000000..4207e2fd378 --- /dev/null +++ b/backend/core/models/migrationscripts/archived/incident_assignee.go @@ -0,0 +1,30 @@ +/* +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 archived + +type IncidentAssignee struct { + IncidentId string `gorm:"primaryKey;type:varchar(255)"` + AssigneeId string `gorm:"primaryKey;type:varchar(255)"` + AssigneeName string `gorm:"type:varchar(255)"` + + NoPKModel +} + +func (IncidentAssignee) TableName() string { + return "incident_assignees" +} diff --git a/backend/core/models/migrationscripts/register.go b/backend/core/models/migrationscripts/register.go index 057a2a3270f..6caa89677a8 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 ee340d078a9..ed625063d4e 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 f96a04b5ba3..530a9b486cc 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/impl/impl_test.go b/backend/plugins/dora/impl/impl_test.go index 461897dc2a6..af9cc43f4b8 100644 --- a/backend/plugins/dora/impl/impl_test.go +++ b/backend/plugins/dora/impl/impl_test.go @@ -19,6 +19,7 @@ package impl import ( "encoding/json" + "github.com/apache/incubator-devlake/plugins/dora/tasks" "testing" coreModels "github.com/apache/incubator-devlake/core/models" @@ -61,6 +62,8 @@ func TestMakeMetricPluginPipelinePlanV200(t *testing.T) { Subtasks: []string{ "calculateChangeLeadTime", "ConnectIncidentToDeployment", + tasks.IssuesToIncidentsMeta.Name, + tasks.PullRequestToIncidentsMeta.Name, }, Options: map[string]interface{}{"projectName": projectName}, }, diff --git a/backend/plugins/dora/tasks/incident_deploy_connector.go b/backend/plugins/dora/tasks/incident_deploy_connector.go index 6d9a1365d63..9dc70f81264 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 project_incident_deployment_relationships WHERE project_name = ?", 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 00000000000..21ffbd36423 --- /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 00000000000..029aaa777f7 --- /dev/null +++ b/backend/plugins/dora/tasks/incident_from_pull_request_generator.go @@ -0,0 +1,59 @@ +/* +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 ( + "github.com/apache/incubator-devlake/core/errors" + "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 010e7bdedd3..36ba3813e6b 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/plugins/webhook/api/issues.go b/backend/plugins/webhook/api/issues.go index 96c695a2826..a73b60e67cb 100644 --- a/backend/plugins/webhook/api/issues.go +++ b/backend/plugins/webhook/api/issues.go @@ -19,6 +19,8 @@ package api import ( "fmt" + "github.com/apache/incubator-devlake/core/log" + "github.com/apache/incubator-devlake/helpers/dbhelper" "net/http" "time" @@ -62,6 +64,24 @@ type WebhookIssueRequest struct { //DeploymentId string } +func saveIncidentRelatedRecordsFromIssue(db dal.Transaction, logger log.Logger, issue *ticket.Issue) error { + incident, err := issue.ToIncident() + if err != nil { + return err + } + if err := db.CreateOrUpdate(incident); err != nil { + return err + } + assignee, err := issue.ToIncidentAssignee() + if err != nil { + return err + } + if err := db.CreateOrUpdate(assignee); err != nil { + return err + } + return nil +} + // PostIssue // @Summary receive a record as defined and save it // @Description receive a record as follow and save it, example: {"url":"","issue_key":"DLK-1234","title":"a feature from DLK","description":"","epic_key":"","type":"BUG","status":"TODO","original_status":"created","story_point":0,"resolution_date":null,"created_date":"2020-01-01T12:00:00+00:00","updated_date":null,"lead_time_minutes":0,"parent_issue_key":"DLK-1200","priority":"","original_estimate_minutes":0,"time_spent_minutes":0,"time_remaining_minutes":0,"creator_id":"user1131","creator_name":"Nick name 1","assignee_id":"user1132","assignee_name":"Nick name 2","severity":"","component":""} @@ -89,7 +109,9 @@ func PostIssue(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, error if err != nil { return &plugin.ApiResourceOutput{Body: err.Error(), Status: http.StatusBadRequest}, nil } - db := basicRes.GetDal() + txHelper := dbhelper.NewTxHelper(basicRes, &err) + defer txHelper.End() + tx := txHelper.Begin() domainIssue := &ticket.Issue{ DomainEntity: domainlayer.DomainEntity{ Id: fmt.Sprintf("%s:%d:%s", "webhook", connection.ID, request.IssueKey), @@ -134,7 +156,7 @@ func PostIssue(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, error } // check if board exists - count, err := db.Count(dal.From(&ticket.Board{}), dal.Where("id = ?", domainBoardId)) + count, err := tx.Count(dal.From(&ticket.Board{}), dal.Where("id = ?", domainBoardId)) if err != nil { return nil, err } @@ -146,22 +168,28 @@ func PostIssue(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, error Id: domainBoardId, }, } - err = db.Create(domainBoard) + err = tx.Create(domainBoard) if err != nil { return nil, err } } // save - err = db.CreateOrUpdate(domainIssue) + err = tx.CreateOrUpdate(domainIssue) if err != nil { return nil, err } - err = db.CreateOrUpdate(boardIssue) + err = tx.CreateOrUpdate(boardIssue) if err != nil { return nil, err } + if domainIssue.IsIncident() { + if err := saveIncidentRelatedRecordsFromIssue(tx, logger, domainIssue); err != nil { + logger.Error(err, "failed to save incident related records") + return nil, errors.Convert(err) + } + } return &plugin.ApiResourceOutput{Body: nil, Status: http.StatusOK}, nil } @@ -181,19 +209,40 @@ func CloseIssue(input *plugin.ApiResourceInput) (*plugin.ApiResourceOutput, erro return nil, err } - db := basicRes.GetDal() + txHelper := dbhelper.NewTxHelper(basicRes, &err) + defer txHelper.End() + tx := txHelper.Begin() + + issueId := fmt.Sprintf("%s:%d:%s", "webhook", connection.ID, input.Params[`issueKey`]) domainIssue := &ticket.Issue{} - err = db.First(domainIssue, dal.Where("id = ?", fmt.Sprintf("%s:%d:%s", "webhook", connection.ID, input.Params[`issueKey`]))) + err = tx.First(domainIssue, dal.Where("id = ?", issueId)) if err != nil { return nil, errors.NotFound.Wrap(err, `issue not found`) } domainIssue.Status = ticket.DONE domainIssue.OriginalStatus = `` - // save - err = db.Update(domainIssue) + err = tx.Update(domainIssue) if err != nil { return nil, err } + + if domainIssue.IsIncident() { + domainIncident := &ticket.Incident{} + incidentId := issueId + err = tx.First(domainIncident, dal.Where("id = ?", incidentId)) + if err == nil { + domainIncident.Status = ticket.DONE + domainIncident.OriginalStatus = `` + // save + err = tx.Update(domainIncident) + if err != nil { + return nil, err + } + } else { + logger.Warn(err, "failed to find incident") + } + } + return &plugin.ApiResourceOutput{Body: nil, Status: http.StatusOK}, nil } diff --git a/backend/python/pydevlake/pydevlake/domain_layer/crossdomain.py b/backend/python/pydevlake/pydevlake/domain_layer/crossdomain.py index 3d7d83e7ab2..b82ee1f02a8 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 58ed4b17337..2d70fd4d7bb 100644 --- a/backend/server/services/project.go +++ b/backend/server/services/project.go @@ -239,9 +239,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), ) @@ -350,7 +350,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 0bf338685fb..5d419197830 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 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days 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(DATE(cdc.finished_date)) as day\n\tFROM cicd_deployment_commits cdc\n\tJOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tpm.project_name in (${project})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n),\n\n_days_weekly_deploy as(\n-- calculate the number of deployment days every week\n\tSELECT\n\t\t\tdate(DATE_ADD(last_few_calendar_months.day, INTERVAL -WEEKDAY(last_few_calendar_months.day) DAY)) as week,\n\t\t\tMAX(if(_production_deployment_days.day is not null, 1, 0)) as weeks_deployed,\n\t\t\tCOUNT(distinct _production_deployment_days.day) as days_deployed\n\tFROM \n\t\tlast_few_calendar_months\n\t\tLEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day\n\tGROUP BY week\n\t),\n\n_days_monthly_deploy as(\n-- calculate the number of deployment days every month\n\tSELECT\n\t\t\tdate(DATE_ADD(last_few_calendar_months.day, INTERVAL -DAY(last_few_calendar_months.day)+1 DAY)) as month,\n\t\t\tMAX(if(_production_deployment_days.day is not null, 1, null)) as months_deployed,\n\t\t COUNT(distinct _production_deployment_days.day) as days_deployed\n\tFROM \n\t\tlast_few_calendar_months\n\t\tLEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day\n\tGROUP BY month\n\t),\n\n_days_six_months_deploy AS (\n SELECT\n month,\n SUM(days_deployed) OVER (\n ORDER BY month\n ROWS BETWEEN 5 PRECEDING AND CURRENT ROW\n ) AS days_deployed_per_six_months,\n COUNT(months_deployed) OVER (\n ORDER BY month\n ROWS BETWEEN 5 PRECEDING AND CURRENT ROW\n ) AS months_deployed_count,\n ROW_NUMBER() OVER (\n PARTITION BY DATE_FORMAT(month, '%Y-%m') DIV 6\n ORDER BY month DESC\n ) AS rn\n FROM _days_monthly_deploy\n),\n\n_median_number_of_deployment_days_per_week_ranks as(\n\tSELECT *, percent_rank() over(order by days_deployed) as ranks\n\tFROM _days_weekly_deploy\n),\n\n_median_number_of_deployment_days_per_week as(\n\tSELECT max(days_deployed) as median_number_of_deployment_days_per_week\n\tFROM _median_number_of_deployment_days_per_week_ranks\n\tWHERE ranks <= 0.5\n),\n\n_median_number_of_deployment_days_per_month_ranks as(\n\tSELECT *, percent_rank() over(order by days_deployed) as ranks\n\tFROM _days_monthly_deploy\n),\n\n_median_number_of_deployment_days_per_month as(\n\tSELECT max(days_deployed) as median_number_of_deployment_days_per_month\n\tFROM _median_number_of_deployment_days_per_month_ranks\n\tWHERE ranks <= 0.5\n),\n\n_days_per_six_months_deploy_by_filter AS (\nSELECT\n month,\n days_deployed_per_six_months,\n months_deployed_count\nFROM _days_six_months_deploy\nWHERE rn%6 = 1\n),\n\n\n_median_number_of_deployment_days_per_six_months_ranks as(\n\tSELECT *, percent_rank() over(order by days_deployed_per_six_months) as ranks\n\tFROM _days_per_six_months_deploy_by_filter\n),\n\n_median_number_of_deployment_days_per_six_months as(\n\tSELECT min(days_deployed_per_six_months) as median_number_of_deployment_days_per_six_months, min(months_deployed_count) as is_collected\n\tFROM _median_number_of_deployment_days_per_six_months_ranks\n\tWHERE ranks >= 0.5\n),\n\n_metric_deployment_frequency as (\n\tSELECT \n\t 'Deployment frequency' as metric, \n\t\tCASE\n\t\t\tWHEN ('$dora_report') = '2023' THEN\n\t\t\t\tCASE \n\t\t\t\t\tWHEN median_number_of_deployment_days_per_week >= 7 THEN 'On-demand(elite)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_month < 1 and is_collected is not null THEN 'Fewer than once per month(low)'\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments.\" END\n\t\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\t\tCASE \n\t\t\t\t\tWHEN median_number_of_deployment_days_per_week >= 7 THEN 'On-demand(elite)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_six_months < 1 and is_collected is not null THEN 'Fewer than once per six months(low)'\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments.\" END\n\t\t\tELSE 'Invalid dora report'\n\t\tEND AS value\n\tFROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months\n),\n\n-- Metric 2: median lead time for changes\n_pr_stats as (\n-- get the cycle time of PRs deployed by the deployments finished in the selected period\n\tSELECT\n\t\tdistinct pr.id,\n\t\tppm.pr_cycle_time\n\tFROM\n\t\tpull_requests pr \n\t\tjoin project_pr_metrics ppm on ppm.id = pr.id\n\t\tjoin project_mapping pm on pr.base_repo_id = pm.row_id and pm.`table` = 'repos'\n\t\tjoin cicd_deployment_commits cdc on ppm.deployment_commit_id = cdc.id\n\tWHERE\n\t pm.project_name in (${project}) \n\t\tand pr.merged_date is not null\n\t\tand ppm.pr_cycle_time is not null\n\t\tand $__timeFilter(cdc.finished_date)\n),\n\n_median_change_lead_time_ranks as(\n\tSELECT *, percent_rank() over(order by pr_cycle_time) as ranks\n\tFROM _pr_stats\n),\n\n_median_change_lead_time as(\n-- use median PR cycle time as the median change lead time\n\tSELECT max(pr_cycle_time) as median_change_lead_time\n\tFROM _median_change_lead_time_ranks\n\tWHERE ranks <= 0.5\n),\n\n_metric_change_lead_time as (\n\tSELECT \n\t\t'Lead time for changes' as metric,\n\t\tCASE\n\t\t\tWHEN ('$dora_report') = '2023' THEN\n\t\t\t\tCASE\n\t\t\t\t\tWHEN median_change_lead_time < 24 * 60 THEN \"Less than one day(elite)\"\n\t\t\t\t\tWHEN median_change_lead_time < 7 * 24 * 60 THEN \"Between one day and one week(high)\"\n\t\t\t\t\tWHEN median_change_lead_time < 30 * 24 * 60 THEN \"Between one week and one month(medium)\"\n\t\t\t\t\tWHEN median_change_lead_time >= 30 * 24 * 60 THEN \"More than one month(low)\"\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments/pull_requests.\"\n\t\t\t\t\tEND\n\t\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\t\tCASE\n\t\t\t\t\tWHEN median_change_lead_time < 60 THEN \"Less than one hour(elite)\"\n\t\t\t\t\tWHEN median_change_lead_time < 7 * 24 * 60 THEN \"Less than one week(high)\"\n\t\t\t\t\tWHEN median_change_lead_time < 180 * 24 * 60 THEN \"Between one week and six months(medium)\"\n\t\t\t\t\tWHEN median_change_lead_time >= 180 * 24 * 60 THEN \"More than six months(low)\"\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments/pull_requests.\"\n\t\t\t\t\tEND\n\t\t\tELSE 'Invalid dora report'\n\t\tEND AS value\nFROM _median_change_lead_time\n),\n\n-- Metric 3: change failure rate\n_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 project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tpm.project_name in (${project})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_issue_metrics pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n),\n\n_change_failure_rate as (\n\tSELECT \n\t\tcase \n\t\t\twhen count(deployment_id) is null then null\n\t\t\telse sum(has_incident)/count(deployment_id) end as change_failure_rate\n\tFROM\n\t\t_failure_caused_by_deployments\n),\n\n_is_collected_data as(\n\tSELECT\n CASE \n WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All'\n WHEN COUNT(i.id) = 0 THEN 'No Incidents' \n WHEN COUNT(cdc.id) = 0 THEN 'No Deployments'\n END AS is_collected\nFROM\n (SELECT 1) AS dummy\nLEFT JOIN\n issues i ON i.type = 'INCIDENT'\nLEFT JOIN\n cicd_deployment_commits cdc ON 1=1\n),\n\n_metric_cfr as (\n\tSELECT\n\t\t'Change failure rate' as metric,\n\t\tCASE\n\t\t\tWHEN ('$dora_report') = '2023' THEN\n\t\t\t\tCASE \n\t\t\t\t WHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\t WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\t WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\t\tWHEN change_failure_rate <= .05 THEN \"0-5%(elite)\"\n\t\t\t\t\tWHEN change_failure_rate <= .10 THEN \"5%-10%(high)\"\n\t\t\t\t\tWHEN change_failure_rate <= .15 THEN \"10%-15%(medium)\"\n\t\t\t\t\tWHEN change_failure_rate > .15 THEN \"> 15%(low)\"\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\t\tEND\n\t\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\t\tCASE \n\t\t\t\t\tWHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\t WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\t WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\t\tWHEN change_failure_rate <= .15 THEN \"0-15%(elite)\"\n\t\t\t\t\tWHEN change_failure_rate <= .20 THEN \"16%-20%(high)\"\n\t\t\t\t\tWHEN change_failure_rate <= .30 THEN \"21%-30%(medium)\"\n\t\t\t\t\tWHEN change_failure_rate > .30 THEN \"> 30%(low)\" \n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\t\tEND\n\t\t\tELSE 'Invalid dora report'\n\t\tEND AS value\n\tFROM \n\t\t_change_failure_rate, _is_collected_data\n),\n\n-- ***** 2023 report ***** --\n-- Metric 4: Failed deployment recovery time\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_issue_metrics pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n_recovery_time_ranks as (\n SELECT *, percent_rank() over(order by TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as ranks\n FROM _incidents_for_deployments\n),\n\n_median_recovery_time as (\n SELECT max(TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as median_recovery_time\n FROM _recovery_time_ranks\n WHERE ranks <= 0.5\n),\n\n_metric_recovery_time_2023_report as(\n\tSELECT \n\t\"Failed deployment recovery time\" as metric,\n\tCASE\n\t\tWHEN ('$dora_report') = '2023' THEN\n\t\tCASE\n\t\t\tWHEN median_recovery_time < 60 THEN \"Less than one hour(elite)\"\n\t\t\tWHEN median_recovery_time < 24 * 60 THEN \"Less than one day(high)\"\n\t\t\tWHEN median_recovery_time < 7 * 24 * 60 THEN \"Between one day and one week(medium)\"\n\t\t\tWHEN median_recovery_time >= 7 * 24 * 60 THEN \"More than one week(low)\"\n\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\tEND\n\tEND AS median_recovery_time\n\tFROM \n\t_median_recovery_time\n),\n\n-- ***** 2021 report ***** --\n-- Metric 4: Median time to restore service \n_incidents as (\n-- get the incidents created within the selected time period in the top-right corner\n\tSELECT\n\t distinct i.id,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\tWHERE\n\t pm.project_name in (${project})\n\t\tand i.type = 'INCIDENT'\n\t\tand $__timeFilter(i.resolution_date)\n),\n\n_median_mttr_ranks as(\n\tSELECT *, percent_rank() over(order by lead_time_minutes) as ranks\n\tFROM _incidents\n),\n\n_median_mttr as(\n\tSELECT max(lead_time_minutes) as median_time_to_resolve\n\tFROM _median_mttr_ranks\n\tWHERE ranks <= 0.5\n),\n\n_metric_mttr_2021_report as(\n\tSELECT \n\t\"Time to restore service\" as metric,\n\tCASE\n\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\tCASE\n\t\t\t\tWHEN median_time_to_resolve < 60 THEN \"Less than one hour(elite)\"\n\t\t\t\tWHEN median_time_to_resolve < 24 * 60 THEN \"Less than one day(high)\"\n\t\t\t\tWHEN median_time_to_resolve < 7 * 24 * 60 THEN \"Between one day and one week(medium)\"\n\t\t\t\tWHEN median_time_to_resolve >= 7 * 24 * 60 THEN \"More than one week(low)\"\n\t\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\t\tEND\n\tEND AS median_time_to_resolve\n\tFROM \n\t\t_median_mttr\n),\n\n_metric_mrt_or_mm as(\n\tSELECT \n\tmetric,\n\tmedian_recovery_time AS value\n\tFROM \n\t_metric_recovery_time_2023_report\n\tWHERE \n\t('$dora_report') = '2023'\n\tUNION\n\tSELECT \n\tmetric,\n\tmedian_time_to_resolve AS value\n\tFROM \n\t_metric_mttr_2021_report\n\tWHERE \n\t('$dora_report') = '2021'\n),\n\n_final_results as (\t\n\tSELECT distinct db.id,db.metric,db.low,db.medium,db.high,db.elite,m1.metric as _metric, m1.value FROM dora_benchmarks db\n\tleft join _metric_deployment_frequency m1 on db.metric = m1.metric\n\tWHERE m1.metric is not null and db.dora_report = ('$dora_report')\n\t\n\tunion \n\t\n\tSELECT distinct db.id,db.metric,db.low,db.medium,db.high,db.elite,m2.metric as _metric, m2.value FROM dora_benchmarks db\n\tleft join _metric_change_lead_time m2 on db.metric = m2.metric\n\tWHERE m2.metric is not null and db.dora_report = ('$dora_report')\n\t\n\tunion \n\t\n\tSELECT distinct db.id,db.metric,db.low,db.medium,db.high,db.elite,m3.metric as _metric, m3.value FROM dora_benchmarks db\n\tleft join _metric_cfr m3 on db.metric = m3.metric\n\tWHERE m3.metric is not null and db.dora_report = ('$dora_report')\n\t\n\tunion \n\t\n\tSELECT distinct db.id,db.metric,db.low,db.medium,db.high,db.elite,m4.metric as _metric, m4.value FROM dora_benchmarks db\n\tleft join _metric_mrt_or_mm m4 on db.metric = m4.metric\n\tWHERE m4.metric is not null and db.dora_report = ('$dora_report')\n)\n\n\nSELECT \n\tmetric,\n\treplace(metric,' ','-') as metric_hidden,\n\tcase when low = value then low else null end as low,\n\tcase when medium = value then medium else null end as medium,\n\tcase when high = value then high else null end as high,\n\tcase when elite = value then elite else null end as elite\nFROM _final_results\nORDER BY id", + "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 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days 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(DATE(cdc.finished_date)) as day\n\tFROM cicd_deployment_commits cdc\n\tJOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tpm.project_name in (${project})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n),\n\n_days_weekly_deploy as(\n-- calculate the number of deployment days every week\n\tSELECT\n\t\t\tdate(DATE_ADD(last_few_calendar_months.day, INTERVAL -WEEKDAY(last_few_calendar_months.day) DAY)) as week,\n\t\t\tMAX(if(_production_deployment_days.day is not null, 1, 0)) as weeks_deployed,\n\t\t\tCOUNT(distinct _production_deployment_days.day) as days_deployed\n\tFROM \n\t\tlast_few_calendar_months\n\t\tLEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day\n\tGROUP BY week\n\t),\n\n_days_monthly_deploy as(\n-- calculate the number of deployment days every month\n\tSELECT\n\t\t\tdate(DATE_ADD(last_few_calendar_months.day, INTERVAL -DAY(last_few_calendar_months.day)+1 DAY)) as month,\n\t\t\tMAX(if(_production_deployment_days.day is not null, 1, null)) as months_deployed,\n\t\t COUNT(distinct _production_deployment_days.day) as days_deployed\n\tFROM \n\t\tlast_few_calendar_months\n\t\tLEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day\n\tGROUP BY month\n\t),\n\n_days_six_months_deploy AS (\n SELECT\n month,\n SUM(days_deployed) OVER (\n ORDER BY month\n ROWS BETWEEN 5 PRECEDING AND CURRENT ROW\n ) AS days_deployed_per_six_months,\n COUNT(months_deployed) OVER (\n ORDER BY month\n ROWS BETWEEN 5 PRECEDING AND CURRENT ROW\n ) AS months_deployed_count,\n ROW_NUMBER() OVER (\n PARTITION BY DATE_FORMAT(month, '%Y-%m') DIV 6\n ORDER BY month DESC\n ) AS rn\n FROM _days_monthly_deploy\n),\n\n_median_number_of_deployment_days_per_week_ranks as(\n\tSELECT *, percent_rank() over(order by days_deployed) as ranks\n\tFROM _days_weekly_deploy\n),\n\n_median_number_of_deployment_days_per_week as(\n\tSELECT max(days_deployed) as median_number_of_deployment_days_per_week\n\tFROM _median_number_of_deployment_days_per_week_ranks\n\tWHERE ranks <= 0.5\n),\n\n_median_number_of_deployment_days_per_month_ranks as(\n\tSELECT *, percent_rank() over(order by days_deployed) as ranks\n\tFROM _days_monthly_deploy\n),\n\n_median_number_of_deployment_days_per_month as(\n\tSELECT max(days_deployed) as median_number_of_deployment_days_per_month\n\tFROM _median_number_of_deployment_days_per_month_ranks\n\tWHERE ranks <= 0.5\n),\n\n_days_per_six_months_deploy_by_filter AS (\nSELECT\n month,\n days_deployed_per_six_months,\n months_deployed_count\nFROM _days_six_months_deploy\nWHERE rn%6 = 1\n),\n\n\n_median_number_of_deployment_days_per_six_months_ranks as(\n\tSELECT *, percent_rank() over(order by days_deployed_per_six_months) as ranks\n\tFROM _days_per_six_months_deploy_by_filter\n),\n\n_median_number_of_deployment_days_per_six_months as(\n\tSELECT min(days_deployed_per_six_months) as median_number_of_deployment_days_per_six_months, min(months_deployed_count) as is_collected\n\tFROM _median_number_of_deployment_days_per_six_months_ranks\n\tWHERE ranks >= 0.5\n),\n\n_metric_deployment_frequency as (\n\tSELECT \n\t 'Deployment frequency' as metric, \n\t\tCASE\n\t\t\tWHEN ('$dora_report') = '2023' THEN\n\t\t\t\tCASE \n\t\t\t\t\tWHEN median_number_of_deployment_days_per_week >= 7 THEN 'On-demand(elite)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_month < 1 and is_collected is not null THEN 'Fewer than once per month(low)'\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments.\" END\n\t\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\t\tCASE \n\t\t\t\t\tWHEN median_number_of_deployment_days_per_week >= 7 THEN 'On-demand(elite)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_six_months < 1 and is_collected is not null THEN 'Fewer than once per six months(low)'\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments.\" END\n\t\t\tELSE 'Invalid dora report'\n\t\tEND AS value\n\tFROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months\n),\n\n-- Metric 2: median lead time for changes\n_pr_stats as (\n-- get the cycle time of PRs deployed by the deployments finished in the selected period\n\tSELECT\n\t\tdistinct pr.id,\n\t\tppm.pr_cycle_time\n\tFROM\n\t\tpull_requests pr \n\t\tjoin project_pr_metrics ppm on ppm.id = pr.id\n\t\tjoin project_mapping pm on pr.base_repo_id = pm.row_id and pm.`table` = 'repos'\n\t\tjoin cicd_deployment_commits cdc on ppm.deployment_commit_id = cdc.id\n\tWHERE\n\t pm.project_name in (${project}) \n\t\tand pr.merged_date is not null\n\t\tand ppm.pr_cycle_time is not null\n\t\tand $__timeFilter(cdc.finished_date)\n),\n\n_median_change_lead_time_ranks as(\n\tSELECT *, percent_rank() over(order by pr_cycle_time) as ranks\n\tFROM _pr_stats\n),\n\n_median_change_lead_time as(\n-- use median PR cycle time as the median change lead time\n\tSELECT max(pr_cycle_time) as median_change_lead_time\n\tFROM _median_change_lead_time_ranks\n\tWHERE ranks <= 0.5\n),\n\n_metric_change_lead_time as (\n\tSELECT \n\t\t'Lead time for changes' as metric,\n\t\tCASE\n\t\t\tWHEN ('$dora_report') = '2023' THEN\n\t\t\t\tCASE\n\t\t\t\t\tWHEN median_change_lead_time < 24 * 60 THEN \"Less than one day(elite)\"\n\t\t\t\t\tWHEN median_change_lead_time < 7 * 24 * 60 THEN \"Between one day and one week(high)\"\n\t\t\t\t\tWHEN median_change_lead_time < 30 * 24 * 60 THEN \"Between one week and one month(medium)\"\n\t\t\t\t\tWHEN median_change_lead_time >= 30 * 24 * 60 THEN \"More than one month(low)\"\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments/pull_requests.\"\n\t\t\t\t\tEND\n\t\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\t\tCASE\n\t\t\t\t\tWHEN median_change_lead_time < 60 THEN \"Less than one hour(elite)\"\n\t\t\t\t\tWHEN median_change_lead_time < 7 * 24 * 60 THEN \"Less than one week(high)\"\n\t\t\t\t\tWHEN median_change_lead_time < 180 * 24 * 60 THEN \"Between one week and six months(medium)\"\n\t\t\t\t\tWHEN median_change_lead_time >= 180 * 24 * 60 THEN \"More than six months(low)\"\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments/pull_requests.\"\n\t\t\t\t\tEND\n\t\t\tELSE 'Invalid dora report'\n\t\tEND AS value\nFROM _median_change_lead_time\n),\n\n-- Metric 3: change failure rate\n_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 project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tpm.project_name in (${project})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_incident_deployment_relationships pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n),\n\n_change_failure_rate as (\n\tSELECT \n\t\tcase \n\t\t\twhen count(deployment_id) is null then null\n\t\t\telse sum(has_incident)/count(deployment_id) end as change_failure_rate\n\tFROM\n\t\t_failure_caused_by_deployments\n),\n\n_is_collected_data as(\n\tSELECT\n CASE \n WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All'\n WHEN COUNT(i.id) = 0 THEN 'No Incidents' \n WHEN COUNT(cdc.id) = 0 THEN 'No Deployments'\n END AS is_collected\nFROM\n (SELECT 1) AS dummy\nLEFT JOIN\n issues i ON i.type = 'INCIDENT'\nLEFT JOIN\n cicd_deployment_commits cdc ON 1=1\n),\n\n_metric_cfr as (\n\tSELECT\n\t\t'Change failure rate' as metric,\n\t\tCASE\n\t\t\tWHEN ('$dora_report') = '2023' THEN\n\t\t\t\tCASE \n\t\t\t\t WHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\t WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\t WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\t\tWHEN change_failure_rate <= .05 THEN \"0-5%(elite)\"\n\t\t\t\t\tWHEN change_failure_rate <= .10 THEN \"5%-10%(high)\"\n\t\t\t\t\tWHEN change_failure_rate <= .15 THEN \"10%-15%(medium)\"\n\t\t\t\t\tWHEN change_failure_rate > .15 THEN \"> 15%(low)\"\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\t\tEND\n\t\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\t\tCASE \n\t\t\t\t\tWHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\t WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\t WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\t\tWHEN change_failure_rate <= .15 THEN \"0-15%(elite)\"\n\t\t\t\t\tWHEN change_failure_rate <= .20 THEN \"16%-20%(high)\"\n\t\t\t\t\tWHEN change_failure_rate <= .30 THEN \"21%-30%(medium)\"\n\t\t\t\t\tWHEN change_failure_rate > .30 THEN \"> 30%(low)\" \n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\t\tEND\n\t\t\tELSE 'Invalid dora report'\n\t\tEND AS value\n\tFROM \n\t\t_change_failure_rate, _is_collected_data\n),\n\n-- ***** 2023 report ***** --\n-- Metric 4: Failed deployment recovery time\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_incident_deployment_relationships pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n_recovery_time_ranks as (\n SELECT *, percent_rank() over(order by TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as ranks\n FROM _incidents_for_deployments\n),\n\n_median_recovery_time as (\n SELECT max(TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as median_recovery_time\n FROM _recovery_time_ranks\n WHERE ranks <= 0.5\n),\n\n_metric_recovery_time_2023_report as(\n\tSELECT \n\t\"Failed deployment recovery time\" as metric,\n\tCASE\n\t\tWHEN ('$dora_report') = '2023' THEN\n\t\tCASE\n\t\t\tWHEN median_recovery_time < 60 THEN \"Less than one hour(elite)\"\n\t\t\tWHEN median_recovery_time < 24 * 60 THEN \"Less than one day(high)\"\n\t\t\tWHEN median_recovery_time < 7 * 24 * 60 THEN \"Between one day and one week(medium)\"\n\t\t\tWHEN median_recovery_time >= 7 * 24 * 60 THEN \"More than one week(low)\"\n\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\tEND\n\tEND AS median_recovery_time\n\tFROM \n\t_median_recovery_time\n),\n\n-- ***** 2021 report ***** --\n-- Metric 4: Median time to restore service \n_incidents as (\n-- get the incidents created within the selected time period in the top-right corner\n\tSELECT\n\t distinct i.id,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\tWHERE\n\t pm.project_name in (${project})\n\t\tand i.type = 'INCIDENT'\n\t\tand $__timeFilter(i.resolution_date)\n),\n\n_median_mttr_ranks as(\n\tSELECT *, percent_rank() over(order by lead_time_minutes) as ranks\n\tFROM _incidents\n),\n\n_median_mttr as(\n\tSELECT max(lead_time_minutes) as median_time_to_resolve\n\tFROM _median_mttr_ranks\n\tWHERE ranks <= 0.5\n),\n\n_metric_mttr_2021_report as(\n\tSELECT \n\t\"Time to restore service\" as metric,\n\tCASE\n\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\tCASE\n\t\t\t\tWHEN median_time_to_resolve < 60 THEN \"Less than one hour(elite)\"\n\t\t\t\tWHEN median_time_to_resolve < 24 * 60 THEN \"Less than one day(high)\"\n\t\t\t\tWHEN median_time_to_resolve < 7 * 24 * 60 THEN \"Between one day and one week(medium)\"\n\t\t\t\tWHEN median_time_to_resolve >= 7 * 24 * 60 THEN \"More than one week(low)\"\n\t\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\t\tEND\n\tEND AS median_time_to_resolve\n\tFROM \n\t\t_median_mttr\n),\n\n_metric_mrt_or_mm as(\n\tSELECT \n\tmetric,\n\tmedian_recovery_time AS value\n\tFROM \n\t_metric_recovery_time_2023_report\n\tWHERE \n\t('$dora_report') = '2023'\n\tUNION\n\tSELECT \n\tmetric,\n\tmedian_time_to_resolve AS value\n\tFROM \n\t_metric_mttr_2021_report\n\tWHERE \n\t('$dora_report') = '2021'\n),\n\n_final_results as (\t\n\tSELECT distinct db.id,db.metric,db.low,db.medium,db.high,db.elite,m1.metric as _metric, m1.value FROM dora_benchmarks db\n\tleft join _metric_deployment_frequency m1 on db.metric = m1.metric\n\tWHERE m1.metric is not null and db.dora_report = ('$dora_report')\n\t\n\tunion \n\t\n\tSELECT distinct db.id,db.metric,db.low,db.medium,db.high,db.elite,m2.metric as _metric, m2.value FROM dora_benchmarks db\n\tleft join _metric_change_lead_time m2 on db.metric = m2.metric\n\tWHERE m2.metric is not null and db.dora_report = ('$dora_report')\n\t\n\tunion \n\t\n\tSELECT distinct db.id,db.metric,db.low,db.medium,db.high,db.elite,m3.metric as _metric, m3.value FROM dora_benchmarks db\n\tleft join _metric_cfr m3 on db.metric = m3.metric\n\tWHERE m3.metric is not null and db.dora_report = ('$dora_report')\n\t\n\tunion \n\t\n\tSELECT distinct db.id,db.metric,db.low,db.medium,db.high,db.elite,m4.metric as _metric, m4.value FROM dora_benchmarks db\n\tleft join _metric_mrt_or_mm m4 on db.metric = m4.metric\n\tWHERE m4.metric is not null and db.dora_report = ('$dora_report')\n)\n\n\nSELECT \n\tmetric,\n\treplace(metric,' ','-') as metric_hidden,\n\tcase when low = value then low else null end as low,\n\tcase when medium = value then medium else null end as medium,\n\tcase when high = value then high else null end as high,\n\tcase when elite = value then elite else null end as elite\nFROM _final_results\nORDER BY id", "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 project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tpm.project_name in (${project})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_issue_metrics pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n),\n\n_change_failure_rate as (\n\tSELECT \n\t\tcase \n\t\t\twhen count(deployment_id) is null then null\n\t\t\telse sum(has_incident)/count(deployment_id) end as change_failure_rate\n\tFROM\n\t\t_failure_caused_by_deployments\n),\n\n_is_collected_data as(\n\tSELECT\n CASE \n WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All'\n WHEN COUNT(i.id) = 0 THEN 'No Incidents' \n WHEN COUNT(cdc.id) = 0 THEN 'No Deployments'\n END AS is_collected\nFROM\n (SELECT 1) AS dummy\nLEFT JOIN\n issues i ON i.type = 'INCIDENT'\nLEFT JOIN\n cicd_deployment_commits cdc ON 1=1\n)\n\n\nSELECT\n CASE\n WHEN ('$dora_report') = '2023' THEN\n\t\t\tCASE \n\t\t\t\tWHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tWHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\tWHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\tWHEN change_failure_rate <= .05 THEN CONCAT(round(change_failure_rate*100,1), \"%(elite)\")\n\t\t\t\tWHEN change_failure_rate <= .10 THEN CONCAT(round(change_failure_rate*100,1), \"%(high)\")\n\t\t\t\tWHEN change_failure_rate <= .15 THEN CONCAT(round(change_failure_rate*100,1), \"%(medium)\")\n\t\t\t\tWHEN change_failure_rate > .15 THEN CONCAT(round(change_failure_rate*100,1), \"%(low)\")\n\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tEND\n\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\tCASE \n\t\t\t WHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tWHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\tWHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\tWHEN change_failure_rate <= .15 THEN CONCAT(round(change_failure_rate*100,1), \"%(elite)\")\n\t\t\t\tWHEN change_failure_rate <= .20 THEN CONCAT(round(change_failure_rate*100,1), \"%(high)\")\n\t\t\t\tWHEN change_failure_rate <= .30 THEN CONCAT(round(change_failure_rate*100,1), \"%(medium)\")\n\t\t\t\tWHEN change_failure_rate > .30 THEN CONCAT(round(change_failure_rate*100,1), \"%(low)\") \n\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tEND\n\t\tELSE 'Invalid dora report'\n\tEND AS change_failure_rate\nFROM \n\t_change_failure_rate, _is_collected_data", + "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 project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tpm.project_name in (${project})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_incident_deployment_relationships pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n),\n\n_change_failure_rate as (\n\tSELECT \n\t\tcase \n\t\t\twhen count(deployment_id) is null then null\n\t\t\telse sum(has_incident)/count(deployment_id) end as change_failure_rate\n\tFROM\n\t\t_failure_caused_by_deployments\n),\n\n_is_collected_data as(\n\tSELECT\n CASE \n WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All'\n WHEN COUNT(i.id) = 0 THEN 'No Incidents' \n WHEN COUNT(cdc.id) = 0 THEN 'No Deployments'\n END AS is_collected\nFROM\n (SELECT 1) AS dummy\nLEFT JOIN\n issues i ON i.type = 'INCIDENT'\nLEFT JOIN\n cicd_deployment_commits cdc ON 1=1\n)\n\n\nSELECT\n CASE\n WHEN ('$dora_report') = '2023' THEN\n\t\t\tCASE \n\t\t\t\tWHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tWHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\tWHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\tWHEN change_failure_rate <= .05 THEN CONCAT(round(change_failure_rate*100,1), \"%(elite)\")\n\t\t\t\tWHEN change_failure_rate <= .10 THEN CONCAT(round(change_failure_rate*100,1), \"%(high)\")\n\t\t\t\tWHEN change_failure_rate <= .15 THEN CONCAT(round(change_failure_rate*100,1), \"%(medium)\")\n\t\t\t\tWHEN change_failure_rate > .15 THEN CONCAT(round(change_failure_rate*100,1), \"%(low)\")\n\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tEND\n\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\tCASE \n\t\t\t WHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tWHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\tWHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\tWHEN change_failure_rate <= .15 THEN CONCAT(round(change_failure_rate*100,1), \"%(elite)\")\n\t\t\t\tWHEN change_failure_rate <= .20 THEN CONCAT(round(change_failure_rate*100,1), \"%(high)\")\n\t\t\t\tWHEN change_failure_rate <= .30 THEN CONCAT(round(change_failure_rate*100,1), \"%(medium)\")\n\t\t\t\tWHEN change_failure_rate > .30 THEN CONCAT(round(change_failure_rate*100,1), \"%(low)\") \n\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tEND\n\t\tELSE 'Invalid dora report'\n\tEND AS change_failure_rate\nFROM \n\t_change_failure_rate, _is_collected_data", "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.environment = 'PRODUCTION'\n GROUP BY 1\n HAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_issue_metrics pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n_recovery_time_ranks as (\n SELECT *, percent_rank() over(order by TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as ranks\n FROM _incidents_for_deployments\n),\n\n_median_recovery_time as (\n SELECT max(TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as median_recovery_time\n FROM _recovery_time_ranks\n WHERE ranks <= 0.5\n),\n\n_metric_recovery_time_2023_report as(\n\tSELECT \n\tCASE\n\t\tWHEN ('$dora_report') = '2023' THEN\n\t\tCASE\n\t\t\tWHEN median_recovery_time < 60 THEN CONCAT(round(median_recovery_time/60,1), \"(elite)\")\n\t\t\tWHEN median_recovery_time < 24 * 60 THEN CONCAT(round(median_recovery_time/60,1), \"(high)\")\n\t\t\tWHEN median_recovery_time < 7 * 24 * 60 THEN CONCAT(round(median_recovery_time/60,1), \"(medium)\")\n\t\t\tWHEN median_recovery_time >= 7 * 24 * 60 THEN CONCAT(round(median_recovery_time/60,1), \"(low)\")\n\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\tEND\n\tEND AS median_recovery_time\n\tFROM \n\t_median_recovery_time\n),\n\n-- ***** 2021 report ***** --\n-- Metric 4: Median time to restore service \n_incidents as (\n-- get the incidents created within the selected time period in the top-right corner\n\tSELECT\n\t distinct i.id,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\tWHERE\n\t pm.project_name in (${project})\n\t\tand i.type = 'INCIDENT'\n\t\tand $__timeFilter(i.resolution_date)\n),\n\n_median_mttr_ranks as(\n\tSELECT *, percent_rank() over(order by lead_time_minutes) as ranks\n\tFROM _incidents\n),\n\n_median_mttr as(\n\tSELECT max(lead_time_minutes) as median_time_to_resolve\n\tFROM _median_mttr_ranks\n\tWHERE ranks <= 0.5\n),\n\n_metric_mttr_2021_report as(\n\tSELECT \n\tCASE\n\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\tCASE\n\t\t\t\tWHEN median_time_to_resolve < 60 THEN CONCAT(round(median_time_to_resolve/60,1), \"(elite)\")\n\t\t\t\tWHEN median_time_to_resolve < 24 * 60 THEN CONCAT(round(median_time_to_resolve/60,1), \"(high)\")\n\t\t\t\tWHEN median_time_to_resolve < 7 * 24 * 60 THEN CONCAT(round(median_time_to_resolve/60,1), \"(medium)\")\n\t\t\t\tWHEN median_time_to_resolve >= 7 * 24 * 60 THEN CONCAT(round(median_time_to_resolve/60,1), \"(low)\")\n\t\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\t\tEND\n\tEND AS median_time_to_resolve\n\tFROM \n\t\t_median_mttr\n)\n\nSELECT \n median_recovery_time AS median_time_in_hour\nFROM \n _metric_recovery_time_2023_report\nWHERE \n ('$dora_report') = '2023'\nUNION\nSELECT \n median_time_to_resolve AS median_time_to_resolve\nFROM \n _metric_mttr_2021_report\nWHERE \n ('$dora_report') = '2021'\n", + "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.environment = 'PRODUCTION'\n GROUP BY 1\n HAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_incident_deployment_relationships pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n_recovery_time_ranks as (\n SELECT *, percent_rank() over(order by TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as ranks\n FROM _incidents_for_deployments\n),\n\n_median_recovery_time as (\n SELECT max(TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as median_recovery_time\n FROM _recovery_time_ranks\n WHERE ranks <= 0.5\n),\n\n_metric_recovery_time_2023_report as(\n\tSELECT \n\tCASE\n\t\tWHEN ('$dora_report') = '2023' THEN\n\t\tCASE\n\t\t\tWHEN median_recovery_time < 60 THEN CONCAT(round(median_recovery_time/60,1), \"(elite)\")\n\t\t\tWHEN median_recovery_time < 24 * 60 THEN CONCAT(round(median_recovery_time/60,1), \"(high)\")\n\t\t\tWHEN median_recovery_time < 7 * 24 * 60 THEN CONCAT(round(median_recovery_time/60,1), \"(medium)\")\n\t\t\tWHEN median_recovery_time >= 7 * 24 * 60 THEN CONCAT(round(median_recovery_time/60,1), \"(low)\")\n\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\tEND\n\tEND AS median_recovery_time\n\tFROM \n\t_median_recovery_time\n),\n\n-- ***** 2021 report ***** --\n-- Metric 4: Median time to restore service \n_incidents as (\n-- get the incidents created within the selected time period in the top-right corner\n\tSELECT\n\t distinct i.id,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\tWHERE\n\t pm.project_name in (${project})\n\t\tand i.type = 'INCIDENT'\n\t\tand $__timeFilter(i.resolution_date)\n),\n\n_median_mttr_ranks as(\n\tSELECT *, percent_rank() over(order by lead_time_minutes) as ranks\n\tFROM _incidents\n),\n\n_median_mttr as(\n\tSELECT max(lead_time_minutes) as median_time_to_resolve\n\tFROM _median_mttr_ranks\n\tWHERE ranks <= 0.5\n),\n\n_metric_mttr_2021_report as(\n\tSELECT \n\tCASE\n\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\tCASE\n\t\t\t\tWHEN median_time_to_resolve < 60 THEN CONCAT(round(median_time_to_resolve/60,1), \"(elite)\")\n\t\t\t\tWHEN median_time_to_resolve < 24 * 60 THEN CONCAT(round(median_time_to_resolve/60,1), \"(high)\")\n\t\t\t\tWHEN median_time_to_resolve < 7 * 24 * 60 THEN CONCAT(round(median_time_to_resolve/60,1), \"(medium)\")\n\t\t\t\tWHEN median_time_to_resolve >= 7 * 24 * 60 THEN CONCAT(round(median_time_to_resolve/60,1), \"(low)\")\n\t\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\t\tEND\n\tEND AS median_time_to_resolve\n\tFROM \n\t\t_median_mttr\n)\n\nSELECT \n median_recovery_time AS median_time_in_hour\nFROM \n _metric_recovery_time_2023_report\nWHERE \n ('$dora_report') = '2023'\nUNION\nSELECT \n median_time_to_resolve AS median_time_to_resolve\nFROM \n _metric_mttr_2021_report\nWHERE \n ('$dora_report') = '2021'\n", "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\tJOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tpm.project_name in (${project})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_issue_metrics pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n),\n\n_change_failure_rate_for_each_month as (\n\tSELECT \n\t\tdate_format(deployment_finished_date,'%y/%m') as month,\n\t\tcase \n\t\t\twhen count(deployment_id) is null then null\n\t\t\telse sum(has_incident)/count(deployment_id) end as change_failure_rate\n\tFROM\n\t\t_failure_caused_by_deployments\n\tGROUP BY 1\n)\n\nSELECT \n\tcm.month,\n\tcfr.change_failure_rate as 'Change Failure Rate'\nFROM \n\tcalendar_months cm\n\tLEFT JOIN _change_failure_rate_for_each_month cfr on cm.month = cfr.month\n\tWHERE $__timeFilter(cm.month_timestamp)", + "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\tJOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tpm.project_name in (${project})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_incident_deployment_relationships pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n),\n\n_change_failure_rate_for_each_month as (\n\tSELECT \n\t\tdate_format(deployment_finished_date,'%y/%m') as month,\n\t\tcase \n\t\t\twhen count(deployment_id) is null then null\n\t\t\telse sum(has_incident)/count(deployment_id) end as change_failure_rate\n\tFROM\n\t\t_failure_caused_by_deployments\n\tGROUP BY 1\n)\n\nSELECT \n\tcm.month,\n\tcfr.change_failure_rate as 'Change Failure Rate'\nFROM \n\tcalendar_months cm\n\tLEFT JOIN _change_failure_rate_for_each_month cfr on cm.month = cfr.month\n\tWHERE $__timeFilter(cm.month_timestamp)", "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.environment = 'PRODUCTION'\n GROUP BY 1\n HAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_issue_metrics pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n_recovery_time_ranks as (\n SELECT *, percent_rank() over(PARTITION BY deployment_finished_month order by TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as ranks\n FROM _incidents_for_deployments\n),\n\n_median_recovery_time as (\n SELECT deployment_finished_month, max(TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as median_recovery_time\n FROM _recovery_time_ranks\n WHERE ranks <= 0.5\n GROUP BY deployment_finished_month\n),\n\n_metric_recovery_time_2023_report as (\n SELECT \n cm.month,\n case \n when m.median_recovery_time is null then 0 \n else m.median_recovery_time/60 \n end as median_recovery_time_in_hour\n FROM \n calendar_months cm\n LEFT JOIN _median_recovery_time m on cm.month = m.deployment_finished_month\n WHERE $__timeFilter(cm.month_timestamp)\n),\n\n\n-- ***** 2021 report ***** --\n-- Metric 4: median time to restore service - MTTR\n_incidents as (\n-- get the number of incidents created each month\n\tSELECT\n\t distinct i.id,\n\t\tdate_format(i.resolution_date,'%y/%m') as month,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\tWHERE\n\t pm.project_name in (${project})\n\t\tand i.type = 'INCIDENT'\n\t\tand i.lead_time_minutes is not null\n),\n\n_find_median_mttr_each_month_ranks as(\n\tSELECT *, percent_rank() over(PARTITION BY month order by lead_time_minutes) as ranks\n\tFROM _incidents\n),\n\n_mttr as(\n\tSELECT month, max(lead_time_minutes) as median_time_to_resolve\n\tFROM _find_median_mttr_each_month_ranks\n\tWHERE ranks <= 0.5\n\tGROUP BY month\n),\n\n_metric_mttr_2021_report as (\n SELECT \n cm.month,\n case \n when m.median_time_to_resolve is null then 0 \n else m.median_time_to_resolve/60 end as median_time_to_resolve_in_hour\n FROM \n calendar_months cm\n LEFT JOIN _mttr m on cm.month = m.month\n WHERE $__timeFilter(cm.month_timestamp)\n)\n\nSELECT \n cm.month,\n CASE \n WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour\n WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour\n END AS '${title_value} In Hours'\nFROM \n calendar_months cm\n LEFT JOIN _metric_recovery_time_2023_report mrt ON cm.month = mrt.month\n LEFT JOIN _metric_mttr_2021_report mm ON cm.month = mm.month\nWHERE \n $__timeFilter(cm.month_timestamp)", + "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.environment = 'PRODUCTION'\n GROUP BY 1\n HAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_incident_deployment_relationships pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n_recovery_time_ranks as (\n SELECT *, percent_rank() over(PARTITION BY deployment_finished_month order by TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as ranks\n FROM _incidents_for_deployments\n),\n\n_median_recovery_time as (\n SELECT deployment_finished_month, max(TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as median_recovery_time\n FROM _recovery_time_ranks\n WHERE ranks <= 0.5\n GROUP BY deployment_finished_month\n),\n\n_metric_recovery_time_2023_report as (\n SELECT \n cm.month,\n case \n when m.median_recovery_time is null then 0 \n else m.median_recovery_time/60 \n end as median_recovery_time_in_hour\n FROM \n calendar_months cm\n LEFT JOIN _median_recovery_time m on cm.month = m.deployment_finished_month\n WHERE $__timeFilter(cm.month_timestamp)\n),\n\n\n-- ***** 2021 report ***** --\n-- Metric 4: median time to restore service - MTTR\n_incidents as (\n-- get the number of incidents created each month\n\tSELECT\n\t distinct i.id,\n\t\tdate_format(i.resolution_date,'%y/%m') as month,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\tWHERE\n\t pm.project_name in (${project})\n\t\tand i.type = 'INCIDENT'\n\t\tand i.lead_time_minutes is not null\n),\n\n_find_median_mttr_each_month_ranks as(\n\tSELECT *, percent_rank() over(PARTITION BY month order by lead_time_minutes) as ranks\n\tFROM _incidents\n),\n\n_mttr as(\n\tSELECT month, max(lead_time_minutes) as median_time_to_resolve\n\tFROM _find_median_mttr_each_month_ranks\n\tWHERE ranks <= 0.5\n\tGROUP BY month\n),\n\n_metric_mttr_2021_report as (\n SELECT \n cm.month,\n case \n when m.median_time_to_resolve is null then 0 \n else m.median_time_to_resolve/60 end as median_time_to_resolve_in_hour\n FROM \n calendar_months cm\n LEFT JOIN _mttr m on cm.month = m.month\n WHERE $__timeFilter(cm.month_timestamp)\n)\n\nSELECT \n cm.month,\n CASE \n WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour\n WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour\n END AS '${title_value} In Hours'\nFROM \n calendar_months cm\n LEFT JOIN _metric_recovery_time_2023_report mrt ON cm.month = mrt.month\n LEFT JOIN _metric_mttr_2021_report mm ON cm.month = mm.month\nWHERE \n $__timeFilter(cm.month_timestamp)", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/DORAByTeam.json b/grafana/dashboards/DORAByTeam.json index 5dcda99807e..c1901c7bd63 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 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days 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(DATE(cdc.finished_date)) as day\n\tFROM cicd_deployment_commits cdc\t\n JOIN 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 teams t on tu.team_id = t.id\n\tJOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tt.name in (${team})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n),\n\n_days_weekly_deploy as(\n-- calculate the number of deployment days every week\n\tSELECT\n\t\t\tdate(DATE_ADD(last_few_calendar_months.day, INTERVAL -WEEKDAY(last_few_calendar_months.day) DAY)) as week,\n\t\t\tMAX(if(_production_deployment_days.day is not null, 1, 0)) as weeks_deployed,\n\t\t\tCOUNT(distinct _production_deployment_days.day) as days_deployed\n\tFROM \n\t\tlast_few_calendar_months\n\t\tLEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day\n\tGROUP BY week\n\t),\n\n_days_monthly_deploy as(\n-- calculate the number of deployment days every month\n\tSELECT\n\t\t\tdate(DATE_ADD(last_few_calendar_months.day, INTERVAL -DAY(last_few_calendar_months.day)+1 DAY)) as month,\n\t\t\tMAX(if(_production_deployment_days.day is not null, 1, null)) as months_deployed,\n\t\t COUNT(distinct _production_deployment_days.day) as days_deployed\n\tFROM \n\t\tlast_few_calendar_months\n\t\tLEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day\n\tGROUP BY month\n\t),\n\n_days_six_months_deploy AS (\n SELECT\n month,\n SUM(days_deployed) OVER (\n ORDER BY month\n ROWS BETWEEN 5 PRECEDING AND CURRENT ROW\n ) AS days_deployed_per_six_months,\n COUNT(months_deployed) OVER (\n ORDER BY month\n ROWS BETWEEN 5 PRECEDING AND CURRENT ROW\n ) AS months_deployed_count,\n ROW_NUMBER() OVER (\n PARTITION BY DATE_FORMAT(month, '%Y-%m') DIV 6\n ORDER BY month DESC\n ) AS rn\n FROM _days_monthly_deploy\n),\n\n_median_number_of_deployment_days_per_week_ranks as(\n\tSELECT *, percent_rank() over(order by days_deployed) as ranks\n\tFROM _days_weekly_deploy\n),\n\n_median_number_of_deployment_days_per_week as(\n\tSELECT max(days_deployed) as median_number_of_deployment_days_per_week\n\tFROM _median_number_of_deployment_days_per_week_ranks\n\tWHERE ranks <= 0.5\n),\n\n_median_number_of_deployment_days_per_month_ranks as(\n\tSELECT *, percent_rank() over(order by days_deployed) as ranks\n\tFROM _days_monthly_deploy\n),\n\n_median_number_of_deployment_days_per_month as(\n\tSELECT max(days_deployed) as median_number_of_deployment_days_per_month\n\tFROM _median_number_of_deployment_days_per_month_ranks\n\tWHERE ranks <= 0.5\n),\n\n_days_per_six_months_deploy_by_filter AS (\nSELECT\n month,\n days_deployed_per_six_months,\n months_deployed_count\nFROM _days_six_months_deploy\nWHERE rn%6 = 1\n),\n\n\n_median_number_of_deployment_days_per_six_months_ranks as(\n\tSELECT *, percent_rank() over(order by days_deployed_per_six_months) as ranks\n\tFROM _days_per_six_months_deploy_by_filter\n),\n\n_median_number_of_deployment_days_per_six_months as(\n\tSELECT min(days_deployed_per_six_months) as median_number_of_deployment_days_per_six_months, min(months_deployed_count) as is_collected\n\tFROM _median_number_of_deployment_days_per_six_months_ranks\n\tWHERE ranks >= 0.5\n),\n\n_metric_deployment_frequency as (\n\tSELECT \n\t 'Deployment frequency' as metric, \n\t\tCASE\n\t\t\tWHEN ('$dora_report') = '2023' THEN\n\t\t\t\tCASE \n\t\t\t\t\tWHEN median_number_of_deployment_days_per_week >= 7 THEN 'On-demand(elite)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_month < 1 and is_collected is not null THEN 'Fewer than once per month(low)'\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments.\" END\n\t\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\t\tCASE \n\t\t\t\t\tWHEN median_number_of_deployment_days_per_week >= 7 THEN 'On-demand(elite)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_six_months < 1 and is_collected is not null THEN 'Fewer than once per six months(low)'\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments.\" END\n\t\t\tELSE 'Invalid dora report'\n\t\tEND AS value\n\tFROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months\n),\n\n-- Metric 2: median lead time for changes\n_pr_stats as (\n-- get the cycle time of PRs deployed by the deployments finished in the selected period\n\tSELECT\n\t\tdistinct pr.id,\n\t\tppm.pr_cycle_time\n\tFROM\n\t\tpull_requests pr \n\t\tjoin user_accounts ua on pr.author_id = ua.account_id\n \tjoin users u on ua.user_id = u.id\n \tjoin team_users tu on u.id = tu.user_id\n \tjoin teams t on tu.team_id = t.id\n\t\tjoin project_pr_metrics ppm on ppm.id = pr.id\n\t\tjoin project_mapping pm on pr.base_repo_id = pm.row_id and pm.`table` = 'repos'\n\t\tjoin cicd_deployment_commits cdc on ppm.deployment_commit_id = cdc.id\n\tWHERE\n\t t.name in (${team}) \n\t\tand pr.merged_date is not null\n\t\tand ppm.pr_cycle_time is not null\n\t\tand $__timeFilter(cdc.finished_date)\n),\n\n_median_change_lead_time_ranks as(\n\tSELECT *, percent_rank() over(order by pr_cycle_time) as ranks\n\tFROM _pr_stats\n),\n\n_median_change_lead_time as(\n-- use median PR cycle time as the median change lead time\n\tSELECT max(pr_cycle_time) as median_change_lead_time\n\tFROM _median_change_lead_time_ranks\n\tWHERE ranks <= 0.5\n),\n\n_metric_change_lead_time as (\n\tSELECT \n\t\t'Lead time for changes' as metric,\n\t\tCASE\n\t\t\tWHEN ('$dora_report') = '2023' THEN\n\t\t\t\tCASE\n\t\t\t\t\tWHEN median_change_lead_time < 24 * 60 THEN \"Less than one day(elite)\"\n\t\t\t\t\tWHEN median_change_lead_time < 7 * 24 * 60 THEN \"Between one day and one week(high)\"\n\t\t\t\t\tWHEN median_change_lead_time < 30 * 24 * 60 THEN \"Between one week and one month(medium)\"\n\t\t\t\t\tWHEN median_change_lead_time >= 30 * 24 * 60 THEN \"More than one month(low)\"\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments/pull_requests.\"\n\t\t\t\t\tEND\n\t\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\t\tCASE\n\t\t\t\t\tWHEN median_change_lead_time < 60 THEN \"Less than one hour(elite)\"\n\t\t\t\t\tWHEN median_change_lead_time < 7 * 24 * 60 THEN \"Less than one week(high)\"\n\t\t\t\t\tWHEN median_change_lead_time < 180 * 24 * 60 THEN \"Between one week and six months(medium)\"\n\t\t\t\t\tWHEN median_change_lead_time >= 180 * 24 * 60 THEN \"More than six months(low)\"\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments/pull_requests.\"\n\t\t\t\t\tEND\n\t\t\tELSE 'Invalid dora report'\n\t\tEND AS value\nFROM _median_change_lead_time\n),\n\n-- Metric 3: change failure rate\n_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 commits c on cdc.commit_sha = c.sha\n\t 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\n join teams t on tu.team_id = t.id\n\t\tJOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tt.name in (${team})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_issue_metrics pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n),\n\n_change_failure_rate as (\n\tSELECT \n\t\tcase \n\t\t\twhen count(deployment_id) is null then null\n\t\t\telse sum(has_incident)/count(deployment_id) end as change_failure_rate\n\tFROM\n\t\t_failure_caused_by_deployments\n),\n\n_is_collected_data as(\n\tSELECT\n CASE \n WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All'\n WHEN COUNT(i.id) = 0 THEN 'No Incidents' \n WHEN COUNT(cdc.id) = 0 THEN 'No Deployments'\n END AS is_collected\nFROM\n (SELECT 1) AS dummy\nLEFT JOIN\n issues i ON i.type = 'INCIDENT'\nLEFT JOIN\n cicd_deployment_commits cdc ON 1=1\n),\n\n_metric_cfr as (\n\tSELECT\n\t\t'Change failure rate' as metric,\n\t\tCASE\n\t\t\tWHEN ('$dora_report') = '2023' THEN\n\t\t\t\tCASE \n\t\t\t\t WHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\t WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\t WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\t\tWHEN change_failure_rate <= .05 THEN \"0-5%(elite)\"\n\t\t\t\t\tWHEN change_failure_rate <= .10 THEN \"5%-10%(high)\"\n\t\t\t\t\tWHEN change_failure_rate <= .15 THEN \"10%-15%(medium)\"\n\t\t\t\t\tWHEN change_failure_rate > .15 THEN \"> 15%(low)\"\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\t\tEND\n\t\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\t\tCASE \n\t\t\t\t\tWHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\t WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\t WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\t\tWHEN change_failure_rate <= .15 THEN \"0-15%(elite)\"\n\t\t\t\t\tWHEN change_failure_rate <= .20 THEN \"16%-20%(high)\"\n\t\t\t\t\tWHEN change_failure_rate <= .30 THEN \"21%-30%(medium)\"\n\t\t\t\t\tWHEN change_failure_rate > .30 THEN \"> 30%(low)\" \n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\t\tEND\n\t\t\tELSE 'Invalid dora report'\n\t\tEND AS value\n\tFROM \n\t\t_change_failure_rate, _is_collected_data\n),\n\n-- ***** 2023 report ***** --\n-- Metric 4: Failed deployment recovery time\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_issue_metrics pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n_recovery_time_ranks as (\n SELECT *, percent_rank() over(order by TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as ranks\n FROM _incidents_for_deployments\n),\n\n_median_recovery_time as (\n SELECT max(TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as median_recovery_time\n FROM _recovery_time_ranks\n WHERE ranks <= 0.5\n),\n\n_metric_recovery_time_2023_report as(\n\tSELECT \n\t\"Failed deployment recovery time\" as metric,\n\tCASE\n\t\tWHEN ('$dora_report') = '2023' THEN\n\t\tCASE\n\t\t\tWHEN median_recovery_time < 60 THEN \"Less than one hour(elite)\"\n\t\t\tWHEN median_recovery_time < 24 * 60 THEN \"Less than one day(high)\"\n\t\t\tWHEN median_recovery_time < 7 * 24 * 60 THEN \"Between one day and one week(medium)\"\n\t\t\tWHEN median_recovery_time >= 7 * 24 * 60 THEN \"More than one week(low)\"\n\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\tEND\n\tEND AS median_recovery_time\n\tFROM \n\t_median_recovery_time\n),\n\n-- ***** 2021 report ***** --\n-- Metric 4: Median time to restore service \n_incidents as (\n-- get the incidents created within the selected time period in the top-right corner\n\tSELECT\n\t distinct i.id,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\t join user_accounts ua on i.assignee_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 teams t on tu.team_id = t.id\n\tWHERE\n\t t.name in (${team})\n\t\tand i.type = 'INCIDENT'\n\t\tand $__timeFilter(i.resolution_date)\n),\n\n_median_mttr_ranks as(\n\tSELECT *, percent_rank() over(order by lead_time_minutes) as ranks\n\tFROM _incidents\n),\n\n_median_mttr as(\n\tSELECT max(lead_time_minutes) as median_time_to_resolve\n\tFROM _median_mttr_ranks\n\tWHERE ranks <= 0.5\n),\n\n_metric_mttr_2021_report as(\n\tSELECT \n\t\"Time to restore service\" as metric,\n\tCASE\n\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\tCASE\n\t\t\t\tWHEN median_time_to_resolve < 60 THEN \"Less than one hour(elite)\"\n\t\t\t\tWHEN median_time_to_resolve < 24 * 60 THEN \"Less than one day(high)\"\n\t\t\t\tWHEN median_time_to_resolve < 7 * 24 * 60 THEN \"Between one day and one week(medium)\"\n\t\t\t\tWHEN median_time_to_resolve >= 7 * 24 * 60 THEN \"More than one week(low)\"\n\t\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\t\tEND\n\tEND AS median_time_to_resolve\n\tFROM \n\t\t_median_mttr\n),\n\n_metric_mrt_or_mm as(\n\tSELECT \n\tmetric,\n\tmedian_recovery_time AS value\n\tFROM \n\t_metric_recovery_time_2023_report\n\tWHERE \n\t('$dora_report') = '2023'\n\tUNION\n\tSELECT \n\tmetric,\n\tmedian_time_to_resolve AS value\n\tFROM \n\t_metric_mttr_2021_report\n\tWHERE \n\t('$dora_report') = '2021'\n),\n\n_final_results as (\t\n\tSELECT distinct db.id,db.metric,db.low,db.medium,db.high,db.elite,m1.metric as _metric, m1.value FROM dora_benchmarks db\n\tleft join _metric_deployment_frequency m1 on db.metric = m1.metric\n\tWHERE m1.metric is not null and db.dora_report = ('$dora_report')\n\t\n\tunion \n\t\n\tSELECT distinct db.id,db.metric,db.low,db.medium,db.high,db.elite,m2.metric as _metric, m2.value FROM dora_benchmarks db\n\tleft join _metric_change_lead_time m2 on db.metric = m2.metric\n\tWHERE m2.metric is not null and db.dora_report = ('$dora_report')\n\t\n\tunion \n\t\n\tSELECT distinct db.id,db.metric,db.low,db.medium,db.high,db.elite,m3.metric as _metric, m3.value FROM dora_benchmarks db\n\tleft join _metric_cfr m3 on db.metric = m3.metric\n\tWHERE m3.metric is not null and db.dora_report = ('$dora_report')\n\t\n\tunion \n\t\n\tSELECT distinct db.id,db.metric,db.low,db.medium,db.high,db.elite,m4.metric as _metric, m4.value FROM dora_benchmarks db\n\tleft join _metric_mrt_or_mm m4 on db.metric = m4.metric\n\tWHERE m4.metric is not null and db.dora_report = ('$dora_report')\n)\n\n\nSELECT \n\tmetric,\n\tcase when low = value then low else null end as low,\n\tcase when medium = value then medium else null end as medium,\n\tcase when high = value then high else null end as high,\n\tcase when elite = value then elite else null end as elite\nFROM _final_results\nORDER BY id", + "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 50 UNION ALL SELECT 60\n\t\t\tUNION ALL SELECT 70 UNION ALL SELECT 80 UNION ALL SELECT 90\n\t\t) T CROSS JOIN ( SELECT 0 U\n\t\t\tUNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3\n\t\t\tUNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6\n\t\t\tUNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9\n\t\t) U\n\tWHERE\n\t\t($__timeTo()-INTERVAL (H+T+U) DAY) > $__timeFrom()\n),\n\n_production_deployment_days 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(DATE(cdc.finished_date)) as day\n\tFROM cicd_deployment_commits cdc\t\n JOIN 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 teams t on tu.team_id = t.id\n\tJOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tt.name in (${team})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n),\n\n_days_weekly_deploy as(\n-- calculate the number of deployment days every week\n\tSELECT\n\t\t\tdate(DATE_ADD(last_few_calendar_months.day, INTERVAL -WEEKDAY(last_few_calendar_months.day) DAY)) as week,\n\t\t\tMAX(if(_production_deployment_days.day is not null, 1, 0)) as weeks_deployed,\n\t\t\tCOUNT(distinct _production_deployment_days.day) as days_deployed\n\tFROM \n\t\tlast_few_calendar_months\n\t\tLEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day\n\tGROUP BY week\n\t),\n\n_days_monthly_deploy as(\n-- calculate the number of deployment days every month\n\tSELECT\n\t\t\tdate(DATE_ADD(last_few_calendar_months.day, INTERVAL -DAY(last_few_calendar_months.day)+1 DAY)) as month,\n\t\t\tMAX(if(_production_deployment_days.day is not null, 1, null)) as months_deployed,\n\t\t COUNT(distinct _production_deployment_days.day) as days_deployed\n\tFROM \n\t\tlast_few_calendar_months\n\t\tLEFT JOIN _production_deployment_days ON _production_deployment_days.day = last_few_calendar_months.day\n\tGROUP BY month\n\t),\n\n_days_six_months_deploy AS (\n SELECT\n month,\n SUM(days_deployed) OVER (\n ORDER BY month\n ROWS BETWEEN 5 PRECEDING AND CURRENT ROW\n ) AS days_deployed_per_six_months,\n COUNT(months_deployed) OVER (\n ORDER BY month\n ROWS BETWEEN 5 PRECEDING AND CURRENT ROW\n ) AS months_deployed_count,\n ROW_NUMBER() OVER (\n PARTITION BY DATE_FORMAT(month, '%Y-%m') DIV 6\n ORDER BY month DESC\n ) AS rn\n FROM _days_monthly_deploy\n),\n\n_median_number_of_deployment_days_per_week_ranks as(\n\tSELECT *, percent_rank() over(order by days_deployed) as ranks\n\tFROM _days_weekly_deploy\n),\n\n_median_number_of_deployment_days_per_week as(\n\tSELECT max(days_deployed) as median_number_of_deployment_days_per_week\n\tFROM _median_number_of_deployment_days_per_week_ranks\n\tWHERE ranks <= 0.5\n),\n\n_median_number_of_deployment_days_per_month_ranks as(\n\tSELECT *, percent_rank() over(order by days_deployed) as ranks\n\tFROM _days_monthly_deploy\n),\n\n_median_number_of_deployment_days_per_month as(\n\tSELECT max(days_deployed) as median_number_of_deployment_days_per_month\n\tFROM _median_number_of_deployment_days_per_month_ranks\n\tWHERE ranks <= 0.5\n),\n\n_days_per_six_months_deploy_by_filter AS (\nSELECT\n month,\n days_deployed_per_six_months,\n months_deployed_count\nFROM _days_six_months_deploy\nWHERE rn%6 = 1\n),\n\n\n_median_number_of_deployment_days_per_six_months_ranks as(\n\tSELECT *, percent_rank() over(order by days_deployed_per_six_months) as ranks\n\tFROM _days_per_six_months_deploy_by_filter\n),\n\n_median_number_of_deployment_days_per_six_months as(\n\tSELECT min(days_deployed_per_six_months) as median_number_of_deployment_days_per_six_months, min(months_deployed_count) as is_collected\n\tFROM _median_number_of_deployment_days_per_six_months_ranks\n\tWHERE ranks >= 0.5\n),\n\n_metric_deployment_frequency as (\n\tSELECT \n\t 'Deployment frequency' as metric, \n\t\tCASE\n\t\t\tWHEN ('$dora_report') = '2023' THEN\n\t\t\t\tCASE \n\t\t\t\t\tWHEN median_number_of_deployment_days_per_week >= 7 THEN 'On-demand(elite)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_week >= 1 THEN 'Between once per day and once per week(high)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per week and once per month(medium)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_month < 1 and is_collected is not null THEN 'Fewer than once per month(low)'\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments.\" END\n\t\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\t\tCASE \n\t\t\t\t\tWHEN median_number_of_deployment_days_per_week >= 7 THEN 'On-demand(elite)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_month >= 1 THEN 'Between once per day and once per month(high)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_six_months >= 1 THEN 'Between once per month and once every 6 months(medium)'\n\t\t\t\t\tWHEN median_number_of_deployment_days_per_six_months < 1 and is_collected is not null THEN 'Fewer than once per six months(low)'\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments.\" END\n\t\t\tELSE 'Invalid dora report'\n\t\tEND AS value\n\tFROM _median_number_of_deployment_days_per_week, _median_number_of_deployment_days_per_month, _median_number_of_deployment_days_per_six_months\n),\n\n-- Metric 2: median lead time for changes\n_pr_stats as (\n-- get the cycle time of PRs deployed by the deployments finished in the selected period\n\tSELECT\n\t\tdistinct pr.id,\n\t\tppm.pr_cycle_time\n\tFROM\n\t\tpull_requests pr \n\t\tjoin user_accounts ua on pr.author_id = ua.account_id\n \tjoin users u on ua.user_id = u.id\n \tjoin team_users tu on u.id = tu.user_id\n \tjoin teams t on tu.team_id = t.id\n\t\tjoin project_pr_metrics ppm on ppm.id = pr.id\n\t\tjoin project_mapping pm on pr.base_repo_id = pm.row_id and pm.`table` = 'repos'\n\t\tjoin cicd_deployment_commits cdc on ppm.deployment_commit_id = cdc.id\n\tWHERE\n\t t.name in (${team}) \n\t\tand pr.merged_date is not null\n\t\tand ppm.pr_cycle_time is not null\n\t\tand $__timeFilter(cdc.finished_date)\n),\n\n_median_change_lead_time_ranks as(\n\tSELECT *, percent_rank() over(order by pr_cycle_time) as ranks\n\tFROM _pr_stats\n),\n\n_median_change_lead_time as(\n-- use median PR cycle time as the median change lead time\n\tSELECT max(pr_cycle_time) as median_change_lead_time\n\tFROM _median_change_lead_time_ranks\n\tWHERE ranks <= 0.5\n),\n\n_metric_change_lead_time as (\n\tSELECT \n\t\t'Lead time for changes' as metric,\n\t\tCASE\n\t\t\tWHEN ('$dora_report') = '2023' THEN\n\t\t\t\tCASE\n\t\t\t\t\tWHEN median_change_lead_time < 24 * 60 THEN \"Less than one day(elite)\"\n\t\t\t\t\tWHEN median_change_lead_time < 7 * 24 * 60 THEN \"Between one day and one week(high)\"\n\t\t\t\t\tWHEN median_change_lead_time < 30 * 24 * 60 THEN \"Between one week and one month(medium)\"\n\t\t\t\t\tWHEN median_change_lead_time >= 30 * 24 * 60 THEN \"More than one month(low)\"\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments/pull_requests.\"\n\t\t\t\t\tEND\n\t\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\t\tCASE\n\t\t\t\t\tWHEN median_change_lead_time < 60 THEN \"Less than one hour(elite)\"\n\t\t\t\t\tWHEN median_change_lead_time < 7 * 24 * 60 THEN \"Less than one week(high)\"\n\t\t\t\t\tWHEN median_change_lead_time < 180 * 24 * 60 THEN \"Between one week and six months(medium)\"\n\t\t\t\t\tWHEN median_change_lead_time >= 180 * 24 * 60 THEN \"More than six months(low)\"\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments/pull_requests.\"\n\t\t\t\t\tEND\n\t\t\tELSE 'Invalid dora report'\n\t\tEND AS value\nFROM _median_change_lead_time\n),\n\n-- Metric 3: change failure rate\n_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 commits c on cdc.commit_sha = c.sha\n\t 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\n join teams t on tu.team_id = t.id\n\t\tJOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tt.name in (${team})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_incident_deployment_relationships pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n),\n\n_change_failure_rate as (\n\tSELECT \n\t\tcase \n\t\t\twhen count(deployment_id) is null then null\n\t\t\telse sum(has_incident)/count(deployment_id) end as change_failure_rate\n\tFROM\n\t\t_failure_caused_by_deployments\n),\n\n_is_collected_data as(\n\tSELECT\n CASE \n WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All'\n WHEN COUNT(i.id) = 0 THEN 'No Incidents' \n WHEN COUNT(cdc.id) = 0 THEN 'No Deployments'\n END AS is_collected\nFROM\n (SELECT 1) AS dummy\nLEFT JOIN\n issues i ON i.type = 'INCIDENT'\nLEFT JOIN\n cicd_deployment_commits cdc ON 1=1\n),\n\n_metric_cfr as (\n\tSELECT\n\t\t'Change failure rate' as metric,\n\t\tCASE\n\t\t\tWHEN ('$dora_report') = '2023' THEN\n\t\t\t\tCASE \n\t\t\t\t WHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\t WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\t WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\t\tWHEN change_failure_rate <= .05 THEN \"0-5%(elite)\"\n\t\t\t\t\tWHEN change_failure_rate <= .10 THEN \"5%-10%(high)\"\n\t\t\t\t\tWHEN change_failure_rate <= .15 THEN \"10%-15%(medium)\"\n\t\t\t\t\tWHEN change_failure_rate > .15 THEN \"> 15%(low)\"\n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\t\tEND\n\t\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\t\tCASE \n\t\t\t\t\tWHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\t WHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\t WHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\t\tWHEN change_failure_rate <= .15 THEN \"0-15%(elite)\"\n\t\t\t\t\tWHEN change_failure_rate <= .20 THEN \"16%-20%(high)\"\n\t\t\t\t\tWHEN change_failure_rate <= .30 THEN \"21%-30%(medium)\"\n\t\t\t\t\tWHEN change_failure_rate > .30 THEN \"> 30%(low)\" \n\t\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\t\tEND\n\t\t\tELSE 'Invalid dora report'\n\t\tEND AS value\n\tFROM \n\t\t_change_failure_rate, _is_collected_data\n),\n\n-- ***** 2023 report ***** --\n-- Metric 4: Failed deployment recovery time\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_incident_deployment_relationships pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n_recovery_time_ranks as (\n SELECT *, percent_rank() over(order by TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as ranks\n FROM _incidents_for_deployments\n),\n\n_median_recovery_time as (\n SELECT max(TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as median_recovery_time\n FROM _recovery_time_ranks\n WHERE ranks <= 0.5\n),\n\n_metric_recovery_time_2023_report as(\n\tSELECT \n\t\"Failed deployment recovery time\" as metric,\n\tCASE\n\t\tWHEN ('$dora_report') = '2023' THEN\n\t\tCASE\n\t\t\tWHEN median_recovery_time < 60 THEN \"Less than one hour(elite)\"\n\t\t\tWHEN median_recovery_time < 24 * 60 THEN \"Less than one day(high)\"\n\t\t\tWHEN median_recovery_time < 7 * 24 * 60 THEN \"Between one day and one week(medium)\"\n\t\t\tWHEN median_recovery_time >= 7 * 24 * 60 THEN \"More than one week(low)\"\n\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\tEND\n\tEND AS median_recovery_time\n\tFROM \n\t_median_recovery_time\n),\n\n-- ***** 2021 report ***** --\n-- Metric 4: Median time to restore service \n_incidents as (\n-- get the incidents created within the selected time period in the top-right corner\n\tSELECT\n\t distinct i.id,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\t join user_accounts ua on i.assignee_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 teams t on tu.team_id = t.id\n\tWHERE\n\t t.name in (${team})\n\t\tand i.type = 'INCIDENT'\n\t\tand $__timeFilter(i.resolution_date)\n),\n\n_median_mttr_ranks as(\n\tSELECT *, percent_rank() over(order by lead_time_minutes) as ranks\n\tFROM _incidents\n),\n\n_median_mttr as(\n\tSELECT max(lead_time_minutes) as median_time_to_resolve\n\tFROM _median_mttr_ranks\n\tWHERE ranks <= 0.5\n),\n\n_metric_mttr_2021_report as(\n\tSELECT \n\t\"Time to restore service\" as metric,\n\tCASE\n\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\tCASE\n\t\t\t\tWHEN median_time_to_resolve < 60 THEN \"Less than one hour(elite)\"\n\t\t\t\tWHEN median_time_to_resolve < 24 * 60 THEN \"Less than one day(high)\"\n\t\t\t\tWHEN median_time_to_resolve < 7 * 24 * 60 THEN \"Between one day and one week(medium)\"\n\t\t\t\tWHEN median_time_to_resolve >= 7 * 24 * 60 THEN \"More than one week(low)\"\n\t\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\t\tEND\n\tEND AS median_time_to_resolve\n\tFROM \n\t\t_median_mttr\n),\n\n_metric_mrt_or_mm as(\n\tSELECT \n\tmetric,\n\tmedian_recovery_time AS value\n\tFROM \n\t_metric_recovery_time_2023_report\n\tWHERE \n\t('$dora_report') = '2023'\n\tUNION\n\tSELECT \n\tmetric,\n\tmedian_time_to_resolve AS value\n\tFROM \n\t_metric_mttr_2021_report\n\tWHERE \n\t('$dora_report') = '2021'\n),\n\n_final_results as (\t\n\tSELECT distinct db.id,db.metric,db.low,db.medium,db.high,db.elite,m1.metric as _metric, m1.value FROM dora_benchmarks db\n\tleft join _metric_deployment_frequency m1 on db.metric = m1.metric\n\tWHERE m1.metric is not null and db.dora_report = ('$dora_report')\n\t\n\tunion \n\t\n\tSELECT distinct db.id,db.metric,db.low,db.medium,db.high,db.elite,m2.metric as _metric, m2.value FROM dora_benchmarks db\n\tleft join _metric_change_lead_time m2 on db.metric = m2.metric\n\tWHERE m2.metric is not null and db.dora_report = ('$dora_report')\n\t\n\tunion \n\t\n\tSELECT distinct db.id,db.metric,db.low,db.medium,db.high,db.elite,m3.metric as _metric, m3.value FROM dora_benchmarks db\n\tleft join _metric_cfr m3 on db.metric = m3.metric\n\tWHERE m3.metric is not null and db.dora_report = ('$dora_report')\n\t\n\tunion \n\t\n\tSELECT distinct db.id,db.metric,db.low,db.medium,db.high,db.elite,m4.metric as _metric, m4.value FROM dora_benchmarks db\n\tleft join _metric_mrt_or_mm m4 on db.metric = m4.metric\n\tWHERE m4.metric is not null and db.dora_report = ('$dora_report')\n)\n\n\nSELECT \n\tmetric,\n\tcase when low = value then low else null end as low,\n\tcase when medium = value then medium else null end as medium,\n\tcase when high = value then high else null end as high,\n\tcase when elite = value then elite else null end as elite\nFROM _final_results\nORDER BY id", "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 commits c on cdc.commit_sha = c.sha\n\t 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\n join teams t on tu.team_id = t.id\n\t\tJOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tt.name in (${team})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_issue_metrics pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n),\n\n_change_failure_rate as (\n\tSELECT \n\t\tcase \n\t\t\twhen count(deployment_id) is null then null\n\t\t\telse sum(has_incident)/count(deployment_id) end as change_failure_rate\n\tFROM\n\t\t_failure_caused_by_deployments\n),\n\n_is_collected_data as(\n\tSELECT\n CASE \n WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All'\n WHEN COUNT(i.id) = 0 THEN 'No Incidents' \n WHEN COUNT(cdc.id) = 0 THEN 'No Deployments'\n END AS is_collected\nFROM\n (SELECT 1) AS dummy\nLEFT JOIN\n issues i ON i.type = 'INCIDENT'\nLEFT JOIN\n cicd_deployment_commits cdc ON 1=1\n)\n\nSELECT\n CASE\n WHEN ('$dora_report') = '2023' THEN\n\t\t\tCASE \n\t\t\t\tWHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tWHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\tWHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\tWHEN change_failure_rate <= .05 THEN CONCAT(round(change_failure_rate*100,1), \"%(elite)\")\n\t\t\t\tWHEN change_failure_rate <= .10 THEN CONCAT(round(change_failure_rate*100,1), \"%(high)\")\n\t\t\t\tWHEN change_failure_rate <= .15 THEN CONCAT(round(change_failure_rate*100,1), \"%(medium)\")\n\t\t\t\tWHEN change_failure_rate > .15 THEN CONCAT(round(change_failure_rate*100,1), \"%(low)\")\n\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tEND\n\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\tCASE \n\t\t\t WHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tWHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\tWHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\tWHEN change_failure_rate <= .15 THEN CONCAT(round(change_failure_rate*100,1), \"%(elite)\")\n\t\t\t\tWHEN change_failure_rate <= .20 THEN CONCAT(round(change_failure_rate*100,1), \"%(high)\")\n\t\t\t\tWHEN change_failure_rate <= .30 THEN CONCAT(round(change_failure_rate*100,1), \"%(medium)\")\n\t\t\t\tWHEN change_failure_rate > .30 THEN CONCAT(round(change_failure_rate*100,1), \"%(low)\") \n\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tEND\n\t\tELSE 'Invalid dora report'\n\tEND AS change_failure_rate\nFROM \n\t_change_failure_rate, _is_collected_data", + "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 commits c on cdc.commit_sha = c.sha\n\t 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\n join teams t on tu.team_id = t.id\n\t\tJOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tt.name in (${team})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_incident_deployment_relationships pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n),\n\n_change_failure_rate as (\n\tSELECT \n\t\tcase \n\t\t\twhen count(deployment_id) is null then null\n\t\t\telse sum(has_incident)/count(deployment_id) end as change_failure_rate\n\tFROM\n\t\t_failure_caused_by_deployments\n),\n\n_is_collected_data as(\n\tSELECT\n CASE \n WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All'\n WHEN COUNT(i.id) = 0 THEN 'No Incidents' \n WHEN COUNT(cdc.id) = 0 THEN 'No Deployments'\n END AS is_collected\nFROM\n (SELECT 1) AS dummy\nLEFT JOIN\n issues i ON i.type = 'INCIDENT'\nLEFT JOIN\n cicd_deployment_commits cdc ON 1=1\n)\n\nSELECT\n CASE\n WHEN ('$dora_report') = '2023' THEN\n\t\t\tCASE \n\t\t\t\tWHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tWHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\tWHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\tWHEN change_failure_rate <= .05 THEN CONCAT(round(change_failure_rate*100,1), \"%(elite)\")\n\t\t\t\tWHEN change_failure_rate <= .10 THEN CONCAT(round(change_failure_rate*100,1), \"%(high)\")\n\t\t\t\tWHEN change_failure_rate <= .15 THEN CONCAT(round(change_failure_rate*100,1), \"%(medium)\")\n\t\t\t\tWHEN change_failure_rate > .15 THEN CONCAT(round(change_failure_rate*100,1), \"%(low)\")\n\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tEND\n\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\tCASE \n\t\t\t WHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tWHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\tWHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\tWHEN change_failure_rate <= .15 THEN CONCAT(round(change_failure_rate*100,1), \"%(elite)\")\n\t\t\t\tWHEN change_failure_rate <= .20 THEN CONCAT(round(change_failure_rate*100,1), \"%(high)\")\n\t\t\t\tWHEN change_failure_rate <= .30 THEN CONCAT(round(change_failure_rate*100,1), \"%(medium)\")\n\t\t\t\tWHEN change_failure_rate > .30 THEN CONCAT(round(change_failure_rate*100,1), \"%(low)\") \n\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tEND\n\t\tELSE 'Invalid dora report'\n\tEND AS change_failure_rate\nFROM \n\t_change_failure_rate, _is_collected_data", "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\n join teams t on tu.team_id = t.id\n JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n WHERE\n\t\tt.name in (${team})\n and cdc.result = 'SUCCESS'\n and cdc.environment = 'PRODUCTION'\n GROUP BY 1\n HAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_issue_metrics pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n_recovery_time_ranks as (\n SELECT *, percent_rank() over(order by TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as ranks\n FROM _incidents_for_deployments\n),\n\n_median_recovery_time as (\n SELECT max(TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as median_recovery_time\n FROM _recovery_time_ranks\n WHERE ranks <= 0.5\n),\n\n_metric_recovery_time_2023_report as(\n\tSELECT \n\tCASE\n\t\tWHEN ('$dora_report') = '2023' THEN\n\t\tCASE\n\t\t\tWHEN median_recovery_time < 60 THEN CONCAT(round(median_recovery_time/60,1), \"(elite)\")\n\t\t\tWHEN median_recovery_time < 24 * 60 THEN CONCAT(round(median_recovery_time/60,1), \"(high)\")\n\t\t\tWHEN median_recovery_time < 7 * 24 * 60 THEN CONCAT(round(median_recovery_time/60,1), \"(medium)\")\n\t\t\tWHEN median_recovery_time >= 7 * 24 * 60 THEN CONCAT(round(median_recovery_time/60,1), \"(low)\")\n\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\tEND\n\tEND AS median_recovery_time\n\tFROM \n\t_median_recovery_time\n),\n\n-- ***** 2021 report ***** --\n-- Metric 4: Median time to restore service \n_incidents as (\n-- get the incidents created within the selected time period in the top-right corner\n\tSELECT\n\t distinct i.id,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\t join user_accounts ua on i.assignee_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 teams t on tu.team_id = t.id\n\tWHERE\n\t t.name in (${team})\n\t\tand i.type = 'INCIDENT'\n\t\tand $__timeFilter(i.resolution_date)\n),\n\n_median_mttr_ranks as(\n\tSELECT *, percent_rank() over(order by lead_time_minutes) as ranks\n\tFROM _incidents\n),\n\n_median_mttr as(\n\tSELECT max(lead_time_minutes) as median_time_to_resolve\n\tFROM _median_mttr_ranks\n\tWHERE ranks <= 0.5\n),\n\n_metric_mttr_2021_report as(\n\tSELECT \n\tCASE\n\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\tCASE\n\t\t\t\tWHEN median_time_to_resolve < 60 THEN CONCAT(round(median_time_to_resolve/60,1), \"(elite)\")\n\t\t\t\tWHEN median_time_to_resolve < 24 * 60 THEN CONCAT(round(median_time_to_resolve/60,1), \"(high)\")\n\t\t\t\tWHEN median_time_to_resolve < 7 * 24 * 60 THEN CONCAT(round(median_time_to_resolve/60,1), \"(medium)\")\n\t\t\t\tWHEN median_time_to_resolve >= 7 * 24 * 60 THEN CONCAT(round(median_time_to_resolve/60,1), \"(low)\")\n\t\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\t\tEND\n\tEND AS median_time_to_resolve\n\tFROM \n\t\t_median_mttr\n)\n\nSELECT \n median_recovery_time AS median_time_in_hour\nFROM \n _metric_recovery_time_2023_report\nWHERE \n ('$dora_report') = '2023'\nUNION\nSELECT \n median_time_to_resolve AS median_time_to_resolve\nFROM \n _metric_mttr_2021_report\nWHERE \n ('$dora_report') = '2021'\n", + "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\n join teams t on tu.team_id = t.id\n JOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n WHERE\n\t\tt.name in (${team})\n and cdc.result = 'SUCCESS'\n and cdc.environment = 'PRODUCTION'\n GROUP BY 1\n HAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_incident_deployment_relationships pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n_recovery_time_ranks as (\n SELECT *, percent_rank() over(order by TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as ranks\n FROM _incidents_for_deployments\n),\n\n_median_recovery_time as (\n SELECT max(TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as median_recovery_time\n FROM _recovery_time_ranks\n WHERE ranks <= 0.5\n),\n\n_metric_recovery_time_2023_report as(\n\tSELECT \n\tCASE\n\t\tWHEN ('$dora_report') = '2023' THEN\n\t\tCASE\n\t\t\tWHEN median_recovery_time < 60 THEN CONCAT(round(median_recovery_time/60,1), \"(elite)\")\n\t\t\tWHEN median_recovery_time < 24 * 60 THEN CONCAT(round(median_recovery_time/60,1), \"(high)\")\n\t\t\tWHEN median_recovery_time < 7 * 24 * 60 THEN CONCAT(round(median_recovery_time/60,1), \"(medium)\")\n\t\t\tWHEN median_recovery_time >= 7 * 24 * 60 THEN CONCAT(round(median_recovery_time/60,1), \"(low)\")\n\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\tEND\n\tEND AS median_recovery_time\n\tFROM \n\t_median_recovery_time\n),\n\n-- ***** 2021 report ***** --\n-- Metric 4: Median time to restore service \n_incidents as (\n-- get the incidents created within the selected time period in the top-right corner\n\tSELECT\n\t distinct i.id,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\t join user_accounts ua on i.assignee_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 teams t on tu.team_id = t.id\n\tWHERE\n\t t.name in (${team})\n\t\tand i.type = 'INCIDENT'\n\t\tand $__timeFilter(i.resolution_date)\n),\n\n_median_mttr_ranks as(\n\tSELECT *, percent_rank() over(order by lead_time_minutes) as ranks\n\tFROM _incidents\n),\n\n_median_mttr as(\n\tSELECT max(lead_time_minutes) as median_time_to_resolve\n\tFROM _median_mttr_ranks\n\tWHERE ranks <= 0.5\n),\n\n_metric_mttr_2021_report as(\n\tSELECT \n\tCASE\n\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\tCASE\n\t\t\t\tWHEN median_time_to_resolve < 60 THEN CONCAT(round(median_time_to_resolve/60,1), \"(elite)\")\n\t\t\t\tWHEN median_time_to_resolve < 24 * 60 THEN CONCAT(round(median_time_to_resolve/60,1), \"(high)\")\n\t\t\t\tWHEN median_time_to_resolve < 7 * 24 * 60 THEN CONCAT(round(median_time_to_resolve/60,1), \"(medium)\")\n\t\t\t\tWHEN median_time_to_resolve >= 7 * 24 * 60 THEN CONCAT(round(median_time_to_resolve/60,1), \"(low)\")\n\t\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\t\tEND\n\tEND AS median_time_to_resolve\n\tFROM \n\t\t_median_mttr\n)\n\nSELECT \n median_recovery_time AS median_time_in_hour\nFROM \n _metric_recovery_time_2023_report\nWHERE \n ('$dora_report') = '2023'\nUNION\nSELECT \n median_time_to_resolve AS median_time_to_resolve\nFROM \n _metric_mttr_2021_report\nWHERE \n ('$dora_report') = '2021'\n", "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\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 teams t on tu.team_id = t.id\n\tJOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tt.name in (${team})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_issue_metrics pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n),\n\n_change_failure_rate_for_each_month as (\n\tSELECT \n\t\tdate_format(deployment_finished_date,'%y/%m') as month,\n\t\tcase \n\t\t\twhen count(deployment_id) is null then null\n\t\t\telse sum(has_incident)/count(deployment_id) end as change_failure_rate\n\tFROM\n\t\t_failure_caused_by_deployments\n\tGROUP BY 1\n)\n\nSELECT \n\tcm.month,\n\tcfr.change_failure_rate as 'Change Failure Rate'\nFROM \n\tcalendar_months cm\n\tleft join _change_failure_rate_for_each_month cfr on cm.month = cfr.month\nWHERE $__timeFilter(month_timestamp) ", + "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\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 teams t on tu.team_id = t.id\n\tJOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tt.name in (${team})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_incident_deployment_relationships pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n),\n\n_change_failure_rate_for_each_month as (\n\tSELECT \n\t\tdate_format(deployment_finished_date,'%y/%m') as month,\n\t\tcase \n\t\t\twhen count(deployment_id) is null then null\n\t\t\telse sum(has_incident)/count(deployment_id) end as change_failure_rate\n\tFROM\n\t\t_failure_caused_by_deployments\n\tGROUP BY 1\n)\n\nSELECT \n\tcm.month,\n\tcfr.change_failure_rate as 'Change Failure Rate'\nFROM \n\tcalendar_months cm\n\tleft join _change_failure_rate_for_each_month cfr on cm.month = cfr.month\nWHERE $__timeFilter(month_timestamp) ", "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 teams t on tu.team_id = t.id\n\tJOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tt.name in (${team})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_issue_metrics pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n_recovery_time_ranks as (\n SELECT *, percent_rank() over(PARTITION BY deployment_finished_month order by TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as ranks\n FROM _incidents_for_deployments\n),\n\n_median_recovery_time as (\n SELECT deployment_finished_month, max(TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as median_recovery_time\n FROM _recovery_time_ranks\n WHERE ranks <= 0.5\n GROUP BY deployment_finished_month\n),\n\n_metric_recovery_time_2023_report as (\n SELECT \n cm.month,\n case \n when m.median_recovery_time is null then 0 \n else m.median_recovery_time/60 \n end as median_recovery_time_in_hour\n FROM \n calendar_months cm\n LEFT JOIN _median_recovery_time m on cm.month = m.deployment_finished_month\n WHERE $__timeFilter(cm.month_timestamp)\n),\n\n\n-- ***** 2021 report ***** --\n-- Metric 4: median time to restore service - MTTR\n_incidents as (\n-- get the number of incidents created each month\n\tSELECT\n\t distinct i.id,\n\t\tdate_format(i.resolution_date,'%y/%m') as month,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\t join user_accounts ua on i.assignee_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 teams t on tu.team_id = t.id\n\tWHERE\n\t t.name in (${team})\n\t\tand i.type = 'INCIDENT'\n\t\tand i.lead_time_minutes is not null\n),\n\n_find_median_mttr_each_month_ranks as(\n\tSELECT *, percent_rank() over(PARTITION BY month order by lead_time_minutes) as ranks\n\tFROM _incidents\n),\n\n_mttr as(\n\tSELECT month, max(lead_time_minutes) as median_time_to_resolve\n\tFROM _find_median_mttr_each_month_ranks\n\tWHERE ranks <= 0.5\n\tGROUP BY month\n),\n\n_metric_mttr_2021_report as (\n SELECT \n cm.month,\n case \n when m.median_time_to_resolve is null then 0 \n else m.median_time_to_resolve/60 end as median_time_to_resolve_in_hour\n FROM \n calendar_months cm\n LEFT JOIN _mttr m on cm.month = m.month\n WHERE $__timeFilter(cm.month_timestamp)\n)\n\nSELECT \n cm.month,\n CASE \n WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour\n WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour\n END AS '${title_value} In Hours'\nFROM \n calendar_months cm\n LEFT JOIN _metric_recovery_time_2023_report mrt ON cm.month = mrt.month\n LEFT JOIN _metric_mttr_2021_report mm ON cm.month = mm.month\nWHERE \n $__timeFilter(cm.month_timestamp)", + "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 teams t on tu.team_id = t.id\n\tJOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tt.name in (${team})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_incident_deployment_relationships pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n_recovery_time_ranks as (\n SELECT *, percent_rank() over(PARTITION BY deployment_finished_month order by TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as ranks\n FROM _incidents_for_deployments\n),\n\n_median_recovery_time as (\n SELECT deployment_finished_month, max(TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as median_recovery_time\n FROM _recovery_time_ranks\n WHERE ranks <= 0.5\n GROUP BY deployment_finished_month\n),\n\n_metric_recovery_time_2023_report as (\n SELECT \n cm.month,\n case \n when m.median_recovery_time is null then 0 \n else m.median_recovery_time/60 \n end as median_recovery_time_in_hour\n FROM \n calendar_months cm\n LEFT JOIN _median_recovery_time m on cm.month = m.deployment_finished_month\n WHERE $__timeFilter(cm.month_timestamp)\n),\n\n\n-- ***** 2021 report ***** --\n-- Metric 4: median time to restore service - MTTR\n_incidents as (\n-- get the number of incidents created each month\n\tSELECT\n\t distinct i.id,\n\t\tdate_format(i.resolution_date,'%y/%m') as month,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\t join user_accounts ua on i.assignee_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 teams t on tu.team_id = t.id\n\tWHERE\n\t t.name in (${team})\n\t\tand i.type = 'INCIDENT'\n\t\tand i.lead_time_minutes is not null\n),\n\n_find_median_mttr_each_month_ranks as(\n\tSELECT *, percent_rank() over(PARTITION BY month order by lead_time_minutes) as ranks\n\tFROM _incidents\n),\n\n_mttr as(\n\tSELECT month, max(lead_time_minutes) as median_time_to_resolve\n\tFROM _find_median_mttr_each_month_ranks\n\tWHERE ranks <= 0.5\n\tGROUP BY month\n),\n\n_metric_mttr_2021_report as (\n SELECT \n cm.month,\n case \n when m.median_time_to_resolve is null then 0 \n else m.median_time_to_resolve/60 end as median_time_to_resolve_in_hour\n FROM \n calendar_months cm\n LEFT JOIN _mttr m on cm.month = m.month\n WHERE $__timeFilter(cm.month_timestamp)\n)\n\nSELECT \n cm.month,\n CASE \n WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour\n WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour\n END AS '${title_value} In Hours'\nFROM \n calendar_months cm\n LEFT JOIN _metric_recovery_time_2023_report mrt ON cm.month = mrt.month\n LEFT JOIN _metric_mttr_2021_report mm ON cm.month = mm.month\nWHERE \n $__timeFilter(cm.month_timestamp)", "refId": "A", "sql": { "columns": [ diff --git a/grafana/dashboards/DORADebug.json b/grafana/dashboards/DORADebug.json index ddc6f4caf86..2b3408b01fd 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.environment = 'PRODUCTION'\n GROUP BY 1\n HAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_issue_metrics pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n_recovery_time_ranks as (\n SELECT *, percent_rank() over(order by TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as ranks\n FROM _incidents_for_deployments\n),\n\n_median_recovery_time as (\n SELECT max(TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as median_recovery_time\n FROM _recovery_time_ranks\n WHERE ranks <= 0.5\n),\n\n_metric_recovery_time_2023_report as(\n\tSELECT \n\tCASE\n\t\tWHEN ('$dora_report') = '2023' THEN\n\t\tCASE\n\t\t\tWHEN median_recovery_time < 60 THEN \"Less than one hour(elite)\"\n\t\t\tWHEN median_recovery_time < 24 * 60 THEN \"Less than one day(high)\"\n\t\t\tWHEN median_recovery_time < 7 * 24 * 60 THEN \"Between one day and one week(medium)\"\n\t\t\tWHEN median_recovery_time >= 7 * 24 * 60 THEN \"More than one week(low)\"\n\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\tEND\n\tEND AS median_recovery_time\n\tFROM \n\t_median_recovery_time\n),\n\n-- ***** 2021 report ***** --\n-- Metric 4: Median time to restore service \n_incidents as (\n-- get the incidents created within the selected time period in the top-right corner\n\tSELECT\n\t distinct i.id,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\tWHERE\n\t pm.project_name in (${project})\n\t\tand i.type = 'INCIDENT'\n\t\tand $__timeFilter(i.created_date)\n),\n\n_median_mttr_ranks as(\n\tSELECT *, percent_rank() over(order by lead_time_minutes) as ranks\n\tFROM _incidents\n),\n\n_median_mttr as(\n\tSELECT max(lead_time_minutes) as median_time_to_resolve\n\tFROM _median_mttr_ranks\n\tWHERE ranks <= 0.5\n),\n\n_metric_mttr_2021_report as(\n\tSELECT \n\tCASE\n\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\tCASE\n\t\t\t\tWHEN median_time_to_resolve < 60 THEN \"Less than one hour(elite)\"\n\t\t\t\tWHEN median_time_to_resolve < 24 * 60 THEN \"Less than one day(high)\"\n\t\t\t\tWHEN median_time_to_resolve < 7 * 24 * 60 THEN \"Between one day and one week(medium)\"\n\t\t\t\tWHEN median_time_to_resolve >= 7 * 24 * 60 THEN \"More than one week(low)\"\n\t\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\t\tEND\n\tEND AS median_time_to_resolve\n\tFROM \n\t\t_median_mttr\n)\n\nSELECT \n median_recovery_time AS median_time_in_hour\nFROM \n _metric_recovery_time_2023_report\nWHERE \n ('$dora_report') = '2023'\nUNION\nSELECT \n median_time_to_resolve AS median_time_to_resolve\nFROM \n _metric_mttr_2021_report\nWHERE \n ('$dora_report') = '2021'\n", + "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.environment = 'PRODUCTION'\n GROUP BY 1\n HAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_incident_deployment_relationships pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n_recovery_time_ranks as (\n SELECT *, percent_rank() over(order by TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as ranks\n FROM _incidents_for_deployments\n),\n\n_median_recovery_time as (\n SELECT max(TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as median_recovery_time\n FROM _recovery_time_ranks\n WHERE ranks <= 0.5\n),\n\n_metric_recovery_time_2023_report as(\n\tSELECT \n\tCASE\n\t\tWHEN ('$dora_report') = '2023' THEN\n\t\tCASE\n\t\t\tWHEN median_recovery_time < 60 THEN \"Less than one hour(elite)\"\n\t\t\tWHEN median_recovery_time < 24 * 60 THEN \"Less than one day(high)\"\n\t\t\tWHEN median_recovery_time < 7 * 24 * 60 THEN \"Between one day and one week(medium)\"\n\t\t\tWHEN median_recovery_time >= 7 * 24 * 60 THEN \"More than one week(low)\"\n\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\tEND\n\tEND AS median_recovery_time\n\tFROM \n\t_median_recovery_time\n),\n\n-- ***** 2021 report ***** --\n-- Metric 4: Median time to restore service \n_incidents as (\n-- get the incidents created within the selected time period in the top-right corner\n\tSELECT\n\t distinct i.id,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\tWHERE\n\t pm.project_name in (${project})\n\t\tand i.type = 'INCIDENT'\n\t\tand $__timeFilter(i.created_date)\n),\n\n_median_mttr_ranks as(\n\tSELECT *, percent_rank() over(order by lead_time_minutes) as ranks\n\tFROM _incidents\n),\n\n_median_mttr as(\n\tSELECT max(lead_time_minutes) as median_time_to_resolve\n\tFROM _median_mttr_ranks\n\tWHERE ranks <= 0.5\n),\n\n_metric_mttr_2021_report as(\n\tSELECT \n\tCASE\n\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\tCASE\n\t\t\t\tWHEN median_time_to_resolve < 60 THEN \"Less than one hour(elite)\"\n\t\t\t\tWHEN median_time_to_resolve < 24 * 60 THEN \"Less than one day(high)\"\n\t\t\t\tWHEN median_time_to_resolve < 7 * 24 * 60 THEN \"Between one day and one week(medium)\"\n\t\t\t\tWHEN median_time_to_resolve >= 7 * 24 * 60 THEN \"More than one week(low)\"\n\t\t\t\tELSE \"N/A. Please check if you have collected incidents.\"\n\t\t\tEND\n\tEND AS median_time_to_resolve\n\tFROM \n\t\t_median_mttr\n)\n\nSELECT \n median_recovery_time AS median_time_in_hour\nFROM \n _metric_recovery_time_2023_report\nWHERE \n ('$dora_report') = '2023'\nUNION\nSELECT \n median_time_to_resolve AS median_time_to_resolve\nFROM \n _metric_mttr_2021_report\nWHERE \n ('$dora_report') = '2021'\n", "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.environment = 'PRODUCTION'\n GROUP BY 1\n HAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_issue_metrics pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n_recovery_time_ranks as (\n SELECT *, percent_rank() over(PARTITION BY deployment_finished_month order by TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as ranks\n FROM _incidents_for_deployments\n),\n\n_median_recovery_time as (\n SELECT deployment_finished_month, max(TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as median_recovery_time\n FROM _recovery_time_ranks\n WHERE ranks <= 0.5\n GROUP BY deployment_finished_month\n),\n\n_metric_recovery_time_2023_report as (\n SELECT \n cm.month,\n case \n when m.median_recovery_time is null then 0 \n else m.median_recovery_time/60 \n end as median_recovery_time_in_hour\n FROM \n calendar_months cm\n LEFT JOIN _median_recovery_time m on cm.month = m.deployment_finished_month\n WHERE $__timeFilter(cm.month_timestamp)\n),\n\n\n-- ***** 2021 report ***** --\n-- Metric 4: median time to restore service - MTTR\n_incidents as (\n-- get the number of incidents created each month\n\tSELECT\n\t distinct i.id,\n\t\tdate_format(i.created_date,'%y/%m') as month,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\tWHERE\n\t pm.project_name in (${project})\n\t\tand i.type = 'INCIDENT'\n\t\tand i.lead_time_minutes is not null\n),\n\n_find_median_mttr_each_month_ranks as(\n\tSELECT *, percent_rank() over(PARTITION BY month order by lead_time_minutes) as ranks\n\tFROM _incidents\n),\n\n_mttr as(\n\tSELECT month, max(lead_time_minutes) as median_time_to_resolve\n\tFROM _find_median_mttr_each_month_ranks\n\tWHERE ranks <= 0.5\n\tGROUP BY month\n),\n\n_metric_mttr_2021_report as (\n SELECT \n cm.month,\n case \n when m.median_time_to_resolve is null then 0 \n else m.median_time_to_resolve/60 end as median_time_to_resolve_in_hour\n FROM \n calendar_months cm\n LEFT JOIN _mttr m on cm.month = m.month\n WHERE $__timeFilter(cm.month_timestamp)\n)\n\nSELECT \n cm.month,\n CASE \n WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour\n WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour\n END AS '${title_value} In Hours'\nFROM \n calendar_months cm\n LEFT JOIN _metric_recovery_time_2023_report mrt ON cm.month = mrt.month\n LEFT JOIN _metric_mttr_2021_report mm ON cm.month = mm.month\nWHERE \n $__timeFilter(cm.month_timestamp)", + "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.environment = 'PRODUCTION'\n GROUP BY 1\n HAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_incident_deployment_relationships pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n_recovery_time_ranks as (\n SELECT *, percent_rank() over(PARTITION BY deployment_finished_month order by TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as ranks\n FROM _incidents_for_deployments\n),\n\n_median_recovery_time as (\n SELECT deployment_finished_month, max(TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as median_recovery_time\n FROM _recovery_time_ranks\n WHERE ranks <= 0.5\n GROUP BY deployment_finished_month\n),\n\n_metric_recovery_time_2023_report as (\n SELECT \n cm.month,\n case \n when m.median_recovery_time is null then 0 \n else m.median_recovery_time/60 \n end as median_recovery_time_in_hour\n FROM \n calendar_months cm\n LEFT JOIN _median_recovery_time m on cm.month = m.deployment_finished_month\n WHERE $__timeFilter(cm.month_timestamp)\n),\n\n\n-- ***** 2021 report ***** --\n-- Metric 4: median time to restore service - MTTR\n_incidents as (\n-- get the number of incidents created each month\n\tSELECT\n\t distinct i.id,\n\t\tdate_format(i.created_date,'%y/%m') as month,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\tWHERE\n\t pm.project_name in (${project})\n\t\tand i.type = 'INCIDENT'\n\t\tand i.lead_time_minutes is not null\n),\n\n_find_median_mttr_each_month_ranks as(\n\tSELECT *, percent_rank() over(PARTITION BY month order by lead_time_minutes) as ranks\n\tFROM _incidents\n),\n\n_mttr as(\n\tSELECT month, max(lead_time_minutes) as median_time_to_resolve\n\tFROM _find_median_mttr_each_month_ranks\n\tWHERE ranks <= 0.5\n\tGROUP BY month\n),\n\n_metric_mttr_2021_report as (\n SELECT \n cm.month,\n case \n when m.median_time_to_resolve is null then 0 \n else m.median_time_to_resolve/60 end as median_time_to_resolve_in_hour\n FROM \n calendar_months cm\n LEFT JOIN _mttr m on cm.month = m.month\n WHERE $__timeFilter(cm.month_timestamp)\n)\n\nSELECT \n cm.month,\n CASE \n WHEN '${dora_report}' = '2023' THEN mrt.median_recovery_time_in_hour\n WHEN '${dora_report}' = '2021' THEN mm.median_time_to_resolve_in_hour\n END AS '${title_value} In Hours'\nFROM \n calendar_months cm\n LEFT JOIN _metric_recovery_time_2023_report mrt ON cm.month = mrt.month\n LEFT JOIN _metric_mttr_2021_report mm ON cm.month = mm.month\nWHERE \n $__timeFilter(cm.month_timestamp)", "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 project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tpm.project_name in (${project})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_issue_metrics pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n),\n\n_change_failure_rate as (\n\tSELECT \n\t\tcase \n\t\t\twhen count(deployment_id) is null then null\n\t\t\telse sum(has_incident)/count(deployment_id) end as change_failure_rate\n\tFROM\n\t\t_failure_caused_by_deployments\n),\n\n_is_collected_data as(\n\tSELECT\n CASE \n WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All'\n WHEN COUNT(i.id) = 0 THEN 'No Incidents' \n WHEN COUNT(cdc.id) = 0 THEN 'No Deployments'\n END AS is_collected\nFROM\n (SELECT 1) AS dummy\nLEFT JOIN\n issues i ON i.type = 'INCIDENT'\nLEFT JOIN\n cicd_deployment_commits cdc ON 1=1\n)\n\n\nSELECT\n CASE\n WHEN ('$dora_report') = '2023' THEN\n\t\t\tCASE \n\t\t\t\tWHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tWHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\tWHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\tWHEN change_failure_rate <= .05 THEN \"0-5%(elite)\"\n\t\t\t\tWHEN change_failure_rate <= .10 THEN \"5%-10%(high)\"\n\t\t\t\tWHEN change_failure_rate <= .15 THEN \"10%-15%(medium)\"\n\t\t\t\tWHEN change_failure_rate > .15 THEN \"> 15%(low)\"\n\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tEND\n\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\tCASE \n\t\t\t WHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tWHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\tWHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\tWHEN change_failure_rate <= .15 THEN \"0-15%(elite)\"\n\t\t\t\tWHEN change_failure_rate <= .20 THEN \"16%-20%(high)\"\n\t\t\t\tWHEN change_failure_rate <= .30 THEN \"21%-30%(medium)\"\n\t\t\t\tWHEN change_failure_rate > .30 THEN \"> 30%(low)\" \n\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tEND\n\t\tELSE 'Invalid dora report'\n\tEND AS change_failure_rate\nFROM \n\t_change_failure_rate, _is_collected_data", + "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 project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tpm.project_name in (${project})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_incident_deployment_relationships pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n),\n\n_change_failure_rate as (\n\tSELECT \n\t\tcase \n\t\t\twhen count(deployment_id) is null then null\n\t\t\telse sum(has_incident)/count(deployment_id) end as change_failure_rate\n\tFROM\n\t\t_failure_caused_by_deployments\n),\n\n_is_collected_data as(\n\tSELECT\n CASE \n WHEN COUNT(i.id) = 0 AND COUNT(cdc.id) = 0 THEN 'No All'\n WHEN COUNT(i.id) = 0 THEN 'No Incidents' \n WHEN COUNT(cdc.id) = 0 THEN 'No Deployments'\n END AS is_collected\nFROM\n (SELECT 1) AS dummy\nLEFT JOIN\n issues i ON i.type = 'INCIDENT'\nLEFT JOIN\n cicd_deployment_commits cdc ON 1=1\n)\n\n\nSELECT\n CASE\n WHEN ('$dora_report') = '2023' THEN\n\t\t\tCASE \n\t\t\t\tWHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tWHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\tWHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\tWHEN change_failure_rate <= .05 THEN \"0-5%(elite)\"\n\t\t\t\tWHEN change_failure_rate <= .10 THEN \"5%-10%(high)\"\n\t\t\t\tWHEN change_failure_rate <= .15 THEN \"10%-15%(medium)\"\n\t\t\t\tWHEN change_failure_rate > .15 THEN \"> 15%(low)\"\n\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tEND\n\t\tWHEN ('$dora_report') = '2021' THEN\n\t\t\tCASE \n\t\t\t WHEN is_collected = \"No All\" THEN \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tWHEN is_collected = \"No Incidents\" THEN \"N/A. Please check if you have collected incidents.\"\n\t\t\t\tWHEN is_collected = \"No Deployments\" THEN \"N/A. Please check if you have collected deployments.\"\n\t\t\t\tWHEN change_failure_rate <= .15 THEN \"0-15%(elite)\"\n\t\t\t\tWHEN change_failure_rate <= .20 THEN \"16%-20%(high)\"\n\t\t\t\tWHEN change_failure_rate <= .30 THEN \"21%-30%(medium)\"\n\t\t\t\tWHEN change_failure_rate > .30 THEN \"> 30%(low)\" \n\t\t\t\tELSE \"N/A. Please check if you have collected deployments/incidents.\"\n\t\t\t\tEND\n\t\tELSE 'Invalid dora report'\n\tEND AS change_failure_rate\nFROM \n\t_change_failure_rate, _is_collected_data", "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 pm.project_name in ($project)\n and $__timeFilter(cdc.finished_date)\norder by 2 desc\n ", + "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_scopes'\nwhere \n pm.project_name in ($project)\n and $__timeFilter(cdc.finished_date)\norder by 2 desc\n ", "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\tJOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tpm.project_name in (${project})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_issue_metrics pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n),\n\n_change_failure_rate_for_each_month as (\n\tSELECT \n\t\tdate_format(deployment_finished_date,'%y/%m') as month,\n\t\tcase \n\t\t\twhen count(deployment_id) is null then null\n\t\t\telse sum(has_incident)/count(deployment_id) end as change_failure_rate\n\tFROM\n\t\t_failure_caused_by_deployments\n\tGROUP BY 1\n)\n\nSELECT \n\tcm.month,\n\tcfr.change_failure_rate as 'Change Failure Rate'\nFROM \n\tcalendar_months cm\n\tLEFT JOIN _change_failure_rate_for_each_month cfr on cm.month = cfr.month\n\tWHERE $__timeFilter(cm.month_timestamp)", + "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\tJOIN project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tpm.project_name in (${project})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_incident_deployment_relationships pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n),\n\n_change_failure_rate_for_each_month as (\n\tSELECT \n\t\tdate_format(deployment_finished_date,'%y/%m') as month,\n\t\tcase \n\t\t\twhen count(deployment_id) is null then null\n\t\t\telse sum(has_incident)/count(deployment_id) end as change_failure_rate\n\tFROM\n\t\t_failure_caused_by_deployments\n\tGROUP BY 1\n)\n\nSELECT \n\tcm.month,\n\tcfr.change_failure_rate as 'Change Failure Rate'\nFROM \n\tcalendar_months cm\n\tLEFT JOIN _change_failure_rate_for_each_month cfr on cm.month = cfr.month\n\tWHERE $__timeFilter(cm.month_timestamp)", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/DORADetails-ChangeFailureRate.json b/grafana/dashboards/DORADetails-ChangeFailureRate.json index 3322f683ac7..e9d165ce7f6 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 project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tpm.project_name in (${project})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_issue_metrics pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n)\n\nSELECT \n\tsum(has_incident)/count(deployment_id) as \"change_failure_rate\"\nFROM\n\t_failure_caused_by_deployments\n", + "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 project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tpm.project_name in (${project})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_incident_deployment_relationships pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n)\n\nSELECT \n\tsum(has_incident)/count(deployment_id) as \"change_failure_rate\"\nFROM\n\t_failure_caused_by_deployments\n", "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 project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tpm.project_name in (${project})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id as \"deployment_id\",\n\t\td.deployment_finished_date,\n\t\tpim.id as incident_id,\n\t\ti.title,\n\t\ti.url,\n\t\ti.url as \"metric_hidden\",\n\t\ti.created_date\n\t\t-- i.resolution_date,\n\tFROM\n\t\t_deployments d\n\t\tleft join project_issue_metrics pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tWHERE pim.id is not null\n\tORDER BY 2\n)\n\nselect * \nFROM _failure_caused_by_deployments\n", + "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 project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tpm.project_name in (${project})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id as \"deployment_id\",\n\t\td.deployment_finished_date,\n\t\tpim.id as incident_id,\n\t\ti.title,\n\t\ti.url,\n\t\ti.url as \"metric_hidden\",\n\t\ti.created_date\n\t\t-- i.resolution_date,\n\tFROM\n\t\t_deployments d\n\t\tleft join project_incident_deployment_relationships pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tWHERE pim.id is not null\n\tORDER BY 2\n)\n\nselect * \nFROM _failure_caused_by_deployments\n", "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 project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tpm.project_name in (${project})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_issue_metrics pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n)\n\nSELECT \n\t\t\tsum(has_incident) as \"incident count\",\n\t\t\tcount(deployment_id) as \"deployment count\"\nFROM\n\t_failure_caused_by_deployments\n", + "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 project_mapping pm on cdc.cicd_scope_id = pm.row_id and pm.`table` = 'cicd_scopes'\n\tWHERE\n\t\tpm.project_name in (${project})\n\t\tand cdc.result = 'SUCCESS'\n\t\tand cdc.environment = 'PRODUCTION'\n\tGROUP BY 1\n\tHAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_failure_caused_by_deployments as (\n-- calculate the number of incidents caused by each deployment\n\tSELECT\n\t\td.deployment_id,\n\t\td.deployment_finished_date,\n\t\tcount(distinct case when i.type = 'INCIDENT' then d.deployment_id else null end) as has_incident\n\tFROM\n\t\t_deployments d\n\t\tleft join project_incident_deployment_relationships pim on d.deployment_id = pim.deployment_id\n\t\tleft join issues i on pim.id = i.id\n\tGROUP BY 1,2\n)\n\nSELECT \n\t\t\tsum(has_incident) as \"incident count\",\n\t\t\tcount(deployment_id) as \"deployment count\"\nFROM\n\t_failure_caused_by_deployments\n", "refId": "A", "select": [ [ diff --git a/grafana/dashboards/DORADetails-FailedDeploymentRecoveryTime.json b/grafana/dashboards/DORADetails-FailedDeploymentRecoveryTime.json index 056e40c3deb..edc06606da1 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.environment = 'PRODUCTION'\n GROUP BY 1\n HAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_issue_metrics pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n_recovery_time_ranks as (\n SELECT *, percent_rank() over(order by TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as ranks\n FROM _incidents_for_deployments\n),\n\n_median_recovery_time as (\n SELECT max(TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as median_recovery_time\n FROM _recovery_time_ranks\n WHERE ranks <= 0.5\n),\n\n-- ***** 2021 report ***** --\n-- Metric 4: Median time to restore service \n_incidents as (\n-- get the incidents created within the selected time period in the top-right corner\n\tSELECT\n\t distinct i.id,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\tWHERE\n\t pm.project_name in (${project})\n\t\tand i.type = 'INCIDENT'\n\t\tand $__timeFilter(i.created_date)\n),\n\n_median_mttr_ranks as(\n\tSELECT *, percent_rank() over(order by lead_time_minutes) as ranks\n\tFROM _incidents\n),\n\n_median_mttr as(\n\tSELECT max(lead_time_minutes) as median_time_to_resolve\n\tFROM _median_mttr_ranks\n\tWHERE ranks <= 0.5\n)\n\nSELECT \n median_recovery_time/60 AS median_recovery_time_in_hours\nFROM \n _median_recovery_time\n", + "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.environment = 'PRODUCTION'\n GROUP BY 1\n HAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_incident_deployment_relationships pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n_recovery_time_ranks as (\n SELECT *, percent_rank() over(order by TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as ranks\n FROM _incidents_for_deployments\n),\n\n_median_recovery_time as (\n SELECT max(TIMESTAMPDIFF(MINUTE, deployment_finished_date, incident_resolution_date)) as median_recovery_time\n FROM _recovery_time_ranks\n WHERE ranks <= 0.5\n),\n\n-- ***** 2021 report ***** --\n-- Metric 4: Median time to restore service \n_incidents as (\n-- get the incidents created within the selected time period in the top-right corner\n\tSELECT\n\t distinct i.id,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\tWHERE\n\t pm.project_name in (${project})\n\t\tand i.type = 'INCIDENT'\n\t\tand $__timeFilter(i.created_date)\n),\n\n_median_mttr_ranks as(\n\tSELECT *, percent_rank() over(order by lead_time_minutes) as ranks\n\tFROM _incidents\n),\n\n_median_mttr as(\n\tSELECT max(lead_time_minutes) as median_time_to_resolve\n\tFROM _median_mttr_ranks\n\tWHERE ranks <= 0.5\n)\n\nSELECT \n median_recovery_time/60 AS median_recovery_time_in_hours\nFROM \n _median_recovery_time\n", "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.environment = 'PRODUCTION'\n GROUP BY 1\n HAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_incidents_for_deployments as (\n SELECT\n\t\tfd.deployment_id as \"deployment_id\",\n fd.deployment_finished_date,\n -- date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month,\n i.id as incident_caused_by_deployment,\n i.title as incident_title,\n i.url as incident_url,\n i.url as \"metric_hidden\",\n -- i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n\t\tTIMESTAMPDIFF(HOUR, fd.deployment_finished_date, i.resolution_date) as \"failed_deployment_recovery_time\"\n FROM\n issues i\n left join project_issue_metrics pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n)\n\nselect \n * \nFROM \n _incidents_for_deployments\nwhere incident_resolution_date IS NOT NULL", + "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.environment = 'PRODUCTION'\n GROUP BY 1\n HAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_incidents_for_deployments as (\n SELECT\n\t\tfd.deployment_id as \"deployment_id\",\n fd.deployment_finished_date,\n -- date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month,\n i.id as incident_caused_by_deployment,\n i.title as incident_title,\n i.url as incident_url,\n i.url as \"metric_hidden\",\n -- i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n\t\tTIMESTAMPDIFF(HOUR, fd.deployment_finished_date, i.resolution_date) as \"failed_deployment_recovery_time\"\n FROM\n issues i\n left join project_incident_deployment_relationships pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n)\n\nselect \n * \nFROM \n _incidents_for_deployments\nwhere incident_resolution_date IS NOT NULL", "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.environment = 'PRODUCTION'\n GROUP BY 1\n HAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_issue_metrics pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n-- ***** 2021 report ***** --\n-- Metric 4: Median time to restore service \n_incidents as (\n-- get the incidents created within the selected time period in the top-right corner\n\tSELECT\n\t distinct i.id,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\tWHERE\n\t pm.project_name in (${project})\n\t\tand i.type = 'INCIDENT'\n\t\tand $__timeFilter(i.created_date)\n)\n\n\nSELECT\n COUNT(incident_id) AS total_count\nFROM\n _incidents_for_deployments\n\n\n\n", + "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.environment = 'PRODUCTION'\n GROUP BY 1\n HAVING $__timeFilter(max(cdc.finished_date))\n),\n\n_incidents_for_deployments as (\n SELECT\n i.id as incident_id,\n i.created_date as incident_create_date,\n i.resolution_date as incident_resolution_date,\n fd.deployment_id as caused_by_deployment,\n fd.deployment_finished_date,\n date_format(fd.deployment_finished_date,'%y/%m') as deployment_finished_month\n FROM\n issues i\n left join project_incident_deployment_relationships pim on i.id = pim.id\n join _deployments fd on pim.deployment_id = fd.deployment_id\n WHERE\n i.type = 'INCIDENT'\n and $__timeFilter(i.resolution_date)\n),\n\n-- ***** 2021 report ***** --\n-- Metric 4: Median time to restore service \n_incidents as (\n-- get the incidents created within the selected time period in the top-right corner\n\tSELECT\n\t distinct i.id,\n\t\tcast(lead_time_minutes as signed) as lead_time_minutes\n\tFROM\n\t\tissues i\n\t join board_issues bi on i.id = bi.issue_id\n\t join boards b on bi.board_id = b.id\n\t join project_mapping pm on b.id = pm.row_id and pm.`table` = 'boards'\n\tWHERE\n\t pm.project_name in (${project})\n\t\tand i.type = 'INCIDENT'\n\t\tand $__timeFilter(i.created_date)\n)\n\n\nSELECT\n COUNT(incident_id) AS total_count\nFROM\n _incidents_for_deployments\n\n\n\n", "refId": "D", "sql": { "columns": [