@@ -32,7 +32,7 @@ type DeploymentMetricService interface {
3232 GetBulkDeploymentMetrics (request * dto.BulkMetricRequest ) (* dto.BulkMetricsResponse , error )
3333
3434 // New DORA metrics functions
35- ProcessSingleDoraMetrics (request * dto.MetricRequest ) (* DoraMetrics , error )
35+ ProcessSingleDoraMetrics (request * dto.MetricRequest ) (* dto. Metrics , error )
3636 ProcessBulkDoraMetrics (request * dto.BulkMetricRequest ) ([]DoraMetrics , error )
3737 CalculateDoraMetrics (appId , envId int , releases []sql.AppRelease , materials []* sql.PipelineMaterial , leadTimes []sql.LeadTime , fromTime , toTime time.Time ) * DoraMetrics
3838 GetDoraMetricsSummary (doraMetrics * DoraMetrics ) * DoraMetricsSummary
@@ -805,8 +805,78 @@ func (impl DeploymentMetricServiceImpl) ProcessBulkDoraMetrics(request *dto.Bulk
805805 return impl .CalculateDoraMetricsForBulk (request .AppEnvPairs , releasesByAppEnv , allLeadTimes , * request .From , * request .To ), nil
806806}
807807
808+ // calculateCycleTimeBetweenReleases calculates the time between consecutive releases
809+ func (impl DeploymentMetricServiceImpl ) calculateCycleTimeBetweenReleases (releases []* dto.Metric , lastRelease * sql.AppRelease ) {
810+ if len (releases ) == 0 {
811+ return
812+ }
813+
814+ // Calculate cycle time between consecutive releases
815+ for i := 0 ; i < len (releases )- 1 ; i ++ {
816+ releases [i ].CycleTime = releases [i ].ReleaseTime .Sub (releases [i + 1 ].ReleaseTime ).Minutes ()
817+ }
818+
819+ // Handle the last release
820+ if lastRelease != nil {
821+ releases [len (releases )- 1 ].CycleTime = releases [len (releases )- 1 ].ReleaseTime .Sub (lastRelease .TriggerTime ).Minutes ()
822+ } else if len (releases ) > 0 {
823+ releases [len (releases )- 1 ].CycleTime = 0
824+ }
825+ }
826+
827+ // populateMetricsWithImprovedLogic populates dto.Metrics using DORA calculation helper functions
828+ func (impl DeploymentMetricServiceImpl ) populateMetricsWithImprovedLogic (appReleases []sql.AppRelease , materials []* sql.PipelineMaterial , leadTimes []sql.LeadTime , lastRelease * sql.AppRelease , fromTime , toTime time.Time ) (* dto.Metrics , error ) {
829+ releases := impl .transform (appReleases , materials , leadTimes )
830+
831+ impl .calculateCycleTimeBetweenReleases (releases , lastRelease )
832+
833+ deploymentFrequency := impl .calculateDeploymentFrequency (releases , fromTime , toTime )
834+ averageLeadTime := impl .calculateMeanLeadTimeForChanges (releases )
835+ changeFailureRate := impl .calculateChangeFailureRateNew (releases )
836+ averageRecoveryTime := impl .calculateMeanTimeToRecovery (releases )
837+
838+ lastFailedTime := ""
839+ recoveryTimeLastFailed := float64 (0 )
840+ for i := 0 ; i < len (releases ); i ++ {
841+ if releases [i ].ReleaseStatus == dto .Failure {
842+ if lastFailedTime == "" {
843+ lastFailedTime = releases [i ].ReleaseTime .Format (constants .Layout )
844+ }
845+ if i < len (releases )- 1 && releases [i + 1 ].ReleaseStatus == dto .Failure {
846+ continue
847+ }
848+ for j := i - 1 ; j >= 0 ; j -- {
849+ if releases [j ].ReleaseStatus == dto .Success {
850+ releases [i ].RecoveryTime = releases [j ].ReleaseTime .Sub (releases [i ].ReleaseTime ).Minutes ()
851+ if recoveryTimeLastFailed == 0 {
852+ recoveryTimeLastFailed = releases [i ].RecoveryTime
853+ }
854+ break
855+ }
856+ }
857+ }
858+ }
859+
860+ metrics := & dto.Metrics {
861+ Series : releases ,
862+ AverageCycleTime : deploymentFrequency ,
863+ AverageLeadTime : averageLeadTime ,
864+ ChangeFailureRate : changeFailureRate ,
865+ AverageRecoveryTime : averageRecoveryTime ,
866+ LastFailedTime : lastFailedTime ,
867+ RecoveryTimeLastFailed : recoveryTimeLastFailed ,
868+ }
869+
870+ // Calculate change size metrics
871+ if len (metrics .Series ) > 0 {
872+ impl .calculateChangeSize (metrics )
873+ }
874+
875+ return metrics , nil
876+ }
877+
808878// ProcessSingleDoraMetrics processes DORA metrics for a single app-env pair
809- func (impl DeploymentMetricServiceImpl ) ProcessSingleDoraMetrics (request * dto.MetricRequest ) (* DoraMetrics , error ) {
879+ func (impl DeploymentMetricServiceImpl ) ProcessSingleDoraMetrics (request * dto.MetricRequest ) (* dto. Metrics , error ) {
810880 from , to , err := utils2 .ParseDateRange (request .From , request .To )
811881 if err != nil {
812882 return nil , err
@@ -819,10 +889,7 @@ func (impl DeploymentMetricServiceImpl) ProcessSingleDoraMetrics(request *dto.Me
819889 }
820890
821891 if len (releases ) == 0 {
822- return & DoraMetrics {
823- AppId : request .AppId ,
824- EnvId : request .EnvId ,
825- }, nil
892+ return utils2 .CreateEmptyMetrics (), nil
826893 }
827894
828895 var releaseIds []int
@@ -842,66 +909,19 @@ func (impl DeploymentMetricServiceImpl) ProcessSingleDoraMetrics(request *dto.Me
842909 return nil , err
843910 }
844911
845- return impl .CalculateDoraMetrics (request .AppId , request .EnvId , releases , materials , leadTimes , from , to ), nil
846- }
912+ // Get previous release with bounds checking
913+ var lastRelease * sql.AppRelease
914+ if len (releases ) > 0 {
915+ lastId := releases [len (releases )- 1 ].Id
916+ lastRelease , err = impl .appReleaseRepository .GetPreviousRelease (request .AppId , request .EnvId , lastId )
917+ if err != nil && ! utils .IsErrNoRows (err ) {
918+ impl .logger .Errorw ("error getting previous release from db " , "err" , err )
919+ // Don't return error, just continue without previous release
920+ }
921+ if utils .IsErrNoRows (err ) {
922+ lastRelease = nil
923+ }
924+ }
847925
848- /*
849- USAGE EXAMPLES:
850-
851- 1. Calculate DORA metrics for a single app-environment pair (includes materials data):
852- doraMetrics, err := deploymentService.ProcessSingleDoraMetrics(&dto.MetricRequest{
853- AppId: 123,
854- EnvId: 456,
855- From: "2024-01-01T00:00:00Z",
856- To: "2024-01-31T23:59:59Z",
857- })
858-
859- 2. Calculate DORA metrics for multiple app-environment pairs (bulk - optimized without materials):
860- doraMetricsArray, err := deploymentService.ProcessBulkDoraMetrics(&dto.BulkMetricRequest{
861- AppEnvPairs: []dto.AppEnvPair{
862- {AppId: 123, EnvId: 456},
863- {AppId: 789, EnvId: 101},
864- },
865- From: &fromTime,
866- To: &toTime,
867- })
868-
869- 3. Get DORA metrics with performance classification:
870- summary := deploymentService.GetDoraMetricsSummary(doraMetrics)
871-
872- 4. Use within existing getBulkDeploymentMetricsWithBulkQueries function:
873- After fetching all releases and leadTimes (materials not needed for bulk), you can calculate DORA metrics:
874-
875- doraMetricsArray := impl.CalculateDoraMetricsForBulk(request.AppEnvPairs, releasesByAppEnv, allLeadTimes, *request.From, *request.To)
876-
877- // Access DORA metrics for each app-env pair:
878- for _, doraMetrics := range doraMetricsArray {
879- fmt.Printf("App-Env %d-%d: Deployment Frequency: %.2f/day, CFR: %.2f%%, Lead Time: %.2f min, MTTR: %.2f min\n",
880- doraMetrics.AppId, doraMetrics.EnvId, doraMetrics.DeploymentFrequency, doraMetrics.ChangeFailureRate,
881- doraMetrics.MeanLeadTimeForChanges, doraMetrics.MeanTimeToRecovery)
882- }
883-
884- DORA METRICS FORMULAS IMPLEMENTED:
885-
886- 1. Deployment Frequency: Successful Deployments ÷ Time Period (in days)
887- - Measures how often successful deployments occur
888- - Result: deployments per day
889-
890- 2. Change Failure Rate: (Failed Deployments ÷ Total Deployments) × 100
891- - Measures percentage of deployments that cause failures
892- - Result: percentage
893-
894- 3. Mean Lead Time for Changes: Σ(Deployment Time - Commit Time) ÷ Number of Changes
895- - Measures average time from commit to successful deployment
896- - Result: minutes
897-
898- 4. Mean Time to Recovery: Σ(Recovery Time - Failure Time) ÷ Number of Incidents
899- - Measures average time to recover from failed deployments
900- - Result: minutes
901-
902- PERFORMANCE CLASSIFICATION:
903- - Elite: Top performers (e.g., >1 deployment/day, <15% CFR, <1 day lead time, <1 hour MTTR)
904- - High: High performers
905- - Medium: Medium performers
906- - Low: Low performers
907- */
926+ return impl .populateMetricsWithImprovedLogic (releases , materials , leadTimes , lastRelease , from , to )
927+ }
0 commit comments