diff --git a/dora_metrics.go b/dora_metrics.go new file mode 100644 index 000000000..769171d7a --- /dev/null +++ b/dora_metrics.go @@ -0,0 +1,96 @@ +package gitlab + +import ( + "fmt" + "net/http" +) + +// DORAMetric represents a single DORA metric data point. +// +// Gitlab API docs: https://docs.gitlab.com/ee/api/dora/metrics.html +type DORAMetric struct { + Date string `json:"date"` + Value float64 `json:"value"` +} + +// Gets a string representation of a DORAMetric data point +// +// GitLab API docs: https://docs.gitlab.com/ee/api/dora/metrics.html +func (m DORAMetric) String() string { + return Stringify(m) +} + +// GetDORAMetricsOptions represent the request body options for getting +// DORA metrics +// +// GitLab API docs: https://docs.gitlab.com/ee/api/dora/metrics.html +type GetDORAMetricsOptions struct { + Metric DORAMetricType `url:"metric,omitempty" json:"metric,omitempty"` + EndDate *ISOTime `url:"end_date,omitempty" json:"end_date,omitempty"` + EnvironmentTiers *[]string `url:"environment_tiers,omitempty" del:"," json:"environment_tiers,omitempty"` + Interval *DORAMetricInterval `url:"interval,omitempty" json:"interval,omitempty"` + StartDate *ISOTime `url:"start_date,omitempty" json:"start_date,omitempty"` + + // Deprecated, use environment tiers instead + EnvironmentTier *string `url:"environment_tier,omitempty" json:"environment_tier,omitempty"` +} + +// DORAMetricsService handles communication with the DORA metrics related methods +// of the GitLab API +// +// Gitlab API docs: https://docs.gitlab.com/ee/api/dora/metrics.html +type DORAMetricsService struct { + client *Client +} + +// GetProjectDORAMetrics gets the DORA metrics for a project. +// +// GitLab API Docs: +// https://docs.gitlab.com/ee/api/dora/metrics.html#get-project-level-dora-metrics +func (s *DORAMetricsService) GetProjectDORAMetrics(pid interface{}, opt GetDORAMetricsOptions, options ...RequestOptionFunc) ([]DORAMetric, *Response, error) { + project, err := parseID(pid) + if err != nil { + return nil, nil, err + } + + u := fmt.Sprintf("projects/%s/dora/metrics", PathEscape(project)) + + req, err := s.client.NewRequest(http.MethodGet, u, opt, options) + if err != nil { + return nil, nil, err + } + + var metrics []DORAMetric + resp, err := s.client.Do(req, &metrics) + if err != nil { + return nil, resp, err + } + + return metrics, resp, err +} + +// GetGroupDORAMetrics gets the DORA metrics for a group. +// +// GitLab API Docs: +// https://docs.gitlab.com/ee/api/dora/metrics.html#get-group-level-dora-metrics +func (s *DORAMetricsService) GetGroupDORAMetrics(gid interface{}, opt GetDORAMetricsOptions, options ...RequestOptionFunc) ([]DORAMetric, *Response, error) { + group, err := parseID(gid) + if err != nil { + return nil, nil, err + } + + u := fmt.Sprintf("groups/%s/dora/metrics", PathEscape(group)) + + req, err := s.client.NewRequest(http.MethodGet, u, opt, options) + if err != nil { + return nil, nil, err + } + + var metrics []DORAMetric + resp, err := s.client.Do(req, &metrics) + if err != nil { + return nil, resp, err + } + + return metrics, resp, err +} diff --git a/dora_metrics_test.go b/dora_metrics_test.go new file mode 100644 index 000000000..3e7414b6a --- /dev/null +++ b/dora_metrics_test.go @@ -0,0 +1,118 @@ +package gitlab + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestDORAMetrics_GetProjectDORAMetrics(t *testing.T) { + mux, client := setup(t) + + mux.HandleFunc("/api/v4/projects/1/dora/metrics", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + query := r.URL.Query() + for k, v := range map[string]string{ + "metric": "deployment_frequency", + "start_date": "2021-03-01", + "end_date": "2021-03-08", + } { + if query.Get(k) != v { + t.Errorf("Query parameter %s: %s, want %s", k, query.Get(k), v) + } + } + + fmt.Fprint(w, ` + [ + { "date": "2021-03-01", "value": 3 }, + { "date": "2021-03-02", "value": 6 }, + { "date": "2021-03-03", "value": 0 }, + { "date": "2021-03-04", "value": 0 }, + { "date": "2021-03-05", "value": 0 }, + { "date": "2021-03-06", "value": 0 }, + { "date": "2021-03-07", "value": 0 }, + { "date": "2021-03-08", "value": 4 } + ] + `) + }) + + want := []DORAMetric{ + {Date: "2021-03-01", Value: 3}, + {Date: "2021-03-02", Value: 6}, + {Date: "2021-03-03", Value: 0}, + {Date: "2021-03-04", Value: 0}, + {Date: "2021-03-05", Value: 0}, + {Date: "2021-03-06", Value: 0}, + {Date: "2021-03-07", Value: 0}, + {Date: "2021-03-08", Value: 4}, + } + + startDate := ISOTime(time.Date(2021, time.March, 1, 0, 0, 0, 0, time.UTC)) + endDate := ISOTime(time.Date(2021, time.March, 8, 0, 0, 0, 0, time.UTC)) + + d, resp, err := client.DORAMetrics.GetProjectDORAMetrics(1, GetDORAMetricsOptions{ + Metric: DORAMetricDeploymentFrequency, + StartDate: &startDate, + EndDate: &endDate, + }) + require.NoError(t, err) + require.NotNil(t, resp) + require.Equal(t, want, d) +} + +func TestDORAMetrics_GetGroupDORAMetrics(t *testing.T) { + mux, client := setup(t) + + mux.HandleFunc("/api/v4/groups/1/dora/metrics", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + query := r.URL.Query() + for k, v := range map[string]string{ + "metric": "deployment_frequency", + "start_date": "2021-03-01", + "end_date": "2021-03-08", + } { + if query.Get(k) != v { + t.Errorf("Query parameter %s: %s, want %s", k, query.Get(k), v) + } + } + + fmt.Fprint(w, ` + [ + { "date": "2021-03-01", "value": 3 }, + { "date": "2021-03-02", "value": 6 }, + { "date": "2021-03-03", "value": 0 }, + { "date": "2021-03-04", "value": 0 }, + { "date": "2021-03-05", "value": 0 }, + { "date": "2021-03-06", "value": 0 }, + { "date": "2021-03-07", "value": 0 }, + { "date": "2021-03-08", "value": 4 } + ] + `) + }) + + want := []DORAMetric{ + {Date: "2021-03-01", Value: 3}, + {Date: "2021-03-02", Value: 6}, + {Date: "2021-03-03", Value: 0}, + {Date: "2021-03-04", Value: 0}, + {Date: "2021-03-05", Value: 0}, + {Date: "2021-03-06", Value: 0}, + {Date: "2021-03-07", Value: 0}, + {Date: "2021-03-08", Value: 4}, + } + + startDate := ISOTime(time.Date(2021, time.March, 1, 0, 0, 0, 0, time.UTC)) + endDate := ISOTime(time.Date(2021, time.March, 8, 0, 0, 0, 0, time.UTC)) + + d, resp, err := client.DORAMetrics.GetGroupDORAMetrics(1, GetDORAMetricsOptions{ + Metric: DORAMetricDeploymentFrequency, + StartDate: &startDate, + EndDate: &endDate, + }) + require.NoError(t, err) + require.NotNil(t, resp) + require.Equal(t, want, d) +} diff --git a/gitlab.go b/gitlab.go index e49205019..6df108fd8 100644 --- a/gitlab.go +++ b/gitlab.go @@ -124,6 +124,7 @@ type Client struct { Deployments *DeploymentsService Discussions *DiscussionsService DockerfileTemplate *DockerfileTemplatesService + DORAMetrics *DORAMetricsService DraftNotes *DraftNotesService Environments *EnvironmentsService EpicIssues *EpicIssuesService @@ -358,6 +359,7 @@ func newClient(options ...ClientOptionFunc) (*Client, error) { c.Deployments = &DeploymentsService{client: c} c.Discussions = &DiscussionsService{client: c} c.DockerfileTemplate = &DockerfileTemplatesService{client: c} + c.DORAMetrics = &DORAMetricsService{client: c} c.DraftNotes = &DraftNotesService{client: c} c.Environments = &EnvironmentsService{client: c} c.EpicIssues = &EpicIssuesService{client: c} diff --git a/types.go b/types.go index 50e03ed5d..9e477c040 100644 --- a/types.go +++ b/types.go @@ -285,6 +285,36 @@ func DeploymentStatus(v DeploymentStatusValue) *DeploymentStatusValue { return Ptr(v) } +// DORAMetricType represents the name of the four types of metric in DORA +// +// GitLab API docs: https://docs.gitlab.com/ee/api/dora/metrics.html +type DORAMetricType string + +// List of available DORA metric type names +// +// GitLab API docs: https://docs.gitlab.com/ee/api/dora/metrics.html +const ( + DORAMetricDeploymentFrequency DORAMetricType = "deployment_frequency" + DORAMetricLeadTimeForChanges DORAMetricType = "lead_time_for_changes" + DORAMetricTimeToRestoreService DORAMetricType = "time_to_restore_service" + DORAMetricChangeFailureRate DORAMetricType = "change_failure_rate" +) + +// DORAMetricInterval represents the time period over which the +// metrics are aggregated +// +// GitLab API docs: https://docs.gitlab.com/ee/api/dora/metrics.html +type DORAMetricInterval string + +// List of available DORA metric interval types +// +// GitLab API docs: https://docs.gitlab.com/ee/api/dora/metrics.html +const ( + DORAMetricIntervalDaily DORAMetricInterval = "daily" + DORAMetricIntervalMonthly DORAMetricInterval = "monthly" + DORAMetricIntervalAll DORAMetricInterval = "all" +) + // EventTypeValue represents actions type for contribution events type EventTypeValue string