Skip to content

Commit d585b87

Browse files
harjas27Harness
authored andcommitted
[feat]:[IDP-6353][IDP-6355]: Tools for Scorecard and Check stats (#176)
* fix test * fix build * Merge branch 'master' into idp_stats * [fix]: [IDP-6353]: Fix sort for list_checks tool (#201) * handle null timestamp * Apply suggestions from code review * use human readable time * remove scope related changes * remove scope related changes * [feat]:[IDP-6353][IDP-6355]: Tools for Scorecard and Check stats
1 parent 186cc25 commit d585b87

File tree

6 files changed

+663
-7
lines changed

6 files changed

+663
-7
lines changed

client/client.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,30 @@ func (c *Client) Get(
105105
}
106106

107107
addQueryParams(httpReq, params)
108+
return c.doRequest(httpReq, headers, response)
109+
}
110+
111+
func (c *Client) GetWithoutSplittingParamValuesOnComma(
112+
ctx context.Context,
113+
path string,
114+
params map[string]string,
115+
headers map[string]string,
116+
response interface{},
117+
) error {
118+
httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, appendPath(c.BaseURL.String(), path), nil)
119+
if err != nil {
120+
return fmt.Errorf("unable to create new http request : %w", err)
121+
}
122+
123+
addQueryParamsWithoutSplittingValuesOnComma(httpReq, params)
124+
return c.doRequest(httpReq, headers, response)
125+
}
126+
127+
func (c *Client) doRequest(
128+
httpReq *http.Request,
129+
headers map[string]string,
130+
response interface{},
131+
) error {
108132
for key, value := range headers {
109133
httpReq.Header.Add(key, value)
110134
}
@@ -587,6 +611,20 @@ func addQueryParams(req *http.Request, params map[string]string) {
587611
req.URL.RawQuery = q.Encode()
588612
}
589613

614+
func addQueryParamsWithoutSplittingValuesOnComma(req *http.Request, params map[string]string) {
615+
if len(params) == 0 {
616+
return
617+
}
618+
619+
q := req.URL.Query()
620+
621+
for key, value := range params {
622+
q.Add(key, value)
623+
}
624+
625+
req.URL.RawQuery = q.Encode()
626+
}
627+
590628
func addScope(scope dto.Scope, params map[string]string) {
591629
if scope.AccountID == "" {
592630
slog.Error("Account ID is empty in scope")

client/dto/idp.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ const (
1616
ScorecardDetailsWeightageStrategyEQUALWEIGHTS string = "EQUAL_WEIGHTS"
1717
)
1818

19+
const (
20+
ChecksSortOrderAsc string = "ASC"
21+
ChecksSortOrderDesc string = "DESC"
22+
)
23+
24+
const (
25+
ChecksSortTypeName string = "name"
26+
ChecksSortTypeDescription string = "description"
27+
ChecksSortTypeDataSource string = "data_source"
28+
)
29+
1930
type GetEntityParams struct {
2031
BranchName string `form:"branch_name,omitempty" json:"branch_name,omitempty"`
2132
ConnectorRef string `form:"connector_ref,omitempty" json:"connector_ref,omitempty"`
@@ -218,6 +229,109 @@ type ScorecardScore struct {
218229
ScorecardName string `json:"scorecard_name"`
219230
}
220231

232+
// ScorecardStats ScorecardStats
233+
type ScorecardStats struct {
234+
Kind string `json:"kind"`
235+
Name string `json:"name"`
236+
Namespace string `json:"namespace"`
237+
Owner string `json:"owner"`
238+
Score int `json:"score"`
239+
System string `json:"system"`
240+
Type string `json:"type"`
241+
}
242+
243+
// ScorecardStatsResponse ScorecardStatsResponse
244+
type ScorecardStatsResponse struct {
245+
Name string `json:"name"`
246+
Stats []ScorecardStats `json:"stats"`
247+
Timestamp *int64 `json:"timestamp,omitempty"`
248+
}
249+
250+
type ScorecardStatsResponseWithHumanReadableTime struct {
251+
Name string `json:"name"`
252+
Stats []ScorecardStats `json:"stats"`
253+
Time string `json:"time,omitempty"`
254+
}
255+
256+
type GetChecksParams struct {
257+
Page int32
258+
Limit int32
259+
Sort string
260+
SearchTerm string
261+
}
262+
263+
type CheckResponseList = []CheckResponse
264+
265+
type CheckListItem struct {
266+
Custom bool `json:"custom"`
267+
DataSource []string `json:"data_source"`
268+
Description *string `json:"description,omitempty"`
269+
Expression *string `json:"expression,omitempty"`
270+
Identifier string `json:"identifier"`
271+
Name string `json:"name"`
272+
Percentage *float64 `json:"percentage,omitempty"`
273+
Tags *[]string `json:"tags,omitempty"`
274+
}
275+
276+
type CheckResponse struct {
277+
Check *CheckListItem `json:"check,omitempty"`
278+
}
279+
280+
type CheckDetails struct {
281+
Custom bool `json:"custom"`
282+
DefaultBehaviour string `json:"default_behaviour"`
283+
Description *string `json:"description,omitempty"`
284+
Expression *string `json:"expression,omitempty"`
285+
FailMessage *string `json:"fail_message,omitempty"`
286+
HarnessManaged *bool `json:"harness_managed,omitempty"`
287+
Identifier string `json:"identifier"`
288+
Name string `json:"name"`
289+
Percentage *float64 `json:"percentage,omitempty"`
290+
RuleStrategy string `json:"rule_strategy"`
291+
Rules []Rule `json:"rules"`
292+
Tags *[]string `json:"tags,omitempty"`
293+
}
294+
295+
type Rule struct {
296+
DataPointIdentifier string `json:"data_point_identifier"`
297+
DataSourceIdentifier string `json:"data_source_identifier"`
298+
Identifier *string `json:"identifier,omitempty"`
299+
InputValues *[]InputValue `json:"input_values,omitempty"`
300+
Operator string `json:"operator"`
301+
Value *string `json:"value,omitempty"`
302+
}
303+
304+
type InputValue struct {
305+
Key string `json:"key"`
306+
Value string `json:"value"`
307+
}
308+
309+
type CheckDetailsResponse struct {
310+
CheckDetails CheckDetails `json:"check_details"`
311+
}
312+
313+
type CheckStats struct {
314+
Kind string `json:"kind"`
315+
Name string `json:"name"`
316+
Namespace string `json:"namespace"`
317+
Owner string `json:"owner"`
318+
Status string `json:"status"`
319+
System string `json:"system"`
320+
Type string `json:"type"`
321+
}
322+
323+
type CheckStatsResponse struct {
324+
Name string `json:"name"`
325+
Stats []CheckStats `json:"stats"`
326+
Timestamp *int64 `json:"timestamp,omitempty"`
327+
}
328+
329+
type CheckStatsResponseWithHumanReadableTime struct {
330+
Name string `json:"name"`
331+
Stats []CheckStats `json:"stats"`
332+
Time string `json:"time,omitempty"`
333+
}
334+
221335
type ExecuteWorkflowRequest struct {
222336
Identifier string `json:"identifier"`
223337
Values interface{} `json:"values,omitempty"`

client/idp.go

Lines changed: 123 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,17 @@ import (
1111

1212
const (
1313
// Base API paths
14-
idpGetEntityPath = "/v1/entities/%s/%s/%s"
15-
idpListEntitiesPath = "/v1/entities"
16-
idpGetScorecardPath = "/v1/scorecards/%s"
17-
idpListScorecardsPath = "/v1/scorecards"
18-
idpGetScoreSummaryPath = "/v1/scores/summary"
19-
idpGetScoresPath = "/v1/scores"
20-
idpExecuteWorkflowPath = "/v2/workflows/execute"
14+
idpGetEntityPath = "/v1/entities/%s/%s/%s"
15+
idpListEntitiesPath = "/v1/entities"
16+
idpGetScorecardPath = "/v1/scorecards/%s"
17+
idpListScorecardsPath = "/v1/scorecards"
18+
idpGetScoreSummaryPath = "/v1/scores/summary"
19+
idpGetScoresPath = "/v1/scores"
20+
idpGetScorecardStatsPath = "/v1/scorecards/%s/stats"
21+
idpGetCheckPath = "/v1/checks/%s"
22+
idpListChecksPath = "/v1/checks"
23+
idpGetCheckStatsPath = "/v1/checks/%s/stats"
24+
idpExecuteWorkflowPath = "/v2/workflows/execute"
2125

2226
// Default values for requests
2327
defaultKind = "component,api,resource"
@@ -184,6 +188,118 @@ func (i *IDPService) GetScorecardScores(ctx context.Context, scope dto.Scope, id
184188
return response, nil
185189
}
186190

191+
func (i *IDPService) GetScorecardStats(ctx context.Context, scope dto.Scope, scorecardIdentifier string) (*dto.ScorecardStatsResponseWithHumanReadableTime, error) {
192+
path := fmt.Sprintf(idpGetScorecardStatsPath, scorecardIdentifier)
193+
194+
headers := make(map[string]string)
195+
addHarnessAccountToHeaders(scope, headers)
196+
197+
params := make(map[string]string)
198+
199+
response := new(dto.ScorecardStatsResponse)
200+
err := i.Client.Get(ctx, path, params, headers, response)
201+
if err != nil {
202+
return nil, err
203+
}
204+
205+
result := &dto.ScorecardStatsResponseWithHumanReadableTime{
206+
Name: response.Name,
207+
Stats: response.Stats,
208+
}
209+
210+
if response.Timestamp != nil {
211+
result.Time = dto.FormatUnixMillisToRFC3339(*response.Timestamp)
212+
} else {
213+
result.Time = ""
214+
}
215+
216+
return result, nil
217+
}
218+
219+
func (i *IDPService) GetCheck(ctx context.Context, scope dto.Scope, checkIdentifier string, isCustom bool) (*dto.CheckDetailsResponse, error) {
220+
path := fmt.Sprintf(idpGetCheckPath, checkIdentifier)
221+
222+
headers := make(map[string]string)
223+
addHarnessAccountToHeaders(scope, headers)
224+
225+
params := make(map[string]string)
226+
params["custom"] = fmt.Sprintf("%v", isCustom)
227+
228+
response := new(dto.CheckDetailsResponse)
229+
230+
err := i.Client.Get(ctx, path, params, headers, response)
231+
if err != nil {
232+
return nil, err
233+
}
234+
235+
return response, nil
236+
}
237+
238+
func (i *IDPService) ListChecks(ctx context.Context, scope dto.Scope, getChecksParams *dto.GetChecksParams) (*dto.CheckResponseList, error) {
239+
path := idpListChecksPath
240+
241+
headers := make(map[string]string)
242+
addHarnessAccountToHeaders(scope, headers)
243+
244+
params := make(map[string]string)
245+
246+
if getChecksParams.Limit == 0 {
247+
params["limit"] = fmt.Sprintf("%d", defaultLimit)
248+
} else if getChecksParams.Limit > maxLimit {
249+
params["limit"] = fmt.Sprintf("%d", maxLimit)
250+
} else {
251+
params["limit"] = fmt.Sprintf("%d", getChecksParams.Limit)
252+
}
253+
254+
params["page"] = fmt.Sprintf("%d", getChecksParams.Page)
255+
256+
if getChecksParams.Sort != "" {
257+
params["sort"] = getChecksParams.Sort
258+
}
259+
260+
if getChecksParams.SearchTerm != "" {
261+
params["search_term"] = getChecksParams.SearchTerm
262+
}
263+
264+
response := new(dto.CheckResponseList)
265+
266+
err := i.Client.GetWithoutSplittingParamValuesOnComma(ctx, path, params, headers, &response)
267+
if err != nil {
268+
return nil, err
269+
}
270+
271+
return response, nil
272+
}
273+
274+
func (i *IDPService) GetCheckStats(ctx context.Context, scope dto.Scope, checkIdentifier string, isCustom bool) (*dto.CheckStatsResponseWithHumanReadableTime, error) {
275+
path := fmt.Sprintf(idpGetCheckStatsPath, checkIdentifier)
276+
277+
headers := make(map[string]string)
278+
addHarnessAccountToHeaders(scope, headers)
279+
280+
params := make(map[string]string)
281+
params["custom"] = fmt.Sprintf("%v", isCustom)
282+
283+
response := new(dto.CheckStatsResponse)
284+
err := i.Client.Get(ctx, path, params, headers, response)
285+
if err != nil {
286+
return nil, err
287+
}
288+
289+
result := &dto.CheckStatsResponseWithHumanReadableTime{
290+
Name: response.Name,
291+
Stats: response.Stats,
292+
}
293+
294+
if response.Timestamp != nil {
295+
result.Time = dto.FormatUnixMillisToRFC3339(*response.Timestamp)
296+
} else {
297+
result.Time = ""
298+
}
299+
300+
return result, nil
301+
}
302+
187303
func (i *IDPService) ExecuteWorkflow(ctx context.Context, scope dto.Scope, identifier string, inputSet map[string]interface{}) (*dto.ExecuteWorkflowResponse, error) {
188304
path := idpExecuteWorkflowPath
189305

0 commit comments

Comments
 (0)