feat: Add anonymized telemetry to report product usage (#2273)

* feat: Add anonymized telemetry to report product usage

This adds a background service to report telemetry to a Coder
server for usage data. There will be realtime event data sent
in the future, but for now usage will report on a CRON.

* Fix flake and requested changes

* Add reporting options for setup

* Add reporting for workspaces

* Add resources as they are reported

* Track API key usage

* Ensure telemetry is tracked prior to exit
This commit is contained in:
Kyle Carberry
2022-06-17 00:26:40 -05:00
committed by GitHub
parent af8a1e3fea
commit 4cce969018
33 changed files with 1674 additions and 95 deletions

View File

@ -27,6 +27,7 @@ import (
"github.com/coder/coder/coderd/httpapi"
"github.com/coder/coder/coderd/httpmw"
"github.com/coder/coder/coderd/rbac"
"github.com/coder/coder/coderd/telemetry"
"github.com/coder/coder/coderd/util/ptr"
"github.com/coder/coder/codersdk"
)
@ -123,7 +124,7 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) {
filter.OwnerUsername = ""
}
workspaces, err := api.Database.GetWorkspacesWithFilter(r.Context(), filter)
workspaces, err := api.Database.GetWorkspaces(r.Context(), filter)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: "Internal error fetching workspaces.",
@ -457,6 +458,11 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
return
}
api.Telemetry.Report(&telemetry.Snapshot{
Workspaces: []telemetry.Workspace{telemetry.ConvertWorkspace(workspace)},
WorkspaceBuilds: []telemetry.WorkspaceBuild{telemetry.ConvertWorkspaceBuild(workspaceBuild)},
})
httpapi.Write(rw, http.StatusCreated, convertWorkspace(workspace, workspaceBuild, templateVersionJob, template,
findUser(apiKey.UserID, users), findUser(workspaceBuild.InitiatorID, users)))
}
@ -945,11 +951,11 @@ func validWorkspaceSchedule(s *string, min time.Duration) (sql.NullString, error
// workspaceSearchQuery takes a query string and returns the workspace filter.
// It also can return the list of validation errors to return to the api.
func workspaceSearchQuery(query string) (database.GetWorkspacesWithFilterParams, []httpapi.Error) {
func workspaceSearchQuery(query string) (database.GetWorkspacesParams, []httpapi.Error) {
searchParams := make(url.Values)
if query == "" {
// No filter
return database.GetWorkspacesWithFilterParams{}, nil
return database.GetWorkspacesParams{}, nil
}
// Because we do this in 2 passes, we want to maintain quotes on the first
// pass.Further splitting occurs on the second pass and quotes will be
@ -968,14 +974,14 @@ func workspaceSearchQuery(query string) (database.GetWorkspacesWithFilterParams,
searchParams.Set("owner", parts[0])
searchParams.Set("name", parts[1])
default:
return database.GetWorkspacesWithFilterParams{}, []httpapi.Error{
return database.GetWorkspacesParams{}, []httpapi.Error{
{Field: "q", Detail: fmt.Sprintf("Query element %q can only contain 1 '/'", element)},
}
}
case 2:
searchParams.Set(parts[0], parts[1])
default:
return database.GetWorkspacesWithFilterParams{}, []httpapi.Error{
return database.GetWorkspacesParams{}, []httpapi.Error{
{Field: "q", Detail: fmt.Sprintf("Query element %q can only contain 1 ':'", element)},
}
}
@ -984,7 +990,7 @@ func workspaceSearchQuery(query string) (database.GetWorkspacesWithFilterParams,
// Using the query param parser here just returns consistent errors with
// other parsing.
parser := httpapi.NewQueryParamParser()
filter := database.GetWorkspacesWithFilterParams{
filter := database.GetWorkspacesParams{
Deleted: false,
OwnerUsername: parser.String(searchParams, "", "owner"),
TemplateName: parser.String(searchParams, "", "template"),