diff --git a/api/graphql/gqlgen.yml b/api/graphql/gqlgen.yml index 8d6daf4f388175146c845c844b6d3b72e60a4de1..63281efdfc27b14a6545a7507bc8014b0f1cb1e3 100644 --- a/api/graphql/gqlgen.yml +++ b/api/graphql/gqlgen.yml @@ -11,7 +11,6 @@ autobind: - "github.com/git-bug/git-bug/api/graphql/models" - "github.com/git-bug/git-bug/repository" - "github.com/git-bug/git-bug/entity" - - "github.com/git-bug/git-bug/entity/dag" - "github.com/git-bug/git-bug/entities/common" - "github.com/git-bug/git-bug/entities/bug" - "github.com/git-bug/git-bug/entities/identity" diff --git a/api/graphql/graph/bug.generated.go b/api/graphql/graph/bug.generated.go index 0a6762a8be83417988168e0250e51b983570b841..12c7ae47566104268737893a7f43557649ef1e2c 100644 --- a/api/graphql/graph/bug.generated.go +++ b/api/graphql/graph/bug.generated.go @@ -1576,7 +1576,7 @@ func (ec *executionContext) fieldContext_BugEdge_node(_ context.Context, field g // region **************************** object.gotpl **************************** -var bugImplementors = []string{"Bug", "Authored"} +var bugImplementors = []string{"Bug", "Authored", "Entity"} func (ec *executionContext) _Bug(ctx context.Context, sel ast.SelectionSet, obj models.BugWrapper) graphql.Marshaler { fields := graphql.CollectFields(ec.OperationContext, sel, bugImplementors) diff --git a/api/graphql/graph/identity.generated.go b/api/graphql/graph/identity.generated.go index bdd411c4e2cf6193171bffc7551449bd5f702a5c..d2d99ee738a52668e086ab5174dd3f23cd8114ee 100644 --- a/api/graphql/graph/identity.generated.go +++ b/api/graphql/graph/identity.generated.go @@ -702,7 +702,7 @@ func (ec *executionContext) fieldContext_IdentityEdge_node(_ context.Context, fi // region **************************** object.gotpl **************************** -var identityImplementors = []string{"Identity"} +var identityImplementors = []string{"Identity", "Entity"} func (ec *executionContext) _Identity(ctx context.Context, sel ast.SelectionSet, obj models.IdentityWrapper) graphql.Marshaler { fields := graphql.CollectFields(ec.OperationContext, sel, identityImplementors) diff --git a/api/graphql/graph/operation.generated.go b/api/graphql/graph/operation.generated.go index 25e579443f0878225426df06740e21c2105b1d8c..9d3d4cdc1312871493a04d8001d4825ab76308c0 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.Operation) + res := resTmp.([]dag.OperationWithApply[*bug.Snapshot]) fc.Result = res - return ec.marshalNOperation2ᚕgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperationᚄ(ctx, field.Selections, res) + return ec.marshalNOperation2ᚕgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperationWithApplyᚄ(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.Operation) + res := resTmp.(dag.OperationWithApply[*bug.Snapshot]) fc.Result = res - return ec.marshalNOperation2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperation(ctx, field.Selections, res) + return ec.marshalNOperation2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperationWithApply(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.Operation) graphql.Marshaler { +func (ec *executionContext) _Operation(ctx context.Context, sel ast.SelectionSet, obj dag.OperationWithApply[*bug.Snapshot]) 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ᚐOperation(ctx context.Context, sel ast.SelectionSet, v dag.Operation) graphql.Marshaler { +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 { 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ᚐOperationᚄ(ctx context.Context, sel ast.SelectionSet, v []dag.Operation) graphql.Marshaler { +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 { 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ᚐOperation(ctx, sel, v[i]) + ret[i] = ec.marshalNOperation2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperationWithApply(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 9ffbfa1f8f4e0aef0b02ec05c6769781292080cb..64520ccfa97f95335030e1060a76d8fe5242021d 100644 --- a/api/graphql/graph/root_.generated.go +++ b/api/graphql/graph/root_.generated.go @@ -116,11 +116,6 @@ type ComplexityRoot struct { MessageIsEmpty func(childComplexity int) int } - BugChange struct { - Bug func(childComplexity int) int - Type func(childComplexity int) int - } - BugChangeLabelPayload struct { Bug func(childComplexity int) int ClientMutationID func(childComplexity int) int @@ -206,6 +201,11 @@ type ComplexityRoot struct { Operation func(childComplexity int) int } + BugEvent struct { + Bug func(childComplexity int) int + Type func(childComplexity int) int + } + BugLabelChangeOperation struct { Added func(childComplexity int) int Author func(childComplexity int) int @@ -288,6 +288,11 @@ type ComplexityRoot struct { R func(childComplexity int) int } + EntityEvent struct { + Entity func(childComplexity int) int + Type func(childComplexity int) int + } + Identity struct { AvatarUrl func(childComplexity int) int DisplayName func(childComplexity int) int @@ -311,6 +316,11 @@ type ComplexityRoot struct { Node func(childComplexity int) int } + IdentityEvent struct { + Identity func(childComplexity int) int + Type func(childComplexity int) int + } + Label struct { Color func(childComplexity int) int Name func(childComplexity int) int @@ -379,7 +389,9 @@ type ComplexityRoot struct { } Subscription struct { - BugChanged func(childComplexity int, repoRef *string, query *string) int + AllEvents func(childComplexity int, repoFilter *string) int + BugEvents func(childComplexity int, repoFilter *string, query *string) int + IdentityEvents func(childComplexity int, repoFilter *string) int } } @@ -693,20 +705,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.BugAddCommentTimelineItem.MessageIsEmpty(childComplexity), true - case "BugChange.bug": - if e.complexity.BugChange.Bug == nil { - break - } - - return e.complexity.BugChange.Bug(childComplexity), true - - case "BugChange.type": - if e.complexity.BugChange.Type == nil { - break - } - - return e.complexity.BugChange.Type(childComplexity), true - case "BugChangeLabelPayload.bug": if e.complexity.BugChangeLabelPayload.Bug == nil { break @@ -1050,6 +1048,20 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.BugEditCommentPayload.Operation(childComplexity), true + case "BugEvent.bug": + if e.complexity.BugEvent.Bug == nil { + break + } + + return e.complexity.BugEvent.Bug(childComplexity), true + + case "BugEvent.type": + if e.complexity.BugEvent.Type == nil { + break + } + + return e.complexity.BugEvent.Type(childComplexity), true + case "BugLabelChangeOperation.added": if e.complexity.BugLabelChangeOperation.Added == nil { break @@ -1372,6 +1384,20 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.Color.R(childComplexity), true + case "EntityEvent.entity": + if e.complexity.EntityEvent.Entity == nil { + break + } + + return e.complexity.EntityEvent.Entity(childComplexity), true + + case "EntityEvent.type": + if e.complexity.EntityEvent.Type == nil { + break + } + + return e.complexity.EntityEvent.Type(childComplexity), true + case "Identity.avatarUrl": if e.complexity.Identity.AvatarUrl == nil { break @@ -1470,6 +1496,20 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.IdentityEdge.Node(childComplexity), true + case "IdentityEvent.identity": + if e.complexity.IdentityEvent.Identity == nil { + break + } + + return e.complexity.IdentityEvent.Identity(childComplexity), true + + case "IdentityEvent.type": + if e.complexity.IdentityEvent.Type == nil { + break + } + + return e.complexity.IdentityEvent.Type(childComplexity), true + case "Label.color": if e.complexity.Label.Color == nil { break @@ -1804,17 +1844,41 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.Repository.ValidLabels(childComplexity, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int)), true - case "Subscription.bugChanged": - if e.complexity.Subscription.BugChanged == nil { + case "Subscription.allEvents": + if e.complexity.Subscription.AllEvents == nil { + break + } + + args, err := ec.field_Subscription_allEvents_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Subscription.AllEvents(childComplexity, args["repoFilter"].(*string)), true + + case "Subscription.bugEvents": + if e.complexity.Subscription.BugEvents == nil { break } - args, err := ec.field_Subscription_bugChanged_args(ctx, rawArgs) + args, err := ec.field_Subscription_bugEvents_args(ctx, rawArgs) if err != nil { return 0, false } - return e.complexity.Subscription.BugChanged(childComplexity, args["repoRef"].(*string), args["query"].(*string)), true + return e.complexity.Subscription.BugEvents(childComplexity, args["repoFilter"].(*string), args["query"].(*string)), true + + case "Subscription.identityEvents": + if e.complexity.Subscription.IdentityEvents == nil { + break + } + + args, err := ec.field_Subscription_identityEvents_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Subscription.IdentityEvents(childComplexity, args["repoFilter"].(*string)), true } return 0, false @@ -1947,7 +2011,7 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er } var sources = []*ast.Source{ - {Name: "../schema/bug.graphql", Input: `type Bug implements Authored { + {Name: "../schema/bug.graphql", Input: `type Bug implements Authored & Entity { """The identifier for this bug""" id: ID! """The human version (truncated) identifier for this bug""" @@ -2472,7 +2536,7 @@ directive @goTag( ) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION `, BuiltIn: false}, {Name: "../schema/identity.graphql", Input: `"""Represents an identity""" -type Identity { +type Identity implements Entity { """The identifier for this identity""" id: ID! """The human version (truncated) identifier for this identity""" @@ -2628,16 +2692,32 @@ type Mutation # See each entity mutations } `, BuiltIn: false}, {Name: "../schema/subscription.graphql", Input: `type Subscription { - bugChanged(repoRef: String, query: String): BugChange! + """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! + """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! + """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! } -enum ChangeType { +enum EventType { CREATED UPDATED + REMOVED } -type BugChange { - type: ChangeType! +type EntityEvent { + type: EventType! + entity: Entity +} + +type IdentityEvent { + type: EventType! + identity: Identity! +} + +type BugEvent { + type: EventType! bug: Bug! } `, BuiltIn: false}, @@ -2672,6 +2752,15 @@ interface Authored { """The author of this object.""" author: Identity! } + + +"""An entity (identity, bug, ...).""" +interface Entity { + """The identifier for this entity""" + id: ID! + """The human version (truncated) identifier for this entity""" + humanId: String! +} `, BuiltIn: false}, } var parsedSchema = gqlparser.MustLoadSchema(sources...) diff --git a/api/graphql/graph/subscription.generated.go b/api/graphql/graph/subscription.generated.go index a6f99919a1ecd301343173228344cf056d81b035..b906c92576d80f5ebbf091d83f88f4040692ebd9 100644 --- a/api/graphql/graph/subscription.generated.go +++ b/api/graphql/graph/subscription.generated.go @@ -18,39 +18,69 @@ import ( // region ************************** generated!.gotpl ************************** type SubscriptionResolver interface { - BugChanged(ctx context.Context, repoRef *string, query *string) (<-chan *models.BugChange, error) + 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) } // endregion ************************** generated!.gotpl ************************** // region ***************************** args.gotpl ***************************** -func (ec *executionContext) field_Subscription_bugChanged_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { +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_bugChanged_argsRepoRef(ctx, rawArgs) + arg0, err := ec.field_Subscription_allEvents_argsRepoFilter(ctx, rawArgs) if err != nil { return nil, err } - args["repoRef"] = arg0 - arg1, err := ec.field_Subscription_bugChanged_argsQuery(ctx, rawArgs) + args["repoFilter"] = arg0 + return args, nil +} +func (ec *executionContext) field_Subscription_allEvents_argsRepoFilter( + ctx context.Context, + rawArgs map[string]any, +) (*string, error) { + if _, ok := rawArgs["repoFilter"]; !ok { + var zeroVal *string + return zeroVal, nil + } + + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("repoFilter")) + if tmp, ok := rawArgs["repoFilter"]; ok { + return ec.unmarshalOString2ᚖstring(ctx, tmp) + } + + var zeroVal *string + 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_bugChanged_argsRepoRef( +func (ec *executionContext) field_Subscription_bugEvents_argsRepoFilter( ctx context.Context, rawArgs map[string]any, ) (*string, error) { - if _, ok := rawArgs["repoRef"]; !ok { + if _, ok := rawArgs["repoFilter"]; !ok { var zeroVal *string return zeroVal, nil } - ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("repoRef")) - if tmp, ok := rawArgs["repoRef"]; ok { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("repoFilter")) + if tmp, ok := rawArgs["repoFilter"]; ok { return ec.unmarshalOString2ᚖstring(ctx, tmp) } @@ -58,7 +88,7 @@ func (ec *executionContext) field_Subscription_bugChanged_argsRepoRef( return zeroVal, nil } -func (ec *executionContext) field_Subscription_bugChanged_argsQuery( +func (ec *executionContext) field_Subscription_bugEvents_argsQuery( ctx context.Context, rawArgs map[string]any, ) (*string, error) { @@ -76,6 +106,34 @@ func (ec *executionContext) field_Subscription_bugChanged_argsQuery( return zeroVal, nil } +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) + if err != nil { + return nil, err + } + args["repoFilter"] = arg0 + return args, nil +} +func (ec *executionContext) field_Subscription_identityEvents_argsRepoFilter( + ctx context.Context, + rawArgs map[string]any, +) (*string, error) { + if _, ok := rawArgs["repoFilter"]; !ok { + var zeroVal *string + return zeroVal, nil + } + + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("repoFilter")) + if tmp, ok := rawArgs["repoFilter"]; ok { + return ec.unmarshalOString2ᚖstring(ctx, tmp) + } + + var zeroVal *string + return zeroVal, nil +} + // endregion ***************************** args.gotpl ***************************** // region ************************** directives.gotpl ************************** @@ -84,8 +142,8 @@ func (ec *executionContext) field_Subscription_bugChanged_argsQuery( // region **************************** field.gotpl ***************************** -func (ec *executionContext) _BugChange_type(ctx context.Context, field graphql.CollectedField, obj *models.BugChange) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_BugChange_type(ctx, field) +func (ec *executionContext) _BugEvent_type(ctx context.Context, field graphql.CollectedField, obj *models.BugEvent) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_BugEvent_type(ctx, field) if err != nil { return graphql.Null } @@ -110,26 +168,26 @@ func (ec *executionContext) _BugChange_type(ctx context.Context, field graphql.C } return graphql.Null } - res := resTmp.(models.ChangeType) + res := resTmp.(models.EventType) fc.Result = res - return ec.marshalNChangeType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐChangeType(ctx, field.Selections, res) + return ec.marshalNEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEventType(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_BugChange_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_BugEvent_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "BugChange", + Object: "BugEvent", Field: field, IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type ChangeType does not have child fields") + return nil, errors.New("field of type EventType does not have child fields") }, } return fc, nil } -func (ec *executionContext) _BugChange_bug(ctx context.Context, field graphql.CollectedField, obj *models.BugChange) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_BugChange_bug(ctx, field) +func (ec *executionContext) _BugEvent_bug(ctx context.Context, field graphql.CollectedField, obj *models.BugEvent) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_BugEvent_bug(ctx, field) if err != nil { return graphql.Null } @@ -159,9 +217,9 @@ func (ec *executionContext) _BugChange_bug(ctx context.Context, field graphql.Co return ec.marshalNBug2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐBugWrapper(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_BugChange_bug(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_BugEvent_bug(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ - Object: "BugChange", + Object: "BugEvent", Field: field, IsMethod: false, IsResolver: false, @@ -200,8 +258,349 @@ func (ec *executionContext) fieldContext_BugChange_bug(_ context.Context, field return fc, nil } -func (ec *executionContext) _Subscription_bugChanged(ctx context.Context, field graphql.CollectedField) (ret func(ctx context.Context) graphql.Marshaler) { - fc, err := ec.fieldContext_Subscription_bugChanged(ctx, field) +func (ec *executionContext) _EntityEvent_type(ctx context.Context, field graphql.CollectedField, obj *models.EntityEvent) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_EntityEvent_type(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Type, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(models.EventType) + fc.Result = res + return ec.marshalNEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEventType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_EntityEvent_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "EntityEvent", + Field: 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 fc, nil +} + +func (ec *executionContext) _EntityEvent_entity(ctx context.Context, field graphql.CollectedField, obj *models.EntityEvent) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_EntityEvent_entity(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Entity, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(models.Entity) + fc.Result = res + return ec.marshalOEntity2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEntity(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_EntityEvent_entity(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "EntityEvent", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("FieldContext.Child cannot be called on type INTERFACE") + }, + } + return fc, nil +} + +func (ec *executionContext) _IdentityEvent_type(ctx context.Context, field graphql.CollectedField, obj *models.IdentityEvent) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_IdentityEvent_type(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Type, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(models.EventType) + fc.Result = res + return ec.marshalNEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEventType(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_IdentityEvent_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "IdentityEvent", + Field: 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 fc, nil +} + +func (ec *executionContext) _IdentityEvent_identity(ctx context.Context, field graphql.CollectedField, obj *models.IdentityEvent) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_IdentityEvent_identity(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Identity, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(models.IdentityWrapper) + fc.Result = res + return ec.marshalNIdentity2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐIdentityWrapper(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_IdentityEvent_identity(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "IdentityEvent", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Identity_id(ctx, field) + case "humanId": + return ec.fieldContext_Identity_humanId(ctx, field) + case "name": + return ec.fieldContext_Identity_name(ctx, field) + case "email": + return ec.fieldContext_Identity_email(ctx, field) + case "login": + return ec.fieldContext_Identity_login(ctx, field) + case "displayName": + return ec.fieldContext_Identity_displayName(ctx, field) + case "avatarUrl": + return ec.fieldContext_Identity_avatarUrl(ctx, field) + case "isProtected": + return ec.fieldContext_Identity_isProtected(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Identity", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Subscription_allEvents(ctx context.Context, field graphql.CollectedField) (ret func(ctx context.Context) graphql.Marshaler) { + fc, err := ec.fieldContext_Subscription_allEvents(ctx, field) + if err != nil { + return nil + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + 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)) + }) + if err != nil { + ec.Error(ctx, err) + return nil + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return nil + } + return func(ctx context.Context) graphql.Marshaler { + select { + case res, ok := <-resTmp.(<-chan *models.EntityEvent): + if !ok { + return nil + } + return graphql.WriterFunc(func(w io.Writer) { + w.Write([]byte{'{'}) + graphql.MarshalString(field.Alias).MarshalGQL(w) + w.Write([]byte{':'}) + ec.marshalNEntityEvent2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEntityEvent(ctx, field.Selections, res).MarshalGQL(w) + w.Write([]byte{'}'}) + }) + case <-ctx.Done(): + return nil + } + } +} + +func (ec *executionContext) fieldContext_Subscription_allEvents(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Subscription", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "type": + return ec.fieldContext_EntityEvent_type(ctx, field) + case "entity": + return ec.fieldContext_EntityEvent_entity(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type EntityEvent", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Subscription_allEvents_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Subscription_identityEvents(ctx context.Context, field graphql.CollectedField) (ret func(ctx context.Context) graphql.Marshaler) { + fc, err := ec.fieldContext_Subscription_identityEvents(ctx, field) + if err != nil { + return nil + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + 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)) + }) + if err != nil { + ec.Error(ctx, err) + return nil + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return nil + } + return func(ctx context.Context) graphql.Marshaler { + select { + case res, ok := <-resTmp.(<-chan *models.IdentityEvent): + if !ok { + return nil + } + return graphql.WriterFunc(func(w io.Writer) { + w.Write([]byte{'{'}) + graphql.MarshalString(field.Alias).MarshalGQL(w) + w.Write([]byte{':'}) + ec.marshalNIdentityEvent2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐIdentityEvent(ctx, field.Selections, res).MarshalGQL(w) + w.Write([]byte{'}'}) + }) + case <-ctx.Done(): + return nil + } + } +} + +func (ec *executionContext) fieldContext_Subscription_identityEvents(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Subscription", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "type": + return ec.fieldContext_IdentityEvent_type(ctx, field) + case "identity": + return ec.fieldContext_IdentityEvent_identity(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type IdentityEvent", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Subscription_identityEvents_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Subscription_bugEvents(ctx context.Context, field graphql.CollectedField) (ret func(ctx context.Context) graphql.Marshaler) { + fc, err := ec.fieldContext_Subscription_bugEvents(ctx, field) if err != nil { return nil } @@ -214,7 +613,7 @@ func (ec *executionContext) _Subscription_bugChanged(ctx context.Context, field }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Subscription().BugChanged(rctx, fc.Args["repoRef"].(*string), fc.Args["query"].(*string)) + return ec.resolvers.Subscription().BugEvents(rctx, fc.Args["repoFilter"].(*string), fc.Args["query"].(*string)) }) if err != nil { ec.Error(ctx, err) @@ -228,7 +627,7 @@ func (ec *executionContext) _Subscription_bugChanged(ctx context.Context, field } return func(ctx context.Context) graphql.Marshaler { select { - case res, ok := <-resTmp.(<-chan *models.BugChange): + case res, ok := <-resTmp.(<-chan *models.BugEvent): if !ok { return nil } @@ -236,7 +635,7 @@ func (ec *executionContext) _Subscription_bugChanged(ctx context.Context, field w.Write([]byte{'{'}) graphql.MarshalString(field.Alias).MarshalGQL(w) w.Write([]byte{':'}) - ec.marshalNBugChange2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐBugChange(ctx, field.Selections, res).MarshalGQL(w) + ec.marshalNBugEvent2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐBugEvent(ctx, field.Selections, res).MarshalGQL(w) w.Write([]byte{'}'}) }) case <-ctx.Done(): @@ -245,7 +644,7 @@ func (ec *executionContext) _Subscription_bugChanged(ctx context.Context, field } } -func (ec *executionContext) fieldContext_Subscription_bugChanged(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Subscription_bugEvents(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Subscription", Field: field, @@ -254,11 +653,11 @@ func (ec *executionContext) fieldContext_Subscription_bugChanged(ctx context.Con Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "type": - return ec.fieldContext_BugChange_type(ctx, field) + return ec.fieldContext_BugEvent_type(ctx, field) case "bug": - return ec.fieldContext_BugChange_bug(ctx, field) + return ec.fieldContext_BugEvent_bug(ctx, field) } - return nil, fmt.Errorf("no field named %q was found under type BugChange", field.Name) + return nil, fmt.Errorf("no field named %q was found under type BugEvent", field.Name) }, } defer func() { @@ -268,7 +667,7 @@ func (ec *executionContext) fieldContext_Subscription_bugChanged(ctx context.Con } }() ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Subscription_bugChanged_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + if fc.Args, err = ec.field_Subscription_bugEvents_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { ec.Error(ctx, err) return fc, err } @@ -287,24 +686,109 @@ func (ec *executionContext) fieldContext_Subscription_bugChanged(ctx context.Con // region **************************** object.gotpl **************************** -var bugChangeImplementors = []string{"BugChange"} +var bugEventImplementors = []string{"BugEvent"} -func (ec *executionContext) _BugChange(ctx context.Context, sel ast.SelectionSet, obj *models.BugChange) graphql.Marshaler { - fields := graphql.CollectFields(ec.OperationContext, sel, bugChangeImplementors) +func (ec *executionContext) _BugEvent(ctx context.Context, sel ast.SelectionSet, obj *models.BugEvent) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, bugEventImplementors) out := graphql.NewFieldSet(fields) deferred := make(map[string]*graphql.FieldSet) for i, field := range fields { switch field.Name { case "__typename": - out.Values[i] = graphql.MarshalString("BugChange") + out.Values[i] = graphql.MarshalString("BugEvent") case "type": - out.Values[i] = ec._BugChange_type(ctx, field, obj) + out.Values[i] = ec._BugEvent_type(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } case "bug": - out.Values[i] = ec._BugChange_bug(ctx, field, obj) + out.Values[i] = ec._BugEvent_bug(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var entityEventImplementors = []string{"EntityEvent"} + +func (ec *executionContext) _EntityEvent(ctx context.Context, sel ast.SelectionSet, obj *models.EntityEvent) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, entityEventImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("EntityEvent") + case "type": + out.Values[i] = ec._EntityEvent_type(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "entity": + out.Values[i] = ec._EntityEvent_entity(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var identityEventImplementors = []string{"IdentityEvent"} + +func (ec *executionContext) _IdentityEvent(ctx context.Context, sel ast.SelectionSet, obj *models.IdentityEvent) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, identityEventImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("IdentityEvent") + case "type": + out.Values[i] = ec._IdentityEvent_type(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "identity": + out.Values[i] = ec._IdentityEvent_identity(ctx, field, obj) if out.Values[i] == graphql.Null { out.Invalids++ } @@ -344,8 +828,12 @@ func (ec *executionContext) _Subscription(ctx context.Context, sel ast.Selection } switch fields[0].Name { - case "bugChanged": - return ec._Subscription_bugChanged(ctx, fields[0]) + case "allEvents": + return ec._Subscription_allEvents(ctx, fields[0]) + case "identityEvents": + return ec._Subscription_identityEvents(ctx, fields[0]) + case "bugEvents": + return ec._Subscription_bugEvents(ctx, fields[0]) default: panic("unknown field " + strconv.Quote(fields[0].Name)) } @@ -355,28 +843,56 @@ func (ec *executionContext) _Subscription(ctx context.Context, sel ast.Selection // region ***************************** type.gotpl ***************************** -func (ec *executionContext) marshalNBugChange2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐBugChange(ctx context.Context, sel ast.SelectionSet, v models.BugChange) graphql.Marshaler { - return ec._BugChange(ctx, sel, &v) +func (ec *executionContext) marshalNBugEvent2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐBugEvent(ctx context.Context, sel ast.SelectionSet, v models.BugEvent) graphql.Marshaler { + return ec._BugEvent(ctx, sel, &v) } -func (ec *executionContext) marshalNBugChange2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐBugChange(ctx context.Context, sel ast.SelectionSet, v *models.BugChange) graphql.Marshaler { +func (ec *executionContext) marshalNBugEvent2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐBugEvent(ctx context.Context, sel ast.SelectionSet, v *models.BugEvent) 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") } return graphql.Null } - return ec._BugChange(ctx, sel, v) + return ec._BugEvent(ctx, sel, v) +} + +func (ec *executionContext) marshalNEntityEvent2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEntityEvent(ctx context.Context, sel ast.SelectionSet, v models.EntityEvent) graphql.Marshaler { + return ec._EntityEvent(ctx, sel, &v) } -func (ec *executionContext) unmarshalNChangeType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐChangeType(ctx context.Context, v any) (models.ChangeType, error) { - var res models.ChangeType +func (ec *executionContext) marshalNEntityEvent2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEntityEvent(ctx context.Context, sel ast.SelectionSet, v *models.EntityEvent) 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") + } + return graphql.Null + } + 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 err := res.UnmarshalGQL(v) return res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalNChangeType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐChangeType(ctx context.Context, sel ast.SelectionSet, v models.ChangeType) graphql.Marshaler { +func (ec *executionContext) marshalNEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEventType(ctx context.Context, sel ast.SelectionSet, v models.EventType) graphql.Marshaler { return v } +func (ec *executionContext) marshalNIdentityEvent2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐIdentityEvent(ctx context.Context, sel ast.SelectionSet, v models.IdentityEvent) graphql.Marshaler { + return ec._IdentityEvent(ctx, sel, &v) +} + +func (ec *executionContext) marshalNIdentityEvent2ᚖgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐIdentityEvent(ctx context.Context, sel ast.SelectionSet, v *models.IdentityEvent) 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") + } + return graphql.Null + } + return ec._IdentityEvent(ctx, sel, v) +} + // endregion ***************************** type.gotpl ***************************** diff --git a/api/graphql/graph/types.generated.go b/api/graphql/graph/types.generated.go index 2ab0ad686ea0a374988f71a74362dd6a366b97cf..43f9354b0dcb28b7452adcc2cff506953659a8e8 100644 --- a/api/graphql/graph/types.generated.go +++ b/api/graphql/graph/types.generated.go @@ -414,6 +414,11 @@ func (ec *executionContext) _Authored(ctx context.Context, sel ast.SelectionSet, return graphql.Null } return ec._BugAddCommentOperation(ctx, sel, obj) + case models.BugWrapper: + if obj == nil { + return graphql.Null + } + return ec._Bug(ctx, sel, obj) case bug.Comment: return ec._BugComment(ctx, sel, &obj) case *bug.Comment: @@ -421,11 +426,25 @@ func (ec *executionContext) _Authored(ctx context.Context, sel ast.SelectionSet, return graphql.Null } return ec._BugComment(ctx, sel, obj) + default: + panic(fmt.Errorf("unexpected type %T", obj)) + } +} + +func (ec *executionContext) _Entity(ctx context.Context, sel ast.SelectionSet, obj models.Entity) graphql.Marshaler { + switch obj := (obj).(type) { + case nil: + return graphql.Null case models.BugWrapper: if obj == nil { return graphql.Null } return ec._Bug(ctx, sel, obj) + case models.IdentityWrapper: + if obj == nil { + return graphql.Null + } + return ec._Identity(ctx, sel, obj) default: panic(fmt.Errorf("unexpected type %T", obj)) } @@ -747,6 +766,13 @@ func (ec *executionContext) marshalNTime2ᚖtimeᚐTime(ctx context.Context, sel return res } +func (ec *executionContext) marshalOEntity2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEntity(ctx context.Context, sel ast.SelectionSet, v models.Entity) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._Entity(ctx, sel, v) +} + func (ec *executionContext) unmarshalOHash2ᚕgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋrepositoryᚐHashᚄ(ctx context.Context, v any) ([]repository.Hash, error) { if v == nil { return nil, nil diff --git a/api/graphql/models/gen_models.go b/api/graphql/models/gen_models.go index 04e0278c545c12bf474399f15e7f8cd0c1ce34f7..94ac43e88484203035c5f0b2b8bea16315b57d5b 100644 --- a/api/graphql/models/gen_models.go +++ b/api/graphql/models/gen_models.go @@ -19,6 +19,11 @@ type Authored interface { IsAuthored() } +// An entity (identity, bug, ...). +type Entity interface { + IsEntity() +} + type BugAddCommentAndCloseInput struct { // A unique identifier for the client performing the mutation. ClientMutationID *string `json:"clientMutationId,omitempty"` @@ -89,11 +94,6 @@ type BugAddCommentPayload struct { Operation *bug.AddCommentOperation `json:"operation"` } -type BugChange struct { - Type ChangeType `json:"type"` - Bug BugWrapper `json:"bug"` -} - type BugChangeLabelInput struct { // A unique identifier for the client performing the mutation. ClientMutationID *string `json:"clientMutationId,omitempty"` @@ -193,6 +193,11 @@ type BugEditCommentPayload struct { Operation *bug.EditCommentOperation `json:"operation"` } +type BugEvent struct { + Type EventType `json:"type"` + Bug BugWrapper `json:"bug"` +} + type BugSetTitleInput struct { // A unique identifier for the client performing the mutation. ClientMutationID *string `json:"clientMutationId,omitempty"` @@ -263,6 +268,11 @@ type BugTimelineItemEdge struct { Node bug.TimelineItem `json:"node"` } +type EntityEvent struct { + Type EventType `json:"type"` + Entity Entity `json:"entity,omitempty"` +} + type IdentityConnection struct { Edges []*IdentityEdge `json:"edges"` Nodes []IdentityWrapper `json:"nodes"` @@ -275,6 +285,11 @@ type IdentityEdge struct { Node IdentityWrapper `json:"node"` } +type IdentityEvent struct { + Type EventType `json:"type"` + Identity IdentityWrapper `json:"identity"` +} + type LabelConnection struct { Edges []*LabelEdge `json:"edges"` Nodes []common.Label `json:"nodes"` @@ -292,16 +307,16 @@ type Mutation struct { // The connection type for an Operation type OperationConnection struct { - Edges []*OperationEdge `json:"edges"` - Nodes []dag.Operation `json:"nodes"` - PageInfo *PageInfo `json:"pageInfo"` - TotalCount int `json:"totalCount"` + Edges []*OperationEdge `json:"edges"` + Nodes []dag.OperationWithApply[*bug.Snapshot] `json:"nodes"` + PageInfo *PageInfo `json:"pageInfo"` + TotalCount int `json:"totalCount"` } // Represent an Operation type OperationEdge struct { - Cursor string `json:"cursor"` - Node dag.Operation `json:"node"` + Cursor string `json:"cursor"` + Node dag.OperationWithApply[*bug.Snapshot] `json:"node"` } // Information about pagination in a connection. @@ -322,48 +337,50 @@ type Query struct { type Subscription struct { } -type ChangeType string +type EventType string const ( - ChangeTypeCreated ChangeType = "CREATED" - ChangeTypeUpdated ChangeType = "UPDATED" + EventTypeCreated EventType = "CREATED" + EventTypeUpdated EventType = "UPDATED" + EventTypeRemoved EventType = "REMOVED" ) -var AllChangeType = []ChangeType{ - ChangeTypeCreated, - ChangeTypeUpdated, +var AllEventType = []EventType{ + EventTypeCreated, + EventTypeUpdated, + EventTypeRemoved, } -func (e ChangeType) IsValid() bool { +func (e EventType) IsValid() bool { switch e { - case ChangeTypeCreated, ChangeTypeUpdated: + case EventTypeCreated, EventTypeUpdated, EventTypeRemoved: return true } return false } -func (e ChangeType) String() string { +func (e EventType) String() string { return string(e) } -func (e *ChangeType) UnmarshalGQL(v any) error { +func (e *EventType) UnmarshalGQL(v any) error { str, ok := v.(string) if !ok { return fmt.Errorf("enums must be strings") } - *e = ChangeType(str) + *e = EventType(str) if !e.IsValid() { - return fmt.Errorf("%s is not a valid ChangeType", str) + return fmt.Errorf("%s is not a valid EventType", str) } return nil } -func (e ChangeType) MarshalGQL(w io.Writer) { +func (e EventType) MarshalGQL(w io.Writer) { fmt.Fprint(w, strconv.Quote(e.String())) } -func (e *ChangeType) UnmarshalJSON(b []byte) error { +func (e *EventType) UnmarshalJSON(b []byte) error { s, err := strconv.Unquote(string(b)) if err != nil { return err @@ -371,7 +388,7 @@ func (e *ChangeType) UnmarshalJSON(b []byte) error { return e.UnmarshalGQL(s) } -func (e ChangeType) MarshalJSON() ([]byte, error) { +func (e EventType) MarshalJSON() ([]byte, error) { var buf bytes.Buffer e.MarshalGQL(&buf) return buf.Bytes(), nil diff --git a/api/graphql/models/lazy_bug.go b/api/graphql/models/lazy_bug.go index daf8990882c9521ffad2eb915a9354637256d2a5..2340dcf39376b0ab010f842ec7da2192fbb9885b 100644 --- a/api/graphql/models/lazy_bug.go +++ b/api/graphql/models/lazy_bug.go @@ -28,7 +28,10 @@ type BugWrapper interface { Timeline() ([]bug.TimelineItem, error) Operations() ([]dag.Operation, error) + // IsAuthored is a sign-post method for gqlgen, to mark compliance to an interface. IsAuthored() + // IsEntity is a sign post-method for gqlgen, to mark compliance to an interface. + IsEntity() } var _ BugWrapper = &lazyBug{} @@ -75,9 +78,6 @@ func (lb *lazyBug) identity(id entity.Id) (IdentityWrapper, error) { return &lazyIdentity{cache: lb.cache, excerpt: i}, nil } -// Sign post method for gqlgen -func (lb *lazyBug) IsAuthored() {} - func (lb *lazyBug) Id() entity.Id { return lb.excerpt.Id() } @@ -154,6 +154,12 @@ func (lb *lazyBug) Operations() ([]dag.Operation, error) { return lb.snap.Operations, nil } +// IsAuthored is a sign-post method for gqlgen, to mark compliance to an interface. +func (lb *lazyBug) IsAuthored() {} + +// IsEntity is a sign post-method for gqlgen, to mark compliance to an interface. +func (lb *lazyBug) IsEntity() {} + var _ BugWrapper = &loadedBug{} type loadedBug struct { @@ -215,3 +221,9 @@ func (l *loadedBug) Timeline() ([]bug.TimelineItem, error) { func (l *loadedBug) Operations() ([]dag.Operation, error) { return l.Snapshot.Operations, nil } + +// IsAuthored is a sign-post method for gqlgen, to mark compliance to an interface. +func (l *loadedBug) IsAuthored() {} + +// IsEntity is a sign post-method for gqlgen, to mark compliance to an interface. +func (l *loadedBug) IsEntity() {} diff --git a/api/graphql/models/lazy_identity.go b/api/graphql/models/lazy_identity.go index a131d450e7c81a092dbc568e3347b1262dcb6188..feb646db287e9e6b99eeedf74383203e07e5439b 100644 --- a/api/graphql/models/lazy_identity.go +++ b/api/graphql/models/lazy_identity.go @@ -21,6 +21,9 @@ type IdentityWrapper interface { Keys() ([]*identity.Key, error) DisplayName() string IsProtected() (bool, error) + + // IsEntity is a sign post-method for gqlgen, to mark compliance to an interface. + IsEntity() } var _ IdentityWrapper = &lazyIdentity{} @@ -108,6 +111,9 @@ func (li *lazyIdentity) IsProtected() (bool, error) { return id.IsProtected(), nil } +// IsEntity is a sign post-method for gqlgen, to mark compliance to an interface. +func (li *lazyIdentity) IsEntity() {} + var _ IdentityWrapper = &loadedIdentity{} type loadedIdentity struct { @@ -137,3 +143,6 @@ func (l loadedIdentity) Keys() ([]*identity.Key, error) { func (l loadedIdentity) IsProtected() (bool, error) { return l.Interface.IsProtected(), nil } + +// IsEntity is a sign post-method for gqlgen, to mark compliance to an interface. +func (l loadedIdentity) IsEntity() {} diff --git a/api/graphql/resolvers/subscription.go b/api/graphql/resolvers/subscription.go index 72ea18aeb5306ea14e72d441dd3b85877680908e..a7422bf8cef781e5b0bf6458f2f3642b564aee59 100644 --- a/api/graphql/resolvers/subscription.go +++ b/api/graphql/resolvers/subscription.go @@ -17,6 +17,38 @@ 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") +} + +var _ cache.Observer = &subscription[any]{} + +type subscription[T any] struct { + out chan *T + filter func(T) bool + excerptResolver func(id entity.Id) cache.Excerpt + repo *cache.RepoCache +} + +func (s subscription[T]) EntityEvent(event cache.EntityEventType, typename string, id entity.Id) { + +} + +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, repoFilter *string, query *string) (<-chan *models.BugEvent, error) { + // TODO implement me + panic("implement me") +} + func (s subscriptionResolver) BugChanged(ctx context.Context, repoRef *string, query *string) (<-chan *models.BugChange, error) { var repo *cache.RepoCache var err error diff --git a/api/graphql/schema/bug.graphql b/api/graphql/schema/bug.graphql index f06f2f8421d09361e8184206947f84a184d13cfe..9b4dbc412436ddf6707f767984b4389356ec5961 100644 --- a/api/graphql/schema/bug.graphql +++ b/api/graphql/schema/bug.graphql @@ -1,4 +1,4 @@ -type Bug implements Authored { +type Bug implements Authored & Entity { """The identifier for this bug""" id: ID! """The human version (truncated) identifier for this bug""" diff --git a/api/graphql/schema/identity.graphql b/api/graphql/schema/identity.graphql index 502b0a4f355437e9f8bd45c258d3fbd0f54167b8..54b1333c83c3cacee6236696096211fdd254cb8e 100644 --- a/api/graphql/schema/identity.graphql +++ b/api/graphql/schema/identity.graphql @@ -1,5 +1,5 @@ """Represents an identity""" -type Identity { +type Identity implements Entity { """The identifier for this identity""" id: ID! """The human version (truncated) identifier for this identity""" diff --git a/api/graphql/schema/subscription.graphql b/api/graphql/schema/subscription.graphql index eb73b7de66e4a7e5e1f852fec823eb727fd2e9f6..29e0106abcf0c88c3614c9bec6e8c5e450f1e4bf 100644 --- a/api/graphql/schema/subscription.graphql +++ b/api/graphql/schema/subscription.graphql @@ -1,13 +1,29 @@ type Subscription { - bugChanged(repoRef: String, query: String): BugChange! + """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! + """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! + """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! } -enum ChangeType { +enum EventType { CREATED UPDATED + REMOVED } -type BugChange { - type: ChangeType! +type EntityEvent { + type: EventType! + entity: Entity +} + +type IdentityEvent { + type: EventType! + identity: Identity! +} + +type BugEvent { + type: EventType! bug: Bug! } diff --git a/api/graphql/schema/types.graphql b/api/graphql/schema/types.graphql index f4284b2deed0ace34c2e847ae0c094a1a1bd8390..0fbf950f16708bb825b47874cb25e8f38037a68c 100644 --- a/api/graphql/schema/types.graphql +++ b/api/graphql/schema/types.graphql @@ -29,3 +29,12 @@ interface Authored { """The author of this object.""" author: Identity! } + + +"""An entity (identity, bug, ...).""" +interface Entity { + """The identifier for this entity""" + id: ID! + """The human version (truncated) identifier for this entity""" + humanId: String! +} diff --git a/cache/events.go b/cache/events.go new file mode 100644 index 0000000000000000000000000000000000000000..9d10b8ef3d20cf67f12c23f94a432e7a3241f236 --- /dev/null +++ b/cache/events.go @@ -0,0 +1,47 @@ +package cache + +import "github.com/git-bug/git-bug/entity" + +type BuildEventType int + +const ( + _ BuildEventType = iota + // BuildEventCacheIsBuilt signal that the cache is being built (aka, not skipped) + BuildEventCacheIsBuilt + // BuildEventRemoveLock signal that an old repo lock has been cleaned + BuildEventRemoveLock + // BuildEventStarted signal the beginning of a cache build for an entity + BuildEventStarted + // BuildEventProgress signal progress in the cache building for an entity + BuildEventProgress + // BuildEventFinished signal the end of a cache build for an entity + BuildEventFinished +) + +// BuildEvent carry an event happening during the cache build process. +type BuildEvent struct { + // Err carry an error if the build process failed. If set, no other field matters. + Err error + // Typename is the name of the entity of which the event relate to. Can be empty if no particular entity is involved. + Typename string + // Event is the type of the event. + Event BuildEventType + // Total is the total number of elements being built. Set if Event is BuildEventStarted. + Total int64 + // Progress is the current count of processed elements. Set if Event is BuildEventProgress. + Progress int64 +} + +type EntityEventType int + +const ( + _ EntityEventType = iota + EntityEventCreated + EntityEventUpdated + EntityEventRemoved +) + +// Observer gets notified of changes in entities in the cache +type Observer interface { + EntityEvent(event EntityEventType, repoRef string, typename string, id entity.Id) +} diff --git a/cache/identity_subcache.go b/cache/identity_subcache.go index 42b4d6865078a56c3361b82fd482b281d7b2ea3e..f531217518625a66064c2453a5b37287215f5b1e 100644 --- a/cache/identity_subcache.go +++ b/cache/identity_subcache.go @@ -1,8 +1,6 @@ package cache import ( - "fmt" - "github.com/git-bug/git-bug/entities/identity" "github.com/git-bug/git-bug/entity" "github.com/git-bug/git-bug/repository" @@ -106,17 +104,7 @@ func (c *RepoCacheIdentity) finishIdentity(i *identity.Identity, metadata map[st return nil, err } - c.mu.Lock() - if _, has := c.cached[i.Id()]; has { - return nil, fmt.Errorf("identity %s already exist in the cache", i.Id()) - } - - cached := NewIdentityCache(i, c.repo, c.entityUpdated) - c.cached[i.Id()] = cached - c.mu.Unlock() - - // force the write of the excerpt - err = c.entityUpdated(i.Id()) + cached, err := c.add(i) if err != nil { return nil, err } diff --git a/cache/multi_repo_cache.go b/cache/multi_repo_cache.go index 91ca34b41c4a23c6fc902c36d79d899038667512..46852ca4ef1817fbcf0e3484acb5756d1b091b6f 100644 --- a/cache/multi_repo_cache.go +++ b/cache/multi_repo_cache.go @@ -24,7 +24,7 @@ func NewMultiRepoCache() *MultiRepoCache { func (c *MultiRepoCache) RegisterRepository(repo repository.ClockedRepo, name string) (*RepoCache, chan BuildEvent) { r, events := NewNamedRepoCache(repo, name) - // intercept events to make sure the cache building process succeed properly + // intercept events to make sure the cache building process succeeds properly out := make(chan BuildEvent) go func() { defer close(out) @@ -69,6 +69,14 @@ func (c *MultiRepoCache) ResolveRepo(name string) (*RepoCache, error) { return r, nil } +func (c *MultiRepoCache) RegisterObserver(observer Observer) { + +} + +func (c *MultiRepoCache) UnregisterObserver(observer Observer) { + +} + // Close will do anything that is needed to close the cache properly func (c *MultiRepoCache) Close() error { for _, cachedRepo := range c.repos { diff --git a/cache/repo_cache.go b/cache/repo_cache.go index 167ae743614643618195886819c42342993a5776..4a514e54d1ac916b67fec239c7fc4d10cb9363ed 100644 --- a/cache/repo_cache.go +++ b/cache/repo_cache.go @@ -40,17 +40,6 @@ type cacheMgmt interface { Close() error } -// Observer gets notified of changes in entities in the cache -type Observer interface { - // EntityCreated notifies that an entity has been created. - // The body of that function should NOT block. - EntityCreated(typename string, id entity.Id) - - // EntityUpdated notifies that an entity has been updated. - // The body of that function should NOT block. - EntityUpdated(typename string, id entity.Id) -} - // RepoCache is a cache for a Repository. This cache has multiple functions: // // 1. After being loaded, a Bug is kept in memory in the cache, allowing for fast @@ -150,18 +139,18 @@ func NewRepoCacheNoEvents(r repository.ClockedRepo) (*RepoCache, error) { return cache, nil } -func (c *RepoCache) RegisterObserver(typename string, observer Observer) { +func (c *RepoCache) registerObserver(repoRef string, typename string, observer Observer) { switch typename { case bug.Typename: - c.bugs.RegisterObserver(observer) + c.bugs.RegisterObserver(repoRef, observer) case identity.Typename: - c.identities.RegisterObserver(observer) + c.identities.RegisterObserver(repoRef, observer) default: panic(fmt.Sprintf("unknown typename %q", typename)) } } -func (c *RepoCache) UnregisterObserver(typename string, observer Observer) { +func (c *RepoCache) unregisterObserver(typename string, observer Observer) { switch typename { case bug.Typename: c.bugs.UnregisterObserver(observer) @@ -241,36 +230,6 @@ func (c *RepoCache) Close() error { return c.repo.LocalStorage().Remove(lockfile) } -type BuildEventType int - -const ( - _ BuildEventType = iota - // BuildEventCacheIsBuilt signal that the cache is being built (aka, not skipped) - BuildEventCacheIsBuilt - // BuildEventRemoveLock signal that an old repo lock has been cleaned - BuildEventRemoveLock - // BuildEventStarted signal the beginning of a cache build for an entity - BuildEventStarted - // BuildEventProgress signal progress in the cache building for an entity - BuildEventProgress - // BuildEventFinished signal the end of a cache build for an entity - BuildEventFinished -) - -// BuildEvent carry an event happening during the cache build process. -type BuildEvent struct { - // Err carry an error if the build process failed. If set, no other field matters. - Err error - // Typename is the name of the entity of which the event relate to. Can be empty if no particular entity is involved. - Typename string - // Event is the type of the event. - Event BuildEventType - // Total is the total number of elements being built. Set if Event is BuildEventStarted. - Total int64 - // Progress is the current count of processed elements. Set if Event is BuildEventProgress. - Progress int64 -} - func (c *RepoCache) buildCache(events chan BuildEvent) { events <- BuildEvent{Event: BuildEventCacheIsBuilt} diff --git a/cache/repo_cache_test.go b/cache/repo_cache_test.go index e974a3c5077113b858b39b0e84f1cb1914c08507..a2a4e510f34b492cdd3705b7e5728a479ddb96c4 100644 --- a/cache/repo_cache_test.go +++ b/cache/repo_cache_test.go @@ -1,6 +1,7 @@ package cache import ( + "fmt" "strings" "testing" "time" @@ -16,6 +17,31 @@ import ( "github.com/git-bug/git-bug/repository" ) +type observerEvent struct { + typename string + id entity.Id +} + +type observer struct { + created []observerEvent + updated []observerEvent + removed []observerEvent +} + +func (o *observer) EntityEvent(event EntityEventType, 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}) + } +} + func TestCache(t *testing.T) { f := test.NewFlaky(t, &test.FlakyOptions{ MaxAttempts: 5, @@ -23,23 +49,34 @@ func TestCache(t *testing.T) { f.Run(func(t testing.TB) { repo := repository.CreateGoGitTestRepo(t, false) + indexCount := func(t testing.TB, name string) uint64 { t.Helper() - idx, err := repo.GetIndex(name) require.NoError(t, err) count, err := idx.DocCount() require.NoError(t, err) - return count } + assertOberserverEvent := func(obs observer, created, updated, removed int) { + t.Helper() + require.Len(t, obs.created, created) + require.Len(t, obs.updated, updated) + require.Len(t, obs.removed, removed) + } cache, err := NewRepoCacheNoEvents(repo) require.NoError(t, err) + var obsIdentity, obsBug observer + cache.RegisterObserver(identity.Typename, &obsIdentity) + cache.RegisterObserver(bug.Typename, &obsBug) + // Create, set and get user identity iden1, err := cache.Identities().New("René Descartes", "rene@descartes.fr") require.NoError(t, err) + assertOberserverEvent(obsIdentity, 1, 0, 0) + assertOberserverEvent(obsBug, 0, 0, 0) err = cache.SetUserIdentity(iden1) require.NoError(t, err) userIden, err := cache.GetUserIdentity() @@ -49,11 +86,13 @@ func TestCache(t *testing.T) { // it's possible to create two identical identities iden2, err := cache.Identities().New("René Descartes", "rene@descartes.fr") require.NoError(t, err) + assertOberserverEvent(obsIdentity, 2, 0, 0) + assertOberserverEvent(obsBug, 0, 0, 0) // Two identical identities yield a different id require.NotEqual(t, iden1.Id(), iden2.Id()) - // There is now two identities in the cache + // There are now two identities in the cache require.Len(t, cache.Identities().AllIds(), 2) require.Len(t, cache.identities.excerpts, 2) require.Len(t, cache.identities.cached, 2) @@ -63,10 +102,14 @@ func TestCache(t *testing.T) { // Create a bug bug1, _, err := cache.Bugs().New("title", "message") require.NoError(t, err) + assertOberserverEvent(obsIdentity, 2, 0, 0) + assertOberserverEvent(obsBug, 1, 0, 0) // It's possible to create two identical bugs bug2, _, err := cache.Bugs().New("title", "marker") require.NoError(t, err) + assertOberserverEvent(obsIdentity, 2, 0, 0) + assertOberserverEvent(obsBug, 2, 0, 0) // two identical bugs yield a different id require.NotEqual(t, bug1.Id(), bug2.Id()) @@ -117,6 +160,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.Len(t, cache.bugs.cached, 0) require.Len(t, cache.bugs.excerpts, 2) @@ -150,8 +195,12 @@ func TestCache(t *testing.T) { // Remove + RemoveAll err = cache.Identities().Remove(iden1.Id().String()[:10]) require.NoError(t, err) + assertOberserverEvent(obsIdentity, 2, 0, 1) + assertOberserverEvent(obsBug, 2, 0, 0) err = cache.Bugs().Remove(bug1.Id().String()[:10]) require.NoError(t, err) + assertOberserverEvent(obsIdentity, 2, 0, 1) + assertOberserverEvent(obsBug, 2, 0, 1) require.Len(t, cache.bugs.cached, 0) require.Len(t, cache.bugs.excerpts, 1) require.Len(t, cache.identities.cached, 0) @@ -161,11 +210,17 @@ 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) _, _, 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) err = cache.RemoveAll() require.NoError(t, err) + assertOberserverEvent(obsIdentity, 3, 0, 3) + assertOberserverEvent(obsBug, 3, 0, 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 c5c3a88d2bc167a853d71760609c226bee97b0d2..5fccfc39a970f7f2e854105c64d32b1ec8961d38 100644 --- a/cache/subcache.go +++ b/cache/subcache.go @@ -338,6 +338,9 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) Close() error { func (sc *SubCache[EntityT, ExcerptT, CacheT]) RegisterObserver(observer Observer) { sc.muObservers.Lock() defer sc.muObservers.Unlock() + if sc.observers == nil { + sc.observers = make(map[Observer]struct{}) + } sc.observers[observer] = struct{}{} } @@ -407,7 +410,7 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) ResolveMatcher(f func(ExcerptT) b return sc.Resolve(id) } -// ResolveExcerpt retrieve an Excerpt matching the exact given id +// ResolveExcerpt retrieves an Excerpt matching the exact given id func (sc *SubCache[EntityT, ExcerptT, CacheT]) ResolveExcerpt(id entity.Id) (ExcerptT, error) { sc.mu.RLock() defer sc.mu.RUnlock() @@ -420,7 +423,7 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) ResolveExcerpt(id entity.Id) (Exc return excerpt, nil } -// ResolveExcerptPrefix retrieve an Excerpt matching an id prefix. It fails if multiple +// ResolveExcerptPrefix retrieves an Excerpt matching an id prefix. It fails if multiple // entities match. func (sc *SubCache[EntityT, ExcerptT, CacheT]) ResolveExcerptPrefix(prefix string) (ExcerptT, error) { return sc.ResolveExcerptMatcher(func(excerpt ExcerptT) bool { @@ -428,6 +431,7 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) ResolveExcerptPrefix(prefix strin }) } +// ResolveExcerptMatcher retrieves an Excerpt selected by the given matcher function. func (sc *SubCache[EntityT, ExcerptT, CacheT]) ResolveExcerptMatcher(f func(ExcerptT) bool) (ExcerptT, error) { id, err := sc.resolveMatcher(f) if err != nil { @@ -475,11 +479,14 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) add(e EntityT) (CacheT, error) { sc.evictIfNeeded() // force the write of the excerpt - err := sc.entityCreated(e.Id()) + err := sc.updateExcerptAndIndex(e.Id()) if err != nil { return *new(CacheT), err } + // defer to notify after the release of the mutex + defer sc.notifyObservers(EntityEventCreated, e.Id()) + return cached, nil } @@ -513,6 +520,9 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) Remove(prefix string) error { return err } + // defer to notify after the release of the mutex + defer sc.notifyObservers(EntityEventRemoved, e.Id()) + return sc.write() } @@ -525,12 +535,16 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) RemoveAll() error { return err } + ids := make(map[entity.Id]struct{}) + for id, _ := range sc.cached { delete(sc.cached, id) sc.lru.Remove(id) + ids[id] = struct{}{} } for id, _ := range sc.excerpts { delete(sc.excerpts, id) + ids[id] = struct{}{} } index, err := sc.repo.GetIndex(sc.namespace) @@ -545,6 +559,13 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) RemoveAll() error { return err } + // defer to notify after the release of the mutex + defer func() { + for id := range ids { + sc.notifyObservers(EntityEventRemoved, id) + } + }() + return sc.write() } @@ -570,7 +591,7 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) MergeAll(remote string) <-chan en } switch result.Status { - case entity.MergeStatusNew, entity.MergeStatusUpdated: + case entity.MergeStatusNew: e := result.Entity.(EntityT) cached := sc.makeCached(e, sc.entityUpdated) @@ -579,6 +600,19 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) MergeAll(remote string) <-chan en // might as well keep them in memory sc.cached[result.Id] = cached sc.mu.Unlock() + sc.notifyObservers(EntityEventCreated, result.Id) + + case entity.MergeStatusUpdated: + // TODO: can that result in multiple copy of the same entity? + e := result.Entity.(EntityT) + cached := sc.makeCached(e, sc.entityUpdated) + + sc.mu.Lock() + sc.excerpts[result.Id] = sc.makeExcerpt(cached) + // might as well keep them in memory + sc.cached[result.Id] = cached + sc.mu.Unlock() + sc.notifyObservers(EntityEventUpdated, result.Id) } } @@ -593,29 +627,24 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) MergeAll(remote string) <-chan en } +// GetNamespace expose the namespace in git where entities are located. func (sc *SubCache[EntityT, ExcerptT, CacheT]) GetNamespace() string { return sc.namespace } -func (sc *SubCache[EntityT, ExcerptT, CacheT]) entityCreated(id entity.Id) error { - sc.muObservers.RLock() - for observer := range sc.observers { - observer.EntityCreated(sc.typename, id) - } - sc.muObservers.RUnlock() - +// entityUpdated is a callback to trigger when the excerpt of an entity changed +func (sc *SubCache[EntityT, ExcerptT, CacheT]) entityUpdated(id entity.Id) error { + sc.notifyObservers(EntityEventUpdated, id) return sc.updateExcerptAndIndex(id) } -// entityUpdated is a callback to trigger when the excerpt of an entity changed -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.EntityCreated(sc.typename, id) + observer.EntityEvent(event, sc.repo.sc.typename, id) } sc.muObservers.RUnlock() - - return sc.updateExcerptAndIndex(id) } func (sc *SubCache[EntityT, ExcerptT, CacheT]) updateExcerptAndIndex(id entity.Id) error { diff --git a/entities/bug/comment.go b/entities/bug/comment.go index 32209fcb0da4f5619973e9aa447ee408d46a242d..bd7c315fe2e482a3c67c2ff3578c8a84a858b09b 100644 --- a/entities/bug/comment.go +++ b/entities/bug/comment.go @@ -48,5 +48,5 @@ func (c Comment) FormatTime() string { return c.unixTime.Time().Format("Mon Jan 2 15:04:05 2006 -0700") } -// IsAuthored is a sign post method for gqlgen +// IsAuthored is a sign-post method for gqlgen, to mark compliance to an interface. func (c Comment) IsAuthored() {} diff --git a/entities/bug/op_add_comment.go b/entities/bug/op_add_comment.go index ea7093972bc2f2887ae8d81cae1fe3b8555020f5..633116aac2506600cefbe6999c36f74552d490c3 100644 --- a/entities/bug/op_add_comment.go +++ b/entities/bug/op_add_comment.go @@ -79,7 +79,7 @@ type AddCommentTimelineItem struct { CommentTimelineItem } -// IsAuthored is a sign post method for gqlgen +// IsAuthored is a sign post-method for gqlgen, to mark compliance to an interface. func (a *AddCommentTimelineItem) IsAuthored() {} // AddComment is a convenience function to add a comment to a bug diff --git a/entities/bug/op_create.go b/entities/bug/op_create.go index c7b6bada38d290110cab186c35bd979d6b59ae69..b817a8feccc6e35a76d1dc6094a528810c728b6f 100644 --- a/entities/bug/op_create.go +++ b/entities/bug/op_create.go @@ -97,7 +97,7 @@ type CreateTimelineItem struct { CommentTimelineItem } -// IsAuthored is a sign post method for gqlgen +// IsAuthored is a sign post-method for gqlgen, to mark compliance to an interface. func (c *CreateTimelineItem) IsAuthored() {} // Create is a convenience function to create a bug diff --git a/entities/bug/op_label_change.go b/entities/bug/op_label_change.go index cf8adfb75bacf5d0477b1be027d0531a26bb227c..96c57127beb58d2ea2b8e1ff1b6105d79e288c1c 100644 --- a/entities/bug/op_label_change.go +++ b/entities/bug/op_label_change.go @@ -117,7 +117,7 @@ func (l LabelChangeTimelineItem) CombinedId() entity.CombinedId { return l.combinedId } -// IsAuthored is a sign post method for gqlgen +// IsAuthored is a sign post-method for gqlgen, to mark compliance to an interface. func (l *LabelChangeTimelineItem) IsAuthored() {} // ChangeLabels is a convenience function to change labels on a bug diff --git a/entities/bug/op_set_status.go b/entities/bug/op_set_status.go index 641065a03879a0ac5803251e68c34ae7f487c3ec..318e252311c5c7ddf1db0942f03d84a041aa839e 100644 --- a/entities/bug/op_set_status.go +++ b/entities/bug/op_set_status.go @@ -68,7 +68,7 @@ func (s SetStatusTimelineItem) CombinedId() entity.CombinedId { return s.combinedId } -// IsAuthored is a sign post method for gqlgen +// IsAuthored is a sign post-method for gqlgen, to mark compliance to an interface. func (s *SetStatusTimelineItem) IsAuthored() {} // Open is a convenience function to change a bugs state to Open diff --git a/entities/bug/op_set_title.go b/entities/bug/op_set_title.go index 7ec98281cd7234e19bb94f7ae2415f7df94ac91d..3233e1c4d55331cae3c6b4e6d86c093cfc2389ff 100644 --- a/entities/bug/op_set_title.go +++ b/entities/bug/op_set_title.go @@ -80,7 +80,7 @@ func (s SetTitleTimelineItem) CombinedId() entity.CombinedId { return s.combinedId } -// IsAuthored is a sign post method for gqlgen +// IsAuthored is a sign post-method for gqlgen, to mark compliance to an interface. func (s *SetTitleTimelineItem) IsAuthored() {} // SetTitle is a convenience function to change a bugs title diff --git a/entities/bug/snapshot.go b/entities/bug/snapshot.go index 7f9e7e580298a30baccc97a6e722b18bd56046b8..258312d719a3e7e0033025b6af80695ab0649815 100644 --- a/entities/bug/snapshot.go +++ b/entities/bug/snapshot.go @@ -155,6 +155,3 @@ func (snap *Snapshot) HasAnyActor(ids ...entity.Id) bool { } return false } - -// IsAuthored is a sign post method for gqlgen -func (snap *Snapshot) IsAuthored() {}