@@ -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,72 @@ 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+ lastFailedTime := ""
833+ recoveryTimeLastFailed := float64 (0 )
834+ for i := 0 ; i < len (releases ); i ++ {
835+ if releases [i ].ReleaseStatus == dto .Failure {
836+ if lastFailedTime == "" {
837+ lastFailedTime = releases [i ].ReleaseTime .Format (constants .Layout )
838+ }
839+ if i < len (releases )- 1 && releases [i + 1 ].ReleaseStatus == dto .Failure {
840+ continue
841+ }
842+ for j := i - 1 ; j >= 0 ; j -- {
843+ if releases [j ].ReleaseStatus == dto .Success {
844+ releases [i ].RecoveryTime = releases [j ].ReleaseTime .Sub (releases [i ].ReleaseTime ).Minutes ()
845+ if recoveryTimeLastFailed == 0 {
846+ recoveryTimeLastFailed = releases [i ].RecoveryTime
847+ }
848+ break
849+ }
850+ }
851+ }
852+ }
853+
854+ metrics := & dto.Metrics {
855+ Series : releases ,
856+ AverageCycleTime : impl .calculateDeploymentFrequency (releases , fromTime , toTime ),
857+ AverageLeadTime : impl .calculateMeanLeadTimeForChanges (releases ),
858+ ChangeFailureRate : impl .calculateChangeFailureRateNew (releases ),
859+ AverageRecoveryTime : impl .calculateMeanTimeToRecovery (releases ),
860+ LastFailedTime : lastFailedTime ,
861+ RecoveryTimeLastFailed : recoveryTimeLastFailed ,
862+ }
863+
864+ // Calculate change size metrics
865+ if len (metrics .Series ) > 0 {
866+ impl .calculateChangeSize (metrics )
867+ }
868+
869+ return metrics , nil
870+ }
871+
808872// ProcessSingleDoraMetrics processes DORA metrics for a single app-env pair
809- func (impl DeploymentMetricServiceImpl ) ProcessSingleDoraMetrics (request * dto.MetricRequest ) (* DoraMetrics , error ) {
873+ func (impl DeploymentMetricServiceImpl ) ProcessSingleDoraMetrics (request * dto.MetricRequest ) (* dto. Metrics , error ) {
810874 from , to , err := utils2 .ParseDateRange (request .From , request .To )
811875 if err != nil {
812876 return nil , err
@@ -819,10 +883,7 @@ func (impl DeploymentMetricServiceImpl) ProcessSingleDoraMetrics(request *dto.Me
819883 }
820884
821885 if len (releases ) == 0 {
822- return & DoraMetrics {
823- AppId : request .AppId ,
824- EnvId : request .EnvId ,
825- }, nil
886+ return utils2 .CreateEmptyMetrics (), nil
826887 }
827888
828889 var releaseIds []int
@@ -842,66 +903,19 @@ func (impl DeploymentMetricServiceImpl) ProcessSingleDoraMetrics(request *dto.Me
842903 return nil , err
843904 }
844905
845- return impl .CalculateDoraMetrics (request .AppId , request .EnvId , releases , materials , leadTimes , from , to ), nil
846- }
906+ // Get previous release with bounds checking
907+ var lastRelease * sql.AppRelease
908+ if len (releases ) > 0 {
909+ lastId := releases [len (releases )- 1 ].Id
910+ lastRelease , err = impl .appReleaseRepository .GetPreviousRelease (request .AppId , request .EnvId , lastId )
911+ if err != nil && ! utils .IsErrNoRows (err ) {
912+ impl .logger .Errorw ("error getting previous release from db " , "err" , err )
913+ // Don't return error, just continue without previous release
914+ }
915+ if utils .IsErrNoRows (err ) {
916+ lastRelease = nil
917+ }
918+ }
847919
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- */
920+ return impl .populateMetricsWithImprovedLogic (releases , materials , leadTimes , lastRelease , from , to )
921+ }
0 commit comments