Skip to content

Commit a94b391

Browse files
Merge branch 'kubecon-2025' into feat/finops-open-api-v1
2 parents be63a99 + cd15792 commit a94b391

11 files changed

Lines changed: 1113 additions & 210 deletions

File tree

.github/workflows/pr-issue-validator.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ on:
1212
- 'release-**'
1313
- 'develop'
1414
- 'hotfix-**'
15+
- 'kubecon-**'
1516
# paths-ignore:
1617
# - 'docs/**'
1718
# - '.github/'
@@ -42,4 +43,4 @@ jobs:
4243
run: |
4344
wget https://raw.githubusercontent.com/devtron-labs/utilities/feat/central-pr-validator/.github/workflows/validateIssue.sh
4445
chmod +x validateIssue.sh
45-
./validateIssue.sh
46+
./validateIssue.sh

common-lib/utils/TimeUtils.go

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
/*
2+
* Copyright (c) 2024. Devtron Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package utils
218

319
import (
@@ -9,7 +25,7 @@ import (
925
type TimeRangeRequest struct {
1026
From *time.Time `json:"from" schema:"from"`
1127
To *time.Time `json:"to" schema:"to"`
12-
TimeWindow *TimeWindows `json:"timeWindow" schema:"timeWindow" validate:"omitempty,oneof=today yesterday week month quarter lastWeek lastMonth"`
28+
TimeWindow *TimeWindows `json:"timeWindow" schema:"timeWindow" validate:"omitempty,oneof=today yesterday week month quarter lastWeek lastMonth lastQuarter last7Days last30Days last90Days"`
1329
}
1430

1531
func NewTimeRangeRequest(from *time.Time, to *time.Time) *TimeRangeRequest {
@@ -34,14 +50,18 @@ func (timeRange TimeWindows) String() string {
3450

3551
// Define constants for different time windows
3652
const (
37-
Today TimeWindows = "today"
38-
Yesterday TimeWindows = "yesterday"
39-
Week TimeWindows = "week"
40-
Month TimeWindows = "month"
41-
Quarter TimeWindows = "quarter"
42-
LastWeek TimeWindows = "lastWeek"
43-
LastMonth TimeWindows = "lastMonth"
44-
Year TimeWindows = "year"
53+
Today TimeWindows = "today"
54+
Yesterday TimeWindows = "yesterday"
55+
Week TimeWindows = "week"
56+
Month TimeWindows = "month"
57+
Quarter TimeWindows = "quarter"
58+
LastWeek TimeWindows = "lastWeek"
59+
LastMonth TimeWindows = "lastMonth"
60+
Year TimeWindows = "year"
61+
LastQuarter TimeWindows = "lastQuarter"
62+
Last7Days TimeWindows = "last7Days"
63+
Last30Days TimeWindows = "last30Days"
64+
Last90Days TimeWindows = "last90Days"
4565
)
4666

4767
func (timeRange *TimeRangeRequest) ParseAndValidateTimeRange() (*TimeRangeRequest, error) {
@@ -89,9 +109,49 @@ func (timeRange *TimeRangeRequest) ParseAndValidateTimeRange() (*TimeRangeReques
89109
lastMonthStart := thisMonthStart.AddDate(0, -1, 0)
90110
lastMonthEnd := thisMonthStart.Add(-time.Second)
91111
return NewTimeRangeRequest(&lastMonthStart, &lastMonthEnd), nil
112+
case LastQuarter:
113+
// Calculate current quarter
114+
currentQuarter := ((int(now.Month()) - 1) / 3) + 1
115+
116+
// Calculate previous quarter
117+
var prevQuarter int
118+
var prevYear int
119+
if currentQuarter == 1 {
120+
// If current quarter is Q1, previous quarter is Q4 of previous year
121+
prevQuarter = 4
122+
prevYear = now.Year() - 1
123+
} else {
124+
// Otherwise, previous quarter is in the same year
125+
prevQuarter = currentQuarter - 1
126+
prevYear = now.Year()
127+
}
128+
129+
// Calculate start and end of previous quarter
130+
prevQuarterStartMonth := time.Month((prevQuarter-1)*3 + 1)
131+
prevQuarterStart := time.Date(prevYear, prevQuarterStartMonth, 1, 0, 0, 0, 0, now.Location())
132+
133+
// End of previous quarter is the start of current quarter minus 1 second
134+
currentQuarterStartMonth := time.Month((currentQuarter-1)*3 + 1)
135+
currentQuarterStart := time.Date(now.Year(), currentQuarterStartMonth, 1, 0, 0, 0, 0, now.Location())
136+
if currentQuarter == 1 {
137+
// If current quarter is Q1, we need to calculate Q4 end of previous year
138+
currentQuarterStart = time.Date(now.Year(), time.January, 1, 0, 0, 0, 0, now.Location())
139+
}
140+
prevQuarterEnd := currentQuarterStart.Add(-time.Second)
141+
142+
return NewTimeRangeRequest(&prevQuarterStart, &prevQuarterEnd), nil
92143
case Year:
93144
start := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location())
94145
return NewTimeRangeRequest(&start, &now), nil
146+
case Last7Days:
147+
start := now.AddDate(0, 0, -7)
148+
return NewTimeRangeRequest(&start, &now), nil
149+
case Last30Days:
150+
start := now.AddDate(0, 0, -30)
151+
return NewTimeRangeRequest(&start, &now), nil
152+
case Last90Days:
153+
start := now.AddDate(0, 0, -90)
154+
return NewTimeRangeRequest(&start, &now), nil
95155
default:
96156
return NewTimeRangeRequest(&time.Time{}, &time.Time{}), fmt.Errorf("unsupported time window: %q", *timeRange.TimeWindow)
97157
}

lens/api/RestHandler.go

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@ package api
1818

1919
import (
2020
"encoding/json"
21-
"github.com/devtron-labs/lens/pkg"
22-
"go.uber.org/zap"
21+
"fmt"
2322
"net/http"
2423
"strconv"
24+
25+
"github.com/devtron-labs/lens/internal/dto"
26+
"github.com/devtron-labs/lens/pkg"
27+
"go.uber.org/zap"
2528
)
2629

2730
type RestHandler interface {
2831
GetDeploymentMetrics(w http.ResponseWriter, r *http.Request)
32+
GetBulkDeploymentMetrics(w http.ResponseWriter, r *http.Request)
2933
ProcessDeploymentEvent(w http.ResponseWriter, r *http.Request)
3034
ResetApplication(w http.ResponseWriter, r *http.Request)
3135
}
@@ -104,7 +108,7 @@ func (impl *RestHandlerImpl) GetDeploymentMetrics(w http.ResponseWriter, r *http
104108
//decoder := json.NewDecoder(r.Body)
105109
v := r.URL.Query()
106110
impl.logger.Infow("metrics request", "req", v)
107-
metricRequest := &pkg.MetricRequest{}
111+
metricRequest := &dto.MetricRequest{}
108112
if v.Get("env_id") != "" {
109113
envId, err := strconv.Atoi(v.Get("env_id"))
110114
if err != nil {
@@ -141,6 +145,35 @@ func (impl *RestHandlerImpl) GetDeploymentMetrics(w http.ResponseWriter, r *http
141145
impl.writeJsonResp(w, err, metrics, 200)
142146
}
143147

148+
func (impl *RestHandlerImpl) GetBulkDeploymentMetrics(w http.ResponseWriter, r *http.Request) {
149+
decoder := json.NewDecoder(r.Body)
150+
bulkRequest := &dto.BulkMetricRequest{}
151+
152+
err := decoder.Decode(bulkRequest)
153+
if err != nil {
154+
impl.logger.Error("error decoding bulk request", "err", err)
155+
impl.writeJsonResp(w, err, nil, http.StatusBadRequest)
156+
return
157+
}
158+
159+
impl.logger.Infow("bulk metrics request", "req", bulkRequest)
160+
161+
// Validate request
162+
if len(bulkRequest.AppEnvPairs) == 0 {
163+
impl.writeJsonResp(w, fmt.Errorf("app_env_pairs cannot be empty"), nil, http.StatusBadRequest)
164+
return
165+
}
166+
167+
if bulkRequest.From == nil || bulkRequest.To == nil {
168+
impl.writeJsonResp(w, fmt.Errorf("from and to dates are required"), nil, http.StatusBadRequest)
169+
return
170+
}
171+
172+
bulkMetrics, err := impl.deploymentMetricService.ProcessBulkDoraMetrics(bulkRequest)
173+
impl.logger.Infof("bulk metrics response: %+v", bulkMetrics)
174+
impl.writeJsonResp(w, err, bulkMetrics, 200)
175+
}
176+
144177
func (impl *RestHandlerImpl) ProcessDeploymentEvent(w http.ResponseWriter, r *http.Request) {
145178
decoder := json.NewDecoder(r.Body)
146179
deploymentEvent := &pkg.DeploymentEvent{}

lens/api/Router.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ package api
1818

1919
import (
2020
"encoding/json"
21+
"net/http"
22+
2123
"github.com/gorilla/mux"
2224
"github.com/prometheus/client_golang/prometheus/promhttp"
2325
"go.uber.org/zap"
24-
"net/http"
2526
)
2627

2728
type MuxRouter struct {
@@ -54,6 +55,7 @@ func (r MuxRouter) Init() {
5455
r.Router.Path("/deployment-metrics").HandlerFunc(r.restHandler.GetDeploymentMetrics).
5556
Queries("app_id", "{app_id}", "env_id", "{env_id}", "from", "{from}", "to", "{to}").
5657
Methods("GET", "OPTIONS")
58+
r.Router.Path("/deployment-metrics/bulk").HandlerFunc(r.restHandler.GetBulkDeploymentMetrics).Methods("GET", "OPTIONS")
5759
r.Router.Path("/new-deployment-event").HandlerFunc(r.restHandler.ProcessDeploymentEvent).Methods("POST")
5860
r.Router.Path("/reset-app-environment").HandlerFunc(r.restHandler.ResetApplication).Methods("POST")
5961

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright (c) 2024. Devtron Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package dto
18+
19+
import (
20+
"time"
21+
)
22+
23+
type Metrics struct {
24+
Series []*Metric `json:"series"`
25+
AverageCycleTime float64 `json:"average_cycle_time"`
26+
AverageLeadTime float64 `json:"average_lead_time"`
27+
ChangeFailureRate float64 `json:"change_failure_rate"`
28+
AverageRecoveryTime float64 `json:"average_recovery_time"`
29+
AverageDeploymentSize float32 `json:"average_deployment_size"`
30+
AverageLineAdded float32 `json:"average_line_added"`
31+
AverageLineDeleted float32 `json:"average_line_deleted"`
32+
LastFailedTime string `json:"last_failed_time"`
33+
RecoveryTimeLastFailed float64 `json:"recovery_time_last_failed"`
34+
}
35+
36+
type Metric struct {
37+
ReleaseType ReleaseType `json:"release_type"`
38+
ReleaseStatus ReleaseStatus `json:"release_status"`
39+
ReleaseTime time.Time `json:"release_time"`
40+
ChangeSizeLineAdded int `json:"change_size_line_added"`
41+
ChangeSizeLineDeleted int `json:"change_size_line_deleted"`
42+
DeploymentSize int `json:"deployment_size"`
43+
CommitHash string `json:"commit_hash"`
44+
CommitTime time.Time `json:"commit_time"`
45+
LeadTime float64 `json:"lead_time"`
46+
CycleTime float64 `json:"cycle_time"`
47+
RecoveryTime float64 `json:"recovery_time"`
48+
}
49+
50+
type MetricRequest struct {
51+
AppId int `json:"app_id"`
52+
EnvId int `json:"env_id"`
53+
From string `json:"from"`
54+
To string `json:"to"`
55+
}
56+
57+
type AppEnvPair struct {
58+
AppId int `json:"appId"`
59+
EnvId int `json:"envId"`
60+
}
61+
62+
type BulkMetricRequest struct {
63+
AppEnvPairs []AppEnvPair `json:"appEnvPairs"`
64+
From *time.Time `json:"from"`
65+
To *time.Time `json:"to"`
66+
}
67+
68+
type AppEnvMetrics struct {
69+
AppId int `json:"appId"`
70+
EnvId int `json:"envId"`
71+
Metrics *Metrics `json:"metrics"`
72+
Error string `json:"error,omitempty"`
73+
}
74+
75+
type BulkMetricsResponse struct {
76+
Results []AppEnvMetrics `json:"results"`
77+
}
78+
79+
// ----------------
80+
type ReleaseType int
81+
82+
const (
83+
Unknown ReleaseType = iota
84+
RollForward
85+
RollBack
86+
Patch
87+
)
88+
89+
func (releaseType ReleaseType) String() string {
90+
return [...]string{"Unknown", "RollForward", "RollBack", "Patch"}[releaseType]
91+
}
92+
93+
// --------------
94+
type ReleaseStatus int
95+
96+
const (
97+
Success ReleaseStatus = iota
98+
Failure
99+
)
100+
101+
func (releaseStatus ReleaseStatus) String() string {
102+
return [...]string{"Success", "Failure"}[releaseStatus]
103+
}
104+
105+
// ------
106+
type ProcessStage int
107+
108+
const (
109+
Init ProcessStage = iota
110+
ReleaseTypeDetermined
111+
LeadTimeFetch
112+
)
113+
114+
func (ProcessStage ProcessStage) String() string {
115+
return [...]string{"Init", "ReleaseTypeDetermined", "LeadTimeFetch"}[ProcessStage]
116+
}

0 commit comments

Comments
 (0)