feat(analytics): moved charts data to the separate module
This commit is contained in:
parent
230924c4b8
commit
763aed14a1
5 changed files with 178 additions and 1 deletions
|
|
@ -37,7 +37,7 @@ func main() {
|
|||
if err != nil {
|
||||
log.Fatal(ctx, "failed while creating router: %s", err)
|
||||
}
|
||||
router.AddHandlers(api.NoPrefix, builder.CardsAPI, builder.DashboardsAPI)
|
||||
router.AddHandlers(api.NoPrefix, builder.CardsAPI, builder.DashboardsAPI, builder.ChartsAPI)
|
||||
router.AddMiddlewares(builder.Auth.Middleware, builder.RateLimiter.Middleware, builder.AuditTrail.Middleware)
|
||||
|
||||
server.Run(ctx, log, &cfg.HTTP, router)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package analytics
|
||||
|
||||
import (
|
||||
"openreplay/backend/pkg/analytics/charts"
|
||||
"time"
|
||||
|
||||
"openreplay/backend/internal/config/analytics"
|
||||
|
|
@ -21,6 +22,7 @@ type ServicesBuilder struct {
|
|||
AuditTrail tracer.Tracer
|
||||
CardsAPI api.Handlers
|
||||
DashboardsAPI api.Handlers
|
||||
ChartsAPI api.Handlers
|
||||
}
|
||||
|
||||
func NewServiceBuilder(log logger.Logger, cfg *analytics.Config, webMetrics web.Web, pgconn pool.Pool) (*ServicesBuilder, error) {
|
||||
|
|
@ -45,11 +47,20 @@ func NewServiceBuilder(log logger.Logger, cfg *analytics.Config, webMetrics web.
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chartsService, err := charts.New(log, pgconn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chartsHandlers, err := charts.NewHandlers(log, cfg, responser, chartsService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ServicesBuilder{
|
||||
Auth: auth.NewAuth(log, cfg.JWTSecret, cfg.JWTSpotSecret, pgconn, nil),
|
||||
RateLimiter: limiter.NewUserRateLimiter(10, 30, 1*time.Minute, 5*time.Minute),
|
||||
AuditTrail: audiTrail,
|
||||
CardsAPI: cardsHandlers,
|
||||
DashboardsAPI: dashboardsHandlers,
|
||||
ChartsAPI: chartsHandlers,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
50
backend/pkg/analytics/charts/charts.go
Normal file
50
backend/pkg/analytics/charts/charts.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package charts
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"openreplay/backend/pkg/db/postgres/pool"
|
||||
"openreplay/backend/pkg/logger"
|
||||
)
|
||||
|
||||
type Charts interface {
|
||||
GetData(projectId int, userId uint64, req *GetCardChartDataRequest) ([]DataPoint, error)
|
||||
}
|
||||
|
||||
type chartsImpl struct {
|
||||
log logger.Logger
|
||||
pgconn pool.Pool
|
||||
}
|
||||
|
||||
func New(log logger.Logger, conn pool.Pool) (Charts, error) {
|
||||
return &chartsImpl{
|
||||
log: log,
|
||||
pgconn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *chartsImpl) GetData(projectId int, userID uint64, req *GetCardChartDataRequest) ([]DataPoint, error) {
|
||||
jsonInput := `
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"timestamp": 1733934939000,
|
||||
"Series A": 100,
|
||||
"Series B": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1733935939000,
|
||||
"Series A": 150,
|
||||
"Series B": 250
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
var resp GetCardChartDataResponse
|
||||
if err := json.Unmarshal([]byte(jsonInput), &resp); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
return resp.Data, nil
|
||||
}
|
||||
95
backend/pkg/analytics/charts/handlers.go
Normal file
95
backend/pkg/analytics/charts/handlers.go
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
package charts
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
config "openreplay/backend/internal/config/analytics"
|
||||
"openreplay/backend/pkg/logger"
|
||||
"openreplay/backend/pkg/server/api"
|
||||
"openreplay/backend/pkg/server/user"
|
||||
)
|
||||
|
||||
func getIDFromRequest(r *http.Request, key string) (int, error) {
|
||||
vars := mux.Vars(r)
|
||||
idStr := vars[key]
|
||||
if idStr == "" {
|
||||
return 0, fmt.Errorf("missing %s in request", key)
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid %s format", key)
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
type handlersImpl struct {
|
||||
log logger.Logger
|
||||
responser *api.Responser
|
||||
jsonSizeLimit int64
|
||||
charts Charts
|
||||
}
|
||||
|
||||
func (e *handlersImpl) GetAll() []*api.Description {
|
||||
return []*api.Description{
|
||||
{"/v1/analytics/{projectId}/cards/{id}/chart", e.getCardChartData, "POST"},
|
||||
{"/v1/analytics/{projectId}/cards/{id}/try", e.getCardChartData, "POST"},
|
||||
}
|
||||
}
|
||||
|
||||
func NewHandlers(log logger.Logger, cfg *config.Config, responser *api.Responser, charts Charts) (api.Handlers, error) {
|
||||
return &handlersImpl{
|
||||
log: log,
|
||||
responser: responser,
|
||||
jsonSizeLimit: cfg.JsonSizeLimit,
|
||||
charts: charts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *handlersImpl) getCardChartData(w http.ResponseWriter, r *http.Request) {
|
||||
startTime := time.Now()
|
||||
bodySize := 0
|
||||
|
||||
projectID, err := getIDFromRequest(r, "projectId")
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
}
|
||||
|
||||
bodyBytes, err := api.ReadBody(e.log, w, r, e.jsonSizeLimit)
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusRequestEntityTooLarge, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
}
|
||||
bodySize = len(bodyBytes)
|
||||
|
||||
req := &GetCardChartDataRequest{}
|
||||
if err := json.Unmarshal(bodyBytes, req); err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
}
|
||||
|
||||
validate := validator.New()
|
||||
err = validate.Struct(req)
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
}
|
||||
|
||||
currentUser := r.Context().Value("userData").(*user.User)
|
||||
resp, err := e.charts.GetData(projectID, currentUser.ID, req)
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
}
|
||||
|
||||
e.responser.ResponseWithJSON(e.log, r.Context(), w, resp, startTime, r.URL.Path, bodySize)
|
||||
}
|
||||
21
backend/pkg/analytics/charts/model.go
Normal file
21
backend/pkg/analytics/charts/model.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package charts
|
||||
|
||||
import "openreplay/backend/pkg/analytics/cards"
|
||||
|
||||
type DataPoint struct {
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Series map[string]int64 `json:"series"`
|
||||
}
|
||||
|
||||
type GetCardChartDataRequest struct {
|
||||
MetricType string `json:"metricType" validate:"required,oneof=timeseries table funnel"`
|
||||
MetricOf string `json:"metricOf" validate:"required,oneof=session_count user_count"`
|
||||
ViewType string `json:"viewType" validate:"required,oneof=line_chart table_view"`
|
||||
MetricFormat string `json:"metricFormat" validate:"required,oneof=default percentage"`
|
||||
SessionID int64 `json:"sessionId"`
|
||||
Series []cards.CardSeries `json:"series" validate:"required,dive"`
|
||||
}
|
||||
|
||||
type GetCardChartDataResponse struct {
|
||||
Data []DataPoint `json:"data"`
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue