Skip to content

Commit 87acfe2

Browse files
committed
feat(frontend): implement session targeting
chore(frontend): load app filter chore(frontend): show paginator and loading bar chore(frontend): design rules table with dummy data chore(frontend): add sampling rule button component chore(frontend): move sampling type label chore(frontend): add create rule page with basic scaffolding chore(frontend): create dummy sampling rules config response chore(frontend): handle create and edit actions chore(frontend): implement event conditions UI chore(frontend): add sampling rate with publish button chore(frontend): implement session conditions UI chore(frontend): improve layout chore(frontend): minor improvements chore(frontend): align trash icons chore(frontend): padding and spacing improvements chore(frontend): add long attr key in dummy data chore(frontend): autoselect first event type chore(frontend): add predefined values for session attrs chore(frontend): add rule name chore(frontend): modify sampling rules schema chore(frontend): fix create page loading state chore(frontend): add connector to conditions chore(frontend): combine attributes in UI chore(frontend): modify samping rate and rule name UI chore(frontend): modify title UI chore(frontend): remove predefined values from session attrs chore(frontend): minor UI changes chore(frontend): refactor components chore(frontend): load sampling rule from server for editing chore(frontend): handle publish rule button state chore(frontend): fix ts errors chore(frontend): refactor navigation chore(frontend): handle sampling rules for trace and session chore(frontend): implement ui conditions to cel conversion chore(frontend): implement update sampling rule chore(frontend): fix attribute opertators state management chore(frontend): modify schema for rules chore(frontend): prepend event type to attrs chore(frontend): implement ui feedback and refactor into smaller components chore(frontend): add data control section chore(frontend): remove trace conditions from session targeting chore(frontend): add a section header for conditions chore(frontend): improve sampling conditions UI styling - Make Configure conditions title match Event/Session conditions style - Update Event/Session conditions titles to be smaller and secondary (text-gray-500) - Add hover background to condition containers for better interactivity - Align connector line colors with border colors for visual consistency chore(frontend): refactor to session targeting chore(frontend): implement more ui feedback chore(frontend): remove unsused code chore(frontend): refactor edit session targeting route chore(frontend): update input fields design chore(frontend): minor spacing changes chore(backend): refactor rules schema chore(backend): increase max conditions to 10 chore(backend): add spacing between event and session conditions chore(backend): remove unused component chore(backend): rename components and files to use session targeting chore(frontend): fix save button state chore(frontend): change scampling rate stpper to 0.01 chore(frontend): change sampling rate to % chore(frontend): fix cel generation chore(frontend): fix sampling rate chore(frontend): fix url to use session targeting chore(frontend): remove unnecessary variable chore(frontend): fix state management chore(frontend): spacing updates chore(frontend): remove status badge chore(frontend): add toggle switch for status change chore(frontend): remove unused code chore(frontend): remove uneeded types file chore(frontend): fix CEL function name chore(frontend): move shared types chore(frontend): improve CEL parser and fix dropdown state sync - Implement enhanced CEL parser with direct condition output and improved error handling - Fix dropdown state synchronization when loading data from API responses - Convert boolean types to 'bool' for UI compatibility - Add operator mapping from CEL format to UI format chore(frontend): fix compilation errors chore(frontend): remove deprecated function usage chore(frontend): implement cel parsing and generation chore(frontend): refactor page state chore(frontend): remove validation chore(frontend): update swith design chore(frontend): remove uneeded padding chore(frontend): improve attribute input field hints chore(frontend): add validation for empty attribute values chore(frontend): improve variable name chore(frontend): simplify ud-attrs logic chore(frontend): add cel generator tests chore(frontend): fix compilation error chore(frontend): improve in-code documentation chore(frontend): add cel parser tests chore(frontend): refactor conditions file structure chore(frontend): reorganize session targeting page code chore(frontend): improve validation error message chore(frontend): add loading state and toast feedback on rule submission chore(frontend): fix compilation error chore(frontend): rename event condition props chore(frontend): add comment chore(frontend): rename label for toggle chore(frontend): fix toggle clickable area chore(frontend): handle redirection on publish or update rule chore(frontend): rename test descriptions chore(frontend): update conditions container color chore(frontend): add spacing below title chore(frontend): improve error message chore(frontend): rename function chore(frontend): improve attribute row sizes chore(frontend): move session targeting to settings chore(frontend): increase spacing chore(frontend): fix patch rule endpoint chore(frontend): update variable names chore(frontend): remove unused method argument chore(frontend): rename function chore(frontend): change logical operator size and color chore(frontend): update status badge padding chore(frontend): rename variables chore(android): remove uneeded check in event cel parsing chore(frontend): rename file chore(frontend): improve error message chore(frontend): add empty state chore(frontend): change pagination and fix sampling rate chore(frontend): implement deep compare to enable-disable save button chore(frontend): refactor page state to split it into more states chore(frontend): remove unnecessary usage of memo chore(frontend): remove unnecesary function chore(frontend): fix compilation error chore(frontend): split modified by and at into different columns chore(frontend): move cel to utils chore(frontend): create session targeting folder under components chore(frontend): logical operator color neutral-300 chore(frontend): modify dropdown select and reuse chore(frontend): add component tests for page state chore(frontend): add component tests for save rule chore(frontend): update table spacing chore(frontend): add test for add or remove attribute chore(frontend): remove dependency on dequal chore(backend): implement session targeting rules chore(backend): implement session targeting rules get API chore(frontend): fix handling of sampling rules API chore(frontend): fix query to get session targeting rules chore(frontend): updte table schema chore(frontend): fix empty state handling chore(backend): integrate get rule api chore(backend): integrate create rule chore(backend): integrate updte session targeting rules chore(backend): integrate session targeting dashboard config chore(frontend): fix failing test chore(backend): modify the get rule query chore(frontend): remove unused param chore(backend): rename functions chore(backend): fix email query chore(backend): rename funciton chore(backend): sort by last updated at timestamp chore(backend): fix rule creation failure chore(frontend): increase pagination limit to 50 chore(backend): sort attributes by key name chore(backend): remove uneeded opertator for string chore(backend): revert pagination limit change chore(backend): add hints to session attributes chore(backend): add comment chore(backend): add more comments chore(frontend): use new switch component chore(frontend): improve session targeting table chore(frontend): set pagination to 5 chore(backend): add validation chore(frontend): create type for session targeting config chore(frontend): refactor attribute naming chore(frontend): refactor sequence of funcitons chore(frontend): simplify props further
1 parent a1e1f25 commit 87acfe2

