diff --git a/api/graphql/gqlgen.yml b/api/graphql/gqlgen.yml index 63281efdfc27b14a6545a7507bc8014b0f1cb1e3..4bca6ec2dc996ee5fda0622b56c69b6dcaa9a98a 100644 --- a/api/graphql/gqlgen.yml +++ b/api/graphql/gqlgen.yml @@ -9,6 +9,7 @@ model: autobind: - "github.com/git-bug/git-bug/api/graphql/models" + - "github.com/git-bug/git-bug/cache" - "github.com/git-bug/git-bug/repository" - "github.com/git-bug/git-bug/entity" - "github.com/git-bug/git-bug/entities/common" diff --git a/api/graphql/graph/operation.generated.go b/api/graphql/graph/operation.generated.go index 9d3d4cdc1312871493a04d8001d4825ab76308c0..25e579443f0878225426df06740e21c2105b1d8c 100644 --- a/api/graphql/graph/operation.generated.go +++ b/api/graphql/graph/operation.generated.go @@ -107,9 +107,9 @@ func (ec *executionContext) _OperationConnection_nodes(ctx context.Context, fiel } return graphql.Null } - res := resTmp.([]dag.OperationWithApply[*bug.Snapshot]) + res := resTmp.([]dag.Operation) fc.Result = res - return ec.marshalNOperation2ᚕgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperationWithApplyᚄ(ctx, field.Selections, res) + return ec.marshalNOperation2ᚕgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperationᚄ(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_OperationConnection_nodes(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -293,9 +293,9 @@ func (ec *executionContext) _OperationEdge_node(ctx context.Context, field graph } return graphql.Null } - res := resTmp.(dag.OperationWithApply[*bug.Snapshot]) + res := resTmp.(dag.Operation) fc.Result = res - return ec.marshalNOperation2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperationWithApply(ctx, field.Selections, res) + return ec.marshalNOperation2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperation(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_OperationEdge_node(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -319,7 +319,7 @@ func (ec *executionContext) fieldContext_OperationEdge_node(_ context.Context, f // region ************************** interface.gotpl *************************** -func (ec *executionContext) _Operation(ctx context.Context, sel ast.SelectionSet, obj dag.OperationWithApply[*bug.Snapshot]) graphql.Marshaler { +func (ec *executionContext) _Operation(ctx context.Context, sel ast.SelectionSet, obj dag.Operation) graphql.Marshaler { switch obj := (obj).(type) { case nil: return graphql.Null @@ -464,7 +464,7 @@ func (ec *executionContext) _OperationEdge(ctx context.Context, sel ast.Selectio // region ***************************** type.gotpl ***************************** -func (ec *executionContext) marshalNOperation2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperationWithApply(ctx context.Context, sel ast.SelectionSet, v dag.OperationWithApply[*bug.Snapshot]) graphql.Marshaler { +func (ec *executionContext) marshalNOperation2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperation(ctx context.Context, sel ast.SelectionSet, v dag.Operation) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { ec.Errorf(ctx, "the requested element is null which the schema does not allow") @@ -474,7 +474,7 @@ func (ec *executionContext) marshalNOperation2githubᚗcomᚋgitᚑbugᚋgitᚑb return ec._Operation(ctx, sel, v) } -func (ec *executionContext) marshalNOperation2ᚕgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperationWithApplyᚄ(ctx context.Context, sel ast.SelectionSet, v []dag.OperationWithApply[*bug.Snapshot]) graphql.Marshaler { +func (ec *executionContext) marshalNOperation2ᚕgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperationᚄ(ctx context.Context, sel ast.SelectionSet, v []dag.Operation) graphql.Marshaler { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup isLen1 := len(v) == 1 @@ -498,7 +498,7 @@ func (ec *executionContext) marshalNOperation2ᚕgithubᚗcomᚋgitᚑbugᚋgit if !isLen1 { defer wg.Done() } - ret[i] = ec.marshalNOperation2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperationWithApply(ctx, sel, v[i]) + ret[i] = ec.marshalNOperation2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperation(ctx, sel, v[i]) } if isLen1 { f(i) diff --git a/api/graphql/graph/root_.generated.go b/api/graphql/graph/root_.generated.go index 64520ccfa97f95335030e1060a76d8fe5242021d..5fed0daf75c51de15f387319ebd8724ceddf0d91 100644 --- a/api/graphql/graph/root_.generated.go +++ b/api/graphql/graph/root_.generated.go @@ -389,9 +389,9 @@ type ComplexityRoot struct { } Subscription struct { - AllEvents func(childComplexity int, repoFilter *string) int - BugEvents func(childComplexity int, repoFilter *string, query *string) int - IdentityEvents func(childComplexity int, repoFilter *string) int + AllEvents func(childComplexity int, repoRef *string, typename *string) int + BugEvents func(childComplexity int, repoRef *string) int + IdentityEvents func(childComplexity int, repoRef *string) int } } @@ -1854,7 +1854,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return 0, false } - return e.complexity.Subscription.AllEvents(childComplexity, args["repoFilter"].(*string)), true + return e.complexity.Subscription.AllEvents(childComplexity, args["repoRef"].(*string), args["typename"].(*string)), true case "Subscription.bugEvents": if e.complexity.Subscription.BugEvents == nil { @@ -1866,7 +1866,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return 0, false } - return e.complexity.Subscription.BugEvents(childComplexity, args["repoFilter"].(*string), args["query"].(*string)), true + return e.complexity.Subscription.BugEvents(childComplexity, args["repoRef"].(*string)), true case "Subscription.identityEvents": if e.complexity.Subscription.IdentityEvents == nil { @@ -1878,7 +1878,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return 0, false } - return e.complexity.Subscription.IdentityEvents(childComplexity, args["repoFilter"].(*string)), true + return e.complexity.Subscription.IdentityEvents(childComplexity, args["repoRef"].(*string)), true } return 0, false @@ -2604,7 +2604,8 @@ type LabelChangeResult { } `, BuiltIn: false}, {Name: "../schema/operation.graphql", Input: `"""An operation applied to an entity.""" -interface Operation { +interface Operation +@goModel(model: "github.com/git-bug/git-bug/entity/dag.Operation") { """The identifier of the operation""" id: ID! """The operations author.""" @@ -2693,31 +2694,31 @@ type Mutation # See each entity mutations `, BuiltIn: false}, {Name: "../schema/subscription.graphql", Input: `type Subscription { """Subscribe to events on all entities. For events on a specific repo you can provide a repo reference. Without it, you get the unique default repo or all repo events.""" - allEvents(repoFilter: String): EntityEvent! + allEvents(repoRef: String, typename: String): EntityEvent! """Subscribe to identity entity events. For events on a specific repo you can provide a repo reference. Without it, you get the unique default repo or all repo events.""" - identityEvents(repoFilter: String): IdentityEvent! + identityEvents(repoRef: String): IdentityEvent! """Subscribe to bug entity events. For events on a specific repo you can provide a repo reference. Without it, you get the unique default repo or all repo events.""" - bugEvents(repoFilter: String, query: String): BugEvent! + bugEvents(repoRef: String): BugEvent! } -enum EventType { +enum EntityEventType { CREATED UPDATED REMOVED } type EntityEvent { - type: EventType! + type: EntityEventType! entity: Entity } type IdentityEvent { - type: EventType! + type: EntityEventType! identity: Identity! } type BugEvent { - type: EventType! + type: EntityEventType! bug: Bug! } `, BuiltIn: false}, diff --git a/api/graphql/graph/subscription.generated.go b/api/graphql/graph/subscription.generated.go index b906c92576d80f5ebbf091d83f88f4040692ebd9..0050bab3c40703a15f60521544227918ce6bbe10 100644 --- a/api/graphql/graph/subscription.generated.go +++ b/api/graphql/graph/subscription.generated.go @@ -12,15 +12,16 @@ import ( "github.com/99designs/gqlgen/graphql" "github.com/git-bug/git-bug/api/graphql/models" + "github.com/git-bug/git-bug/cache" "github.com/vektah/gqlparser/v2/ast" ) // region ************************** generated!.gotpl ************************** type SubscriptionResolver interface { - AllEvents(ctx context.Context, repoFilter *string) (<-chan *models.EntityEvent, error) - IdentityEvents(ctx context.Context, repoFilter *string) (<-chan *models.IdentityEvent, error) - BugEvents(ctx context.Context, repoFilter *string, query *string) (<-chan *models.BugEvent, error) + AllEvents(ctx context.Context, repoRef *string, typename *string) (<-chan *models.EntityEvent, error) + IdentityEvents(ctx context.Context, repoRef *string) (<-chan *models.IdentityEvent, error) + BugEvents(ctx context.Context, repoRef *string) (<-chan *models.BugEvent, error) } // endregion ************************** generated!.gotpl ************************** @@ -30,24 +31,29 @@ type SubscriptionResolver interface { func (ec *executionContext) field_Subscription_allEvents_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} - arg0, err := ec.field_Subscription_allEvents_argsRepoFilter(ctx, rawArgs) + arg0, err := ec.field_Subscription_allEvents_argsRepoRef(ctx, rawArgs) if err != nil { return nil, err } - args["repoFilter"] = arg0 + args["repoRef"] = arg0 + arg1, err := ec.field_Subscription_allEvents_argsTypename(ctx, rawArgs) + if err != nil { + return nil, err + } + args["typename"] = arg1 return args, nil } -func (ec *executionContext) field_Subscription_allEvents_argsRepoFilter( +func (ec *executionContext) field_Subscription_allEvents_argsRepoRef( ctx context.Context, rawArgs map[string]any, ) (*string, error) { - if _, ok := rawArgs["repoFilter"]; !ok { + if _, ok := rawArgs["repoRef"]; !ok { var zeroVal *string return zeroVal, nil } - ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("repoFilter")) - if tmp, ok := rawArgs["repoFilter"]; ok { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("repoRef")) + if tmp, ok := rawArgs["repoRef"]; ok { return ec.unmarshalOString2ᚖstring(ctx, tmp) } @@ -55,32 +61,17 @@ func (ec *executionContext) field_Subscription_allEvents_argsRepoFilter( return zeroVal, nil } -func (ec *executionContext) field_Subscription_bugEvents_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { - var err error - args := map[string]any{} - arg0, err := ec.field_Subscription_bugEvents_argsRepoFilter(ctx, rawArgs) - if err != nil { - return nil, err - } - args["repoFilter"] = arg0 - arg1, err := ec.field_Subscription_bugEvents_argsQuery(ctx, rawArgs) - if err != nil { - return nil, err - } - args["query"] = arg1 - return args, nil -} -func (ec *executionContext) field_Subscription_bugEvents_argsRepoFilter( +func (ec *executionContext) field_Subscription_allEvents_argsTypename( ctx context.Context, rawArgs map[string]any, ) (*string, error) { - if _, ok := rawArgs["repoFilter"]; !ok { + if _, ok := rawArgs["typename"]; !ok { var zeroVal *string return zeroVal, nil } - ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("repoFilter")) - if tmp, ok := rawArgs["repoFilter"]; ok { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("typename")) + if tmp, ok := rawArgs["typename"]; ok { return ec.unmarshalOString2ᚖstring(ctx, tmp) } @@ -88,17 +79,27 @@ func (ec *executionContext) field_Subscription_bugEvents_argsRepoFilter( return zeroVal, nil } -func (ec *executionContext) field_Subscription_bugEvents_argsQuery( +func (ec *executionContext) field_Subscription_bugEvents_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Subscription_bugEvents_argsRepoRef(ctx, rawArgs) + if err != nil { + return nil, err + } + args["repoRef"] = arg0 + return args, nil +} +func (ec *executionContext) field_Subscription_bugEvents_argsRepoRef( ctx context.Context, rawArgs map[string]any, ) (*string, error) { - if _, ok := rawArgs["query"]; !ok { + if _, ok := rawArgs["repoRef"]; !ok { var zeroVal *string return zeroVal, nil } - ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("query")) - if tmp, ok := rawArgs["query"]; ok { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("repoRef")) + if tmp, ok := rawArgs["repoRef"]; ok { return ec.unmarshalOString2ᚖstring(ctx, tmp) } @@ -109,24 +110,24 @@ func (ec *executionContext) field_Subscription_bugEvents_argsQuery( func (ec *executionContext) field_Subscription_identityEvents_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} - arg0, err := ec.field_Subscription_identityEvents_argsRepoFilter(ctx, rawArgs) + arg0, err := ec.field_Subscription_identityEvents_argsRepoRef(ctx, rawArgs) if err != nil { return nil, err } - args["repoFilter"] = arg0 + args["repoRef"] = arg0 return args, nil } -func (ec *executionContext) field_Subscription_identityEvents_argsRepoFilter( +func (ec *executionContext) field_Subscription_identityEvents_argsRepoRef( ctx context.Context, rawArgs map[string]any, ) (*string, error) { - if _, ok := rawArgs["repoFilter"]; !ok { + if _, ok := rawArgs["repoRef"]; !ok { var zeroVal *string return zeroVal, nil } - ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("repoFilter")) - if tmp, ok := rawArgs["repoFilter"]; ok { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("repoRef")) + if tmp, ok := rawArgs["repoRef"]; ok { return ec.unmarshalOString2ᚖstring(ctx, tmp) } @@ -168,9 +169,9 @@ func (ec *executionContext) _BugEvent_type(ctx context.Context, field graphql.Co } return graphql.Null } - res := resTmp.(models.EventType) + res := resTmp.(cache.EntityEventType) fc.Result = res - return ec.marshalNEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEventType(ctx, field.Selections, res) + return ec.marshalNEntityEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋcacheᚐEntityEventType(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_BugEvent_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -180,7 +181,7 @@ func (ec *executionContext) fieldContext_BugEvent_type(_ context.Context, field IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type EventType does not have child fields") + return nil, errors.New("field of type EntityEventType does not have child fields") }, } return fc, nil @@ -284,9 +285,9 @@ func (ec *executionContext) _EntityEvent_type(ctx context.Context, field graphql } return graphql.Null } - res := resTmp.(models.EventType) + res := resTmp.(cache.EntityEventType) fc.Result = res - return ec.marshalNEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEventType(ctx, field.Selections, res) + return ec.marshalNEntityEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋcacheᚐEntityEventType(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_EntityEvent_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -296,7 +297,7 @@ func (ec *executionContext) fieldContext_EntityEvent_type(_ context.Context, fie IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type EventType does not have child fields") + return nil, errors.New("field of type EntityEventType does not have child fields") }, } return fc, nil @@ -369,9 +370,9 @@ func (ec *executionContext) _IdentityEvent_type(ctx context.Context, field graph } return graphql.Null } - res := resTmp.(models.EventType) + res := resTmp.(cache.EntityEventType) fc.Result = res - return ec.marshalNEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEventType(ctx, field.Selections, res) + return ec.marshalNEntityEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋcacheᚐEntityEventType(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_IdentityEvent_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -381,7 +382,7 @@ func (ec *executionContext) fieldContext_IdentityEvent_type(_ context.Context, f IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type EventType does not have child fields") + return nil, errors.New("field of type EntityEventType does not have child fields") }, } return fc, nil @@ -463,7 +464,7 @@ func (ec *executionContext) _Subscription_allEvents(ctx context.Context, field g }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Subscription().AllEvents(rctx, fc.Args["repoFilter"].(*string)) + return ec.resolvers.Subscription().AllEvents(rctx, fc.Args["repoRef"].(*string), fc.Args["typename"].(*string)) }) if err != nil { ec.Error(ctx, err) @@ -538,7 +539,7 @@ func (ec *executionContext) _Subscription_identityEvents(ctx context.Context, fi }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Subscription().IdentityEvents(rctx, fc.Args["repoFilter"].(*string)) + return ec.resolvers.Subscription().IdentityEvents(rctx, fc.Args["repoRef"].(*string)) }) if err != nil { ec.Error(ctx, err) @@ -613,7 +614,7 @@ func (ec *executionContext) _Subscription_bugEvents(ctx context.Context, field g }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Subscription().BugEvents(rctx, fc.Args["repoFilter"].(*string), fc.Args["query"].(*string)) + return ec.resolvers.Subscription().BugEvents(rctx, fc.Args["repoRef"].(*string)) }) if err != nil { ec.Error(ctx, err) @@ -871,13 +872,13 @@ func (ec *executionContext) marshalNEntityEvent2ᚖgithubᚗcomᚋgitᚑbugᚋgi return ec._EntityEvent(ctx, sel, v) } -func (ec *executionContext) unmarshalNEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEventType(ctx context.Context, v any) (models.EventType, error) { - var res models.EventType +func (ec *executionContext) unmarshalNEntityEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋcacheᚐEntityEventType(ctx context.Context, v any) (cache.EntityEventType, error) { + var res cache.EntityEventType err := res.UnmarshalGQL(v) return res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalNEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEventType(ctx context.Context, sel ast.SelectionSet, v models.EventType) graphql.Marshaler { +func (ec *executionContext) marshalNEntityEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋcacheᚐEntityEventType(ctx context.Context, sel ast.SelectionSet, v cache.EntityEventType) graphql.Marshaler { return v } diff --git a/api/graphql/models/gen_models.go b/api/graphql/models/gen_models.go index 94ac43e88484203035c5f0b2b8bea16315b57d5b..5a9affaa72d30449118e6fffb2f5d57e75bde9c1 100644 --- a/api/graphql/models/gen_models.go +++ b/api/graphql/models/gen_models.go @@ -3,11 +3,7 @@ package models import ( - "bytes" - "fmt" - "io" - "strconv" - + "github.com/git-bug/git-bug/cache" "github.com/git-bug/git-bug/entities/bug" "github.com/git-bug/git-bug/entities/common" "github.com/git-bug/git-bug/entity/dag" @@ -194,8 +190,8 @@ type BugEditCommentPayload struct { } type BugEvent struct { - Type EventType `json:"type"` - Bug BugWrapper `json:"bug"` + Type cache.EntityEventType `json:"type"` + Bug BugWrapper `json:"bug"` } type BugSetTitleInput struct { @@ -269,8 +265,8 @@ type BugTimelineItemEdge struct { } type EntityEvent struct { - Type EventType `json:"type"` - Entity Entity `json:"entity,omitempty"` + Type cache.EntityEventType `json:"type"` + Entity Entity `json:"entity,omitempty"` } type IdentityConnection struct { @@ -286,8 +282,8 @@ type IdentityEdge struct { } type IdentityEvent struct { - Type EventType `json:"type"` - Identity IdentityWrapper `json:"identity"` + Type cache.EntityEventType `json:"type"` + Identity IdentityWrapper `json:"identity"` } type LabelConnection struct { @@ -307,16 +303,16 @@ type Mutation struct { // The connection type for an Operation type OperationConnection struct { - Edges []*OperationEdge `json:"edges"` - Nodes []dag.OperationWithApply[*bug.Snapshot] `json:"nodes"` - PageInfo *PageInfo `json:"pageInfo"` - TotalCount int `json:"totalCount"` + Edges []*OperationEdge `json:"edges"` + Nodes []dag.Operation `json:"nodes"` + PageInfo *PageInfo `json:"pageInfo"` + TotalCount int `json:"totalCount"` } // Represent an Operation type OperationEdge struct { - Cursor string `json:"cursor"` - Node dag.OperationWithApply[*bug.Snapshot] `json:"node"` + Cursor string `json:"cursor"` + Node dag.Operation `json:"node"` } // Information about pagination in a connection. @@ -336,60 +332,3 @@ type Query struct { type Subscription struct { } - -type EventType string - -const ( - EventTypeCreated EventType = "CREATED" - EventTypeUpdated EventType = "UPDATED" - EventTypeRemoved EventType = "REMOVED" -) - -var AllEventType = []EventType{ - EventTypeCreated, - EventTypeUpdated, - EventTypeRemoved, -} - -func (e EventType) IsValid() bool { - switch e { - case EventTypeCreated, EventTypeUpdated, EventTypeRemoved: - return true - } - return false -} - -func (e EventType) String() string { - return string(e) -} - -func (e *EventType) UnmarshalGQL(v any) error { - str, ok := v.(string) - if !ok { - return fmt.Errorf("enums must be strings") - } - - *e = EventType(str) - if !e.IsValid() { - return fmt.Errorf("%s is not a valid EventType", str) - } - return nil -} - -func (e EventType) MarshalGQL(w io.Writer) { - fmt.Fprint(w, strconv.Quote(e.String())) -} - -func (e *EventType) UnmarshalJSON(b []byte) error { - s, err := strconv.Unquote(string(b)) - if err != nil { - return err - } - return e.UnmarshalGQL(s) -} - -func (e EventType) MarshalJSON() ([]byte, error) { - var buf bytes.Buffer - e.MarshalGQL(&buf) - return buf.Bytes(), nil -} diff --git a/api/graphql/resolvers/subscription.go b/api/graphql/resolvers/subscription.go index a7422bf8cef781e5b0bf6458f2f3642b564aee59..ffcd764e7f167dda103b06a88c9fde50fd3e4c2c 100644 --- a/api/graphql/resolvers/subscription.go +++ b/api/graphql/resolvers/subscription.go @@ -8,6 +8,7 @@ import ( "github.com/git-bug/git-bug/api/graphql/models" "github.com/git-bug/git-bug/cache" "github.com/git-bug/git-bug/entities/bug" + "github.com/git-bug/git-bug/entities/identity" "github.com/git-bug/git-bug/entity" ) @@ -17,91 +18,128 @@ type subscriptionResolver struct { cache *cache.MultiRepoCache } -func (s subscriptionResolver) AllEvents(ctx context.Context, repoFilter *string) (<-chan *models.EntityEvent, error) { - // TODO implement me - panic("implement me") -} +func (s subscriptionResolver) AllEvents(ctx context.Context, repoRef *string, typename *string) (<-chan *models.EntityEvent, error) { + out := make(chan *models.EntityEvent) + sub := &subscription[models.EntityEvent]{ + cache: s.cache, + out: out, + makeEvent: func(repo *cache.RepoCache, excerpt cache.Excerpt, eventType cache.EntityEventType) *models.EntityEvent { + switch excerpt := excerpt.(type) { + case *cache.BugExcerpt: + return &models.EntityEvent{Type: eventType, Entity: models.NewLazyBug(repo, excerpt)} + case *cache.IdentityExcerpt: + return &models.EntityEvent{Type: eventType, Entity: models.NewLazyIdentity(repo, excerpt)} + default: + panic(fmt.Sprintf("unknown excerpt type: %T", excerpt)) + } + }, + } -var _ cache.Observer = &subscription[any]{} + var repoRefStr string + if repoRef != nil { + repoRefStr = *repoRef + } -type subscription[T any] struct { - out chan *T - filter func(T) bool - excerptResolver func(id entity.Id) cache.Excerpt - repo *cache.RepoCache -} + var typenameStr string + if typename != nil { + typenameStr = *typename + } -func (s subscription[T]) EntityEvent(event cache.EntityEventType, typename string, id entity.Id) { + err := s.cache.RegisterObserver(sub, repoRefStr, typenameStr) + if err != nil { + return nil, err + } + go func() { + <-ctx.Done() + s.cache.UnregisterObserver(sub) + }() + + return out, nil } -func (s subscriptionResolver) IdentityEvents(ctx context.Context, repoFilter *string) (<-chan *models.IdentityEvent, error) { - out := make(chan *models.IdentityEvent) - sub := &subscription[models.IdentityEvent]{ - out: out, - filter: func(e models.IdentityEvent) bool { return true}, - excerptResolver: s.cache. +func (s subscriptionResolver) BugEvents(ctx context.Context, repoRef *string) (<-chan *models.BugEvent, error) { + out := make(chan *models.BugEvent) + sub := &subscription[models.BugEvent]{ + cache: s.cache, + out: out, + makeEvent: func(repo *cache.RepoCache, excerpt cache.Excerpt, event cache.EntityEventType) *models.BugEvent { + return &models.BugEvent{Type: event, Bug: models.NewLazyBug(repo, excerpt.(*cache.BugExcerpt))} + }, + } + + var repoRefStr string + if repoRef != nil { + repoRefStr = *repoRef } -} -func (s subscriptionResolver) BugEvents(ctx context.Context, repoFilter *string, query *string) (<-chan *models.BugEvent, error) { - // TODO implement me - panic("implement me") + err := s.cache.RegisterObserver(sub, repoRefStr, bug.Typename) + if err != nil { + return nil, err + } + + go func() { + <-ctx.Done() + s.cache.UnregisterObserver(sub) + }() + + return out, nil } -func (s subscriptionResolver) BugChanged(ctx context.Context, repoRef *string, query *string) (<-chan *models.BugChange, error) { - var repo *cache.RepoCache - var err error +func (s subscriptionResolver) IdentityEvents(ctx context.Context, repoRef *string) (<-chan *models.IdentityEvent, error) { + out := make(chan *models.IdentityEvent) + sub := &subscription[models.IdentityEvent]{ + cache: s.cache, + out: out, + makeEvent: func(repo *cache.RepoCache, excerpt cache.Excerpt, event cache.EntityEventType) *models.IdentityEvent { + return &models.IdentityEvent{Type: event, Identity: models.NewLazyIdentity(repo, excerpt.(*cache.IdentityExcerpt))} + }, + } - if repoRef == nil { - repo, err = s.cache.DefaultRepo() - } else { - repo, err = s.cache.ResolveRepo(*repoRef) + var repoRefStr string + if repoRef != nil { + repoRefStr = *repoRef } + err := s.cache.RegisterObserver(sub, repoRefStr, identity.Typename) if err != nil { return nil, err } - out := make(chan *models.BugChange) - sub := bugSubscription{out: out, repo: repo} - repo.RegisterObserver(bug.Typename, sub) - go func() { <-ctx.Done() - repo.RegisterObserver(bug.Typename, sub) + s.cache.UnregisterObserver(sub) }() return out, nil } -type bugSubscription struct { - out chan *models.BugChange - repo *cache.RepoCache +var _ cache.Observer = &subscription[any]{} + +type subscription[eventT any] struct { + cache *cache.MultiRepoCache + out chan *eventT + filter func(cache.Excerpt) bool + makeEvent func(repo *cache.RepoCache, excerpt cache.Excerpt, event cache.EntityEventType) *eventT } -func (bs bugSubscription) EntityCreated(_ string, id entity.Id) { - excerpt, err := bs.repo.Bugs().ResolveExcerpt(id) +func (s subscription[eventT]) EntityEvent(event cache.EntityEventType, repoName string, typename string, id entity.Id) { + repo, err := s.cache.ResolveRepo(repoName) if err != nil { - // Should never happen - fmt.Printf("bug in the cache: could not resolve excerpt for %s: %s\n", id, err) + // something terrible happened return } - bs.out <- &models.BugChange{ - Type: models.ChangeTypeCreated, - Bug: models.NewLazyBug(bs.repo, excerpt), + var excerpt cache.Excerpt + switch typename { + case bug.Typename: + excerpt, err = repo.Bugs().ResolveExcerpt(id) + case identity.Typename: + excerpt, err = repo.Identities().ResolveExcerpt(id) + default: + panic(fmt.Sprintf("unknown typename: %s", typename)) } -} - -func (bs bugSubscription) EntityUpdated(_ string, id entity.Id) { - excerpt, err := bs.repo.Bugs().ResolveExcerpt(id) - if err != nil { - // Should never happen - fmt.Printf("bug in the cache: could not resolve excerpt for %s: %s\n", id, err) + if s.filter != nil && !s.filter(excerpt) { return } - bs.out <- &models.BugChange{ - Type: models.ChangeTypeUpdated, - Bug: models.NewLazyBug(bs.repo, excerpt), - } + s.out <- s.makeEvent(repo, excerpt, event) } diff --git a/api/graphql/schema/operation.graphql b/api/graphql/schema/operation.graphql index ed952c06d9280758d0491e713593e98d311a3a4b..46455ccd6e1f431ab54ee656286e28a4d300c99e 100644 --- a/api/graphql/schema/operation.graphql +++ b/api/graphql/schema/operation.graphql @@ -1,5 +1,6 @@ """An operation applied to an entity.""" -interface Operation { +interface Operation +@goModel(model: "github.com/git-bug/git-bug/entity/dag.Operation") { """The identifier of the operation""" id: ID! """The operations author.""" diff --git a/api/graphql/schema/subscription.graphql b/api/graphql/schema/subscription.graphql index 29e0106abcf0c88c3614c9bec6e8c5e450f1e4bf..7843afa268baa4a3b8bad53e0df5b25614a7c462 100644 --- a/api/graphql/schema/subscription.graphql +++ b/api/graphql/schema/subscription.graphql @@ -1,29 +1,29 @@ type Subscription { """Subscribe to events on all entities. For events on a specific repo you can provide a repo reference. Without it, you get the unique default repo or all repo events.""" - allEvents(repoFilter: String): EntityEvent! + allEvents(repoRef: String, typename: String): EntityEvent! """Subscribe to identity entity events. For events on a specific repo you can provide a repo reference. Without it, you get the unique default repo or all repo events.""" - identityEvents(repoFilter: String): IdentityEvent! + identityEvents(repoRef: String): IdentityEvent! """Subscribe to bug entity events. For events on a specific repo you can provide a repo reference. Without it, you get the unique default repo or all repo events.""" - bugEvents(repoFilter: String, query: String): BugEvent! + bugEvents(repoRef: String): BugEvent! } -enum EventType { +enum EntityEventType { CREATED UPDATED REMOVED } type EntityEvent { - type: EventType! + type: EntityEventType! entity: Entity } type IdentityEvent { - type: EventType! + type: EntityEventType! identity: Identity! } type BugEvent { - type: EventType! + type: EntityEventType! bug: Bug! } diff --git a/cache/events.go b/cache/events.go index 9d10b8ef3d20cf67f12c23f94a432e7a3241f236..3fb041fa2d6b486ff62eeffce89fcf0337f2aa0f 100644 --- a/cache/events.go +++ b/cache/events.go @@ -1,6 +1,12 @@ package cache -import "github.com/git-bug/git-bug/entity" +import ( + "fmt" + "io" + "strconv" + + "github.com/git-bug/git-bug/entity" +) type BuildEventType int @@ -43,5 +49,36 @@ const ( // Observer gets notified of changes in entities in the cache type Observer interface { - EntityEvent(event EntityEventType, repoRef string, typename string, id entity.Id) + EntityEvent(event EntityEventType, repoName string, typename string, id entity.Id) +} + +func (e EntityEventType) MarshalGQL(w io.Writer) { + switch e { + case EntityEventCreated: + _, _ = w.Write([]byte(strconv.Quote("CREATED"))) + case EntityEventUpdated: + _, _ = w.Write([]byte(strconv.Quote("UPDATED"))) + case EntityEventRemoved: + _, _ = w.Write([]byte(strconv.Quote("REMOVED"))) + default: + panic("missing case") + } +} + +func (e *EntityEventType) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + switch str { + case "CREATED": + *e = EntityEventCreated + case "UPDATED": + *e = EntityEventUpdated + case "REMOVED": + *e = EntityEventRemoved + default: + return fmt.Errorf("%s is not a valid EntityEventType", str) + } + return nil } diff --git a/cache/multi_repo_cache.go b/cache/multi_repo_cache.go index 46852ca4ef1817fbcf0e3484acb5756d1b091b6f..333fb34008c94d44624126b4f526518864ee6765 100644 --- a/cache/multi_repo_cache.go +++ b/cache/multi_repo_cache.go @@ -20,7 +20,7 @@ func NewMultiRepoCache() *MultiRepoCache { } } -// RegisterRepository register a named repository. Use this for multi-repo setup +// RegisterRepository registers a named repository. Use this for multi-repo setup func (c *MultiRepoCache) RegisterRepository(repo repository.ClockedRepo, name string) (*RepoCache, chan BuildEvent) { r, events := NewNamedRepoCache(repo, name) @@ -42,12 +42,12 @@ func (c *MultiRepoCache) RegisterRepository(repo repository.ClockedRepo, name st return r, out } -// RegisterDefaultRepository register an unnamed repository. Use this for single-repo setup +// RegisterDefaultRepository registers an unnamed repository. Use this for single-repo setup func (c *MultiRepoCache) RegisterDefaultRepository(repo repository.ClockedRepo) (*RepoCache, chan BuildEvent) { return c.RegisterRepository(repo, defaultRepoName) } -// DefaultRepo retrieve the default repository +// DefaultRepo retrieves the default repository func (c *MultiRepoCache) DefaultRepo() (*RepoCache, error) { if len(c.repos) != 1 { return nil, fmt.Errorf("repository is not unique") @@ -60,7 +60,7 @@ func (c *MultiRepoCache) DefaultRepo() (*RepoCache, error) { panic("unreachable") } -// ResolveRepo retrieve a repository by name +// ResolveRepo retrieves a repository by name func (c *MultiRepoCache) ResolveRepo(name string) (*RepoCache, error) { r, ok := c.repos[name] if !ok { @@ -69,12 +69,44 @@ func (c *MultiRepoCache) ResolveRepo(name string) (*RepoCache, error) { return r, nil } -func (c *MultiRepoCache) RegisterObserver(observer Observer) { +// RegisterObserver registers an Observer on repo and entity, according to nameFilter and typename. +// - if nameFilter is empty, the observer is registered on all available repo +// - if nameFilter is not empty, the observer is registered on the repo with the matching name +// - if typename is empty, the observer is registered on all available entities +// - if typename is not empty, the observer is registered on the matching entity type only +func (c *MultiRepoCache) RegisterObserver(observer Observer, nameFilter string, typename string) error { + if nameFilter == "" { + for repoName, repo := range c.repos { + if typename == "" { + repo.registerAllObservers(repoName, observer) + } else { + if err := repo.registerObserver(repoName, typename, observer); err != nil { + return err + } + } + } + return nil + } + r, err := c.ResolveRepo(nameFilter) + if err != nil { + return err + } + if typename == "" { + r.registerAllObservers(r.Name(), observer) + } else { + if err := r.registerObserver(r.Name(), typename, observer); err != nil { + return err + } + } + return nil } +// UnregisterObserver deregisters the observer from all repos and all entity types. func (c *MultiRepoCache) UnregisterObserver(observer Observer) { - + for _, repo := range c.repos { + repo.unregisterAllObservers(observer) + } } // Close will do anything that is needed to close the cache properly diff --git a/cache/repo_cache.go b/cache/repo_cache.go index 4a514e54d1ac916b67fec239c7fc4d10cb9363ed..c43cc0a1c7cd15066f699e0d0c3776f447954bab 100644 --- a/cache/repo_cache.go +++ b/cache/repo_cache.go @@ -5,6 +5,7 @@ import ( "io" "os" "strconv" + "strings" "sync" "github.com/git-bug/git-bug/entities/bug" @@ -37,6 +38,8 @@ type cacheMgmt interface { RemoveAll() error MergeAll(remote string) <-chan entity.MergeResult GetNamespace() string + RegisterObserver(repoName string, observer Observer) + UnregisterObserver(observer Observer) Close() error } @@ -139,28 +142,6 @@ func NewRepoCacheNoEvents(r repository.ClockedRepo) (*RepoCache, error) { return cache, nil } -func (c *RepoCache) registerObserver(repoRef string, typename string, observer Observer) { - switch typename { - case bug.Typename: - c.bugs.RegisterObserver(repoRef, observer) - case identity.Typename: - c.identities.RegisterObserver(repoRef, observer) - default: - panic(fmt.Sprintf("unknown typename %q", typename)) - } -} - -func (c *RepoCache) unregisterObserver(typename string, observer Observer) { - switch typename { - case bug.Typename: - c.bugs.UnregisterObserver(observer) - case identity.Typename: - c.identities.UnregisterObserver(observer) - default: - panic(fmt.Sprintf("unknown typename %q", typename)) - } -} - // Bugs gives access to the Bug entities func (c *RepoCache) Bugs() *RepoCacheBug { return c.bugs @@ -251,6 +232,34 @@ func (c *RepoCache) buildCache(events chan BuildEvent) { wg.Wait() } +func (c *RepoCache) registerObserver(repoName string, typename string, observer Observer) error { + switch typename { + case bug.Typename: + c.bugs.RegisterObserver(repoName, observer) + case identity.Typename: + c.identities.RegisterObserver(repoName, observer) + default: + var allTypenames []string + for _, subcache := range c.subcaches { + allTypenames = append(allTypenames, subcache.Typename()) + } + return fmt.Errorf("unknown typename `%s`, available types are [%s]", typename, strings.Join(allTypenames, ", ")) + } + return nil +} + +func (c *RepoCache) registerAllObservers(repoName string, observer Observer) { + for _, subcache := range c.subcaches { + subcache.RegisterObserver(repoName, observer) + } +} + +func (c *RepoCache) unregisterAllObservers(observer Observer) { + for _, subcache := range c.subcaches { + subcache.UnregisterObserver(observer) + } +} + // repoIsAvailable check is the given repository is locked by a Cache. // Note: this is a smart function that will clean the lock file if the // corresponding process is not there anymore. diff --git a/cache/repo_cache_test.go b/cache/repo_cache_test.go index a2a4e510f34b492cdd3705b7e5728a479ddb96c4..19445b409b513d97663c1df28fc81a2f4f2d53db 100644 --- a/cache/repo_cache_test.go +++ b/cache/repo_cache_test.go @@ -1,7 +1,6 @@ package cache import ( - "fmt" "strings" "testing" "time" @@ -22,22 +21,21 @@ type observerEvent struct { id entity.Id } +var _ Observer = &observer{} + type observer struct { created []observerEvent updated []observerEvent removed []observerEvent } -func (o *observer) EntityEvent(event EntityEventType, typename string, id entity.Id) { +func (o *observer) EntityEvent(event EntityEventType, _ string, typename string, id entity.Id) { switch event { case EntityEventCreated: - fmt.Printf("Created %s: %s\n", typename, id.Human()) o.created = append(o.created, observerEvent{typename, id}) case EntityEventUpdated: - fmt.Printf("Updated %s: %s\n", typename, id.Human()) o.updated = append(o.updated, observerEvent{typename, id}) case EntityEventRemoved: - fmt.Printf("Removed %s: %s\n", typename, id.Human()) o.removed = append(o.removed, observerEvent{typename, id}) } } @@ -69,8 +67,8 @@ func TestCache(t *testing.T) { require.NoError(t, err) var obsIdentity, obsBug observer - cache.RegisterObserver(identity.Typename, &obsIdentity) - cache.RegisterObserver(bug.Typename, &obsBug) + require.NoError(t, cache.registerObserver("repotest", identity.Typename, &obsIdentity)) + require.NoError(t, cache.registerObserver("repotest", bug.Typename, &obsBug)) // Create, set and get user identity iden1, err := cache.Identities().New("René Descartes", "rene@descartes.fr") @@ -149,6 +147,12 @@ func TestCache(t *testing.T) { require.NoError(t, err) require.Len(t, res, 1) + // Updating + _, _, err = bug1.AddComment("new comment") + require.NoError(t, err) + assertOberserverEvent(obsIdentity, 2, 0, 0) + assertOberserverEvent(obsBug, 2, 1, 0) + // Close require.NoError(t, cache.Close()) require.Empty(t, cache.bugs.cached) @@ -160,8 +164,8 @@ func TestCache(t *testing.T) { // to check the signatures, we also load the identity used above cache, err = NewRepoCacheNoEvents(repo) require.NoError(t, err) - cache.RegisterObserver(identity.Typename, &obsIdentity) - cache.RegisterObserver(bug.Typename, &obsBug) + require.NoError(t, cache.registerObserver("repotest", identity.Typename, &obsIdentity)) + require.NoError(t, cache.registerObserver("repotest", bug.Typename, &obsBug)) require.Len(t, cache.bugs.cached, 0) require.Len(t, cache.bugs.excerpts, 2) @@ -196,11 +200,11 @@ func TestCache(t *testing.T) { err = cache.Identities().Remove(iden1.Id().String()[:10]) require.NoError(t, err) assertOberserverEvent(obsIdentity, 2, 0, 1) - assertOberserverEvent(obsBug, 2, 0, 0) + assertOberserverEvent(obsBug, 2, 1, 0) err = cache.Bugs().Remove(bug1.Id().String()[:10]) require.NoError(t, err) assertOberserverEvent(obsIdentity, 2, 0, 1) - assertOberserverEvent(obsBug, 2, 0, 1) + assertOberserverEvent(obsBug, 2, 1, 1) require.Len(t, cache.bugs.cached, 0) require.Len(t, cache.bugs.excerpts, 1) require.Len(t, cache.identities.cached, 0) @@ -211,16 +215,16 @@ func TestCache(t *testing.T) { _, err = cache.Identities().New("René Descartes", "rene@descartes.fr") require.NoError(t, err) assertOberserverEvent(obsIdentity, 3, 0, 1) - assertOberserverEvent(obsBug, 2, 0, 1) + assertOberserverEvent(obsBug, 2, 1, 1) _, _, err = cache.Bugs().NewRaw(iden2, time.Now().Unix(), "title", "message", nil, nil) require.NoError(t, err) assertOberserverEvent(obsIdentity, 3, 0, 1) - assertOberserverEvent(obsBug, 3, 0, 1) + assertOberserverEvent(obsBug, 3, 1, 1) err = cache.RemoveAll() require.NoError(t, err) assertOberserverEvent(obsIdentity, 3, 0, 3) - assertOberserverEvent(obsBug, 3, 0, 3) + assertOberserverEvent(obsBug, 3, 1, 3) require.Len(t, cache.bugs.cached, 0) require.Len(t, cache.bugs.excerpts, 0) require.Len(t, cache.identities.cached, 0) diff --git a/cache/subcache.go b/cache/subcache.go index 5fccfc39a970f7f2e854105c64d32b1ec8961d38..c9aa5f68444d632dd2d951119d90e0760eb54626 100644 --- a/cache/subcache.go +++ b/cache/subcache.go @@ -61,7 +61,7 @@ type SubCache[EntityT entity.Interface, ExcerptT Excerpt, CacheT CacheEntity] st lru lruIdCache muObservers sync.RWMutex - observers map[Observer]struct{} + observers map[Observer]string // observer --> repo name } func NewSubCache[EntityT entity.Interface, ExcerptT Excerpt, CacheT CacheEntity]( @@ -335,13 +335,13 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) Close() error { return nil } -func (sc *SubCache[EntityT, ExcerptT, CacheT]) RegisterObserver(observer Observer) { +func (sc *SubCache[EntityT, ExcerptT, CacheT]) RegisterObserver(repoName string, observer Observer) { sc.muObservers.Lock() defer sc.muObservers.Unlock() if sc.observers == nil { - sc.observers = make(map[Observer]struct{}) + sc.observers = make(map[Observer]string) } - sc.observers[observer] = struct{}{} + sc.observers[observer] = repoName } func (sc *SubCache[EntityT, ExcerptT, CacheT]) UnregisterObserver(observer Observer) { @@ -641,8 +641,8 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) entityUpdated(id entity.Id) error // notifyObservers notifies all the observers when something happening for an entity func (sc *SubCache[EntityT, ExcerptT, CacheT]) notifyObservers(event EntityEventType, id entity.Id) { sc.muObservers.RLock() - for observer := range sc.observers { - observer.EntityEvent(event, sc.repo.sc.typename, id) + for observer, repoName := range sc.observers { + observer.EntityEvent(event, repoName, sc.typename, id) } sc.muObservers.RUnlock() }