mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-10-11 05:51:30 +08:00
chore: refactor handler and utils
Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
This commit is contained in:
parent
95bc3987bb
commit
03e50d3bc3
@ -1,6 +1,7 @@
|
|||||||
package impltracefunnel
|
package impltracefunnel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@ -8,9 +9,7 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/errors"
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
"github.com/SigNoz/signoz/pkg/http/render"
|
"github.com/SigNoz/signoz/pkg/http/render"
|
||||||
"github.com/SigNoz/signoz/pkg/modules/tracefunnel"
|
"github.com/SigNoz/signoz/pkg/modules/tracefunnel"
|
||||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
|
||||||
tf "github.com/SigNoz/signoz/pkg/types/tracefunnel"
|
tf "github.com/SigNoz/signoz/pkg/types/tracefunnel"
|
||||||
"github.com/SigNoz/signoz/pkg/valuer"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,6 +21,25 @@ func NewHandler(module tracefunnel.Module) tracefunnel.Handler {
|
|||||||
return &handler{module: module}
|
return &handler{module: module}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to check for duplicate funnel names
|
||||||
|
func (handler *handler) checkDuplicateName(ctx context.Context, orgID string, name string, excludeID string) error {
|
||||||
|
funnels, err := handler.module.List(ctx, orgID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Newf(errors.TypeInvalidInput,
|
||||||
|
errors.CodeInvalidInput,
|
||||||
|
"failed to list funnels: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range funnels {
|
||||||
|
if f.ID.String() != excludeID && f.Name == name {
|
||||||
|
return errors.Newf(errors.TypeInvalidInput,
|
||||||
|
errors.CodeInvalidInput,
|
||||||
|
"a funnel with name '%s' already exists in this organization", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (handler *handler) New(rw http.ResponseWriter, r *http.Request) {
|
func (handler *handler) New(rw http.ResponseWriter, r *http.Request) {
|
||||||
var req tf.FunnelRequest
|
var req tf.FunnelRequest
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
@ -29,48 +47,26 @@ func (handler *handler) New(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
claims, err := tracefunnel.GetClaims(r)
|
||||||
if err != nil {
|
|
||||||
render.Error(rw, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userID := claims.UserID
|
|
||||||
orgID := claims.OrgID
|
|
||||||
|
|
||||||
funnels, err := handler.module.List(r.Context(), orgID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(rw, err)
|
render.Error(rw, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range funnels {
|
if err := handler.checkDuplicateName(r.Context(), claims.OrgID, req.Name, ""); err != nil {
|
||||||
if f.Name == req.Name {
|
render.Error(rw, err)
|
||||||
render.Error(rw,
|
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
|
||||||
errors.CodeInvalidInput,
|
|
||||||
"a funnel with name '%s' already exists in this organization",
|
|
||||||
req.Name))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
funnel, err := handler.module.Create(r.Context(), req.Timestamp, req.Name, userID, orgID)
|
|
||||||
if err != nil {
|
|
||||||
render.Error(rw,
|
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
|
||||||
errors.CodeInvalidInput,
|
|
||||||
"failed to create funnel"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response := tf.FunnelResponse{
|
funnel, err := handler.module.Create(r.Context(), req.Timestamp, req.Name, claims.UserID, claims.OrgID)
|
||||||
FunnelID: funnel.ID.String(),
|
if err != nil {
|
||||||
FunnelName: funnel.Name,
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||||
CreatedAt: req.Timestamp,
|
errors.CodeInvalidInput,
|
||||||
UserEmail: claims.Email,
|
"failed to create funnel"))
|
||||||
OrgID: orgID,
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
response := tracefunnel.ConstructFunnelResponse(funnel, claims)
|
||||||
render.Success(rw, http.StatusOK, response)
|
render.Success(rw, http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,80 +77,42 @@ func (handler *handler) UpdateSteps(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
claims, err := tracefunnel.GetClaims(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(rw, err)
|
render.Error(rw, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userID := claims.UserID
|
|
||||||
orgID := claims.OrgID
|
|
||||||
|
|
||||||
if err := tracefunnel.ValidateTimestamp(req.Timestamp, "timestamp"); err != nil {
|
updatedAt, err := tracefunnel.ValidateAndConvertTimestamp(req.Timestamp)
|
||||||
render.Error(rw,
|
if err != nil {
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
render.Error(rw, err)
|
||||||
errors.CodeInvalidInput,
|
|
||||||
"timestamp is invalid: %v", err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
funnel, err := handler.module.Get(r.Context(), req.FunnelID.String())
|
funnel, err := handler.module.Get(r.Context(), req.FunnelID.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(rw,
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
errors.CodeInvalidInput,
|
||||||
errors.CodeInvalidInput,
|
"funnel not found: %v", err))
|
||||||
"funnel not found: %v", err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if name is being updated and if it already exists
|
|
||||||
if req.Name != "" && req.Name != funnel.Name {
|
if req.Name != "" && req.Name != funnel.Name {
|
||||||
funnels, err := handler.module.List(r.Context(), orgID)
|
if err := handler.checkDuplicateName(r.Context(), claims.OrgID, req.Name, funnel.ID.String()); err != nil {
|
||||||
if err != nil {
|
render.Error(rw, err)
|
||||||
render.Error(rw,
|
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
|
||||||
errors.CodeInvalidInput,
|
|
||||||
"failed to list funnels: %v", err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range funnels {
|
|
||||||
if f.Name == req.Name {
|
|
||||||
render.Error(rw,
|
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
|
||||||
errors.CodeInvalidInput,
|
|
||||||
"a funnel with name '%s' already exists in this organization", req.Name))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process each step in the request
|
steps, err := tracefunnel.ProcessFunnelSteps(req.Steps)
|
||||||
for i := range req.Steps {
|
if err != nil {
|
||||||
if req.Steps[i].Order < 1 {
|
render.Error(rw, err)
|
||||||
req.Steps[i].Order = int64(i + 1) // Default to sequential ordering if not specified
|
|
||||||
}
|
|
||||||
// Generate a new UUID for the step if it doesn't have one
|
|
||||||
if req.Steps[i].Id.IsZero() {
|
|
||||||
newUUID := valuer.GenerateUUID()
|
|
||||||
req.Steps[i].Id = newUUID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tracefunnel.ValidateFunnelSteps(req.Steps); err != nil {
|
|
||||||
render.Error(rw,
|
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
|
||||||
errors.CodeInvalidInput,
|
|
||||||
"invalid funnel steps: %v", err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize step orders
|
funnel.Steps = steps
|
||||||
req.Steps = tracefunnel.NormalizeFunnelSteps(req.Steps)
|
funnel.UpdatedAt = updatedAt
|
||||||
|
funnel.UpdatedBy = claims.UserID
|
||||||
// Update the funnel with new steps
|
|
||||||
funnel.Steps = req.Steps
|
|
||||||
funnel.UpdatedAt = time.Unix(0, req.Timestamp*1000000) // Convert to nanoseconds
|
|
||||||
funnel.UpdatedBy = userID
|
|
||||||
|
|
||||||
if req.Name != "" {
|
if req.Name != "" {
|
||||||
funnel.Name = req.Name
|
funnel.Name = req.Name
|
||||||
@ -163,39 +121,22 @@ func (handler *handler) UpdateSteps(rw http.ResponseWriter, r *http.Request) {
|
|||||||
funnel.Description = req.Description
|
funnel.Description = req.Description
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update funnel in database
|
if err := handler.module.Update(r.Context(), funnel, claims.UserID); err != nil {
|
||||||
err = handler.module.Update(r.Context(), funnel, userID)
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||||
if err != nil {
|
errors.CodeInvalidInput,
|
||||||
render.Error(rw,
|
"failed to update funnel in database: %v", err))
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
|
||||||
errors.CodeInvalidInput,
|
|
||||||
"failed to update funnel in database: %v", err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the updated funnel to return in response
|
|
||||||
updatedFunnel, err := handler.module.Get(r.Context(), funnel.ID.String())
|
updatedFunnel, err := handler.module.Get(r.Context(), funnel.ID.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(rw,
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
errors.CodeInvalidInput,
|
||||||
errors.CodeInvalidInput,
|
"failed to get updated funnel: %v", err))
|
||||||
"failed to get updated funnel: %v", err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response := tf.FunnelResponse{
|
response := tracefunnel.ConstructFunnelResponse(updatedFunnel, claims)
|
||||||
FunnelName: updatedFunnel.Name,
|
|
||||||
FunnelID: updatedFunnel.ID.String(),
|
|
||||||
Steps: updatedFunnel.Steps,
|
|
||||||
CreatedAt: updatedFunnel.CreatedAt.UnixNano() / 1000000,
|
|
||||||
CreatedBy: updatedFunnel.CreatedBy,
|
|
||||||
OrgID: updatedFunnel.OrgID.String(),
|
|
||||||
UpdatedBy: userID,
|
|
||||||
UpdatedAt: updatedFunnel.UpdatedAt.UnixNano() / 1000000,
|
|
||||||
Description: updatedFunnel.Description,
|
|
||||||
UserEmail: claims.Email,
|
|
||||||
}
|
|
||||||
|
|
||||||
render.Success(rw, http.StatusOK, response)
|
render.Success(rw, http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,45 +147,38 @@ func (handler *handler) UpdateFunnel(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
claims, err := tracefunnel.GetClaims(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(rw, err)
|
render.Error(rw, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userID := claims.UserID
|
|
||||||
orgID := claims.OrgID
|
|
||||||
|
|
||||||
if err := tracefunnel.ValidateTimestamp(req.Timestamp, "timestamp"); err != nil {
|
updatedAt, err := tracefunnel.ValidateAndConvertTimestamp(req.Timestamp)
|
||||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "timestamp is invalid: %v", err))
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
funnelID := vars["funnel_id"]
|
funnelID := vars["funnel_id"]
|
||||||
|
|
||||||
funnel, err := handler.module.Get(r.Context(), funnelID)
|
funnel, err := handler.module.Get(r.Context(), funnelID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "funnel not found: %v", err))
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||||
|
errors.CodeInvalidInput,
|
||||||
|
"funnel not found: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if name is being updated and if it already exists
|
|
||||||
if req.Name != "" && req.Name != funnel.Name {
|
if req.Name != "" && req.Name != funnel.Name {
|
||||||
funnels, err := handler.module.List(r.Context(), orgID)
|
if err := handler.checkDuplicateName(r.Context(), claims.OrgID, req.Name, funnel.ID.String()); err != nil {
|
||||||
if err != nil {
|
render.Error(rw, err)
|
||||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to list funnels: %v", err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range funnels {
|
|
||||||
if f.Name == req.Name {
|
|
||||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "a funnel with name '%s' already exists in this organization", req.Name))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
funnel.UpdatedAt = time.Unix(0, req.Timestamp*1000000) // Convert to nanoseconds
|
funnel.UpdatedAt = updatedAt
|
||||||
funnel.UpdatedBy = userID
|
funnel.UpdatedBy = claims.UserID
|
||||||
|
|
||||||
if req.Name != "" {
|
if req.Name != "" {
|
||||||
funnel.Name = req.Name
|
funnel.Name = req.Name
|
||||||
@ -253,74 +187,43 @@ func (handler *handler) UpdateFunnel(rw http.ResponseWriter, r *http.Request) {
|
|||||||
funnel.Description = req.Description
|
funnel.Description = req.Description
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.module.Update(r.Context(), funnel, userID)
|
if err := handler.module.Update(r.Context(), funnel, claims.UserID); err != nil {
|
||||||
if err != nil {
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to update funnel in database: %v", err))
|
errors.CodeInvalidInput,
|
||||||
|
"failed to update funnel in database: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the updated funnel to return in response
|
|
||||||
updatedFunnel, err := handler.module.Get(r.Context(), funnel.ID.String())
|
updatedFunnel, err := handler.module.Get(r.Context(), funnel.ID.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to get updated funnel: %v", err))
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||||
|
errors.CodeInvalidInput,
|
||||||
|
"failed to get updated funnel: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response := tf.FunnelResponse{
|
response := tracefunnel.ConstructFunnelResponse(updatedFunnel, claims)
|
||||||
FunnelName: updatedFunnel.Name,
|
|
||||||
FunnelID: updatedFunnel.ID.String(),
|
|
||||||
Steps: updatedFunnel.Steps,
|
|
||||||
CreatedAt: updatedFunnel.CreatedAt.UnixNano() / 1000000,
|
|
||||||
CreatedBy: updatedFunnel.CreatedBy,
|
|
||||||
OrgID: updatedFunnel.OrgID.String(),
|
|
||||||
UpdatedBy: userID,
|
|
||||||
UpdatedAt: updatedFunnel.UpdatedAt.UnixNano() / 1000000,
|
|
||||||
Description: updatedFunnel.Description,
|
|
||||||
UserEmail: claims.Email,
|
|
||||||
}
|
|
||||||
|
|
||||||
render.Success(rw, http.StatusOK, response)
|
render.Success(rw, http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (handler *handler) List(rw http.ResponseWriter, r *http.Request) {
|
func (handler *handler) List(rw http.ResponseWriter, r *http.Request) {
|
||||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
claims, err := tracefunnel.GetClaims(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(rw,
|
render.Error(rw, err)
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
|
||||||
errors.CodeInvalidInput,
|
|
||||||
"unauthenticated"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
orgID := claims.OrgID
|
funnels, err := handler.module.List(r.Context(), claims.OrgID)
|
||||||
funnels, err := handler.module.List(r.Context(), orgID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(rw,
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
errors.CodeInvalidInput,
|
||||||
errors.CodeInvalidInput,
|
"failed to list funnels: %v", err))
|
||||||
"failed to list funnels: %v", err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var response []tf.FunnelResponse
|
var response []tf.FunnelResponse
|
||||||
for _, f := range funnels {
|
for _, f := range funnels {
|
||||||
funnelResp := tf.FunnelResponse{
|
response = append(response, tracefunnel.ConstructFunnelResponse(f, claims))
|
||||||
FunnelName: f.Name,
|
|
||||||
FunnelID: f.ID.String(),
|
|
||||||
CreatedAt: f.CreatedAt.UnixNano() / 1000000,
|
|
||||||
CreatedBy: f.CreatedBy,
|
|
||||||
OrgID: f.OrgID.String(),
|
|
||||||
UpdatedAt: f.UpdatedAt.UnixNano() / 1000000,
|
|
||||||
UpdatedBy: f.UpdatedBy,
|
|
||||||
Description: f.Description,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get user email if available
|
|
||||||
if f.CreatedByUser != nil {
|
|
||||||
funnelResp.UserEmail = f.CreatedByUser.Email
|
|
||||||
}
|
|
||||||
|
|
||||||
response = append(response, funnelResp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render.Success(rw, http.StatusOK, response)
|
render.Success(rw, http.StatusOK, response)
|
||||||
@ -332,29 +235,14 @@ func (handler *handler) Get(rw http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
funnel, err := handler.module.Get(r.Context(), funnelID)
|
funnel, err := handler.module.Get(r.Context(), funnelID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(rw,
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
errors.CodeInvalidInput,
|
||||||
errors.CodeInvalidInput,
|
"funnel not found: %v", err))
|
||||||
"funnel not found: %v", err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response := tf.FunnelResponse{
|
claims, _ := tracefunnel.GetClaims(r) // Ignore error as email is optional
|
||||||
FunnelID: funnel.ID.String(),
|
response := tracefunnel.ConstructFunnelResponse(funnel, claims)
|
||||||
FunnelName: funnel.Name,
|
|
||||||
Description: funnel.Description,
|
|
||||||
CreatedAt: funnel.CreatedAt.UnixNano() / 1000000,
|
|
||||||
UpdatedAt: funnel.UpdatedAt.UnixNano() / 1000000,
|
|
||||||
CreatedBy: funnel.CreatedBy,
|
|
||||||
UpdatedBy: funnel.UpdatedBy,
|
|
||||||
OrgID: funnel.OrgID.String(),
|
|
||||||
Steps: funnel.Steps,
|
|
||||||
}
|
|
||||||
|
|
||||||
if funnel.CreatedByUser != nil {
|
|
||||||
response.UserEmail = funnel.CreatedByUser.Email
|
|
||||||
}
|
|
||||||
|
|
||||||
render.Success(rw, http.StatusOK, response)
|
render.Success(rw, http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,12 +250,10 @@ func (handler *handler) Delete(rw http.ResponseWriter, r *http.Request) {
|
|||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
funnelID := vars["funnel_id"]
|
funnelID := vars["funnel_id"]
|
||||||
|
|
||||||
err := handler.module.Delete(r.Context(), funnelID)
|
if err := handler.module.Delete(r.Context(), funnelID); err != nil {
|
||||||
if err != nil {
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||||
render.Error(rw,
|
errors.CodeInvalidInput,
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
"failed to delete funnel: %v", err))
|
||||||
errors.CodeInvalidInput,
|
|
||||||
"failed to delete funnel: %v", err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,30 +263,23 @@ func (handler *handler) Delete(rw http.ResponseWriter, r *http.Request) {
|
|||||||
func (handler *handler) Save(rw http.ResponseWriter, r *http.Request) {
|
func (handler *handler) Save(rw http.ResponseWriter, r *http.Request) {
|
||||||
var req tf.FunnelRequest
|
var req tf.FunnelRequest
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
render.Error(rw,
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
errors.CodeInvalidInput,
|
||||||
errors.CodeInvalidInput,
|
"invalid request: %v", err))
|
||||||
"invalid request: %v", err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
claims, err := tracefunnel.GetClaims(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(rw,
|
render.Error(rw, err)
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
|
||||||
errors.CodeInvalidInput,
|
|
||||||
"unauthenticated"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
orgID := claims.OrgID
|
|
||||||
usrID := claims.UserID
|
|
||||||
|
|
||||||
funnel, err := handler.module.Get(r.Context(), req.FunnelID.String())
|
funnel, err := handler.module.Get(r.Context(), req.FunnelID.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(rw,
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
errors.CodeInvalidInput,
|
||||||
errors.CodeInvalidInput,
|
"funnel not found: %v", err))
|
||||||
"funnel not found: %v", err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,45 +287,48 @@ func (handler *handler) Save(rw http.ResponseWriter, r *http.Request) {
|
|||||||
if updateTimestamp == 0 {
|
if updateTimestamp == 0 {
|
||||||
updateTimestamp = time.Now().UnixMilli()
|
updateTimestamp = time.Now().UnixMilli()
|
||||||
} else if !tracefunnel.ValidateTimestampIsMilliseconds(updateTimestamp) {
|
} else if !tracefunnel.ValidateTimestampIsMilliseconds(updateTimestamp) {
|
||||||
render.Error(rw,
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
errors.CodeInvalidInput,
|
||||||
errors.CodeInvalidInput,
|
"timestamp must be in milliseconds format (13 digits)"))
|
||||||
"timestamp must be in milliseconds format (13 digits)"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
funnel.UpdatedAt = time.Unix(0, updateTimestamp*1000000) // Convert to nanoseconds
|
|
||||||
|
|
||||||
if req.UserID != "" {
|
updatedAt, err := tracefunnel.ValidateAndConvertTimestamp(updateTimestamp)
|
||||||
funnel.UpdatedBy = usrID
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
funnel.UpdatedAt = updatedAt
|
||||||
|
if req.UserID != "" {
|
||||||
|
funnel.UpdatedBy = claims.UserID
|
||||||
|
}
|
||||||
funnel.Description = req.Description
|
funnel.Description = req.Description
|
||||||
|
|
||||||
if err := handler.module.Save(r.Context(), funnel, funnel.UpdatedBy, orgID); err != nil {
|
if err := handler.module.Save(r.Context(), funnel, funnel.UpdatedBy, claims.OrgID); err != nil {
|
||||||
render.Error(rw,
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
errors.CodeInvalidInput,
|
||||||
errors.CodeInvalidInput,
|
"failed to save funnel: %v", err))
|
||||||
"failed to save funnel: %v", err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
createdAt, updatedAt, extraDataFromDB, err := handler.module.GetFunnelMetadata(r.Context(), funnel.ID.String())
|
createdAtMillis, updatedAtMillis, extraDataFromDB, err := handler.module.GetFunnelMetadata(r.Context(), funnel.ID.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Error(rw,
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput,
|
||||||
errors.Newf(errors.TypeInvalidInput,
|
errors.CodeInvalidInput,
|
||||||
errors.CodeInvalidInput,
|
"failed to get funnel metadata: %v", err))
|
||||||
"failed to get funnel metadata: %v", err))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := tf.FunnelResponse{
|
resp := tf.FunnelResponse{
|
||||||
FunnelName: funnel.Name,
|
FunnelName: funnel.Name,
|
||||||
CreatedAt: createdAt,
|
CreatedAt: createdAtMillis,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAtMillis,
|
||||||
CreatedBy: funnel.CreatedBy,
|
CreatedBy: funnel.CreatedBy,
|
||||||
UpdatedBy: funnel.UpdatedBy,
|
UpdatedBy: funnel.UpdatedBy,
|
||||||
OrgID: funnel.OrgID.String(),
|
OrgID: funnel.OrgID.String(),
|
||||||
Description: extraDataFromDB,
|
Description: extraDataFromDB,
|
||||||
|
UserEmail: claims.Email,
|
||||||
}
|
}
|
||||||
|
|
||||||
render.Success(rw, http.StatusOK, resp)
|
render.Success(rw, http.StatusOK, resp)
|
||||||
|
@ -2,8 +2,13 @@ package tracefunnel
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||||
tracefunnel "github.com/SigNoz/signoz/pkg/types/tracefunnel"
|
tracefunnel "github.com/SigNoz/signoz/pkg/types/tracefunnel"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateTimestamp validates a timestamp
|
// ValidateTimestamp validates a timestamp
|
||||||
@ -54,3 +59,63 @@ func NormalizeFunnelSteps(steps []tracefunnel.FunnelStep) []tracefunnel.FunnelSt
|
|||||||
|
|
||||||
return steps
|
return steps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetClaims(r *http.Request) (*authtypes.Claims, error) {
|
||||||
|
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Newf(errors.TypeInvalidInput,
|
||||||
|
errors.CodeInvalidInput,
|
||||||
|
"unauthenticated")
|
||||||
|
}
|
||||||
|
return &claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateAndConvertTimestamp(timestamp int64) (time.Time, error) {
|
||||||
|
if err := ValidateTimestamp(timestamp, "timestamp"); err != nil {
|
||||||
|
return time.Time{}, errors.Newf(errors.TypeInvalidInput,
|
||||||
|
errors.CodeInvalidInput,
|
||||||
|
"timestamp is invalid: %v", err)
|
||||||
|
}
|
||||||
|
return time.Unix(0, timestamp*1000000), nil // Convert to nanoseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConstructFunnelResponse(funnel *tracefunnel.Funnel, claims *authtypes.Claims) tracefunnel.FunnelResponse {
|
||||||
|
resp := tracefunnel.FunnelResponse{
|
||||||
|
FunnelName: funnel.Name,
|
||||||
|
FunnelID: funnel.ID.String(),
|
||||||
|
Steps: funnel.Steps,
|
||||||
|
CreatedAt: funnel.CreatedAt.UnixNano() / 1000000,
|
||||||
|
CreatedBy: funnel.CreatedBy,
|
||||||
|
OrgID: funnel.OrgID.String(),
|
||||||
|
UpdatedBy: funnel.UpdatedBy,
|
||||||
|
UpdatedAt: funnel.UpdatedAt.UnixNano() / 1000000,
|
||||||
|
Description: funnel.Description,
|
||||||
|
}
|
||||||
|
|
||||||
|
if funnel.CreatedByUser != nil {
|
||||||
|
resp.UserEmail = funnel.CreatedByUser.Email
|
||||||
|
} else if claims != nil {
|
||||||
|
resp.UserEmail = claims.Email
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProcessFunnelSteps(steps []tracefunnel.FunnelStep) ([]tracefunnel.FunnelStep, error) {
|
||||||
|
for i := range steps {
|
||||||
|
if steps[i].Order < 1 {
|
||||||
|
steps[i].Order = int64(i + 1)
|
||||||
|
}
|
||||||
|
if steps[i].Id.IsZero() {
|
||||||
|
steps[i].Id = valuer.GenerateUUID()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ValidateFunnelSteps(steps); err != nil {
|
||||||
|
return nil, errors.Newf(errors.TypeInvalidInput,
|
||||||
|
errors.CodeInvalidInput,
|
||||||
|
"invalid funnel steps: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return NormalizeFunnelSteps(steps), nil
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user