From e7f343211359252dd38a638071c646cb79c68b27 Mon Sep 17 00:00:00 2001 From: Steven Tang Date: Fri, 28 Jan 2022 22:28:47 +1100 Subject: [PATCH] feat: GET /heartbeat endpoint (resolves #241) --- main.go | 9 +- models/compat/wakatime/v1/heartbeat.go | 29 +++- routes/api/heartbeat.go | 6 + routes/compat/wakatime/v1/heartbeat.go | 85 ++++++++++ services/imports/wakatime.go | 7 +- static/docs/docs.go | 147 ++++++++++++++++++ static/docs/swagger.json | 147 ++++++++++++++++++ static/docs/swagger.yaml | 100 ++++++++++++ .../Wakapi API Tests.postman_collection.json | 117 +++++++++++++- 9 files changed, 638 insertions(+), 9 deletions(-) create mode 100644 routes/compat/wakatime/v1/heartbeat.go diff --git a/main.go b/main.go index d92bf54..c0c56c3 100644 --- a/main.go +++ b/main.go @@ -2,9 +2,6 @@ package main import ( "embed" - "github.com/lpar/gzipped/v2" - "github.com/muety/wakapi/models" - "github.com/muety/wakapi/routes/relay" "io/fs" "log" "net" @@ -13,6 +10,10 @@ import ( "strconv" "time" + "github.com/lpar/gzipped/v2" + "github.com/muety/wakapi/models" + "github.com/muety/wakapi/routes/relay" + "github.com/emvi/logbuch" "github.com/gorilla/handlers" conf "github.com/muety/wakapi/config" @@ -187,6 +188,7 @@ func main() { wakatimeV1StatsHandler := wtV1Routes.NewStatsHandler(userService, summaryService) wakatimeV1UsersHandler := wtV1Routes.NewUsersHandler(userService, heartbeatService) wakatimeV1ProjectsHandler := wtV1Routes.NewProjectsHandler(userService, heartbeatService) + wakatimeV1HeartbeatsHandler := wtV1Routes.NewHeartbeatHandler(userService, heartbeatService) shieldV1BadgeHandler := shieldsV1Routes.NewBadgeHandler(summaryService, userService) // MVC Handlers @@ -241,6 +243,7 @@ func main() { wakatimeV1StatsHandler.RegisterRoutes(apiRouter) wakatimeV1UsersHandler.RegisterRoutes(apiRouter) wakatimeV1ProjectsHandler.RegisterRoutes(apiRouter) + wakatimeV1HeartbeatsHandler.RegisterRoutes(apiRouter) shieldV1BadgeHandler.RegisterRoutes(apiRouter) // Static Routes diff --git a/models/compat/wakatime/v1/heartbeat.go b/models/compat/wakatime/v1/heartbeat.go index 5a93bcd..09304dd 100644 --- a/models/compat/wakatime/v1/heartbeat.go +++ b/models/compat/wakatime/v1/heartbeat.go @@ -1,6 +1,8 @@ package v1 import ( + "strconv" + "github.com/muety/wakapi/models" ) @@ -19,11 +21,34 @@ type HeartbeatEntry struct { IsWrite bool `json:"is_write"` Language string `json:"language"` Project string `json:"project"` - Time models.CustomTime `json:"time"` + Time float64 `json:"time"` Type string `json:"type"` UserId string `json:"user_id"` MachineNameId string `json:"machine_name_id"` UserAgentId string `json:"user_agent_id"` CreatedAt models.CustomTime `json:"created_at"` - ModifiedAt models.CustomTime `json:"created_at"` + ModifiedAt models.CustomTime `json:"created_at",omitempty` +} + +func ToHeartbeatEntry(entries []*models.Heartbeat) []HeartbeatEntry { + out := make([]HeartbeatEntry, len(entries)) + for i := 0; i < len(entries); i++ { + entry := entries[i] + out[i] = HeartbeatEntry{ + Id: strconv.FormatUint(entry.ID, 10), + Branch: entry.Branch, + Category: entry.Category, + Entity: entry.Entity, + IsWrite: entry.IsWrite, + Language: entry.Language, + Project: entry.Project, + Time: float64(entry.Time.T().Unix()), + Type: entry.Type, + UserId: entry.UserID, + MachineNameId: entry.Machine, + UserAgentId: entry.UserAgent, + CreatedAt: entry.CreatedAt, + } + } + return out } diff --git a/routes/api/heartbeat.go b/routes/api/heartbeat.go index 653178e..856556c 100644 --- a/routes/api/heartbeat.go +++ b/routes/api/heartbeat.go @@ -145,6 +145,7 @@ func constructSuccessResponse(n int) *heartbeatResponseVm { // @Tags heartbeat // @Accept json // @Param heartbeat body models.Heartbeat true "A single heartbeat" +// @Param user path string true "Username (or current)" // @Security ApiKeyAuth // @Success 201 // @Router /v1/users/{user}/heartbeats [post] @@ -155,6 +156,7 @@ func (h *HeartbeatApiHandler) postAlias1() {} // @Tags heartbeat // @Accept json // @Param heartbeat body models.Heartbeat true "A single heartbeat" +// @Param user path string true "Username (or current)" // @Security ApiKeyAuth // @Success 201 // @Router /compat/wakatime/v1/users/{user}/heartbeats [post] @@ -165,6 +167,7 @@ func (h *HeartbeatApiHandler) postAlias2() {} // @Tags heartbeat // @Accept json // @Param heartbeat body models.Heartbeat true "A single heartbeat" +// @Param user path string true "Username (or current)" // @Security ApiKeyAuth // @Success 201 // @Router /users/{user}/heartbeats [post] @@ -185,6 +188,7 @@ func (h *HeartbeatApiHandler) postAlias4() {} // @Tags heartbeat // @Accept json // @Param heartbeat body []models.Heartbeat true "Multiple heartbeats" +// @Param user path string true "Username (or current)" // @Security ApiKeyAuth // @Success 201 // @Router /v1/users/{user}/heartbeats.bulk [post] @@ -195,6 +199,7 @@ func (h *HeartbeatApiHandler) postAlias5() {} // @Tags heartbeat // @Accept json // @Param heartbeat body []models.Heartbeat true "Multiple heartbeats" +// @Param user path string true "Username (or current)" // @Security ApiKeyAuth // @Success 201 // @Router /compat/wakatime/v1/users/{user}/heartbeats.bulk [post] @@ -205,6 +210,7 @@ func (h *HeartbeatApiHandler) postAlias6() {} // @Tags heartbeat // @Accept json // @Param heartbeat body []models.Heartbeat true "Multiple heartbeats" +// @Param user path string true "Username (or current)" // @Security ApiKeyAuth // @Success 201 // @Router /users/{user}/heartbeats.bulk [post] diff --git a/routes/compat/wakatime/v1/heartbeat.go b/routes/compat/wakatime/v1/heartbeat.go new file mode 100644 index 0000000..9e31aa0 --- /dev/null +++ b/routes/compat/wakatime/v1/heartbeat.go @@ -0,0 +1,85 @@ +package v1 + +import ( + "net/http" + "time" + + "github.com/gorilla/mux" + conf "github.com/muety/wakapi/config" + "github.com/muety/wakapi/middlewares" + wakatime "github.com/muety/wakapi/models/compat/wakatime/v1" + routeutils "github.com/muety/wakapi/routes/utils" + "github.com/muety/wakapi/services" + "github.com/muety/wakapi/utils" +) + +type HeartbeatsResult struct { + Data []wakatime.HeartbeatEntry `json:"data"` + End string `json:"end"` + Start string `json:"start"` + Timezone string `json:"timezone"` +} + +type HeartbeatHandler struct { + userSrvc services.IUserService + heartbeatSrvc services.IHeartbeatService +} + +func NewHeartbeatHandler(userService services.IUserService, heartbeatService services.IHeartbeatService) *HeartbeatHandler { + return &HeartbeatHandler{ + userSrvc: userService, + heartbeatSrvc: heartbeatService, + } +} + +func (h *HeartbeatHandler) RegisterRoutes(router *mux.Router) { + r := router.PathPrefix("").Subrouter() + r.Use( + middlewares.NewAuthenticateMiddleware(h.userSrvc).Handler, + ) + r.Path("/compat/wakatime/v1/users/{user}/heartbeats").Methods(http.MethodGet).HandlerFunc(h.Get) +} + +// @Summary Get heartbeats of user for specified date +// @ID get-heartbeats +// @Tags heartbeat +// @Param date query string true "Date" +// @Param user path string true "Username (or current)" +// @Security ApiKeyAuth +// @Success 200 {object} v1.HeartbeatEntry +// @Failure 400 {string} string "bad date" +// @Router /compat/wakatime/v1/users/{user}/heartbeats [get] +func (h *HeartbeatHandler) Get(w http.ResponseWriter, r *http.Request) { + user, err := routeutils.CheckEffectiveUser(w, r, h.userSrvc, "current") + if err != nil { + return // response was already sent by util function + } + + params := r.URL.Query() + dateParam := params.Get("date") + date, err := time.Parse(conf.SimpleDateFormat, dateParam) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("bad date")) + return + } + + timezone := user.TZ() + rangeFrom, rangeTo := utils.StartOfDay(date.In(timezone)), utils.EndOfDay(date.In(timezone)) + + heartbeats, err := h.heartbeatSrvc.GetAllWithin(rangeFrom, rangeTo, user) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(conf.ErrInternalServerError)) + conf.Log().Request(r).Error("failed to retrieve heartbeats - %v", err) + return + } + + res := HeartbeatsResult{ + Data: wakatime.ToHeartbeatEntry(heartbeats), + Start: rangeFrom.UTC().Format(time.RFC3339), + End: rangeTo.UTC().Format(time.RFC3339), + Timezone: timezone.String(), + } + utils.RespondJSON(w, r, http.StatusOK, res) +} diff --git a/services/imports/wakatime.go b/services/imports/wakatime.go index 98a4d5a..0a0fcd5 100644 --- a/services/imports/wakatime.go +++ b/services/imports/wakatime.go @@ -6,6 +6,9 @@ import ( "encoding/json" "errors" "fmt" + "net/http" + "time" + "github.com/emvi/logbuch" "github.com/muety/wakapi/config" "github.com/muety/wakapi/models" @@ -13,8 +16,6 @@ import ( "github.com/muety/wakapi/utils" "go.uber.org/atomic" "golang.org/x/sync/semaphore" - "net/http" - "time" ) const OriginWakatime = "wakatime" @@ -284,7 +285,7 @@ func mapHeartbeat( OperatingSystem: ua.Os, Machine: ma.Value, UserAgent: ua.Value, - Time: entry.Time, + Time: models.CustomTime(time.Unix(0, int64(entry.Time*1e9))), Origin: OriginWakatime, OriginId: entry.Id, }).Hashed() diff --git a/static/docs/docs.go b/static/docs/docs.go index 0780cdb..7b43356 100644 --- a/static/docs/docs.go +++ b/static/docs/docs.go @@ -160,6 +160,48 @@ var doc = `{ } }, "/compat/wakatime/v1/users/{user}/heartbeats": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "heartbeat" + ], + "summary": "Get heartbeats of user for specified date", + "operationId": "get-heartbeats", + "parameters": [ + { + "type": "string", + "description": "Date", + "name": "date", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Username (or current)", + "name": "user", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.HeartbeatEntry" + } + }, + "400": { + "description": "bad date", + "schema": { + "type": "string" + } + } + } + }, "post": { "security": [ { @@ -183,6 +225,13 @@ var doc = `{ "schema": { "$ref": "#/definitions/models.Heartbeat" } + }, + { + "type": "string", + "description": "Username (or current)", + "name": "user", + "in": "path", + "required": true } ], "responses": { @@ -219,6 +268,13 @@ var doc = `{ "$ref": "#/definitions/models.Heartbeat" } } + }, + { + "type": "string", + "description": "Username (or current)", + "name": "user", + "in": "path", + "required": true } ], "responses": { @@ -863,6 +919,13 @@ var doc = `{ "schema": { "$ref": "#/definitions/models.Heartbeat" } + }, + { + "type": "string", + "description": "Username (or current)", + "name": "user", + "in": "path", + "required": true } ], "responses": { @@ -899,6 +962,13 @@ var doc = `{ "$ref": "#/definitions/models.Heartbeat" } } + }, + { + "type": "string", + "description": "Username (or current)", + "name": "user", + "in": "path", + "required": true } ], "responses": { @@ -967,6 +1037,13 @@ var doc = `{ "schema": { "$ref": "#/definitions/models.Heartbeat" } + }, + { + "type": "string", + "description": "Username (or current)", + "name": "user", + "in": "path", + "required": true } ], "responses": { @@ -1003,6 +1080,13 @@ var doc = `{ "$ref": "#/definitions/models.Heartbeat" } } + }, + { + "type": "string", + "description": "Username (or current)", + "name": "user", + "in": "path", + "required": true } ], "responses": { @@ -1094,6 +1178,13 @@ var doc = `{ "models.Summary": { "type": "object", "properties": { + "branches": { + "description": "branches are not persisted, but calculated at runtime in case a project filter is applied", + "type": "array", + "items": { + "$ref": "#/definitions/models.SummaryItem" + } + }, "editors": { "type": "array", "items": { @@ -1222,6 +1313,50 @@ var doc = `{ } } }, + "v1.HeartbeatEntry": { + "type": "object", + "properties": { + "branch": { + "type": "string" + }, + "category": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "entity": { + "type": "string" + }, + "id": { + "type": "string" + }, + "is_write": { + "type": "boolean" + }, + "language": { + "type": "string" + }, + "machine_name_id": { + "type": "string" + }, + "project": { + "type": "string" + }, + "time": { + "type": "number" + }, + "type": { + "type": "string" + }, + "user_agent_id": { + "type": "string" + }, + "user_id": { + "type": "string" + } + } + }, "v1.Project": { "type": "object", "properties": { @@ -1250,6 +1385,12 @@ var doc = `{ "v1.StatsData": { "type": "object", "properties": { + "branches": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, "daily_average": { "type": "number" }, @@ -1325,6 +1466,12 @@ var doc = `{ "v1.SummariesData": { "type": "object", "properties": { + "branches": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, "categories": { "type": "array", "items": { diff --git a/static/docs/swagger.json b/static/docs/swagger.json index b315ad5..0f7c4ea 100644 --- a/static/docs/swagger.json +++ b/static/docs/swagger.json @@ -145,6 +145,48 @@ } }, "/compat/wakatime/v1/users/{user}/heartbeats": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "heartbeat" + ], + "summary": "Get heartbeats of user for specified date", + "operationId": "get-heartbeats", + "parameters": [ + { + "type": "string", + "description": "Date", + "name": "date", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Username (or current)", + "name": "user", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.HeartbeatEntry" + } + }, + "400": { + "description": "bad date", + "schema": { + "type": "string" + } + } + } + }, "post": { "security": [ { @@ -168,6 +210,13 @@ "schema": { "$ref": "#/definitions/models.Heartbeat" } + }, + { + "type": "string", + "description": "Username (or current)", + "name": "user", + "in": "path", + "required": true } ], "responses": { @@ -204,6 +253,13 @@ "$ref": "#/definitions/models.Heartbeat" } } + }, + { + "type": "string", + "description": "Username (or current)", + "name": "user", + "in": "path", + "required": true } ], "responses": { @@ -848,6 +904,13 @@ "schema": { "$ref": "#/definitions/models.Heartbeat" } + }, + { + "type": "string", + "description": "Username (or current)", + "name": "user", + "in": "path", + "required": true } ], "responses": { @@ -884,6 +947,13 @@ "$ref": "#/definitions/models.Heartbeat" } } + }, + { + "type": "string", + "description": "Username (or current)", + "name": "user", + "in": "path", + "required": true } ], "responses": { @@ -952,6 +1022,13 @@ "schema": { "$ref": "#/definitions/models.Heartbeat" } + }, + { + "type": "string", + "description": "Username (or current)", + "name": "user", + "in": "path", + "required": true } ], "responses": { @@ -988,6 +1065,13 @@ "$ref": "#/definitions/models.Heartbeat" } } + }, + { + "type": "string", + "description": "Username (or current)", + "name": "user", + "in": "path", + "required": true } ], "responses": { @@ -1079,6 +1163,13 @@ "models.Summary": { "type": "object", "properties": { + "branches": { + "description": "branches are not persisted, but calculated at runtime in case a project filter is applied", + "type": "array", + "items": { + "$ref": "#/definitions/models.SummaryItem" + } + }, "editors": { "type": "array", "items": { @@ -1207,6 +1298,50 @@ } } }, + "v1.HeartbeatEntry": { + "type": "object", + "properties": { + "branch": { + "type": "string" + }, + "category": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "entity": { + "type": "string" + }, + "id": { + "type": "string" + }, + "is_write": { + "type": "boolean" + }, + "language": { + "type": "string" + }, + "machine_name_id": { + "type": "string" + }, + "project": { + "type": "string" + }, + "time": { + "type": "number" + }, + "type": { + "type": "string" + }, + "user_agent_id": { + "type": "string" + }, + "user_id": { + "type": "string" + } + } + }, "v1.Project": { "type": "object", "properties": { @@ -1235,6 +1370,12 @@ "v1.StatsData": { "type": "object", "properties": { + "branches": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, "daily_average": { "type": "number" }, @@ -1310,6 +1451,12 @@ "v1.SummariesData": { "type": "object", "properties": { + "branches": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, "categories": { "type": "array", "items": { diff --git a/static/docs/swagger.yaml b/static/docs/swagger.yaml index a705287..dce41a6 100644 --- a/static/docs/swagger.yaml +++ b/static/docs/swagger.yaml @@ -54,6 +54,12 @@ definitions: type: object models.Summary: properties: + branches: + description: branches are not persisted, but calculated at runtime in case + a project filter is applied + items: + $ref: '#/definitions/models.SummaryItem' + type: array editors: items: $ref: '#/definitions/models.SummaryItem' @@ -142,6 +148,35 @@ definitions: schemaVersion: type: integer type: object + v1.HeartbeatEntry: + properties: + branch: + type: string + category: + type: string + created_at: + type: string + entity: + type: string + id: + type: string + is_write: + type: boolean + language: + type: string + machine_name_id: + type: string + project: + type: string + time: + type: number + type: + type: string + user_agent_id: + type: string + user_id: + type: string + type: object v1.Project: properties: id: @@ -160,6 +195,10 @@ definitions: type: object v1.StatsData: properties: + branches: + items: + $ref: '#/definitions/v1.SummariesEntry' + type: array daily_average: type: number days_including_holidays: @@ -209,6 +248,10 @@ definitions: type: object v1.SummariesData: properties: + branches: + items: + $ref: '#/definitions/v1.SummariesEntry' + type: array categories: items: $ref: '#/definitions/v1.SummariesEntry' @@ -441,6 +484,33 @@ paths: tags: - wakatime /compat/wakatime/v1/users/{user}/heartbeats: + get: + operationId: get-heartbeats + parameters: + - description: Date + in: query + name: date + required: true + type: string + - description: Username (or current) + in: path + name: user + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/v1.HeartbeatEntry' + "400": + description: bad date + schema: + type: string + security: + - ApiKeyAuth: [] + summary: Get heartbeats of user for specified date + tags: + - heartbeat post: consumes: - application/json @@ -452,6 +522,11 @@ paths: required: true schema: $ref: '#/definitions/models.Heartbeat' + - description: Username (or current) + in: path + name: user + required: true + type: string responses: "201": description: "" @@ -474,6 +549,11 @@ paths: items: $ref: '#/definitions/models.Heartbeat' type: array + - description: Username (or current) + in: path + name: user + required: true + type: string responses: "201": description: "" @@ -900,6 +980,11 @@ paths: required: true schema: $ref: '#/definitions/models.Heartbeat' + - description: Username (or current) + in: path + name: user + required: true + type: string responses: "201": description: "" @@ -922,6 +1007,11 @@ paths: items: $ref: '#/definitions/models.Heartbeat' type: array + - description: Username (or current) + in: path + name: user + required: true + type: string responses: "201": description: "" @@ -965,6 +1055,11 @@ paths: required: true schema: $ref: '#/definitions/models.Heartbeat' + - description: Username (or current) + in: path + name: user + required: true + type: string responses: "201": description: "" @@ -987,6 +1082,11 @@ paths: items: $ref: '#/definitions/models.Heartbeat' type: array + - description: Username (or current) + in: path + name: user + required: true + type: string responses: "201": description: "" diff --git a/testing/Wakapi API Tests.postman_collection.json b/testing/Wakapi API Tests.postman_collection.json index 98f93d6..c5fa8ce 100644 --- a/testing/Wakapi API Tests.postman_collection.json +++ b/testing/Wakapi API Tests.postman_collection.json @@ -965,6 +965,121 @@ } }, "response": [] + }, + { + "name": "Create heartbeats (get heartbeats test)", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// 1640995199 Friday, 31 December 2021 11:59:59 PM (Jan 1st in +1, +2)", + "// 1641074399 Saturday, 1 January 2022 9:59:59 PM (Jan 1st in +1, +2)", + "// 1641081599 Saturday, 1 January 2022 11:59:59 PM (Jan 2nd in +1, +2)", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{WRITEUSER_TOKEN}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "[{\n \"entity\": \"/home/user1/dev/project1/main.go\",\n \"project\": \"wakapi\",\n \"language\": \"Go\",\n \"is_write\": true,\n \"type\": \"file\",\n \"category\": null,\n \"branch\": null,\n \"time\": 1640995199\n},\n{\n \"entity\": \"/home/user1/dev/project1/main.go\",\n \"project\": \"wakapi\",\n \"language\": \"Go\",\n \"is_write\": true,\n \"type\": \"file\",\n \"category\": null,\n \"branch\": null,\n \"time\": 1641074399\n},\n{\n \"entity\": \"/home/user1/dev/project1/main.go\",\n \"project\": \"wakapi\",\n \"language\": \"Go\",\n \"is_write\": true,\n \"type\": \"file\",\n \"category\": null,\n \"branch\": null,\n \"time\": 1641081599\n}]", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{BASE_URL}}/api/heartbeat", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "api", + "heartbeat" + ] + } + }, + "response": [] + }, + { + "name": "Get heartbeats", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Response body is correct\", function () {", + " var jsonData = pm.response.json();", + " pm.expect(jsonData.timezone).to.eql(pm.collectionVariables.get('TZ'));", + " var date = new Date(\"2022-01-01T00:00:00+0100\")", + " pm.expect(new Date(jsonData.start)).to.eql(date);", + " pm.expect(new Date(jsonData.end)).to.eql(new Date(date.getTime() + 3600 * 1000 * 24));", + " pm.expect(jsonData.data.length).to.eql(2);", + "});" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disableCookies": true + }, + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{WRITEUSER_TOKEN}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{BASE_URL}}/api/compat/wakatime/v1/users/current/heartbeats?date=2022-01-01", + "host": [ + "{{BASE_URL}}" + ], + "path": [ + "api", + "compat", + "wakatime", + "v1", + "users", + "current", + "heartbeats" + ], + "query": [ + { + "key": "date", + "value": "2022-01-01" + } + ] + } + }, + "response": [] } ] }, @@ -1982,7 +2097,7 @@ "", "pm.test(\"Correct content\", function () {", " const jsonData = pm.response.json();", - " pm.expect(jsonData.data.text).to.eql('0 hrs 2 mins');", + " pm.expect(jsonData.data.text).to.eql('0 hrs 8 mins');", "});" ], "type": "text/javascript"