77"errors"
88"fmt"
99"net/http"
10+ "slices"
1011"strconv"
1112"time"
1213
@@ -15,6 +16,7 @@ import (
1516"golang.org/x/xerrors"
1617
1718"cdr.dev/slog"
19+ "github.com/coder/coder/v2/agent/proto"
1820"github.com/coder/coder/v2/coderd/audit"
1921"github.com/coder/coder/v2/coderd/database"
2022"github.com/coder/coder/v2/coderd/database/db2sdk"
@@ -1105,7 +1107,9 @@ func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) {
11051107// @ID post-workspace-usage-by-id
11061108// @Security CoderSessionToken
11071109// @Tags Workspaces
1110+ // @Accept json
11081111// @Param workspace path string true "Workspace ID" format(uuid)
1112+ // @Param request body codersdk.PostWorkspaceUsageRequest false "Post workspace usage request"
11091113// @Success 204
11101114// @Router /workspaces/{workspace}/usage [post]
11111115func (api * API )postWorkspaceUsage (rw http.ResponseWriter ,r * http.Request ) {
@@ -1116,6 +1120,102 @@ func (api *API) postWorkspaceUsage(rw http.ResponseWriter, r *http.Request) {
11161120}
11171121
11181122api .statsReporter .TrackUsage (workspace .ID )
1123+
1124+ if ! api .Experiments .Enabled (codersdk .ExperimentWorkspaceUsage ) {
1125+ // Continue previous behavior if the experiment is not enabled.
1126+ rw .WriteHeader (http .StatusNoContent )
1127+ return
1128+ }
1129+
1130+ if r .Body == http .NoBody {
1131+ // Continue previous behavior if no body is present.
1132+ rw .WriteHeader (http .StatusNoContent )
1133+ return
1134+ }
1135+
1136+ ctx := r .Context ()
1137+ var req codersdk.PostWorkspaceUsageRequest
1138+ if ! httpapi .Read (ctx ,rw ,r ,& req ) {
1139+ return
1140+ }
1141+
1142+ if req .AgentID == uuid .Nil && req .AppName == "" {
1143+ // Continue previous behavior if body is empty.
1144+ rw .WriteHeader (http .StatusNoContent )
1145+ return
1146+ }
1147+ if req .AgentID == uuid .Nil {
1148+ httpapi .Write (ctx ,rw ,http .StatusBadRequest , codersdk.Response {
1149+ Message :"Invalid request" ,
1150+ Validations : []codersdk.ValidationError {{
1151+ Field :"agent_id" ,
1152+ Detail :"must be set when app_name is set" ,
1153+ }},
1154+ })
1155+ return
1156+ }
1157+ if req .AppName == "" {
1158+ httpapi .Write (ctx ,rw ,http .StatusBadRequest , codersdk.Response {
1159+ Message :"Invalid request" ,
1160+ Validations : []codersdk.ValidationError {{
1161+ Field :"app_name" ,
1162+ Detail :"must be set when agent_id is set" ,
1163+ }},
1164+ })
1165+ return
1166+ }
1167+ if ! slices .Contains (codersdk .AllowedAppNames ,req .AppName ) {
1168+ httpapi .Write (ctx ,rw ,http .StatusBadRequest , codersdk.Response {
1169+ Message :"Invalid request" ,
1170+ Validations : []codersdk.ValidationError {{
1171+ Field :"app_name" ,
1172+ Detail :fmt .Sprintf ("must be one of %v" ,codersdk .AllowedAppNames ),
1173+ }},
1174+ })
1175+ return
1176+ }
1177+
1178+ stat := & proto.Stats {
1179+ ConnectionCount :1 ,
1180+ }
1181+ switch req .AppName {
1182+ case codersdk .UsageAppNameVscode :
1183+ stat .SessionCountVscode = 1
1184+ case codersdk .UsageAppNameJetbrains :
1185+ stat .SessionCountJetbrains = 1
1186+ case codersdk .UsageAppNameReconnectingPty :
1187+ stat .SessionCountReconnectingPty = 1
1188+ case codersdk .UsageAppNameSSH :
1189+ stat .SessionCountSsh = 1
1190+ default :
1191+ // This means the app_name is in the codersdk.AllowedAppNames but not being
1192+ // handled by this switch statement.
1193+ httpapi .InternalServerError (rw ,xerrors .Errorf ("unknown app_name %q" ,req .AppName ))
1194+ return
1195+ }
1196+
1197+ agent ,err := api .Database .GetWorkspaceAgentByID (ctx ,req .AgentID )
1198+ if err != nil {
1199+ if httpapi .Is404Error (err ) {
1200+ httpapi .ResourceNotFound (rw )
1201+ return
1202+ }
1203+ httpapi .InternalServerError (rw ,err )
1204+ return
1205+ }
1206+
1207+ template ,err := api .Database .GetTemplateByID (ctx ,workspace .TemplateID )
1208+ if err != nil {
1209+ httpapi .InternalServerError (rw ,err )
1210+ return
1211+ }
1212+
1213+ err = api .statsReporter .ReportAgentStats (ctx ,dbtime .Now (),workspace ,agent ,template .Name ,stat )
1214+ if err != nil {
1215+ httpapi .InternalServerError (rw ,err )
1216+ return
1217+ }
1218+
11191219rw .WriteHeader (http .StatusNoContent )
11201220}
11211221