31 files changed

+5901
-3
lines changed

backend/api/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ func main() {
113113
apps.GET(":id/bugReports/:bugReportId", measure.GetBugReport)
114114
apps.PATCH(":id/bugReports/:bugReportId", measure.UpdateBugReportStatus)
115115
apps.GET(":id/alerts", measure.GetAlertsOverview)
116+
apps.GET(":id/sessionTargetingRules", measure.GetSessionTargetingRules)
117+
apps.GET(":id/sessionTargetingRules/:ruleId", measure.GetSessionTargetingRule)
118+
apps.POST(":id/sessionTargetingRules", measure.CreateSessionTargetingRule)
119+
apps.PATCH(":id/sessionTargetingRules/:ruleId", measure.UpdateSessionTargetingRule)
120+
apps.GET(":id/sessionTargetingRules/config", measure.GetSessionTargetingDashboardConfig)
116121
}
117122

118123
teams := r.Group("/teams", measure.ValidateAccessToken())

backend/api/measure/app.go

Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6457,3 +6457,385 @@ func GetAlertsOverview(c *gin.Context) {
64576457
},
64586458
})
64596459
}
6460+
6461+
func GetSessionTargetingRules(c *gin.Context) {
6462+
ctx := c.Request.Context()
6463+
id, err := uuid.Parse(c.Param("id"))
6464+
if err != nil {
6465+
msg := `id invalid or missing`
6466+
fmt.Println(msg, err)
6467+
c.JSON(http.StatusBadRequest, gin.H{"error": msg})
6468+
return
6469+
}
6470+
6471+
af := filter.AppFilter{
6472+
AppID: id,
6473+
}
6474+
6475+
if err := c.ShouldBindQuery(&af); err != nil {
6476+
msg := `failed to parse query parameters`
6477+
fmt.Println(msg, err)
6478+
c.JSON(http.StatusBadRequest, gin.H{
6479+
"error": msg,
6480+
"details": err.Error(),
6481+
})
6482+
return
6483+
}
6484+
6485+
if err := af.Expand(ctx); err != nil {
6486+
msg := `failed to expand filters`
6487+
fmt.Println(msg, err)
6488+
status := http.StatusInternalServerError
6489+
if errors.Is(err, pgx.ErrNoRows) {
6490+
status = http.StatusNotFound
6491+
}
6492+
c.JSON(status, gin.H{
6493+
"error": msg,
6494+
"details": err.Error(),
6495+
})
6496+
return
6497+
}
6498+
6499+
msg := "session targeting rules request validation failed"
6500+
if err := af.Validate(); err != nil {
6501+
fmt.Println(msg, err)
6502+
c.JSON(http.StatusBadRequest, gin.H{
6503+
"error": msg,
6504+
"details": err.Error(),
6505+
})
6506+
return
6507+
}
6508+
6509+
app := App{
6510+
ID: &id,
6511+
}
6512+
team, err := app.getTeam(ctx)
6513+
if err != nil {
6514+
msg := "failed to get team from app id"
6515+
fmt.Println(msg, err)
6516+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6517+
return
6518+
}
6519+
if team == nil {
6520+
msg := fmt.Sprintf("no team exists for app [%s]", app.ID)
6521+
c.JSON(http.StatusBadRequest, gin.H{"error": msg})
6522+
return
6523+
}
6524+
6525+
userId := c.GetString("userId")
6526+
okTeam, err := PerformAuthz(userId, team.ID.String(), *ScopeTeamRead)
6527+
if err != nil {
6528+
msg := `failed to perform authorization`
6529+
fmt.Println(msg, err)
6530+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6531+
return
6532+
}
6533+
6534+
okApp, err := PerformAuthz(userId, team.ID.String(), *ScopeAppRead)
6535+
if err != nil {
6536+
msg := `failed to perform authorization`
6537+
fmt.Println(msg, err)
6538+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6539+
return
6540+
}
6541+
6542+
if !okTeam || !okApp {
6543+
msg := `you are not authorized to access this app`
6544+
c.JSON(http.StatusForbidden, gin.H{"error": msg})
6545+
return
6546+
}
6547+
6548+
rules, next, previous, err := GetSTRules(ctx, &af)
6549+
if err != nil {
6550+
msg := "failed to get app's session targeting rules"
6551+
fmt.Println(msg, err)
6552+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6553+
return
6554+
}
6555+
6556+
c.JSON(http.StatusOK, gin.H{
6557+
"results": rules,
6558+
"meta": gin.H{
6559+
"next": next,
6560+
"previous": previous,
6561+
},
6562+
})
6563+
}
6564+
6565+
func GetSessionTargetingRule(c *gin.Context) {
6566+
ctx := c.Request.Context()
6567+
id, err := uuid.Parse(c.Param("id"))
6568+
if err != nil {
6569+
msg := `id invalid or missing`
6570+
fmt.Println(msg, err)
6571+
c.JSON(http.StatusBadRequest, gin.H{"error": msg})
6572+
return
6573+
}
6574+
6575+
ruleId := c.Param("ruleId")
6576+
6577+
app := App{
6578+
ID: &id,
6579+
}
6580+
team, err := app.getTeam(ctx)
6581+
if err != nil {
6582+
msg := "failed to get team from app id"
6583+
fmt.Println(msg, err)
6584+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6585+
return
6586+
}
6587+
if team == nil {
6588+
msg := fmt.Sprintf("no team exists for app [%s]", app.ID)
6589+
c.JSON(http.StatusBadRequest, gin.H{"error": msg})
6590+
return
6591+
}
6592+
6593+
userId := c.GetString("userId")
6594+
okTeam, err := PerformAuthz(userId, team.ID.String(), *ScopeTeamRead)
6595+
if err != nil {
6596+
msg := `failed to perform authorization`
6597+
fmt.Println(msg, err)
6598+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6599+
return
6600+
}
6601+
6602+
okApp, err := PerformAuthz(userId, team.ID.String(), *ScopeAppRead)
6603+
if err != nil {
6604+
msg := `failed to perform authorization`
6605+
fmt.Println(msg, err)
6606+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6607+
return
6608+
}
6609+
6610+
if !okTeam || !okApp {
6611+
msg := `you are not authorized to access this app`
6612+
c.JSON(http.StatusForbidden, gin.H{"error": msg})
6613+
return
6614+
}
6615+
6616+
trace, err := GetSTRuleById(ctx, ruleId)
6617+
if err != nil {
6618+
msg := "failed to get trace"
6619+
fmt.Println(msg, err)
6620+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6621+
return
6622+
}
6623+
6624+
c.JSON(http.StatusOK, trace)
6625+
}
6626+
6627+
func CreateSessionTargetingRule(c *gin.Context) {
6628+
ctx := c.Request.Context()
6629+
id, err := uuid.Parse(c.Param("id"))
6630+
if err != nil {
6631+
msg := `id invalid or missing`
6632+
fmt.Println(msg, err)
6633+
c.JSON(http.StatusBadRequest, gin.H{"error": msg})
6634+
return
6635+
}
6636+
6637+
app := App{
6638+
ID: &id,
6639+
}
6640+
team, err := app.getTeam(ctx)
6641+
if err != nil {
6642+
msg := "failed to get team from app id"
6643+
fmt.Println(msg, err)
6644+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6645+
return
6646+
}
6647+
if team == nil {
6648+
msg := fmt.Sprintf("no team exists for app [%s]", app.ID)
6649+
c.JSON(http.StatusBadRequest, gin.H{"error": msg})
6650+
return
6651+
}
6652+
6653+
userId := c.GetString("userId")
6654+
okTeam, err := PerformAuthz(userId, team.ID.String(), *ScopeTeamRead)
6655+
if err != nil {
6656+
msg := `failed to perform authorization`
6657+
fmt.Println(msg, err)
6658+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6659+
return
6660+
}
6661+
6662+
okApp, err := PerformAuthz(userId, team.ID.String(), *ScopeAppRead)
6663+
if err != nil {
6664+
msg := `failed to perform authorization`
6665+
fmt.Println(msg, err)
6666+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6667+
return
6668+
}
6669+
6670+
if !okTeam || !okApp {
6671+
msg := `you are not authorized to access this app`
6672+
c.JSON(http.StatusForbidden, gin.H{"error": msg})
6673+
return
6674+
}
6675+
6676+
var payload CreateSTRulePayload
6677+
if err := c.ShouldBindJSON(&payload); err != nil {
6678+
msg := `failed to parse session targeting rule json payload`
6679+
fmt.Println(msg, err)
6680+
c.JSON(http.StatusBadRequest, gin.H{"error": msg})
6681+
return
6682+
}
6683+
6684+
_, err = CreateSTRule(ctx, team.ID.String(), app.ID.String(), userId, payload)
6685+
if err != nil {
6686+
msg := "failed to create session targeting rule"
6687+
fmt.Println(msg, err)
6688+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6689+
return
6690+
}
6691+
6692+
c.JSON(http.StatusOK, gin.H{"ok": "done"})
6693+
}
6694+
6695+
func UpdateSessionTargetingRule(c *gin.Context) {
6696+
ctx := c.Request.Context()
6697+
id, err := uuid.Parse(c.Param("id"))
6698+
if err != nil {
6699+
msg := `id invalid or missing`
6700+
fmt.Println(msg, err)
6701+
c.JSON(http.StatusBadRequest, gin.H{"error": msg})
6702+
return
6703+
}
6704+
6705+
ruleId := c.Param("ruleId")
6706+
6707+
app := App{
6708+
ID: &id,
6709+
}
6710+
team, err := app.getTeam(ctx)
6711+
if err != nil {
6712+
msg := "failed to get team from app id"
6713+
fmt.Println(msg, err)
6714+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6715+
return
6716+
}
6717+
if team == nil {
6718+
msg := fmt.Sprintf("no team exists for app [%s]", app.ID)
6719+
c.JSON(http.StatusBadRequest, gin.H{"error": msg})
6720+
return
6721+
}
6722+
6723+
userId := c.GetString("userId")
6724+
okTeam, err := PerformAuthz(userId, team.ID.String(), *ScopeTeamRead)
6725+
if err != nil {
6726+
msg := `failed to perform authorization`
6727+
fmt.Println(msg, err)
6728+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6729+
return
6730+
}
6731+
6732+
okApp, err := PerformAuthz(userId, team.ID.String(), *ScopeAppRead)
6733+
if err != nil {
6734+
msg := `failed to perform authorization`
6735+
fmt.Println(msg, err)
6736+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6737+
return
6738+
}
6739+
6740+
if !okTeam || !okApp {
6741+
msg := `you are not authorized to access this app`
6742+
c.JSON(http.StatusForbidden, gin.H{"error": msg})
6743+
return
6744+
}
6745+
6746+
var payload UpdateSTRulePayload
6747+
if err := c.ShouldBindJSON(&payload); err != nil {
6748+
msg := `failed to parse session targeting rule json payload`
6749+
fmt.Println(msg, err)
6750+
c.JSON(http.StatusBadRequest, gin.H{"error": msg})
6751+
return
6752+
}
6753+
6754+
err = UpdateSTRule(ctx, team.ID.String(), app.ID.String(), userId, ruleId, payload)
6755+
if err != nil {
6756+
msg := "failed to update session targeting rule"
6757+
fmt.Println(msg, err)
6758+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6759+
return
6760+
}
6761+
6762+
c.JSON(http.StatusOK, gin.H{"ok": "done"})
6763+
}
6764+
6765+
func GetSessionTargetingDashboardConfig(c *gin.Context) {
6766+
ctx := c.Request.Context()
6767+
id, err := uuid.Parse(c.Param("id"))
6768+
if err != nil {
6769+
msg := `id invalid or missing`
6770+
fmt.Println(msg, err)
6771+
c.JSON(http.StatusBadRequest, gin.H{"error": msg})
6772+
return
6773+
}
6774+
6775+
app := App{
6776+
ID: &id,
6777+
}
6778+
6779+
if err := app.Populate(ctx); err != nil {
6780+
msg := `failed to fetch app details`
6781+
fmt.Println(msg, err)
6782+
status := http.StatusInternalServerError
6783+
6784+
if errors.Is(err, pgx.ErrNoRows) {
6785+
status = http.StatusNotFound
6786+
msg = fmt.Sprintf(`app with id %q does not exist`, app.ID)
6787+
}
6788+
6789+
c.JSON(status, gin.H{
6790+
"error": msg,
6791+
})
6792+
6793+
return
6794+
}
6795+
6796+
team, err := app.getTeam(ctx)
6797+
if err != nil {
6798+
msg := "failed to get team from app id"
6799+
fmt.Println(msg, err)
6800+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6801+
return
6802+
}
6803+
if team == nil {
6804+
msg := fmt.Sprintf("no team exists for app [%s]", app.ID)
6805+
c.JSON(http.StatusBadRequest, gin.H{"error": msg})
6806+
return
6807+
}
6808+
6809+
userId := c.GetString("userId")
6810+
okTeam, err := PerformAuthz(userId, team.ID.String(), *ScopeTeamRead)
6811+
if err != nil {
6812+
msg := `failed to perform authorization`
6813+
fmt.Println(msg, err)
6814+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6815+
return
6816+
}
6817+
6818+
okApp, err := PerformAuthz(userId, team.ID.String(), *ScopeAppRead)
6819+
if err != nil {
6820+
msg := `failed to perform authorization`
6821+
fmt.Println(msg, err)
6822+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6823+
return
6824+
}
6825+
6826+
if !okTeam || !okApp {
6827+
msg := `you are not authorized to access this app`
6828+
c.JSON(http.StatusForbidden, gin.H{"error": msg})
6829+
return
6830+
}
6831+
6832+
config, err := GetSTDashboardConfig(ctx, id, app.OSName)
6833+
if err != nil {
6834+
msg := `falid to retrieve dashboard config`
6835+
fmt.Println(msg, err)
6836+
c.JSON(http.StatusInternalServerError, gin.H{"error": msg})
6837+
return
6838+
}
6839+
6840+
c.JSON(http.StatusOK, gin.H{"result": config})
6841+
}

0 commit comments

Comments
 (0)