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) }