coder/codersdk/insights.go

284 lines
10 KiB
Go

package codersdk
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/google/uuid"
"golang.org/x/xerrors"
)
// Duplicated in coderd.
const insightsTimeLayout = time.RFC3339
// InsightsReportInterval is the interval of time over which to generate a
// smaller insights report within a time range.
type InsightsReportInterval string
// Days returns the duration of the interval in days.
func (interval InsightsReportInterval) Days() int32 {
switch interval {
case InsightsReportIntervalDay:
return 1
case InsightsReportIntervalWeek:
return 7
default:
panic("developer error: unsupported report interval")
}
}
// InsightsReportInterval enums.
const (
InsightsReportIntervalDay InsightsReportInterval = "day"
InsightsReportIntervalWeek InsightsReportInterval = "week"
)
// TemplateInsightsSection defines the section to be included in the template insights response.
type TemplateInsightsSection string
// TemplateInsightsSection enums.
const (
TemplateInsightsSectionIntervalReports TemplateInsightsSection = "interval_reports"
TemplateInsightsSectionReport TemplateInsightsSection = "report"
)
// UserLatencyInsightsResponse is the response from the user latency insights
// endpoint.
type UserLatencyInsightsResponse struct {
Report UserLatencyInsightsReport `json:"report"`
}
// UserLatencyInsightsReport is the report from the user latency insights
// endpoint.
type UserLatencyInsightsReport struct {
StartTime time.Time `json:"start_time" format:"date-time"`
EndTime time.Time `json:"end_time" format:"date-time"`
TemplateIDs []uuid.UUID `json:"template_ids" format:"uuid"`
Users []UserLatency `json:"users"`
}
// UserLatency shows the connection latency for a user.
type UserLatency struct {
TemplateIDs []uuid.UUID `json:"template_ids" format:"uuid"`
UserID uuid.UUID `json:"user_id" format:"uuid"`
Username string `json:"username"`
AvatarURL string `json:"avatar_url" format:"uri"`
LatencyMS ConnectionLatency `json:"latency_ms"`
}
// UserActivityInsightsResponse is the response from the user activity insights
// endpoint.
type UserActivityInsightsResponse struct {
Report UserActivityInsightsReport `json:"report"`
}
// UserActivityInsightsReport is the report from the user activity insights
// endpoint.
type UserActivityInsightsReport struct {
StartTime time.Time `json:"start_time" format:"date-time"`
EndTime time.Time `json:"end_time" format:"date-time"`
TemplateIDs []uuid.UUID `json:"template_ids" format:"uuid"`
Users []UserActivity `json:"users"`
}
// UserActivity shows the session time for a user.
type UserActivity struct {
TemplateIDs []uuid.UUID `json:"template_ids" format:"uuid"`
UserID uuid.UUID `json:"user_id" format:"uuid"`
Username string `json:"username"`
AvatarURL string `json:"avatar_url" format:"uri"`
Seconds int64 `json:"seconds" example:"80500"`
}
// ConnectionLatency shows the latency for a connection.
type ConnectionLatency struct {
P50 float64 `json:"p50" example:"31.312"`
P95 float64 `json:"p95" example:"119.832"`
}
type UserLatencyInsightsRequest struct {
StartTime time.Time `json:"start_time" format:"date-time"`
EndTime time.Time `json:"end_time" format:"date-time"`
TemplateIDs []uuid.UUID `json:"template_ids" format:"uuid"`
}
func (c *Client) UserLatencyInsights(ctx context.Context, req UserLatencyInsightsRequest) (UserLatencyInsightsResponse, error) {
qp := url.Values{}
qp.Add("start_time", req.StartTime.Format(insightsTimeLayout))
qp.Add("end_time", req.EndTime.Format(insightsTimeLayout))
if len(req.TemplateIDs) > 0 {
var templateIDs []string
for _, id := range req.TemplateIDs {
templateIDs = append(templateIDs, id.String())
}
qp.Add("template_ids", strings.Join(templateIDs, ","))
}
reqURL := fmt.Sprintf("/api/v2/insights/user-latency?%s", qp.Encode())
resp, err := c.Request(ctx, http.MethodGet, reqURL, nil)
if err != nil {
return UserLatencyInsightsResponse{}, xerrors.Errorf("make request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return UserLatencyInsightsResponse{}, ReadBodyAsError(resp)
}
var result UserLatencyInsightsResponse
return result, json.NewDecoder(resp.Body).Decode(&result)
}
type UserActivityInsightsRequest struct {
StartTime time.Time `json:"start_time" format:"date-time"`
EndTime time.Time `json:"end_time" format:"date-time"`
TemplateIDs []uuid.UUID `json:"template_ids" format:"uuid"`
}
func (c *Client) UserActivityInsights(ctx context.Context, req UserActivityInsightsRequest) (UserActivityInsightsResponse, error) {
qp := url.Values{}
qp.Add("start_time", req.StartTime.Format(insightsTimeLayout))
qp.Add("end_time", req.EndTime.Format(insightsTimeLayout))
if len(req.TemplateIDs) > 0 {
var templateIDs []string
for _, id := range req.TemplateIDs {
templateIDs = append(templateIDs, id.String())
}
qp.Add("template_ids", strings.Join(templateIDs, ","))
}
reqURL := fmt.Sprintf("/api/v2/insights/user-activity?%s", qp.Encode())
resp, err := c.Request(ctx, http.MethodGet, reqURL, nil)
if err != nil {
return UserActivityInsightsResponse{}, xerrors.Errorf("make request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return UserActivityInsightsResponse{}, ReadBodyAsError(resp)
}
var result UserActivityInsightsResponse
return result, json.NewDecoder(resp.Body).Decode(&result)
}
// TemplateInsightsResponse is the response from the template insights endpoint.
type TemplateInsightsResponse struct {
Report *TemplateInsightsReport `json:"report,omitempty"`
IntervalReports []TemplateInsightsIntervalReport `json:"interval_reports,omitempty"`
}
// TemplateInsightsReport is the report from the template insights endpoint.
type TemplateInsightsReport struct {
StartTime time.Time `json:"start_time" format:"date-time"`
EndTime time.Time `json:"end_time" format:"date-time"`
TemplateIDs []uuid.UUID `json:"template_ids" format:"uuid"`
ActiveUsers int64 `json:"active_users" example:"22"`
AppsUsage []TemplateAppUsage `json:"apps_usage"`
ParametersUsage []TemplateParameterUsage `json:"parameters_usage"`
}
// TemplateInsightsIntervalReport is the report from the template insights
// endpoint for a specific interval.
type TemplateInsightsIntervalReport struct {
StartTime time.Time `json:"start_time" format:"date-time"`
EndTime time.Time `json:"end_time" format:"date-time"`
TemplateIDs []uuid.UUID `json:"template_ids" format:"uuid"`
Interval InsightsReportInterval `json:"interval" example:"week"`
ActiveUsers int64 `json:"active_users" example:"14"`
}
// TemplateAppsType defines the type of app reported.
type TemplateAppsType string
// TemplateAppsType enums.
const (
TemplateAppsTypeBuiltin TemplateAppsType = "builtin"
TemplateAppsTypeApp TemplateAppsType = "app"
)
// Enums define the display name of the builtin app reported.
const (
TemplateBuiltinAppDisplayNameVSCode string = "Visual Studio Code"
TemplateBuiltinAppDisplayNameJetBrains string = "JetBrains"
TemplateBuiltinAppDisplayNameWebTerminal string = "Web Terminal"
TemplateBuiltinAppDisplayNameSSH string = "SSH"
TemplateBuiltinAppDisplayNameSFTP string = "SFTP"
)
// TemplateAppUsage shows the usage of an app for one or more templates.
type TemplateAppUsage struct {
TemplateIDs []uuid.UUID `json:"template_ids" format:"uuid"`
Type TemplateAppsType `json:"type" example:"builtin"`
DisplayName string `json:"display_name" example:"Visual Studio Code"`
Slug string `json:"slug" example:"vscode"`
Icon string `json:"icon"`
Seconds int64 `json:"seconds" example:"80500"`
}
// TemplateParameterUsage shows the usage of a parameter for one or more
// templates.
type TemplateParameterUsage struct {
TemplateIDs []uuid.UUID `json:"template_ids" format:"uuid"`
DisplayName string `json:"display_name"`
Name string `json:"name"`
Type string `json:"type"`
Description string `json:"description"`
Options []TemplateVersionParameterOption `json:"options,omitempty"`
Values []TemplateParameterValue `json:"values"`
}
// TemplateParameterValue shows the usage of a parameter value for one or more
// templates.
type TemplateParameterValue struct {
Value string `json:"value"`
Count int64 `json:"count"`
}
type TemplateInsightsRequest struct {
StartTime time.Time `json:"start_time" format:"date-time"`
EndTime time.Time `json:"end_time" format:"date-time"`
TemplateIDs []uuid.UUID `json:"template_ids" format:"uuid"`
Interval InsightsReportInterval `json:"interval" example:"day"`
Sections []TemplateInsightsSection `json:"sections" example:"report"`
}
func (c *Client) TemplateInsights(ctx context.Context, req TemplateInsightsRequest) (TemplateInsightsResponse, error) {
qp := url.Values{}
qp.Add("start_time", req.StartTime.Format(insightsTimeLayout))
qp.Add("end_time", req.EndTime.Format(insightsTimeLayout))
if len(req.TemplateIDs) > 0 {
var templateIDs []string
for _, id := range req.TemplateIDs {
templateIDs = append(templateIDs, id.String())
}
qp.Add("template_ids", strings.Join(templateIDs, ","))
}
if req.Interval != "" {
qp.Add("interval", string(req.Interval))
}
if len(req.Sections) > 0 {
var sections []string
for _, sec := range req.Sections {
sections = append(sections, string(sec))
}
qp.Add("sections", strings.Join(sections, ","))
}
reqURL := fmt.Sprintf("/api/v2/insights/templates?%s", qp.Encode())
resp, err := c.Request(ctx, http.MethodGet, reqURL, nil)
if err != nil {
return TemplateInsightsResponse{}, xerrors.Errorf("make request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return TemplateInsightsResponse{}, ReadBodyAsError(resp)
}
var result TemplateInsightsResponse
return result, json.NewDecoder(resp.Body).Decode(&result)
}