Ctrl K
ComunidadGuías…

codersdk/insights.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package codersdk

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"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, ReadBodyAsJSON(resp, &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, ReadBodyAsJSON(resp, &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"`
	// UsageTotalSeconds counts each user's active minutes once, even when apps
	// overlap. Summed per-app seconds can exceed this total.
	UsageTotalSeconds int64                    `json:"usage_total_seconds" example:"120000"`
	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"`
}

// TemplateAppUsage shows the usage of an app for one or more templates.
type TemplateAppUsage struct {
	TemplateIDs []uuid.UUID `json:"template_ids" format:"uuid"`
	// Method is how the app connected. Method and Slug identify an app.
	Method      ConnectionLogMethod `json:"method" example:"ssh"`
	DisplayName string              `json:"display_name" example:"VS Code"`
	Slug        string              `json:"slug" example:"vscode"`
	Icon        string              `json:"icon"`
	Seconds     int64               `json:"seconds" example:"80500"`
	TimesUsed   int64               `json:"times_used" example:"2"`
	// SecondsIsUpperBound marks overflow usage that includes folded apps.
	// Their overlap is not stored, so Seconds may overestimate active time.
	SecondsIsUpperBound bool `json:"seconds_is_upper_bound,omitempty" example:"false"`
}

// TemplateInsightsMaxUnregisteredApps limits individually listed unregistered
// apps to the busiest names. Registered apps are exempt; the rest fold into
// overflow.
const TemplateInsightsMaxUnregisteredApps = 64

// 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, ReadBodyAsJSON(resp, &result)
}

type GetUserStatusCountsResponse struct {
	StatusCounts map[UserStatus][]UserStatusChangeCount `json:"status_counts"`
}

type UserStatusChangeCount struct {
	Date  time.Time `json:"date" format:"date-time"`
	Count int64     `json:"count" example:"10"`
}

type GetUserStatusCountsRequest struct {
	Timezone string `json:"timezone" example:"America/St_Johns"`
	// Deprecated: Use Timezone instead. Offset is ignored when Timezone is provided.
	Offset int `json:"offset,omitempty" example:"-2"`
}

func (c *Client) GetUserStatusCounts(ctx context.Context, req GetUserStatusCountsRequest) (GetUserStatusCountsResponse, error) {
	qp := url.Values{}
	if req.Timezone != "" {
		qp.Add("timezone", req.Timezone)
	} else {
		qp.Add("tz_offset", strconv.Itoa(req.Offset))
	}

	reqURL := fmt.Sprintf("/api/v2/insights/user-status-counts?%s", qp.Encode())
	resp, err := c.Request(ctx, http.MethodGet, reqURL, nil)
	if err != nil {
		return GetUserStatusCountsResponse{}, xerrors.Errorf("make request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return GetUserStatusCountsResponse{}, ReadBodyAsError(resp)
	}
	var result GetUserStatusCountsResponse
	return result, ReadBodyAsJSON(resp, &result)
}