feat(analytics): moved charts data to the separate module

This commit is contained in:
Alexander 2024-12-23 15:32:30 +01:00
parent 230924c4b8
commit 763aed14a1
5 changed files with 178 additions and 1 deletions

View file

@ -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)

View file

@ -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
}

View 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
}

View 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)
}

View 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"`
}