From 70ff487a59976c2f105c33317923ea461c480994 Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Mon, 11 Sep 2023 13:44:38 +1000 Subject: [PATCH 1/2] feat: ConsoleService.GetTimeline This introduces a 1:1 mapping from the DAL timeline query filters to an equivalent proto, and adds a `GetTimeline()` that uses it. `StreamTimeline()` hasn't been converted yet, but it should be relatively straightforward. --- Bitfile | 24 +- backend/common/log/api.go | 7 + backend/controller/console.go | 204 ++- backend/controller/internal/dal/dal_test.go | 10 +- backend/controller/internal/dal/events.go | 54 +- bin/{.bit-0.0.1.pkg => .bit-0.1.0.pkg} | 0 bin/bit | 2 +- .../client/src/features/timeline/Timeline.tsx | 12 +- .../src/features/timeline/TimelineIcon.tsx | 8 +- .../details/TimelineDeploymentDetails.tsx | 26 +- .../timeline/details/TimelineLogDetails.tsx | 4 +- .../protos/google/protobuf/timestamp_pb.ts | 2 +- .../metrics/v1/metrics_service_pb.ts | 2 +- .../proto/common/v1/common_pb.ts | 2 +- .../proto/metrics/v1/metrics_pb.ts | 2 +- .../proto/resource/v1/resource_pb.ts | 2 +- .../opentelemetry/proto/trace/v1/trace_pb.ts | 2 +- .../block/ftl/v1/console/console_connect.ts | 11 +- .../xyz/block/ftl/v1/console/console_pb.ts | 579 +++++- .../selected-timeline-entry-provider.tsx | 8 +- protos/xyz/block/ftl/v1/console/console.pb.go | 1596 +++++++++++++---- protos/xyz/block/ftl/v1/console/console.proto | 87 +- .../pbconsoleconnect/console.connect.go | 25 + 23 files changed, 2192 insertions(+), 477 deletions(-) rename bin/{.bit-0.0.1.pkg => .bit-0.1.0.pkg} (100%) diff --git a/Bitfile b/Bitfile index 4c10e23508..2d530f5d17 100644 --- a/Bitfile +++ b/Bitfile @@ -8,7 +8,7 @@ SCHEMA_IN = backend/schema/schema.go backend/schema/protobuf.go \ cmd/ftl/cmd_schema.go SCHEMA_OUT = protos/xyz/block/ftl/v1/schema/schema.proto -PROTO_IN = **/*.proto **/buf.* protos/xyz/block/ftl/v1/schema/schema.proto +PROTO_IN = **/*.proto **/buf.* PROTO_OUT = protos/xyz/block/ftl/v1/ftlv1connect/ftl.connect.go \ protos/xyz/block/ftl/v1/schema/schema.pb.go \ protos/xyz/block/ftl/v1/console/console.pb.go \ @@ -28,7 +28,9 @@ KT_MVN_OUT = kotlin-runtime/ftl-runtime/target/ftl-runtime-1.0-SNAPSHOT-jar-with KT_RUNTIME_OUT = build/template/ftl/jars/ftl-runtime.jar CLIENT_OUT = console/client/dist/index.html -CLIENT_IN = console/client/**/* +CLIENT_IN = console/client/src/**/* +NODE_MODULES_OUT = console/client/node_modules +NODE_MODULES_IN = console/client/package{,-lock}.json #virtual release: # inputs: %{RELEASE}/ftl %{RELEASE}/ftl-controller %{RELEASE}/ftl-runner @@ -38,10 +40,10 @@ CLIENT_IN = console/client/**/* # Build all binaries implicit %{RELEASE}/%{1}: cmd/* - inputs: %{RELEASE} %{GO_SOURCES} + inputs: %{RELEASE} %{GO_SOURCES} %{CLIENT_OUT} build: go build -o %{OUT} -tags release -ldflags "-X main.version=%{VERSION}" ./cmd/%{1} -#%{RELEASE}/ftl-controller: %{RELEASE} %{GO_SOURCES} #%{CLIENT_OUT} +#%{RELEASE}/ftl-controller: %{RELEASE} %{GO_SOURCES} %{CLIENT_OUT} # build: go build -o %{OUT} -tags release -ldflags "-X main.version=%{VERSION}" ./cmd/ftl-controller %{SCHEMA_OUT}: %{SCHEMA_IN} @@ -70,8 +72,12 @@ implicit %{RELEASE}/%{1}: cmd/* %{COMMON_LOG_OUT}: %{COMMON_LOG_IN} build: go generate %{IN} -#%{CLIENT_OUT}: %{CLIENT_IN} -# cd console/client -# build: -# npm install -# npm run build +%{NODE_MODULES_OUT}: %{NODE_MODULES_IN} + cd console/client + build: npm --no-color --no-progress install + -clean # Don't clean node_modules + +%{CLIENT_OUT}: %{CLIENT_IN} %{NODE_MODULES_OUT} + cd console/client + build: npm --no-color run build + clean: rm -rf dist .parcel-cache diff --git a/backend/common/log/api.go b/backend/common/log/api.go index cfdac73db8..682c3f120d 100644 --- a/backend/common/log/api.go +++ b/backend/common/log/api.go @@ -40,6 +40,13 @@ func (l Level) Severity() int { return int(l) } +// ParseLevel parses a log level from text. +func ParseLevel(input string) (Level, error) { + var level Level + err := level.UnmarshalText([]byte(input)) + return level, err +} + type contextKey struct{} // FromContext retrieves the current logger from the context or panics diff --git a/backend/controller/console.go b/backend/controller/console.go index a1f7b25b12..2f7b71e1a4 100644 --- a/backend/controller/console.go +++ b/backend/controller/console.go @@ -11,6 +11,7 @@ import ( "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" + "github.com/TBD54566975/ftl/backend/common/log" "github.com/TBD54566975/ftl/backend/common/model" "github.com/TBD54566975/ftl/backend/common/slices" "github.com/TBD54566975/ftl/backend/controller/internal/dal" @@ -102,13 +103,13 @@ func (c *ConsoleService) GetModules(ctx context.Context, req *connect.Request[pb } func (c *ConsoleService) GetCalls(ctx context.Context, req *connect.Request[pbconsole.GetCallsRequest]) (*connect.Response[pbconsole.GetCallsResponse], error) { - events, err := c.dal.QueryEvents(ctx, time.Time{}, time.Now(), dal.FilterCall(types.None[string](), req.Msg.Module, types.Some(req.Msg.Verb))) + events, err := c.dal.QueryEvents(ctx, dal.FilterCall(types.None[string](), req.Msg.Module, types.Some(req.Msg.Verb))) if err != nil { return nil, errors.WithStack(err) } return connect.NewResponse(&pbconsole.GetCallsResponse{ - Calls: slices.Map(filterCallEvents(events), callEventToCall), + Calls: slices.Map(filterEvents[*dal.CallEvent](events), callEventToCall), }), nil } @@ -118,16 +119,31 @@ func (c *ConsoleService) GetRequestCalls(ctx context.Context, req *connect.Reque return nil, errors.WithStack(err) } - events, err := c.dal.QueryEvents(ctx, time.Time{}, time.Now(), dal.FilterRequests(requestKey)) + events, err := c.dal.QueryEvents(ctx, dal.FilterRequests(requestKey)) if err != nil { return nil, errors.WithStack(err) } return connect.NewResponse(&pbconsole.GetRequestCallsResponse{ - Calls: slices.Map(filterCallEvents(events), callEventToCall), + Calls: slices.Map(filterEvents[*dal.CallEvent](events), callEventToCall), }), nil } +func (c *ConsoleService) GetTimeline(ctx context.Context, req *connect.Request[pbconsole.TimelineQuery]) (*connect.Response[pbconsole.GetTimelineResponse], error) { + query, err := timelineQueryProtoToDAL(req.Msg) + if err != nil { + return nil, errors.WithStack(err) + } + results, err := c.dal.QueryEvents(ctx, query...) + if err != nil { + return nil, errors.WithStack(err) + } + response := &pbconsole.GetTimelineResponse{ + Events: slices.Map(results, eventDALToProto), + } + return connect.NewResponse(response), nil +} + func (c *ConsoleService) StreamTimeline(ctx context.Context, req *connect.Request[pbconsole.StreamTimelineRequest], stream *connect.ServerStream[pbconsole.StreamTimelineResponse]) error { // Default to 1 second interval if not specified. updateInterval := 1 * time.Second @@ -143,50 +159,26 @@ func (c *ConsoleService) StreamTimeline(ctx context.Context, req *connect.Reques } query = append(query, dal.FilterDeployments(deploymentName)) } + var lastEventTime time.Time + if req.Msg.AfterTime != nil { + lastEventTime = req.Msg.AfterTime.AsTime() + } else { + lastEventTime = time.Now() + } - lastEventTime := req.Msg.AfterTime.AsTime() for { thisRequestTime := time.Now() - events, err := c.dal.QueryEvents(ctx, lastEventTime, thisRequestTime, query...) + events, err := c.dal.QueryEvents(ctx, append(query, dal.FilterTimeRange(thisRequestTime, lastEventTime))...) if err != nil { return errors.WithStack(err) } - timelineEvents := filterTimelineEvents(events) - for index, timelineEvent := range timelineEvents { + for index, timelineEvent := range events { more := len(events) > index+1 - var err error - - switch event := timelineEvent.(type) { - case *dal.CallEvent: - err = stream.Send(&pbconsole.StreamTimelineResponse{ - TimeStamp: timestamppb.New(event.Time), - Id: event.ID, - Entry: &pbconsole.StreamTimelineResponse_Call{ - Call: callEventToCall(event), - }, - More: more, - }) - case *dal.LogEvent: - err = stream.Send(&pbconsole.StreamTimelineResponse{ - TimeStamp: timestamppb.New(event.Time), - Id: event.ID, - Entry: &pbconsole.StreamTimelineResponse_Log{ - Log: logEventToLogEntry(event), - }, - More: more, - }) - case *dal.DeploymentEvent: - err = stream.Send(&pbconsole.StreamTimelineResponse{ - TimeStamp: timestamppb.New(event.Time), - Id: event.ID, - Entry: &pbconsole.StreamTimelineResponse_Deployment{ - Deployment: deploymentEventToDeployment(event), - }, - More: more, - }) - } - + err := stream.Send(&pbconsole.StreamTimelineResponse{ + Event: eventDALToProto(timelineEvent), + More: more, + }) if err != nil { return errors.WithStack(err) } @@ -203,7 +195,7 @@ func (c *ConsoleService) StreamTimeline(ctx context.Context, req *connect.Reques func (c *ConsoleService) StreamLogs(ctx context.Context, req *connect.Request[pbconsole.StreamLogsRequest], stream *connect.ServerStream[pbconsole.StreamLogsResponse]) error { // Default to 1 second interval if not specified. updateInterval := 1 * time.Second - if req.Msg.UpdateInterval != nil && req.Msg.UpdateInterval.AsDuration() > time.Second { // Minimum 1s interval. + if interval := req.Msg.UpdateInterval; interval != nil && interval.AsDuration() > time.Second { // Minimum 1s interval. updateInterval = req.Msg.UpdateInterval.AsDuration() } @@ -215,16 +207,15 @@ func (c *ConsoleService) StreamLogs(ctx context.Context, req *connect.Request[pb } query = append(query, dal.FilterDeployments(deploymentName)) } - lastLogTime := req.Msg.AfterTime.AsTime() for { thisRequestTime := time.Now() - events, err := c.dal.QueryEvents(ctx, lastLogTime, thisRequestTime, query...) + events, err := c.dal.QueryEvents(ctx, append(query, dal.FilterTimeRange(thisRequestTime, lastLogTime))...) if err != nil { return errors.WithStack(err) } - logEvents := filterLogEvents(events) + logEvents := filterEvents[*dal.LogEvent](events) for index, log := range logEvents { var requestKey *string if r, ok := log.RequestKey.Get(); ok { @@ -309,6 +300,8 @@ func deploymentEventToDeployment(event *dal.DeploymentEvent) *pbconsole.Deployme eventType = pbconsole.DeploymentEventType_DEPLOYMENT_UPDATED case dal.DeploymentReplaced: eventType = pbconsole.DeploymentEventType_DEPLOYMENT_REPLACED + default: + panic(errors.Errorf("unknown deployment event type %v", event.Type)) } var replaced *string @@ -326,38 +319,121 @@ func deploymentEventToDeployment(event *dal.DeploymentEvent) *pbconsole.Deployme } } -func filterCallEvents(events []dal.Event) []*dal.CallEvent { - var filtered []*dal.CallEvent +func filterEvents[E dal.Event](events []dal.Event) []E { + var filtered []E for _, event := range events { - if call, ok := event.(*dal.CallEvent); ok { - filtered = append(filtered, call) + if e, ok := event.(E); ok { + filtered = append(filtered, e) } } return filtered } -func filterLogEvents(events []dal.Event) []*dal.LogEvent { - var filtered []*dal.LogEvent - for _, event := range events { - if log, ok := event.(*dal.LogEvent); ok { - filtered = append(filtered, log) +func timelineQueryProtoToDAL(pb *pbconsole.TimelineQuery) ([]dal.EventFilter, error) { + var query []dal.EventFilter + for _, filter := range pb.Filters { + switch filter := filter.Filter.(type) { + case *pbconsole.TimelineQuery_Filter_Deployments: + deploymentNames := make([]model.DeploymentName, 0, len(filter.Deployments.Deployments)) + for _, deployment := range filter.Deployments.Deployments { + deploymentName, err := model.ParseDeploymentName(deployment) + if err != nil { + return nil, connect.NewError(connect.CodeInvalidArgument, errors.WithStack(err)) + } + deploymentNames = append(deploymentNames, deploymentName) + } + query = append(query, dal.FilterDeployments(deploymentNames...)) + + case *pbconsole.TimelineQuery_Filter_Requests: + requestKeys := make([]model.IngressRequestKey, 0, len(filter.Requests.Requests)) + for _, request := range filter.Requests.Requests { + requestKey, err := model.ParseIngressRequestKey(request) + if err != nil { + return nil, connect.NewError(connect.CodeInvalidArgument, errors.WithStack(err)) + } + requestKeys = append(requestKeys, requestKey) + } + query = append(query, dal.FilterRequests(requestKeys...)) + + case *pbconsole.TimelineQuery_Filter_EventTypes: + eventTypes := make([]dal.EventType, 0, len(filter.EventTypes.EventTypes)) + for _, eventType := range filter.EventTypes.EventTypes { + switch eventType { + case pbconsole.EventType_EVENT_TYPE_CALL: + eventTypes = append(eventTypes, dal.EventTypeCall) + case pbconsole.EventType_EVENT_TYPE_LOG: + eventTypes = append(eventTypes, dal.EventTypeLog) + case pbconsole.EventType_EVENT_TYPE_DEPLOYMENT: + eventTypes = append(eventTypes, dal.EventTypeDeployment) + default: + return nil, connect.NewError(connect.CodeInvalidArgument, errors.Errorf("unknown event type %v", eventType)) + } + } + + case *pbconsole.TimelineQuery_Filter_LogLevel: + level := log.Level(filter.LogLevel.LogLevel) + if level < log.Trace || level > log.Error { + return nil, connect.NewError(connect.CodeInvalidArgument, errors.Errorf("unknown log level %v", filter.LogLevel.LogLevel)) + } + query = append(query, dal.FilterLogLevel(level)) + + case *pbconsole.TimelineQuery_Filter_Time: + var newerThan, olderThan time.Time + if filter.Time.NewerThan != nil { + newerThan = filter.Time.NewerThan.AsTime() + } + if filter.Time.OlderThan != nil { + olderThan = filter.Time.OlderThan.AsTime() + } + query = append(query, dal.FilterTimeRange(newerThan, olderThan)) + + case *pbconsole.TimelineQuery_Filter_Id: + var lowerThan, higherThan int64 + if filter.Id.LowerThan != nil { + lowerThan = *filter.Id.LowerThan + } + if filter.Id.HigherThan != nil { + higherThan = *filter.Id.HigherThan + } + query = append(query, dal.FilterIDRange(lowerThan, higherThan)) + + default: + return nil, connect.NewError(connect.CodeInvalidArgument, errors.Errorf("unknown filter %T", filter)) } } - return filtered + return query, nil } -func filterTimelineEvents(events []dal.Event) []dal.Event { - var filtered []dal.Event - for _, event := range events { - if _, ok := event.(*dal.LogEvent); ok { - filtered = append(filtered, event) +func eventDALToProto(event dal.Event) *pbconsole.TimelineEvent { + switch event := event.(type) { + case *dal.CallEvent: + return &pbconsole.TimelineEvent{ + TimeStamp: timestamppb.New(event.Time), + Id: event.ID, + Entry: &pbconsole.TimelineEvent_Call{ + Call: callEventToCall(event), + }, } - if _, ok := event.(*dal.CallEvent); ok { - filtered = append(filtered, event) + + case *dal.LogEvent: + return &pbconsole.TimelineEvent{ + TimeStamp: timestamppb.New(event.Time), + Id: event.ID, + Entry: &pbconsole.TimelineEvent_Log{ + Log: logEventToLogEntry(event), + }, } - if _, ok := event.(*dal.DeploymentEvent); ok { - filtered = append(filtered, event) + + case *dal.DeploymentEvent: + return &pbconsole.TimelineEvent{ + TimeStamp: timestamppb.New(event.Time), + Id: event.ID, + Entry: &pbconsole.TimelineEvent_Deployment{ + Deployment: deploymentEventToDeployment(event), + }, } + + default: + panic(errors.Errorf("unknown event type %T", event)) } - return filtered } diff --git a/backend/controller/internal/dal/dal_test.go b/backend/controller/internal/dal/dal_test.go index 706d4115f1..992b305b35 100644 --- a/backend/controller/internal/dal/dal_test.go +++ b/backend/controller/internal/dal/dal_test.go @@ -171,7 +171,7 @@ func TestDAL(t *testing.T) { }) t.Run("ExpireRunnerClaims", func(t *testing.T) { - time.Sleep(time.Millisecond * 200) + time.Sleep(time.Millisecond * 500) count, err := dal.ExpireRunnerClaims(ctx) assert.NoError(t, err) assert.Equal(t, 1, count) @@ -255,25 +255,25 @@ func TestDAL(t *testing.T) { t.Run("QueryEvents", func(t *testing.T) { t.Run("NoFilters", func(t *testing.T) { - events, err := dal.QueryEvents(ctx, time.Time{}, time.Now()) + events, err := dal.QueryEvents(ctx) assert.NoError(t, err) assertEventsEqual(t, []Event{expectedDeploymentEvent, callEvent, logEvent}, events) }) t.Run("ByDeployment", func(t *testing.T) { - events, err := dal.QueryEvents(ctx, time.Time{}, time.Now(), FilterDeployments(deploymentName)) + events, err := dal.QueryEvents(ctx, FilterDeployments(deploymentName)) assert.NoError(t, err) assertEventsEqual(t, []Event{expectedDeploymentEvent, callEvent, logEvent}, events) }) t.Run("ByCall", func(t *testing.T) { - events, err := dal.QueryEvents(ctx, time.Time{}, time.Now(), FilterTypes(EventTypeCall), FilterCall(types.None[string](), "time", types.None[string]())) + events, err := dal.QueryEvents(ctx, FilterTypes(EventTypeCall), FilterCall(types.None[string](), "time", types.None[string]())) assert.NoError(t, err) assertEventsEqual(t, []Event{callEvent}, events) }) t.Run("ByLogLevel", func(t *testing.T) { - events, err := dal.QueryEvents(ctx, time.Time{}, time.Now(), FilterTypes(EventTypeLog), FilterLogs(log.Trace)) + events, err := dal.QueryEvents(ctx, FilterTypes(EventTypeLog), FilterLogLevel(log.Trace)) assert.NoError(t, err) assertEventsEqual(t, []Event{logEvent}, events) }) diff --git a/backend/controller/internal/dal/events.go b/backend/controller/internal/dal/events.go index 3d0e545223..d76274e312 100644 --- a/backend/controller/internal/dal/events.go +++ b/backend/controller/internal/dal/events.go @@ -87,16 +87,20 @@ type eventFilterCall struct { } type eventFilter struct { - level *log.Level - calls []*eventFilterCall - types []EventType - deployments []model.DeploymentName - requests []sqltypes.Key + level *log.Level + calls []*eventFilterCall + types []EventType + deployments []model.DeploymentName + requests []sqltypes.Key + newerThan time.Time + olderThan time.Time + idHigherThan int64 + idLowerThan int64 } type EventFilter func(query *eventFilter) -func FilterLogs(level log.Level) EventFilter { +func FilterLogLevel(level log.Level) EventFilter { return func(query *eventFilter) { query.level = &level } @@ -133,6 +137,24 @@ func FilterTypes(types ...sql.EventType) EventFilter { } } +// FilterTimeRange filters events between the given times, inclusive. +// +// Either maybe be zero to indicate no upper or lower bound. +func FilterTimeRange(olderThan, newerThan time.Time) EventFilter { + return func(query *eventFilter) { + query.newerThan = newerThan + query.olderThan = olderThan + } +} + +// FilterIDRange filters events between the given IDs, inclusive. +func FilterIDRange(higherThan, lowerThan int64) EventFilter { + return func(query *eventFilter) { + query.idHigherThan = higherThan + query.idLowerThan = lowerThan + } +} + // The internal JSON payload of a call event. type eventCallJSON struct { DurationMS int64 `json:"duration_ms"` @@ -158,7 +180,7 @@ type eventRow struct { RequestKey types.Option[model.IngressRequestKey] } -func (d *DAL) QueryEvents(ctx context.Context, after, before time.Time, filters ...EventFilter) ([]Event, error) { +func (d *DAL) QueryEvents(ctx context.Context, filters ...EventFilter) ([]Event, error) { // Build query. q := `SELECT e.id AS id, d.name AS deployment_name, @@ -173,20 +195,32 @@ func (d *DAL) QueryEvents(ctx context.Context, after, before time.Time, filters FROM events e INNER JOIN deployments d on e.deployment_id = d.id LEFT JOIN ingress_requests ir on e.request_id = ir.id - WHERE time_stamp BETWEEN $1::TIMESTAMPTZ and $2::TIMESTAMPTZ + WHERE true -- The "true" is to simplify the ANDs below. ` filter := eventFilter{} for _, f := range filters { f(&filter) } - args := []any{after, before} - index := 3 + var args []any + index := 1 param := func(v any) int { args = append(args, v) index++ return index - 1 } + if !filter.olderThan.IsZero() { + q += fmt.Sprintf(" AND time_stamp <= $%d::TIMESTAMPTZ", param(filter.olderThan)) + } + if !filter.newerThan.IsZero() { + q += fmt.Sprintf(" AND time_stamp >= $%d::TIMESTAMPTZ", param(filter.newerThan)) + } + if filter.idHigherThan != 0 { + q += fmt.Sprintf(" AND id >= $%d::BIGINT", param(filter.idHigherThan)) + } + if filter.idLowerThan != 0 { + q += fmt.Sprintf(" AND id <= $%d::BIGINT", param(filter.idLowerThan)) + } if filter.deployments != nil { q += fmt.Sprintf(` AND d.name = ANY($%d::TEXT[])`, param(filter.deployments)) } diff --git a/bin/.bit-0.0.1.pkg b/bin/.bit-0.1.0.pkg similarity index 100% rename from bin/.bit-0.0.1.pkg rename to bin/.bit-0.1.0.pkg diff --git a/bin/bit b/bin/bit index 95e8cd8173..27c09bfcec 120000 --- a/bin/bit +++ b/bin/bit @@ -1 +1 @@ -.bit-0.0.1.pkg \ No newline at end of file +.bit-0.1.0.pkg \ No newline at end of file diff --git a/console/client/src/features/timeline/Timeline.tsx b/console/client/src/features/timeline/Timeline.tsx index ed645c6c05..05047eb5ba 100644 --- a/console/client/src/features/timeline/Timeline.tsx +++ b/console/client/src/features/timeline/Timeline.tsx @@ -2,7 +2,7 @@ import {Timestamp} from '@bufbuild/protobuf' import React from 'react' import {useClient} from '../../hooks/use-client.ts' import {ConsoleService} from '../../protos/xyz/block/ftl/v1/console/console_connect.ts' -import {StreamTimelineResponse} from '../../protos/xyz/block/ftl/v1/console/console_pb.ts' +import {TimelineEvent} from '../../protos/xyz/block/ftl/v1/console/console_pb.ts' import {SidePanelContext} from '../../providers/side-panel-provider.tsx' import {formatTimestampShort} from '../../utils/date.utils.ts' import {panelColor} from '../../utils/style.utils.ts' @@ -19,9 +19,9 @@ import {TimelineFilterBar} from './filters/TimelineFilterBar.tsx' export const Timeline = () => { const client = useClient(ConsoleService) const {openPanel, closePanel, isOpen} = React.useContext(SidePanelContext) - const [entries, setEntries] = React.useState([]) + const [entries, setEntries] = React.useState([]) const [selectedEntry, setSelectedEntry] = - React.useState(null) + React.useState(null) const [selectedEventTypes, setSelectedEventTypes] = React.useState([ 'log', 'call', @@ -45,8 +45,8 @@ export const Timeline = () => { {afterTime: Timestamp.fromDate(afterTime)}, {signal: abortController.signal} )) { - if (response.entry) { - setEntries(prevEntries => [response, ...prevEntries]) + if (response.event != null) { + setEntries(prevEntries => [response.event!, ...prevEntries]) } } } @@ -63,7 +63,7 @@ export const Timeline = () => { } }, [isOpen]) - const handleEntryClicked = (entry: StreamTimelineResponse) => { + const handleEntryClicked = (entry: TimelineEvent) => { if (selectedEntry === entry) { setSelectedEntry(null) closePanel() diff --git a/console/client/src/features/timeline/TimelineIcon.tsx b/console/client/src/features/timeline/TimelineIcon.tsx index 1ccb6ed993..e737e75324 100644 --- a/console/client/src/features/timeline/TimelineIcon.tsx +++ b/console/client/src/features/timeline/TimelineIcon.tsx @@ -5,10 +5,10 @@ import { PhoneCallback, RocketLaunch, } from '@mui/icons-material' -import {StreamTimelineResponse} from '../../protos/xyz/block/ftl/v1/console/console_pb' +import {TimelineEvent} from '../../protos/xyz/block/ftl/v1/console/console_pb' type Props = { - entry: StreamTimelineResponse + entry: TimelineEvent } export const logLevelIconColor = { @@ -20,7 +20,7 @@ export const logLevelIconColor = { } export const TimelineIcon: React.FC = ({entry}) => { - const iconColor = (entry: StreamTimelineResponse) => { + const iconColor = (entry: TimelineEvent) => { switch (entry.entry.case) { case 'call': return entry.entry.value.error ? 'text-red-600' : 'text-indigo-600' @@ -31,7 +31,7 @@ export const TimelineIcon: React.FC = ({entry}) => { } } - const icon = (entry: StreamTimelineResponse) => { + const icon = (entry: TimelineEvent) => { const iconSize = 20 switch (entry.entry.case) { case 'call': diff --git a/console/client/src/features/timeline/details/TimelineDeploymentDetails.tsx b/console/client/src/features/timeline/details/TimelineDeploymentDetails.tsx index d489358b7c..fac581989c 100644 --- a/console/client/src/features/timeline/details/TimelineDeploymentDetails.tsx +++ b/console/client/src/features/timeline/details/TimelineDeploymentDetails.tsx @@ -1,27 +1,33 @@ import {Timestamp} from '@bufbuild/protobuf' import { Deployment, - StreamTimelineResponse, + TimelineEvent, + DeploymentEventType, } from '../../../protos/xyz/block/ftl/v1/console/console_pb' import {classNames} from '../../../utils/react.utils' import {textColor} from '../../../utils/style.utils' import {TimelineTimestamp} from './TimelineTimestamp' type Props = { - entry: StreamTimelineResponse + entry: TimelineEvent deployment: Deployment } -export const deploymentTypeText = { - 0: 'Created', - 1: 'Updated', - 2: 'Replaced', +export const deploymentTypeText: {[key in DeploymentEventType]: string} = { + [DeploymentEventType.DEPLOYMENT_UNKNOWN]: 'Unknown', + [DeploymentEventType.DEPLOYMENT_CREATED]: 'Created', + [DeploymentEventType.DEPLOYMENT_UPDATED]: 'Updated', + [DeploymentEventType.DEPLOYMENT_REPLACED]: 'Replaced', } -export const deploymentTypeBadge = { - 0: 'text-green-600 bg-green-400/30 dark:text-green-300 dark:bg-green-700/10', - 1: 'text-blue-350 bg-blue-300/30 dark:text-blue-300 dark:bg-blue-700/30', - 2: 'text-indigo-600 bg-indigo-400/30 dark:text-indigo-300 dark:bg-indigo-700/10', +export const deploymentTypeBadge: {[key in DeploymentEventType]: string} = { + [DeploymentEventType.DEPLOYMENT_UNKNOWN]: '', + [DeploymentEventType.DEPLOYMENT_CREATED]: + 'text-green-600 bg-green-400/30 dark:text-green-300 dark:bg-green-700/10', + [DeploymentEventType.DEPLOYMENT_UPDATED]: + 'text-blue-350 bg-blue-300/30 dark:text-blue-300 dark:bg-blue-700/30', + [DeploymentEventType.DEPLOYMENT_REPLACED]: + 'text-indigo-600 bg-indigo-400/30 dark:text-indigo-300 dark:bg-indigo-700/10', } export const TimelineDeploymentDetails: React.FC = ({ diff --git a/console/client/src/features/timeline/details/TimelineLogDetails.tsx b/console/client/src/features/timeline/details/TimelineLogDetails.tsx index e2ba1b2c2b..e3bf89620d 100644 --- a/console/client/src/features/timeline/details/TimelineLogDetails.tsx +++ b/console/client/src/features/timeline/details/TimelineLogDetails.tsx @@ -2,7 +2,7 @@ import {Timestamp} from '@bufbuild/protobuf' import {CodeBlock} from '../../../components/CodeBlock' import { LogEntry, - StreamTimelineResponse, + TimelineEvent, } from '../../../protos/xyz/block/ftl/v1/console/console_pb' import {classNames} from '../../../utils/react.utils' import { @@ -13,7 +13,7 @@ import { import {TimelineTimestamp} from './TimelineTimestamp' type Props = { - entry: StreamTimelineResponse + entry: TimelineEvent log: LogEntry } diff --git a/console/client/src/protos/google/protobuf/timestamp_pb.ts b/console/client/src/protos/google/protobuf/timestamp_pb.ts index d74b510a0e..2284fe54cf 100644 --- a/console/client/src/protos/google/protobuf/timestamp_pb.ts +++ b/console/client/src/protos/google/protobuf/timestamp_pb.ts @@ -28,7 +28,7 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// @generated by protoc-gen-es v1.3.0 with parameter "target=ts" +// @generated by protoc-gen-es v1.3.1 with parameter "target=ts" // @generated from file google/protobuf/timestamp.proto (package google.protobuf, syntax proto3) /* eslint-disable */ // @ts-nocheck diff --git a/console/client/src/protos/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.ts b/console/client/src/protos/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.ts index 486b1a8e6a..d10f47902e 100644 --- a/console/client/src/protos/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.ts +++ b/console/client/src/protos/opentelemetry/proto/collector/metrics/v1/metrics_service_pb.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @generated by protoc-gen-es v1.3.0 with parameter "target=ts" +// @generated by protoc-gen-es v1.3.1 with parameter "target=ts" // @generated from file opentelemetry/proto/collector/metrics/v1/metrics_service.proto (package opentelemetry.proto.collector.metrics.v1, syntax proto3) /* eslint-disable */ // @ts-nocheck diff --git a/console/client/src/protos/opentelemetry/proto/common/v1/common_pb.ts b/console/client/src/protos/opentelemetry/proto/common/v1/common_pb.ts index 144ba24033..13b5b9d420 100644 --- a/console/client/src/protos/opentelemetry/proto/common/v1/common_pb.ts +++ b/console/client/src/protos/opentelemetry/proto/common/v1/common_pb.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @generated by protoc-gen-es v1.3.0 with parameter "target=ts" +// @generated by protoc-gen-es v1.3.1 with parameter "target=ts" // @generated from file opentelemetry/proto/common/v1/common.proto (package opentelemetry.proto.common.v1, syntax proto3) /* eslint-disable */ // @ts-nocheck diff --git a/console/client/src/protos/opentelemetry/proto/metrics/v1/metrics_pb.ts b/console/client/src/protos/opentelemetry/proto/metrics/v1/metrics_pb.ts index 76170a5aaa..52860efb74 100644 --- a/console/client/src/protos/opentelemetry/proto/metrics/v1/metrics_pb.ts +++ b/console/client/src/protos/opentelemetry/proto/metrics/v1/metrics_pb.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @generated by protoc-gen-es v1.3.0 with parameter "target=ts" +// @generated by protoc-gen-es v1.3.1 with parameter "target=ts" // @generated from file opentelemetry/proto/metrics/v1/metrics.proto (package opentelemetry.proto.metrics.v1, syntax proto3) /* eslint-disable */ // @ts-nocheck diff --git a/console/client/src/protos/opentelemetry/proto/resource/v1/resource_pb.ts b/console/client/src/protos/opentelemetry/proto/resource/v1/resource_pb.ts index d347d24ab7..c2c117bbd6 100644 --- a/console/client/src/protos/opentelemetry/proto/resource/v1/resource_pb.ts +++ b/console/client/src/protos/opentelemetry/proto/resource/v1/resource_pb.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @generated by protoc-gen-es v1.3.0 with parameter "target=ts" +// @generated by protoc-gen-es v1.3.1 with parameter "target=ts" // @generated from file opentelemetry/proto/resource/v1/resource.proto (package opentelemetry.proto.resource.v1, syntax proto3) /* eslint-disable */ // @ts-nocheck diff --git a/console/client/src/protos/opentelemetry/proto/trace/v1/trace_pb.ts b/console/client/src/protos/opentelemetry/proto/trace/v1/trace_pb.ts index 321e388aa9..dc729e8ef1 100644 --- a/console/client/src/protos/opentelemetry/proto/trace/v1/trace_pb.ts +++ b/console/client/src/protos/opentelemetry/proto/trace/v1/trace_pb.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// @generated by protoc-gen-es v1.3.0 with parameter "target=ts" +// @generated by protoc-gen-es v1.3.1 with parameter "target=ts" // @generated from file opentelemetry/proto/trace/v1/trace.proto (package opentelemetry.proto.trace.v1, syntax proto3) /* eslint-disable */ // @ts-nocheck diff --git a/console/client/src/protos/xyz/block/ftl/v1/console/console_connect.ts b/console/client/src/protos/xyz/block/ftl/v1/console/console_connect.ts index 8d286a6ec7..c3800a9a10 100644 --- a/console/client/src/protos/xyz/block/ftl/v1/console/console_connect.ts +++ b/console/client/src/protos/xyz/block/ftl/v1/console/console_connect.ts @@ -5,7 +5,7 @@ import { PingRequest, PingResponse } from "../ftl_pb.js"; import { MethodIdempotency, MethodKind } from "@bufbuild/protobuf"; -import { GetCallsRequest, GetCallsResponse, GetModulesRequest, GetModulesResponse, GetRequestCallsRequest, GetRequestCallsResponse, StreamLogsRequest, StreamLogsResponse, StreamTimelineRequest, StreamTimelineResponse } from "./console_pb.js"; +import { GetCallsRequest, GetCallsResponse, GetModulesRequest, GetModulesResponse, GetRequestCallsRequest, GetRequestCallsResponse, GetTimelineResponse, StreamLogsRequest, StreamLogsResponse, StreamTimelineRequest, StreamTimelineResponse, TimelineQuery } from "./console_pb.js"; /** * @generated from service xyz.block.ftl.v1.console.ConsoleService @@ -61,6 +61,15 @@ export const ConsoleService = { O: StreamTimelineResponse, kind: MethodKind.ServerStreaming, }, + /** + * @generated from rpc xyz.block.ftl.v1.console.ConsoleService.GetTimeline + */ + getTimeline: { + name: "GetTimeline", + I: TimelineQuery, + O: GetTimelineResponse, + kind: MethodKind.Unary, + }, /** * @generated from rpc xyz.block.ftl.v1.console.ConsoleService.StreamLogs */ diff --git a/console/client/src/protos/xyz/block/ftl/v1/console/console_pb.ts b/console/client/src/protos/xyz/block/ftl/v1/console/console_pb.ts index 0018403d86..3d02d3bfb7 100644 --- a/console/client/src/protos/xyz/block/ftl/v1/console/console_pb.ts +++ b/console/client/src/protos/xyz/block/ftl/v1/console/console_pb.ts @@ -7,30 +7,106 @@ import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialM import { Duration, Message, proto3, protoInt64, Timestamp } from "@bufbuild/protobuf"; import { Data as Data$1, Verb as Verb$1, VerbRef } from "../schema/schema_pb.js"; +/** + * @generated from enum xyz.block.ftl.v1.console.EventType + */ +export enum EventType { + /** + * @generated from enum value: EVENT_TYPE_UNKNOWN = 0; + */ + UNKNOWN = 0, + + /** + * @generated from enum value: EVENT_TYPE_DEPLOYMENT = 1; + */ + DEPLOYMENT = 1, + + /** + * @generated from enum value: EVENT_TYPE_CALL = 2; + */ + CALL = 2, + + /** + * @generated from enum value: EVENT_TYPE_LOG = 3; + */ + LOG = 3, +} +// Retrieve enum metadata with: proto3.getEnumType(EventType) +proto3.util.setEnumType(EventType, "xyz.block.ftl.v1.console.EventType", [ + { no: 0, name: "EVENT_TYPE_UNKNOWN" }, + { no: 1, name: "EVENT_TYPE_DEPLOYMENT" }, + { no: 2, name: "EVENT_TYPE_CALL" }, + { no: 3, name: "EVENT_TYPE_LOG" }, +]); + /** * @generated from enum xyz.block.ftl.v1.console.DeploymentEventType */ export enum DeploymentEventType { /** - * @generated from enum value: DEPLOYMENT_CREATED = 0; + * @generated from enum value: DEPLOYMENT_UNKNOWN = 0; */ - DEPLOYMENT_CREATED = 0, + DEPLOYMENT_UNKNOWN = 0, /** - * @generated from enum value: DEPLOYMENT_UPDATED = 1; + * @generated from enum value: DEPLOYMENT_CREATED = 1; */ - DEPLOYMENT_UPDATED = 1, + DEPLOYMENT_CREATED = 1, /** - * @generated from enum value: DEPLOYMENT_REPLACED = 2; + * @generated from enum value: DEPLOYMENT_UPDATED = 2; */ - DEPLOYMENT_REPLACED = 2, + DEPLOYMENT_UPDATED = 2, + + /** + * @generated from enum value: DEPLOYMENT_REPLACED = 3; + */ + DEPLOYMENT_REPLACED = 3, } // Retrieve enum metadata with: proto3.getEnumType(DeploymentEventType) proto3.util.setEnumType(DeploymentEventType, "xyz.block.ftl.v1.console.DeploymentEventType", [ - { no: 0, name: "DEPLOYMENT_CREATED" }, - { no: 1, name: "DEPLOYMENT_UPDATED" }, - { no: 2, name: "DEPLOYMENT_REPLACED" }, + { no: 0, name: "DEPLOYMENT_UNKNOWN" }, + { no: 1, name: "DEPLOYMENT_CREATED" }, + { no: 2, name: "DEPLOYMENT_UPDATED" }, + { no: 3, name: "DEPLOYMENT_REPLACED" }, +]); + +/** + * @generated from enum xyz.block.ftl.v1.console.LogLevel + */ +export enum LogLevel { + /** + * @generated from enum value: LOG_LEVEL_TRACE = 0; + */ + TRACE = 0, + + /** + * @generated from enum value: LOG_LEVEL_DEBUG = 1; + */ + DEBUG = 1, + + /** + * @generated from enum value: LOG_LEVEL_INFO = 2; + */ + INFO = 2, + + /** + * @generated from enum value: LOG_LEVEL_WARN = 3; + */ + WARN = 3, + + /** + * @generated from enum value: LOG_LEVEL_ERROR = 4; + */ + ERROR = 4, +} +// Retrieve enum metadata with: proto3.getEnumType(LogLevel) +proto3.util.setEnumType(LogLevel, "xyz.block.ftl.v1.console.LogLevel", [ + { no: 0, name: "LOG_LEVEL_TRACE" }, + { no: 1, name: "LOG_LEVEL_DEBUG" }, + { no: 2, name: "LOG_LEVEL_INFO" }, + { no: 3, name: "LOG_LEVEL_WARN" }, + { no: 4, name: "LOG_LEVEL_ERROR" }, ]); /** @@ -145,7 +221,7 @@ export class Deployment extends Message { /** * @generated from field: xyz.block.ftl.v1.console.DeploymentEventType event_type = 5; */ - eventType = DeploymentEventType.DEPLOYMENT_CREATED; + eventType = DeploymentEventType.DEPLOYMENT_UNKNOWN; /** * @generated from field: optional string replaced = 6; @@ -639,6 +715,375 @@ export class GetRequestCallsResponse extends Message { } } +/** + * Query for timeline events. + * + * @generated from message xyz.block.ftl.v1.console.TimelineQuery + */ +export class TimelineQuery extends Message { + /** + * @generated from field: repeated xyz.block.ftl.v1.console.TimelineQuery.Filter filters = 1; + */ + filters: TimelineQuery_Filter[] = []; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "xyz.block.ftl.v1.console.TimelineQuery"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "filters", kind: "message", T: TimelineQuery_Filter, repeated: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): TimelineQuery { + return new TimelineQuery().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): TimelineQuery { + return new TimelineQuery().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): TimelineQuery { + return new TimelineQuery().fromJsonString(jsonString, options); + } + + static equals(a: TimelineQuery | PlainMessage | undefined, b: TimelineQuery | PlainMessage | undefined): boolean { + return proto3.util.equals(TimelineQuery, a, b); + } +} + +/** + * Filters events by log level. + * + * @generated from message xyz.block.ftl.v1.console.TimelineQuery.LogLevelFilter + */ +export class TimelineQuery_LogLevelFilter extends Message { + /** + * @generated from field: xyz.block.ftl.v1.console.LogLevel log_level = 1; + */ + logLevel = LogLevel.TRACE; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "xyz.block.ftl.v1.console.TimelineQuery.LogLevelFilter"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "log_level", kind: "enum", T: proto3.getEnumType(LogLevel) }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): TimelineQuery_LogLevelFilter { + return new TimelineQuery_LogLevelFilter().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): TimelineQuery_LogLevelFilter { + return new TimelineQuery_LogLevelFilter().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): TimelineQuery_LogLevelFilter { + return new TimelineQuery_LogLevelFilter().fromJsonString(jsonString, options); + } + + static equals(a: TimelineQuery_LogLevelFilter | PlainMessage | undefined, b: TimelineQuery_LogLevelFilter | PlainMessage | undefined): boolean { + return proto3.util.equals(TimelineQuery_LogLevelFilter, a, b); + } +} + +/** + * Filters events by deployment name. + * + * @generated from message xyz.block.ftl.v1.console.TimelineQuery.DeploymentFilter + */ +export class TimelineQuery_DeploymentFilter extends Message { + /** + * @generated from field: repeated string deployments = 1; + */ + deployments: string[] = []; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "xyz.block.ftl.v1.console.TimelineQuery.DeploymentFilter"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "deployments", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): TimelineQuery_DeploymentFilter { + return new TimelineQuery_DeploymentFilter().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): TimelineQuery_DeploymentFilter { + return new TimelineQuery_DeploymentFilter().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): TimelineQuery_DeploymentFilter { + return new TimelineQuery_DeploymentFilter().fromJsonString(jsonString, options); + } + + static equals(a: TimelineQuery_DeploymentFilter | PlainMessage | undefined, b: TimelineQuery_DeploymentFilter | PlainMessage | undefined): boolean { + return proto3.util.equals(TimelineQuery_DeploymentFilter, a, b); + } +} + +/** + * Filters events by request key. + * + * @generated from message xyz.block.ftl.v1.console.TimelineQuery.RequestFilter + */ +export class TimelineQuery_RequestFilter extends Message { + /** + * @generated from field: repeated string requests = 1; + */ + requests: string[] = []; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "xyz.block.ftl.v1.console.TimelineQuery.RequestFilter"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "requests", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): TimelineQuery_RequestFilter { + return new TimelineQuery_RequestFilter().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): TimelineQuery_RequestFilter { + return new TimelineQuery_RequestFilter().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): TimelineQuery_RequestFilter { + return new TimelineQuery_RequestFilter().fromJsonString(jsonString, options); + } + + static equals(a: TimelineQuery_RequestFilter | PlainMessage | undefined, b: TimelineQuery_RequestFilter | PlainMessage | undefined): boolean { + return proto3.util.equals(TimelineQuery_RequestFilter, a, b); + } +} + +/** + * Filters events by event type. + * + * @generated from message xyz.block.ftl.v1.console.TimelineQuery.EventTypeFilter + */ +export class TimelineQuery_EventTypeFilter extends Message { + /** + * @generated from field: repeated xyz.block.ftl.v1.console.EventType event_types = 1; + */ + eventTypes: EventType[] = []; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "xyz.block.ftl.v1.console.TimelineQuery.EventTypeFilter"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "event_types", kind: "enum", T: proto3.getEnumType(EventType), repeated: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): TimelineQuery_EventTypeFilter { + return new TimelineQuery_EventTypeFilter().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): TimelineQuery_EventTypeFilter { + return new TimelineQuery_EventTypeFilter().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): TimelineQuery_EventTypeFilter { + return new TimelineQuery_EventTypeFilter().fromJsonString(jsonString, options); + } + + static equals(a: TimelineQuery_EventTypeFilter | PlainMessage | undefined, b: TimelineQuery_EventTypeFilter | PlainMessage | undefined): boolean { + return proto3.util.equals(TimelineQuery_EventTypeFilter, a, b); + } +} + +/** + * Filters events by time. + * + * Either end of the time range can be omitted to indicate no bound. + * + * @generated from message xyz.block.ftl.v1.console.TimelineQuery.TimeFilter + */ +export class TimelineQuery_TimeFilter extends Message { + /** + * @generated from field: optional google.protobuf.Timestamp older_than = 1; + */ + olderThan?: Timestamp; + + /** + * @generated from field: optional google.protobuf.Timestamp newer_than = 2; + */ + newerThan?: Timestamp; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "xyz.block.ftl.v1.console.TimelineQuery.TimeFilter"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "older_than", kind: "message", T: Timestamp, opt: true }, + { no: 2, name: "newer_than", kind: "message", T: Timestamp, opt: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): TimelineQuery_TimeFilter { + return new TimelineQuery_TimeFilter().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): TimelineQuery_TimeFilter { + return new TimelineQuery_TimeFilter().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): TimelineQuery_TimeFilter { + return new TimelineQuery_TimeFilter().fromJsonString(jsonString, options); + } + + static equals(a: TimelineQuery_TimeFilter | PlainMessage | undefined, b: TimelineQuery_TimeFilter | PlainMessage | undefined): boolean { + return proto3.util.equals(TimelineQuery_TimeFilter, a, b); + } +} + +/** + * Filters events by ID. + * + * Either end of the ID range can be omitted to indicate no bound. + * + * @generated from message xyz.block.ftl.v1.console.TimelineQuery.IDFilter + */ +export class TimelineQuery_IDFilter extends Message { + /** + * @generated from field: optional int64 lower_than = 1; + */ + lowerThan?: bigint; + + /** + * @generated from field: optional int64 higher_than = 2; + */ + higherThan?: bigint; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "xyz.block.ftl.v1.console.TimelineQuery.IDFilter"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "lower_than", kind: "scalar", T: 3 /* ScalarType.INT64 */, opt: true }, + { no: 2, name: "higher_than", kind: "scalar", T: 3 /* ScalarType.INT64 */, opt: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): TimelineQuery_IDFilter { + return new TimelineQuery_IDFilter().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): TimelineQuery_IDFilter { + return new TimelineQuery_IDFilter().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): TimelineQuery_IDFilter { + return new TimelineQuery_IDFilter().fromJsonString(jsonString, options); + } + + static equals(a: TimelineQuery_IDFilter | PlainMessage | undefined, b: TimelineQuery_IDFilter | PlainMessage | undefined): boolean { + return proto3.util.equals(TimelineQuery_IDFilter, a, b); + } +} + +/** + * @generated from message xyz.block.ftl.v1.console.TimelineQuery.Filter + */ +export class TimelineQuery_Filter extends Message { + /** + * These map 1:1 with filters in backend/controller/internal/dal/events.go + * + * @generated from oneof xyz.block.ftl.v1.console.TimelineQuery.Filter.filter + */ + filter: { + /** + * @generated from field: xyz.block.ftl.v1.console.TimelineQuery.LogLevelFilter log_level = 1; + */ + value: TimelineQuery_LogLevelFilter; + case: "logLevel"; + } | { + /** + * @generated from field: xyz.block.ftl.v1.console.TimelineQuery.DeploymentFilter deployments = 2; + */ + value: TimelineQuery_DeploymentFilter; + case: "deployments"; + } | { + /** + * @generated from field: xyz.block.ftl.v1.console.TimelineQuery.RequestFilter requests = 3; + */ + value: TimelineQuery_RequestFilter; + case: "requests"; + } | { + /** + * @generated from field: xyz.block.ftl.v1.console.TimelineQuery.EventTypeFilter event_types = 4; + */ + value: TimelineQuery_EventTypeFilter; + case: "eventTypes"; + } | { + /** + * @generated from field: xyz.block.ftl.v1.console.TimelineQuery.TimeFilter time = 5; + */ + value: TimelineQuery_TimeFilter; + case: "time"; + } | { + /** + * @generated from field: xyz.block.ftl.v1.console.TimelineQuery.IDFilter id = 6; + */ + value: TimelineQuery_IDFilter; + case: "id"; + } | { case: undefined; value?: undefined } = { case: undefined }; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "xyz.block.ftl.v1.console.TimelineQuery.Filter"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "log_level", kind: "message", T: TimelineQuery_LogLevelFilter, oneof: "filter" }, + { no: 2, name: "deployments", kind: "message", T: TimelineQuery_DeploymentFilter, oneof: "filter" }, + { no: 3, name: "requests", kind: "message", T: TimelineQuery_RequestFilter, oneof: "filter" }, + { no: 4, name: "event_types", kind: "message", T: TimelineQuery_EventTypeFilter, oneof: "filter" }, + { no: 5, name: "time", kind: "message", T: TimelineQuery_TimeFilter, oneof: "filter" }, + { no: 6, name: "id", kind: "message", T: TimelineQuery_IDFilter, oneof: "filter" }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): TimelineQuery_Filter { + return new TimelineQuery_Filter().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): TimelineQuery_Filter { + return new TimelineQuery_Filter().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): TimelineQuery_Filter { + return new TimelineQuery_Filter().fromJsonString(jsonString, options); + } + + static equals(a: TimelineQuery_Filter | PlainMessage | undefined, b: TimelineQuery_Filter | PlainMessage | undefined): boolean { + return proto3.util.equals(TimelineQuery_Filter, a, b); + } +} + /** * @generated from message xyz.block.ftl.v1.console.StreamTimelineRequest */ @@ -692,6 +1137,52 @@ export class StreamTimelineRequest extends Message { * @generated from message xyz.block.ftl.v1.console.StreamTimelineResponse */ export class StreamTimelineResponse extends Message { + /** + * @generated from field: xyz.block.ftl.v1.console.TimelineEvent event = 1; + */ + event?: TimelineEvent; + + /** + * If true there are more logs immediately following this one as part of the initial batch. + * If false this is the last log in the initial batch, but others may follow later. + * + * @generated from field: bool more = 2; + */ + more = false; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "xyz.block.ftl.v1.console.StreamTimelineResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "event", kind: "message", T: TimelineEvent }, + { no: 2, name: "more", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): StreamTimelineResponse { + return new StreamTimelineResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): StreamTimelineResponse { + return new StreamTimelineResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): StreamTimelineResponse { + return new StreamTimelineResponse().fromJsonString(jsonString, options); + } + + static equals(a: StreamTimelineResponse | PlainMessage | undefined, b: StreamTimelineResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(StreamTimelineResponse, a, b); + } +} + +/** + * @generated from message xyz.block.ftl.v1.console.TimelineEvent + */ +export class TimelineEvent extends Message { /** * @generated from field: google.protobuf.Timestamp time_stamp = 1; */ @@ -705,7 +1196,7 @@ export class StreamTimelineResponse extends Message { id = protoInt64.zero; /** - * @generated from oneof xyz.block.ftl.v1.console.StreamTimelineResponse.entry + * @generated from oneof xyz.block.ftl.v1.console.TimelineEvent.entry */ entry: { /** @@ -727,44 +1218,35 @@ export class StreamTimelineResponse extends Message { case: "log"; } | { case: undefined; value?: undefined } = { case: undefined }; - /** - * If true there are more logs immediately following this one as part of the initial batch. - * If false this is the last log in the initial batch, but others may follow later. - * - * @generated from field: bool more = 6; - */ - more = false; - - constructor(data?: PartialMessage) { + constructor(data?: PartialMessage) { super(); proto3.util.initPartial(data, this); } static readonly runtime: typeof proto3 = proto3; - static readonly typeName = "xyz.block.ftl.v1.console.StreamTimelineResponse"; + static readonly typeName = "xyz.block.ftl.v1.console.TimelineEvent"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ { no: 1, name: "time_stamp", kind: "message", T: Timestamp }, { no: 2, name: "id", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, { no: 3, name: "call", kind: "message", T: Call, oneof: "entry" }, { no: 4, name: "deployment", kind: "message", T: Deployment, oneof: "entry" }, { no: 5, name: "log", kind: "message", T: LogEntry, oneof: "entry" }, - { no: 6, name: "more", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, ]); - static fromBinary(bytes: Uint8Array, options?: Partial): StreamTimelineResponse { - return new StreamTimelineResponse().fromBinary(bytes, options); + static fromBinary(bytes: Uint8Array, options?: Partial): TimelineEvent { + return new TimelineEvent().fromBinary(bytes, options); } - static fromJson(jsonValue: JsonValue, options?: Partial): StreamTimelineResponse { - return new StreamTimelineResponse().fromJson(jsonValue, options); + static fromJson(jsonValue: JsonValue, options?: Partial): TimelineEvent { + return new TimelineEvent().fromJson(jsonValue, options); } - static fromJsonString(jsonString: string, options?: Partial): StreamTimelineResponse { - return new StreamTimelineResponse().fromJsonString(jsonString, options); + static fromJsonString(jsonString: string, options?: Partial): TimelineEvent { + return new TimelineEvent().fromJsonString(jsonString, options); } - static equals(a: StreamTimelineResponse | PlainMessage | undefined, b: StreamTimelineResponse | PlainMessage | undefined): boolean { - return proto3.util.equals(StreamTimelineResponse, a, b); + static equals(a: TimelineEvent | PlainMessage | undefined, b: TimelineEvent | PlainMessage | undefined): boolean { + return proto3.util.equals(TimelineEvent, a, b); } } @@ -863,3 +1345,40 @@ export class StreamLogsResponse extends Message { } } +/** + * @generated from message xyz.block.ftl.v1.console.GetTimelineResponse + */ +export class GetTimelineResponse extends Message { + /** + * @generated from field: repeated xyz.block.ftl.v1.console.TimelineEvent events = 1; + */ + events: TimelineEvent[] = []; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "xyz.block.ftl.v1.console.GetTimelineResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "events", kind: "message", T: TimelineEvent, repeated: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): GetTimelineResponse { + return new GetTimelineResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): GetTimelineResponse { + return new GetTimelineResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): GetTimelineResponse { + return new GetTimelineResponse().fromJsonString(jsonString, options); + } + + static equals(a: GetTimelineResponse | PlainMessage | undefined, b: GetTimelineResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(GetTimelineResponse, a, b); + } +} + diff --git a/console/client/src/providers/selected-timeline-entry-provider.tsx b/console/client/src/providers/selected-timeline-entry-provider.tsx index 9d995d9104..529731c078 100644 --- a/console/client/src/providers/selected-timeline-entry-provider.tsx +++ b/console/client/src/providers/selected-timeline-entry-provider.tsx @@ -1,10 +1,10 @@ import {PropsWithChildren, createContext, useState} from 'react' -import {StreamTimelineResponse} from '../protos/xyz/block/ftl/v1/console/console_pb' +import {TimelineEvent} from '../protos/xyz/block/ftl/v1/console/console_pb' type SelectedTimelineEntryContextType = { - selectedEntry: StreamTimelineResponse | null + selectedEntry: TimelineEvent | null setSelectedEntry: React.Dispatch< - React.SetStateAction + React.SetStateAction > } @@ -16,7 +16,7 @@ export const SelectedTimelineEntryContext = export const SelectedTimelineEntryProvider = (props: PropsWithChildren) => { const [selectedEntry, setSelectedEntry] = - useState(null) + useState(null) return ( google.protobuf.Timestamp - 19, // 1: xyz.block.ftl.v1.console.Call.source_verb_ref:type_name -> xyz.block.ftl.v1.schema.VerbRef - 19, // 2: xyz.block.ftl.v1.console.Call.destination_verb_ref:type_name -> xyz.block.ftl.v1.schema.VerbRef - 20, // 3: xyz.block.ftl.v1.console.Call.duration:type_name -> google.protobuf.Duration - 0, // 4: xyz.block.ftl.v1.console.Deployment.event_type:type_name -> xyz.block.ftl.v1.console.DeploymentEventType - 18, // 5: xyz.block.ftl.v1.console.LogEntry.time_stamp:type_name -> google.protobuf.Timestamp - 17, // 6: xyz.block.ftl.v1.console.LogEntry.attributes:type_name -> xyz.block.ftl.v1.console.LogEntry.AttributesEntry - 21, // 7: xyz.block.ftl.v1.console.Verb.verb:type_name -> xyz.block.ftl.v1.schema.Verb - 22, // 8: xyz.block.ftl.v1.console.Data.data:type_name -> xyz.block.ftl.v1.schema.Data - 4, // 9: xyz.block.ftl.v1.console.Module.verbs:type_name -> xyz.block.ftl.v1.console.Verb - 5, // 10: xyz.block.ftl.v1.console.Module.data:type_name -> xyz.block.ftl.v1.console.Data - 6, // 11: xyz.block.ftl.v1.console.GetModulesResponse.modules:type_name -> xyz.block.ftl.v1.console.Module - 1, // 12: xyz.block.ftl.v1.console.GetCallsResponse.calls:type_name -> xyz.block.ftl.v1.console.Call - 1, // 13: xyz.block.ftl.v1.console.GetRequestCallsResponse.calls:type_name -> xyz.block.ftl.v1.console.Call - 20, // 14: xyz.block.ftl.v1.console.StreamTimelineRequest.update_interval:type_name -> google.protobuf.Duration - 18, // 15: xyz.block.ftl.v1.console.StreamTimelineRequest.after_time:type_name -> google.protobuf.Timestamp - 18, // 16: xyz.block.ftl.v1.console.StreamTimelineResponse.time_stamp:type_name -> google.protobuf.Timestamp - 1, // 17: xyz.block.ftl.v1.console.StreamTimelineResponse.call:type_name -> xyz.block.ftl.v1.console.Call - 2, // 18: xyz.block.ftl.v1.console.StreamTimelineResponse.deployment:type_name -> xyz.block.ftl.v1.console.Deployment - 3, // 19: xyz.block.ftl.v1.console.StreamTimelineResponse.log:type_name -> xyz.block.ftl.v1.console.LogEntry - 20, // 20: xyz.block.ftl.v1.console.StreamLogsRequest.update_interval:type_name -> google.protobuf.Duration - 18, // 21: xyz.block.ftl.v1.console.StreamLogsRequest.after_time:type_name -> google.protobuf.Timestamp - 3, // 22: xyz.block.ftl.v1.console.StreamLogsResponse.log:type_name -> xyz.block.ftl.v1.console.LogEntry - 23, // 23: xyz.block.ftl.v1.console.ConsoleService.Ping:input_type -> xyz.block.ftl.v1.PingRequest - 7, // 24: xyz.block.ftl.v1.console.ConsoleService.GetModules:input_type -> xyz.block.ftl.v1.console.GetModulesRequest - 9, // 25: xyz.block.ftl.v1.console.ConsoleService.GetCalls:input_type -> xyz.block.ftl.v1.console.GetCallsRequest - 11, // 26: xyz.block.ftl.v1.console.ConsoleService.GetRequestCalls:input_type -> xyz.block.ftl.v1.console.GetRequestCallsRequest - 13, // 27: xyz.block.ftl.v1.console.ConsoleService.StreamTimeline:input_type -> xyz.block.ftl.v1.console.StreamTimelineRequest - 15, // 28: xyz.block.ftl.v1.console.ConsoleService.StreamLogs:input_type -> xyz.block.ftl.v1.console.StreamLogsRequest - 24, // 29: xyz.block.ftl.v1.console.ConsoleService.Ping:output_type -> xyz.block.ftl.v1.PingResponse - 8, // 30: xyz.block.ftl.v1.console.ConsoleService.GetModules:output_type -> xyz.block.ftl.v1.console.GetModulesResponse - 10, // 31: xyz.block.ftl.v1.console.ConsoleService.GetCalls:output_type -> xyz.block.ftl.v1.console.GetCallsResponse - 12, // 32: xyz.block.ftl.v1.console.ConsoleService.GetRequestCalls:output_type -> xyz.block.ftl.v1.console.GetRequestCallsResponse - 14, // 33: xyz.block.ftl.v1.console.ConsoleService.StreamTimeline:output_type -> xyz.block.ftl.v1.console.StreamTimelineResponse - 16, // 34: xyz.block.ftl.v1.console.ConsoleService.StreamLogs:output_type -> xyz.block.ftl.v1.console.StreamLogsResponse - 29, // [29:35] is the sub-list for method output_type - 23, // [23:29] is the sub-list for method input_type - 23, // [23:23] is the sub-list for extension type_name - 23, // [23:23] is the sub-list for extension extendee - 0, // [0:23] is the sub-list for field type_name + 30, // 0: xyz.block.ftl.v1.console.Call.time_stamp:type_name -> google.protobuf.Timestamp + 31, // 1: xyz.block.ftl.v1.console.Call.source_verb_ref:type_name -> xyz.block.ftl.v1.schema.VerbRef + 31, // 2: xyz.block.ftl.v1.console.Call.destination_verb_ref:type_name -> xyz.block.ftl.v1.schema.VerbRef + 32, // 3: xyz.block.ftl.v1.console.Call.duration:type_name -> google.protobuf.Duration + 1, // 4: xyz.block.ftl.v1.console.Deployment.event_type:type_name -> xyz.block.ftl.v1.console.DeploymentEventType + 30, // 5: xyz.block.ftl.v1.console.LogEntry.time_stamp:type_name -> google.protobuf.Timestamp + 22, // 6: xyz.block.ftl.v1.console.LogEntry.attributes:type_name -> xyz.block.ftl.v1.console.LogEntry.AttributesEntry + 33, // 7: xyz.block.ftl.v1.console.Verb.verb:type_name -> xyz.block.ftl.v1.schema.Verb + 34, // 8: xyz.block.ftl.v1.console.Data.data:type_name -> xyz.block.ftl.v1.schema.Data + 6, // 9: xyz.block.ftl.v1.console.Module.verbs:type_name -> xyz.block.ftl.v1.console.Verb + 7, // 10: xyz.block.ftl.v1.console.Module.data:type_name -> xyz.block.ftl.v1.console.Data + 8, // 11: xyz.block.ftl.v1.console.GetModulesResponse.modules:type_name -> xyz.block.ftl.v1.console.Module + 3, // 12: xyz.block.ftl.v1.console.GetCallsResponse.calls:type_name -> xyz.block.ftl.v1.console.Call + 3, // 13: xyz.block.ftl.v1.console.GetRequestCallsResponse.calls:type_name -> xyz.block.ftl.v1.console.Call + 29, // 14: xyz.block.ftl.v1.console.TimelineQuery.filters:type_name -> xyz.block.ftl.v1.console.TimelineQuery.Filter + 32, // 15: xyz.block.ftl.v1.console.StreamTimelineRequest.update_interval:type_name -> google.protobuf.Duration + 30, // 16: xyz.block.ftl.v1.console.StreamTimelineRequest.after_time:type_name -> google.protobuf.Timestamp + 18, // 17: xyz.block.ftl.v1.console.StreamTimelineResponse.event:type_name -> xyz.block.ftl.v1.console.TimelineEvent + 30, // 18: xyz.block.ftl.v1.console.TimelineEvent.time_stamp:type_name -> google.protobuf.Timestamp + 3, // 19: xyz.block.ftl.v1.console.TimelineEvent.call:type_name -> xyz.block.ftl.v1.console.Call + 4, // 20: xyz.block.ftl.v1.console.TimelineEvent.deployment:type_name -> xyz.block.ftl.v1.console.Deployment + 5, // 21: xyz.block.ftl.v1.console.TimelineEvent.log:type_name -> xyz.block.ftl.v1.console.LogEntry + 32, // 22: xyz.block.ftl.v1.console.StreamLogsRequest.update_interval:type_name -> google.protobuf.Duration + 30, // 23: xyz.block.ftl.v1.console.StreamLogsRequest.after_time:type_name -> google.protobuf.Timestamp + 5, // 24: xyz.block.ftl.v1.console.StreamLogsResponse.log:type_name -> xyz.block.ftl.v1.console.LogEntry + 18, // 25: xyz.block.ftl.v1.console.GetTimelineResponse.events:type_name -> xyz.block.ftl.v1.console.TimelineEvent + 2, // 26: xyz.block.ftl.v1.console.TimelineQuery.LogLevelFilter.log_level:type_name -> xyz.block.ftl.v1.console.LogLevel + 0, // 27: xyz.block.ftl.v1.console.TimelineQuery.EventTypeFilter.event_types:type_name -> xyz.block.ftl.v1.console.EventType + 30, // 28: xyz.block.ftl.v1.console.TimelineQuery.TimeFilter.older_than:type_name -> google.protobuf.Timestamp + 30, // 29: xyz.block.ftl.v1.console.TimelineQuery.TimeFilter.newer_than:type_name -> google.protobuf.Timestamp + 23, // 30: xyz.block.ftl.v1.console.TimelineQuery.Filter.log_level:type_name -> xyz.block.ftl.v1.console.TimelineQuery.LogLevelFilter + 24, // 31: xyz.block.ftl.v1.console.TimelineQuery.Filter.deployments:type_name -> xyz.block.ftl.v1.console.TimelineQuery.DeploymentFilter + 25, // 32: xyz.block.ftl.v1.console.TimelineQuery.Filter.requests:type_name -> xyz.block.ftl.v1.console.TimelineQuery.RequestFilter + 26, // 33: xyz.block.ftl.v1.console.TimelineQuery.Filter.event_types:type_name -> xyz.block.ftl.v1.console.TimelineQuery.EventTypeFilter + 27, // 34: xyz.block.ftl.v1.console.TimelineQuery.Filter.time:type_name -> xyz.block.ftl.v1.console.TimelineQuery.TimeFilter + 28, // 35: xyz.block.ftl.v1.console.TimelineQuery.Filter.id:type_name -> xyz.block.ftl.v1.console.TimelineQuery.IDFilter + 35, // 36: xyz.block.ftl.v1.console.ConsoleService.Ping:input_type -> xyz.block.ftl.v1.PingRequest + 9, // 37: xyz.block.ftl.v1.console.ConsoleService.GetModules:input_type -> xyz.block.ftl.v1.console.GetModulesRequest + 11, // 38: xyz.block.ftl.v1.console.ConsoleService.GetCalls:input_type -> xyz.block.ftl.v1.console.GetCallsRequest + 13, // 39: xyz.block.ftl.v1.console.ConsoleService.GetRequestCalls:input_type -> xyz.block.ftl.v1.console.GetRequestCallsRequest + 16, // 40: xyz.block.ftl.v1.console.ConsoleService.StreamTimeline:input_type -> xyz.block.ftl.v1.console.StreamTimelineRequest + 15, // 41: xyz.block.ftl.v1.console.ConsoleService.GetTimeline:input_type -> xyz.block.ftl.v1.console.TimelineQuery + 19, // 42: xyz.block.ftl.v1.console.ConsoleService.StreamLogs:input_type -> xyz.block.ftl.v1.console.StreamLogsRequest + 36, // 43: xyz.block.ftl.v1.console.ConsoleService.Ping:output_type -> xyz.block.ftl.v1.PingResponse + 10, // 44: xyz.block.ftl.v1.console.ConsoleService.GetModules:output_type -> xyz.block.ftl.v1.console.GetModulesResponse + 12, // 45: xyz.block.ftl.v1.console.ConsoleService.GetCalls:output_type -> xyz.block.ftl.v1.console.GetCallsResponse + 14, // 46: xyz.block.ftl.v1.console.ConsoleService.GetRequestCalls:output_type -> xyz.block.ftl.v1.console.GetRequestCallsResponse + 17, // 47: xyz.block.ftl.v1.console.ConsoleService.StreamTimeline:output_type -> xyz.block.ftl.v1.console.StreamTimelineResponse + 21, // 48: xyz.block.ftl.v1.console.ConsoleService.GetTimeline:output_type -> xyz.block.ftl.v1.console.GetTimelineResponse + 20, // 49: xyz.block.ftl.v1.console.ConsoleService.StreamLogs:output_type -> xyz.block.ftl.v1.console.StreamLogsResponse + 43, // [43:50] is the sub-list for method output_type + 36, // [36:43] is the sub-list for method input_type + 36, // [36:36] is the sub-list for extension type_name + 36, // [36:36] is the sub-list for extension extendee + 0, // [0:36] is the sub-list for field type_name } func init() { file_xyz_block_ftl_v1_console_console_proto_init() } @@ -1637,7 +2467,7 @@ func file_xyz_block_ftl_v1_console_console_proto_init() { } } file_xyz_block_ftl_v1_console_console_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StreamTimelineRequest); i { + switch v := v.(*TimelineQuery); i { case 0: return &v.state case 1: @@ -1649,7 +2479,7 @@ func file_xyz_block_ftl_v1_console_console_proto_init() { } } file_xyz_block_ftl_v1_console_console_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StreamTimelineResponse); i { + switch v := v.(*StreamTimelineRequest); i { case 0: return &v.state case 1: @@ -1661,7 +2491,7 @@ func file_xyz_block_ftl_v1_console_console_proto_init() { } } file_xyz_block_ftl_v1_console_console_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StreamLogsRequest); i { + switch v := v.(*StreamTimelineResponse); i { case 0: return &v.state case 1: @@ -1673,6 +2503,30 @@ func file_xyz_block_ftl_v1_console_console_proto_init() { } } file_xyz_block_ftl_v1_console_console_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TimelineEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_xyz_block_ftl_v1_console_console_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StreamLogsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_xyz_block_ftl_v1_console_console_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StreamLogsResponse); i { case 0: return &v.state @@ -1684,24 +2538,130 @@ func file_xyz_block_ftl_v1_console_console_proto_init() { return nil } } + file_xyz_block_ftl_v1_console_console_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetTimelineResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_xyz_block_ftl_v1_console_console_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TimelineQuery_LogLevelFilter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_xyz_block_ftl_v1_console_console_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TimelineQuery_DeploymentFilter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_xyz_block_ftl_v1_console_console_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TimelineQuery_RequestFilter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_xyz_block_ftl_v1_console_console_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TimelineQuery_EventTypeFilter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_xyz_block_ftl_v1_console_console_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TimelineQuery_TimeFilter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_xyz_block_ftl_v1_console_console_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TimelineQuery_IDFilter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_xyz_block_ftl_v1_console_console_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TimelineQuery_Filter); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_xyz_block_ftl_v1_console_console_proto_msgTypes[0].OneofWrappers = []interface{}{} file_xyz_block_ftl_v1_console_console_proto_msgTypes[1].OneofWrappers = []interface{}{} file_xyz_block_ftl_v1_console_console_proto_msgTypes[2].OneofWrappers = []interface{}{} - file_xyz_block_ftl_v1_console_console_proto_msgTypes[12].OneofWrappers = []interface{}{} - file_xyz_block_ftl_v1_console_console_proto_msgTypes[13].OneofWrappers = []interface{}{ - (*StreamTimelineResponse_Call)(nil), - (*StreamTimelineResponse_Deployment)(nil), - (*StreamTimelineResponse_Log)(nil), + file_xyz_block_ftl_v1_console_console_proto_msgTypes[13].OneofWrappers = []interface{}{} + file_xyz_block_ftl_v1_console_console_proto_msgTypes[15].OneofWrappers = []interface{}{ + (*TimelineEvent_Call)(nil), + (*TimelineEvent_Deployment)(nil), + (*TimelineEvent_Log)(nil), + } + file_xyz_block_ftl_v1_console_console_proto_msgTypes[16].OneofWrappers = []interface{}{} + file_xyz_block_ftl_v1_console_console_proto_msgTypes[24].OneofWrappers = []interface{}{} + file_xyz_block_ftl_v1_console_console_proto_msgTypes[25].OneofWrappers = []interface{}{} + file_xyz_block_ftl_v1_console_console_proto_msgTypes[26].OneofWrappers = []interface{}{ + (*TimelineQuery_Filter_LogLevel)(nil), + (*TimelineQuery_Filter_Deployments)(nil), + (*TimelineQuery_Filter_Requests)(nil), + (*TimelineQuery_Filter_EventTypes)(nil), + (*TimelineQuery_Filter_Time)(nil), + (*TimelineQuery_Filter_Id)(nil), } - file_xyz_block_ftl_v1_console_console_proto_msgTypes[14].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_xyz_block_ftl_v1_console_console_proto_rawDesc, - NumEnums: 1, - NumMessages: 17, + NumEnums: 3, + NumMessages: 27, NumExtensions: 0, NumServices: 1, }, diff --git a/protos/xyz/block/ftl/v1/console/console.proto b/protos/xyz/block/ftl/v1/console/console.proto index 932428751f..ccc70dd639 100644 --- a/protos/xyz/block/ftl/v1/console/console.proto +++ b/protos/xyz/block/ftl/v1/console/console.proto @@ -22,10 +22,18 @@ message Call { optional string error = 9; } +enum EventType { + EVENT_TYPE_UNKNOWN = 0; + EVENT_TYPE_DEPLOYMENT = 1; + EVENT_TYPE_CALL = 2; + EVENT_TYPE_LOG = 3; +} + enum DeploymentEventType { - DEPLOYMENT_CREATED = 0; - DEPLOYMENT_UPDATED = 1; - DEPLOYMENT_REPLACED = 2; + DEPLOYMENT_UNKNOWN = 0; + DEPLOYMENT_CREATED = 1; + DEPLOYMENT_UPDATED = 2; + DEPLOYMENT_REPLACED = 3; } message Deployment { @@ -89,6 +97,62 @@ message GetRequestCallsResponse { repeated Call calls = 1; } +enum LogLevel { + LOG_LEVEL_TRACE = 0; + LOG_LEVEL_DEBUG = 1; + LOG_LEVEL_INFO = 2; + LOG_LEVEL_WARN = 3; + LOG_LEVEL_ERROR = 4; +} + +// Query for timeline events. +message TimelineQuery { + // Filters events by log level. + message LogLevelFilter { + LogLevel log_level = 1; + } + // Filters events by deployment name. + message DeploymentFilter { + repeated string deployments = 1; + } + // Filters events by request key. + message RequestFilter { + repeated string requests = 1; + } + // Filters events by event type. + message EventTypeFilter { + repeated EventType event_types = 1; + } + // Filters events by time. + // + // Either end of the time range can be omitted to indicate no bound. + message TimeFilter { + optional google.protobuf.Timestamp older_than = 1; + optional google.protobuf.Timestamp newer_than = 2; + } + // Filters events by ID. + // + // Either end of the ID range can be omitted to indicate no bound. + message IDFilter { + optional int64 lower_than = 1; + optional int64 higher_than = 2; + } + + message Filter { + // These map 1:1 with filters in backend/controller/internal/dal/events.go + oneof filter { + LogLevelFilter log_level = 1; + DeploymentFilter deployments = 2; + RequestFilter requests = 3; + EventTypeFilter event_types = 4; + TimeFilter time = 5; + IDFilter id = 6; + } + } + + repeated Filter filters = 1; +} + message StreamTimelineRequest { optional google.protobuf.Duration update_interval = 1; google.protobuf.Timestamp after_time = 2; @@ -96,6 +160,14 @@ message StreamTimelineRequest { } message StreamTimelineResponse { + TimelineEvent event = 1; + + // If true there are more logs immediately following this one as part of the initial batch. + // If false this is the last log in the initial batch, but others may follow later. + bool more = 2; +} + +message TimelineEvent { google.protobuf.Timestamp time_stamp = 1; // Unique ID for event. int64 id = 2; @@ -104,10 +176,6 @@ message StreamTimelineResponse { Deployment deployment = 4; LogEntry log = 5; } - - // If true there are more logs immediately following this one as part of the initial batch. - // If false this is the last log in the initial batch, but others may follow later. - bool more = 6; } message StreamLogsRequest { @@ -124,6 +192,10 @@ message StreamLogsResponse { bool more = 2; } +message GetTimelineResponse { + repeated TimelineEvent events = 1; +} + service ConsoleService { // Ping service for readiness. rpc Ping(PingRequest) returns (PingResponse) { @@ -134,5 +206,6 @@ service ConsoleService { rpc GetCalls(GetCallsRequest) returns (GetCallsResponse); rpc GetRequestCalls(GetRequestCallsRequest) returns (GetRequestCallsResponse); rpc StreamTimeline(StreamTimelineRequest) returns (stream StreamTimelineResponse); + rpc GetTimeline(TimelineQuery) returns (GetTimelineResponse); rpc StreamLogs(StreamLogsRequest) returns (stream StreamLogsResponse); } diff --git a/protos/xyz/block/ftl/v1/console/pbconsoleconnect/console.connect.go b/protos/xyz/block/ftl/v1/console/pbconsoleconnect/console.connect.go index 8d91121d4c..fb258f0829 100644 --- a/protos/xyz/block/ftl/v1/console/pbconsoleconnect/console.connect.go +++ b/protos/xyz/block/ftl/v1/console/pbconsoleconnect/console.connect.go @@ -47,6 +47,9 @@ const ( // ConsoleServiceStreamTimelineProcedure is the fully-qualified name of the ConsoleService's // StreamTimeline RPC. ConsoleServiceStreamTimelineProcedure = "/xyz.block.ftl.v1.console.ConsoleService/StreamTimeline" + // ConsoleServiceGetTimelineProcedure is the fully-qualified name of the ConsoleService's + // GetTimeline RPC. + ConsoleServiceGetTimelineProcedure = "/xyz.block.ftl.v1.console.ConsoleService/GetTimeline" // ConsoleServiceStreamLogsProcedure is the fully-qualified name of the ConsoleService's StreamLogs // RPC. ConsoleServiceStreamLogsProcedure = "/xyz.block.ftl.v1.console.ConsoleService/StreamLogs" @@ -60,6 +63,7 @@ type ConsoleServiceClient interface { GetCalls(context.Context, *connect_go.Request[console.GetCallsRequest]) (*connect_go.Response[console.GetCallsResponse], error) GetRequestCalls(context.Context, *connect_go.Request[console.GetRequestCallsRequest]) (*connect_go.Response[console.GetRequestCallsResponse], error) StreamTimeline(context.Context, *connect_go.Request[console.StreamTimelineRequest]) (*connect_go.ServerStreamForClient[console.StreamTimelineResponse], error) + GetTimeline(context.Context, *connect_go.Request[console.TimelineQuery]) (*connect_go.Response[console.GetTimelineResponse], error) StreamLogs(context.Context, *connect_go.Request[console.StreamLogsRequest]) (*connect_go.ServerStreamForClient[console.StreamLogsResponse], error) } @@ -99,6 +103,11 @@ func NewConsoleServiceClient(httpClient connect_go.HTTPClient, baseURL string, o baseURL+ConsoleServiceStreamTimelineProcedure, opts..., ), + getTimeline: connect_go.NewClient[console.TimelineQuery, console.GetTimelineResponse]( + httpClient, + baseURL+ConsoleServiceGetTimelineProcedure, + opts..., + ), streamLogs: connect_go.NewClient[console.StreamLogsRequest, console.StreamLogsResponse]( httpClient, baseURL+ConsoleServiceStreamLogsProcedure, @@ -114,6 +123,7 @@ type consoleServiceClient struct { getCalls *connect_go.Client[console.GetCallsRequest, console.GetCallsResponse] getRequestCalls *connect_go.Client[console.GetRequestCallsRequest, console.GetRequestCallsResponse] streamTimeline *connect_go.Client[console.StreamTimelineRequest, console.StreamTimelineResponse] + getTimeline *connect_go.Client[console.TimelineQuery, console.GetTimelineResponse] streamLogs *connect_go.Client[console.StreamLogsRequest, console.StreamLogsResponse] } @@ -142,6 +152,11 @@ func (c *consoleServiceClient) StreamTimeline(ctx context.Context, req *connect_ return c.streamTimeline.CallServerStream(ctx, req) } +// GetTimeline calls xyz.block.ftl.v1.console.ConsoleService.GetTimeline. +func (c *consoleServiceClient) GetTimeline(ctx context.Context, req *connect_go.Request[console.TimelineQuery]) (*connect_go.Response[console.GetTimelineResponse], error) { + return c.getTimeline.CallUnary(ctx, req) +} + // StreamLogs calls xyz.block.ftl.v1.console.ConsoleService.StreamLogs. func (c *consoleServiceClient) StreamLogs(ctx context.Context, req *connect_go.Request[console.StreamLogsRequest]) (*connect_go.ServerStreamForClient[console.StreamLogsResponse], error) { return c.streamLogs.CallServerStream(ctx, req) @@ -156,6 +171,7 @@ type ConsoleServiceHandler interface { GetCalls(context.Context, *connect_go.Request[console.GetCallsRequest]) (*connect_go.Response[console.GetCallsResponse], error) GetRequestCalls(context.Context, *connect_go.Request[console.GetRequestCallsRequest]) (*connect_go.Response[console.GetRequestCallsResponse], error) StreamTimeline(context.Context, *connect_go.Request[console.StreamTimelineRequest], *connect_go.ServerStream[console.StreamTimelineResponse]) error + GetTimeline(context.Context, *connect_go.Request[console.TimelineQuery]) (*connect_go.Response[console.GetTimelineResponse], error) StreamLogs(context.Context, *connect_go.Request[console.StreamLogsRequest], *connect_go.ServerStream[console.StreamLogsResponse]) error } @@ -192,6 +208,11 @@ func NewConsoleServiceHandler(svc ConsoleServiceHandler, opts ...connect_go.Hand svc.StreamTimeline, opts..., )) + mux.Handle(ConsoleServiceGetTimelineProcedure, connect_go.NewUnaryHandler( + ConsoleServiceGetTimelineProcedure, + svc.GetTimeline, + opts..., + )) mux.Handle(ConsoleServiceStreamLogsProcedure, connect_go.NewServerStreamHandler( ConsoleServiceStreamLogsProcedure, svc.StreamLogs, @@ -223,6 +244,10 @@ func (UnimplementedConsoleServiceHandler) StreamTimeline(context.Context, *conne return connect_go.NewError(connect_go.CodeUnimplemented, errors.New("xyz.block.ftl.v1.console.ConsoleService.StreamTimeline is not implemented")) } +func (UnimplementedConsoleServiceHandler) GetTimeline(context.Context, *connect_go.Request[console.TimelineQuery]) (*connect_go.Response[console.GetTimelineResponse], error) { + return nil, connect_go.NewError(connect_go.CodeUnimplemented, errors.New("xyz.block.ftl.v1.console.ConsoleService.GetTimeline is not implemented")) +} + func (UnimplementedConsoleServiceHandler) StreamLogs(context.Context, *connect_go.Request[console.StreamLogsRequest], *connect_go.ServerStream[console.StreamLogsResponse]) error { return connect_go.NewError(connect_go.CodeUnimplemented, errors.New("xyz.block.ftl.v1.console.ConsoleService.StreamLogs is not implemented")) } From 5e6346f01090d4766dc789398a8ac99daa3389a0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 11 Sep 2023 04:19:51 +0000 Subject: [PATCH 2/2] chore(autofmt): Automated formatting --- .../src/providers/selected-timeline-entry-provider.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/console/client/src/providers/selected-timeline-entry-provider.tsx b/console/client/src/providers/selected-timeline-entry-provider.tsx index 529731c078..ff65643219 100644 --- a/console/client/src/providers/selected-timeline-entry-provider.tsx +++ b/console/client/src/providers/selected-timeline-entry-provider.tsx @@ -3,9 +3,7 @@ import {TimelineEvent} from '../protos/xyz/block/ftl/v1/console/console_pb' type SelectedTimelineEntryContextType = { selectedEntry: TimelineEvent | null - setSelectedEntry: React.Dispatch< - React.SetStateAction - > + setSelectedEntry: React.Dispatch> } export const SelectedTimelineEntryContext = @@ -15,8 +13,7 @@ export const SelectedTimelineEntryContext = }) export const SelectedTimelineEntryProvider = (props: PropsWithChildren) => { - const [selectedEntry, setSelectedEntry] = - useState(null) + const [selectedEntry, setSelectedEntry] = useState(null) return (