1package models
2
3import (
4 "sync"
5 "time"
6
7 "github.com/git-bug/git-bug/cache"
8 "github.com/git-bug/git-bug/entities/bug"
9 "github.com/git-bug/git-bug/entities/common"
10 "github.com/git-bug/git-bug/entity"
11 "github.com/git-bug/git-bug/entity/dag"
12)
13
14// BugWrapper is an interface used by the GraphQL resolvers to handle a bug.
15// Depending on the situation, a Bug can already be fully loaded in memory or not.
16// This interface is used to wrap either a lazyBug or a loadedBug depending on the situation.
17type BugWrapper interface {
18 Id() entity.Id
19 LastEdit() time.Time
20 Status() common.Status
21 Title() string
22 Comments() ([]bug.Comment, error)
23 Labels() []common.Label
24 Author() (IdentityWrapper, error)
25 Actors() ([]IdentityWrapper, error)
26 Participants() ([]IdentityWrapper, error)
27 CreatedAt() time.Time
28 Timeline() ([]bug.TimelineItem, error)
29 Operations() ([]dag.Operation, error)
30
31 // IsAuthored is a sign-post method for gqlgen, to mark compliance to an interface.
32 IsAuthored()
33 // IsEntity is a sign post-method for gqlgen, to mark compliance to an interface.
34 IsEntity()
35}
36
37var _ BugWrapper = &lazyBug{}
38
39// lazyBug is a lazy-loading wrapper that fetches data from the cache (BugExcerpt) in priority,
40// and load the complete bug and snapshot only when necessary.
41type lazyBug struct {
42 cache *cache.RepoCache
43 excerpt *cache.BugExcerpt
44
45 mu sync.Mutex
46 snap *bug.Snapshot
47}
48
49func NewLazyBug(cache *cache.RepoCache, excerpt *cache.BugExcerpt) *lazyBug {
50 return &lazyBug{
51 cache: cache,
52 excerpt: excerpt,
53 }
54}
55
56func (lb *lazyBug) load() error {
57 lb.mu.Lock()
58 defer lb.mu.Unlock()
59
60 if lb.snap != nil {
61 return nil
62 }
63
64 b, err := lb.cache.Bugs().Resolve(lb.excerpt.Id())
65 if err != nil {
66 return err
67 }
68
69 lb.snap = b.Snapshot()
70 return nil
71}
72
73func (lb *lazyBug) identity(id entity.Id) (IdentityWrapper, error) {
74 i, err := lb.cache.Identities().ResolveExcerpt(id)
75 if err != nil {
76 return nil, err
77 }
78 return &lazyIdentity{cache: lb.cache, excerpt: i}, nil
79}
80
81func (lb *lazyBug) Id() entity.Id {
82 return lb.excerpt.Id()
83}
84
85func (lb *lazyBug) LastEdit() time.Time {
86 return lb.excerpt.EditTime()
87}
88
89func (lb *lazyBug) Status() common.Status {
90 return lb.excerpt.Status
91}
92
93func (lb *lazyBug) Title() string {
94 return lb.excerpt.Title
95}
96
97func (lb *lazyBug) Comments() ([]bug.Comment, error) {
98 err := lb.load()
99 if err != nil {
100 return nil, err
101 }
102 return lb.snap.Comments, nil
103}
104
105func (lb *lazyBug) Labels() []common.Label {
106 return lb.excerpt.Labels
107}
108
109func (lb *lazyBug) Author() (IdentityWrapper, error) {
110 return lb.identity(lb.excerpt.AuthorId)
111}
112
113func (lb *lazyBug) Actors() ([]IdentityWrapper, error) {
114 result := make([]IdentityWrapper, len(lb.excerpt.Actors))
115 for i, actorId := range lb.excerpt.Actors {
116 actor, err := lb.identity(actorId)
117 if err != nil {
118 return nil, err
119 }
120 result[i] = actor
121 }
122 return result, nil
123}
124
125func (lb *lazyBug) Participants() ([]IdentityWrapper, error) {
126 result := make([]IdentityWrapper, len(lb.excerpt.Participants))
127 for i, participantId := range lb.excerpt.Participants {
128 participant, err := lb.identity(participantId)
129 if err != nil {
130 return nil, err
131 }
132 result[i] = participant
133 }
134 return result, nil
135}
136
137func (lb *lazyBug) CreatedAt() time.Time {
138 return lb.excerpt.CreateTime()
139}
140
141func (lb *lazyBug) Timeline() ([]bug.TimelineItem, error) {
142 err := lb.load()
143 if err != nil {
144 return nil, err
145 }
146 return lb.snap.Timeline, nil
147}
148
149func (lb *lazyBug) Operations() ([]dag.Operation, error) {
150 err := lb.load()
151 if err != nil {
152 return nil, err
153 }
154 return lb.snap.Operations, nil
155}
156
157// IsAuthored is a sign-post method for gqlgen, to mark compliance to an interface.
158func (lb *lazyBug) IsAuthored() {}
159
160// IsEntity is a sign post-method for gqlgen, to mark compliance to an interface.
161func (lb *lazyBug) IsEntity() {}
162
163var _ BugWrapper = &loadedBug{}
164
165type loadedBug struct {
166 *bug.Snapshot
167}
168
169func NewLoadedBug(snap *bug.Snapshot) *loadedBug {
170 return &loadedBug{Snapshot: snap}
171}
172
173func (l *loadedBug) LastEdit() time.Time {
174 return l.Snapshot.EditTime()
175}
176
177func (l *loadedBug) Status() common.Status {
178 return l.Snapshot.Status
179}
180
181func (l *loadedBug) Title() string {
182 return l.Snapshot.Title
183}
184
185func (l *loadedBug) Comments() ([]bug.Comment, error) {
186 return l.Snapshot.Comments, nil
187}
188
189func (l *loadedBug) Labels() []common.Label {
190 return l.Snapshot.Labels
191}
192
193func (l *loadedBug) Author() (IdentityWrapper, error) {
194 return NewLoadedIdentity(l.Snapshot.Author), nil
195}
196
197func (l *loadedBug) Actors() ([]IdentityWrapper, error) {
198 res := make([]IdentityWrapper, len(l.Snapshot.Actors))
199 for i, actor := range l.Snapshot.Actors {
200 res[i] = NewLoadedIdentity(actor)
201 }
202 return res, nil
203}
204
205func (l *loadedBug) Participants() ([]IdentityWrapper, error) {
206 res := make([]IdentityWrapper, len(l.Snapshot.Participants))
207 for i, participant := range l.Snapshot.Participants {
208 res[i] = NewLoadedIdentity(participant)
209 }
210 return res, nil
211}
212
213func (l *loadedBug) CreatedAt() time.Time {
214 return l.Snapshot.CreateTime
215}
216
217func (l *loadedBug) Timeline() ([]bug.TimelineItem, error) {
218 return l.Snapshot.Timeline, nil
219}
220
221func (l *loadedBug) Operations() ([]dag.Operation, error) {
222 return l.Snapshot.Operations, nil
223}
224
225// IsAuthored is a sign-post method for gqlgen, to mark compliance to an interface.
226func (l *loadedBug) IsAuthored() {}
227
228// IsEntity is a sign post-method for gqlgen, to mark compliance to an interface.
229func (l *loadedBug) IsEntity() {}