Skip to content

Commit cffa7ec

Browse files
dmashudamoribellamytvarney13brianvans
authored
feat: view SDK events on ldcli dev server (#596)
* Add endpoint * Update logging * Add run configs * Fix printly * Fix precommit * fix typo in function name * SSE plumbing * observer pattern * rm nil check * Add toggle for new "events" section on SPA. * modes for SPA * Revert "modes for SPA" This reverts commit 0774bfc. * Revert "Add toggle for new "events" section on SPA." This reverts commit 4a251b4. * update git ignore * rename app to flagpage * finish multi-page refactor * Revert "finish multi-page refactor" This reverts commit 62b25bf. * Revert "rename app to flagpage" This reverts commit 9731ced. * add buttons to swap * Add event splitting * Switch to raw message * initial stream page * Add kind selection * take event rendering to be its own component * list * add basic tabularization of summary events * more context types * add react router * clean up buttons to be dropdown * add feature events to list * fix refresh without dev server * Add basic sql for event writting * Add query * Add debug session * Add event store * Add query * remove random text * enable feature events to be visible based on if a flag is overridden or not * Add sqllite writter * ui improvements * make feature events more visible * clean up link column * add pagination for debug events * Add enpoint for debug sessions * dark mode css * Fix data parsing * streaming css * Fix linting * add streaming button * timestamp fallback * rename key to target * refactor events table * hideButtonSometimes * Up the limit * remove limit * Vibes * Vibes: add debug sessions page * remove per event filtering * more parsing of context kinds * remove back link for debug session * css * bigger boxes * handle empty list * Add debug session deletion * Add delete endpoint * Handle debug sessions * copy button improvements * Clean up empty debug session on startup * add filter for debug session page * Don't log json raw message * new search * remove explicit any * more lint * better types * lint * lint * more test * TS * dist * prettier * Fix sql semantics * Add overrides * remove run configs * fix clipboard link * dist * Docs * Move event base * Move events api to its own package because its a cross-cutting concern * Use launchdpad components for everything * Fix debug sessions page zero state crash * Use event timestamps over now() where possible * Heading > h3 * Fix issue where some pages dont re-render Resolves an issue where viewing a debug session and then going back in history caused pages to stop re-rendering * Linter * Revive the favicon by skipping SPA for /ui/*.svg * Remove log spam --------- Co-authored-by: Mori Bellamy <[email protected]> Co-authored-by: Tom Varney <[email protected]> Co-authored-by: Brian Van Staalduine <[email protected]>
1 parent 2d8fdff commit cffa7ec

35 files changed

+2601
-109
lines changed

internal/dev_server/api/api.yaml

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,92 @@ paths:
188188
$ref: "#/components/responses/ErrorResponse"
189189
400:
190190
$ref: "#/components/responses/ErrorResponse"
191+
/debug-sessions:
192+
get:
193+
operationId: getDebugSessions
194+
summary: list all debug sessions with event counts
195+
parameters:
196+
- name: limit
197+
in: query
198+
description: limit the number of debug sessions returned
199+
required: false
200+
schema:
201+
type: integer
202+
default: 50
203+
- name: offset
204+
in: query
205+
description: offset for pagination
206+
required: false
207+
schema:
208+
type: integer
209+
default: 0
210+
responses:
211+
200:
212+
description: OK. List of debug sessions
213+
content:
214+
application/json:
215+
schema:
216+
$ref: "#/components/schemas/DebugSessionsPage"
217+
400:
218+
$ref: "#/components/responses/ErrorResponse"
219+
/debug-sessions/{debugSessionKey}:
220+
delete:
221+
operationId: deleteDebugSession
222+
summary: delete a specific debug session and all its events
223+
parameters:
224+
- name: debugSessionKey
225+
in: path
226+
required: true
227+
schema:
228+
type: string
229+
description: unique identifier for the debug session
230+
responses:
231+
204:
232+
description: OK. Debug session and all associated events were deleted
233+
404:
234+
$ref: "#/components/responses/ErrorResponse"
235+
/debug-sessions/{debugSessionKey}/events:
236+
get:
237+
operationId: getDebugSessionEvents
238+
summary: get events for a specific debug session
239+
parameters:
240+
- name: debugSessionKey
241+
in: path
242+
required: true
243+
schema:
244+
type: string
245+
description: unique identifier for the debug session
246+
- name: kind
247+
in: query
248+
description: filter events by kind (e.g., summary, diagnostic, feature)
249+
required: false
250+
schema:
251+
type: string
252+
- name: limit
253+
in: query
254+
description: limit the number of events returned
255+
required: false
256+
schema:
257+
type: integer
258+
default: 50
259+
- name: offset
260+
in: query
261+
description: offset for pagination
262+
required: false
263+
schema:
264+
type: integer
265+
default: 0
266+
responses:
267+
200:
268+
description: OK. List of events for the debug session
269+
content:
270+
application/json:
271+
schema:
272+
$ref: "#/components/schemas/EventsPage"
273+
404:
274+
$ref: "#/components/responses/ErrorResponse"
275+
400:
276+
$ref: "#/components/responses/ErrorResponse"
191277
components:
192278
parameters:
193279
flagKey:
@@ -294,6 +380,89 @@ components:
294380
type: string
295381
name:
296382
type: string
383+
DebugSession:
384+
description: Debug session with event count
385+
type: object
386+
required:
387+
- key
388+
- written_at
389+
- event_count
390+
properties:
391+
key:
392+
type: string
393+
description: unique identifier for the debug session
394+
written_at:
395+
type: string
396+
format: date-time
397+
description: timestamp when the debug session was created
398+
event_count:
399+
type: integer
400+
format: int64
401+
description: number of events associated with this debug session
402+
DebugSessionsPage:
403+
description: Paginated response of debug sessions
404+
type: object
405+
required:
406+
- sessions
407+
- total_count
408+
- has_more
409+
properties:
410+
sessions:
411+
type: array
412+
items:
413+
$ref: "#/components/schemas/DebugSession"
414+
description: list of debug sessions
415+
total_count:
416+
type: integer
417+
format: int64
418+
description: total number of debug sessions available
419+
has_more:
420+
type: boolean
421+
description: whether there are more results available
422+
Event:
423+
description: A stored event with metadata
424+
type: object
425+
required:
426+
- id
427+
- written_at
428+
- kind
429+
- data
430+
properties:
431+
id:
432+
type: integer
433+
format: int64
434+
description: unique identifier for the event
435+
written_at:
436+
type: string
437+
format: date-time
438+
description: timestamp when the event was written
439+
kind:
440+
type: string
441+
description: type of event (e.g., summary, diagnostic, feature)
442+
data:
443+
type: object
444+
description: raw event data as JSON
445+
x-go-type: json.RawMessage
446+
EventsPage:
447+
description: Paginated response of events
448+
type: object
449+
required:
450+
- events
451+
- total_count
452+
- has_more
453+
properties:
454+
events:
455+
type: array
456+
items:
457+
$ref: "#/components/schemas/Event"
458+
description: list of events
459+
total_count:
460+
type: integer
461+
format: int64
462+
description: total number of events available
463+
has_more:
464+
type: boolean
465+
description: whether there are more results available
297466
responses:
298467
FlagOverride:
299468
description: Flag override
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package api
2+
3+
import (
4+
"context"
5+
6+
"github.com/launchdarkly/ldcli/internal/dev_server/model"
7+
)
8+
9+
func (s server) DeleteDebugSession(ctx context.Context, request DeleteDebugSessionRequestObject) (DeleteDebugSessionResponseObject, error) {
10+
eventStore := model.EventStoreFromContext(ctx)
11+
12+
// Delete the debug session
13+
err := eventStore.DeleteDebugSession(ctx, request.DebugSessionKey)
14+
if err != nil {
15+
return nil, err
16+
}
17+
18+
return DeleteDebugSession204Response{}, nil
19+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package events
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"log"
7+
"net/http"
8+
9+
"github.com/google/uuid"
10+
"github.com/launchdarkly/ldcli/internal/dev_server/model"
11+
"github.com/pkg/errors"
12+
13+
"github.com/launchdarkly/ldcli/internal/dev_server/sdk"
14+
)
15+
16+
type sdkEventObserver struct {
17+
ctx context.Context
18+
debugSessionKey string
19+
updateChan chan<- sdk.Message
20+
}
21+
22+
func newSdkEventObserver(updateChan chan<- sdk.Message, ctx context.Context) sdkEventObserver {
23+
debugSessionKey := uuid.New().String()
24+
db := model.EventStoreFromContext(ctx)
25+
err := db.CreateDebugSession(ctx, debugSessionKey)
26+
if err != nil {
27+
log.Printf("sdkEventObserver: error writting debug session: %v", err)
28+
}
29+
return sdkEventObserver{
30+
debugSessionKey: debugSessionKey,
31+
ctx: ctx,
32+
updateChan: updateChan,
33+
}
34+
}
35+
36+
func (o sdkEventObserver) Handle(message interface{}) {
37+
str, ok := message.(json.RawMessage)
38+
if !ok {
39+
return
40+
}
41+
42+
event := sdk.SDKEventBase{}
43+
err := json.Unmarshal(str, &event)
44+
if err != nil {
45+
log.Printf("sdkEventObserver: error unmarshaling event: %v", err)
46+
return
47+
}
48+
49+
db := model.EventStoreFromContext(o.ctx)
50+
51+
err = db.WriteEvent(o.ctx, o.debugSessionKey, event.Kind, str)
52+
if err != nil {
53+
log.Printf("sdkEventObserver: error writting event: %v", err)
54+
return
55+
}
56+
57+
o.updateChan <- sdk.Message{Event: sdk.TYPE_PUT, Data: str}
58+
}
59+
60+
func SdkEventsTeeHandler(writer http.ResponseWriter, request *http.Request) {
61+
updateChan, errChan := sdk.OpenStream(
62+
writer,
63+
request.Context().Done(),
64+
sdk.Message{Event: sdk.TYPE_PUT, Data: []byte{}},
65+
)
66+
defer close(updateChan)
67+
observers := model.GetObserversFromContext(request.Context())
68+
69+
observerId := observers.RegisterObserver(newSdkEventObserver(updateChan, request.Context()))
70+
defer func() {
71+
ok := observers.DeregisterObserver(observerId)
72+
if !ok {
73+
log.Printf("unable to remove observer")
74+
}
75+
}()
76+
77+
err := <-errChan
78+
if err != nil {
79+
sdk.WriteError(request.Context(), writer, errors.Wrap(err, "stream failure"))
80+
return
81+
}
82+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package events
2+
3+
import "github.com/gorilla/mux"
4+
5+
func BindRoutes(router *mux.Router) {
6+
router.HandleFunc("/events/tee", SdkEventsTeeHandler)
7+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package api
2+
3+
import (
4+
"context"
5+
"github.com/launchdarkly/ldcli/internal/dev_server/model"
6+
)
7+
8+
func (s server) GetDebugSessionEvents(ctx context.Context, request GetDebugSessionEventsRequestObject) (GetDebugSessionEventsResponseObject, error) {
9+
eventStore := model.EventStoreFromContext(ctx)
10+
11+
// Set default values for pagination
12+
limit := 50
13+
offset := 0
14+
15+
if request.Params.Limit != nil {
16+
limit = *request.Params.Limit
17+
}
18+
if request.Params.Offset != nil {
19+
offset = *request.Params.Offset
20+
}
21+
22+
// Validate parameters
23+
if limit < 1 || limit > 10000 {
24+
return GetDebugSessionEvents400JSONResponse{ErrorResponseJSONResponse{
25+
Code: "invalid_parameter",
26+
Message: "limit must be between 1 and 10000",
27+
}}, nil
28+
}
29+
30+
if offset < 0 {
31+
return GetDebugSessionEvents400JSONResponse{ErrorResponseJSONResponse{
32+
Code: "invalid_parameter",
33+
Message: "offset must be non-negative",
34+
}}, nil
35+
}
36+
37+
// Query events from the event store
38+
page, err := eventStore.QueryEvents(ctx, request.DebugSessionKey, request.Params.Kind, limit, offset)
39+
if err != nil {
40+
return nil, err
41+
}
42+
43+
// Convert model.Event to API Event
44+
var apiEvents []Event
45+
for _, event := range page.Events {
46+
apiEvents = append(apiEvents, Event{
47+
Id: event.ID,
48+
WrittenAt: event.WrittenAt,
49+
Kind: event.Kind,
50+
Data: event.Data,
51+
})
52+
}
53+
54+
response := EventsPage{
55+
Events: apiEvents,
56+
TotalCount: page.TotalCount,
57+
HasMore: page.HasMore,
58+
}
59+
60+
return GetDebugSessionEvents200JSONResponse(response), nil
61+
}

0 commit comments

Comments
 (0)