fix(web): send the issues changed notice of a merge request page once
- commit
- 0fa0a1afd9720e32f6a27e945ae54f23cfabeaea
- author
- brice · · 2026-10-22 18:38 UTC
- committer
- brice · · 2026-10-22 18:41 UTC
- parent
- 2a12bb1
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +27 -3
M internal/adapters/web/fragments.go +16 -3
| @@ -39,6 +39,9 @@ type liveStream struct { | |||
| 39 | 39 | headRuns map[int64]bool | |
| 40 | 40 | // mergeChecksHTML is the last merge-form part sent, not sent again unchanged. | |
| 41 | 41 | mergeChecksHTML string | |
| 42 | + | // issuesNoticeSent says the "issues changed" notice of a merge request | |
| 43 | + | // page was sent: a merge resolving several issues sends it once. | |
| 44 | + | issuesNoticeSent bool | |
| 42 | 45 | } | |
| 43 | 46 | ||
| 44 | 47 | // maxHeadRuns bounds headRuns: past it the map starts again. | |
| @@ -243,17 +246,17 @@ func (l *liveStream) repoPage(ctx context.Context, we ports.WatchEvent) ([]fragm | |||
| 243 | 246 | return l.issueNotice(ctx, ev.Issue.Number, "an issue was opened — reload") | |
| 244 | 247 | case domain.IssueResolved: | |
| 245 | 248 | if l.view == "mr" { | |
| 246 | - | return l.notice(ctx, "issues changed — reload", mrBase(l.repo)+"/"+l.n) | |
| 249 | + | return l.issuesChanged(ctx) | |
| 247 | 250 | } | |
| 248 | 251 | return l.issueNotice(ctx, ev.Issue.Number, "this issue was resolved — reload") | |
| 249 | 252 | case domain.IssueClosed: | |
| 250 | 253 | if l.view == "mr" { | |
| 251 | - | return l.notice(ctx, "issues changed — reload", mrBase(l.repo)+"/"+l.n) | |
| 254 | + | return l.issuesChanged(ctx) | |
| 252 | 255 | } | |
| 253 | 256 | return l.issueNotice(ctx, ev.Issue.Number, "this issue was closed — reload") | |
| 254 | 257 | case domain.IssueReopened: | |
| 255 | 258 | if l.view == "mr" { | |
| 256 | - | return l.notice(ctx, "issues changed — reload", mrBase(l.repo)+"/"+l.n) | |
| 259 | + | return l.issuesChanged(ctx) | |
| 257 | 260 | } | |
| 258 | 261 | return l.issueNotice(ctx, ev.Issue.Number, "this issue was reopened — reload") | |
| 259 | 262 | case domain.IssueCommented: | |
| @@ -577,6 +580,16 @@ func (l *liveStream) mrMergeability(ctx context.Context) ([]fragment, error) { | |||
| 577 | 580 | return []fragment{f}, err | |
| 578 | 581 | } | |
| 579 | 582 | ||
| 583 | + | // issuesChanged tells a merge request page that its issues changed, once per | |
| 584 | + | // stream: the notice already asks for a reload. | |
| 585 | + | func (l *liveStream) issuesChanged(ctx context.Context) ([]fragment, error) { | |
| 586 | + | if l.issuesNoticeSent { | |
| 587 | + | return nil, nil | |
| 588 | + | } | |
| 589 | + | l.issuesNoticeSent = true | |
| 590 | + | return l.notice(ctx, "issues changed — reload", mrBase(l.repo)+"/"+l.n) | |
| 591 | + | } | |
| 592 | + | ||
| 580 | 593 | func (l *liveStream) notice(ctx context.Context, text, href string) ([]fragment, error) { | |
| 581 | 594 | f, err := renderFragment(ctx, "mr-notice", MRNotice(text, href)) | |
| 582 | 595 | return []fragment{f}, err | |
| @@ -39,6 +39,9 @@ type liveStream struct { | |||||
| 39 | headRuns map[int64]bool | 39 | headRuns map[int64]bool | ||
| 40 | // mergeChecksHTML is the last merge-form part sent, not sent again unchanged. | 40 | // mergeChecksHTML is the last merge-form part sent, not sent again unchanged. | ||
| 41 | mergeChecksHTML string | 41 | mergeChecksHTML string | ||
| 42 | + | // issuesNoticeSent says the "issues changed" notice of a merge request | |||
| 43 | + | // page was sent: a merge resolving several issues sends it once. | |||
| 44 | + | issuesNoticeSent bool | |||
| 42 | } | 45 | } | ||
| 43 | 46 | ||||
| 44 | // maxHeadRuns bounds headRuns: past it the map starts again. | 47 | // maxHeadRuns bounds headRuns: past it the map starts again. | ||
| @@ -243,17 +246,17 @@ func (l *liveStream) repoPage(ctx context.Context, we ports.WatchEvent) ([]fragm | |||||
| 243 | return l.issueNotice(ctx, ev.Issue.Number, "an issue was opened — reload") | 246 | return l.issueNotice(ctx, ev.Issue.Number, "an issue was opened — reload") | ||
| 244 | case domain.IssueResolved: | 247 | case domain.IssueResolved: | ||
| 245 | if l.view == "mr" { | 248 | if l.view == "mr" { | ||
| 246 | - | return l.notice(ctx, "issues changed — reload", mrBase(l.repo)+"/"+l.n) | 249 | + | return l.issuesChanged(ctx) |
| 247 | } | 250 | } | ||
| 248 | return l.issueNotice(ctx, ev.Issue.Number, "this issue was resolved — reload") | 251 | return l.issueNotice(ctx, ev.Issue.Number, "this issue was resolved — reload") | ||
| 249 | case domain.IssueClosed: | 252 | case domain.IssueClosed: | ||
| 250 | if l.view == "mr" { | 253 | if l.view == "mr" { | ||
| 251 | - | return l.notice(ctx, "issues changed — reload", mrBase(l.repo)+"/"+l.n) | 254 | + | return l.issuesChanged(ctx) |
| 252 | } | 255 | } | ||
| 253 | return l.issueNotice(ctx, ev.Issue.Number, "this issue was closed — reload") | 256 | return l.issueNotice(ctx, ev.Issue.Number, "this issue was closed — reload") | ||
| 254 | case domain.IssueReopened: | 257 | case domain.IssueReopened: | ||
| 255 | if l.view == "mr" { | 258 | if l.view == "mr" { | ||
| 256 | - | return l.notice(ctx, "issues changed — reload", mrBase(l.repo)+"/"+l.n) | 259 | + | return l.issuesChanged(ctx) |
| 257 | } | 260 | } | ||
| 258 | return l.issueNotice(ctx, ev.Issue.Number, "this issue was reopened — reload") | 261 | return l.issueNotice(ctx, ev.Issue.Number, "this issue was reopened — reload") | ||
| 259 | case domain.IssueCommented: | 262 | case domain.IssueCommented: | ||
| @@ -577,6 +580,16 @@ func (l *liveStream) mrMergeability(ctx context.Context) ([]fragment, error) { | |||||
| 577 | return []fragment{f}, err | 580 | return []fragment{f}, err | ||
| 578 | } | 581 | } | ||
| 579 | 582 | ||||
| 583 | + | // issuesChanged tells a merge request page that its issues changed, once per | |||
| 584 | + | // stream: the notice already asks for a reload. | |||
| 585 | + | func (l *liveStream) issuesChanged(ctx context.Context) ([]fragment, error) { | |||
| 586 | + | if l.issuesNoticeSent { | |||
| 587 | + | return nil, nil | |||
| 588 | + | } | |||
| 589 | + | l.issuesNoticeSent = true | |||
| 590 | + | return l.notice(ctx, "issues changed — reload", mrBase(l.repo)+"/"+l.n) | |||
| 591 | + | } | |||
| 592 | + | ||||
| 580 | func (l *liveStream) notice(ctx context.Context, text, href string) ([]fragment, error) { | 593 | func (l *liveStream) notice(ctx context.Context, text, href string) ([]fragment, error) { | ||
| 581 | f, err := renderFragment(ctx, "mr-notice", MRNotice(text, href)) | 594 | f, err := renderFragment(ctx, "mr-notice", MRNotice(text, href)) | ||
| 582 | return []fragment{f}, err | 595 | return []fragment{f}, err | ||
M internal/adapters/web/fragments_test.go +11 -0
| @@ -133,3 +133,14 @@ func TestIssueTransitionsRefreshMRRail(t *testing.T) { | |||
| 133 | 133 | }) | |
| 134 | 134 | } | |
| 135 | 135 | } | |
| 136 | + | ||
| 137 | + | func TestIssueTransitionsNoticeMROnce(t *testing.T) { | |
| 138 | + | l := liveStream{repo: "demo", view: "mr", n: "7"} | |
| 139 | + | var notices int | |
| 140 | + | for _, ev := range []domain.Event{domain.IssueResolved{}, domain.IssueResolved{}, domain.IssueReopened{}} { | |
| 141 | + | fs, err := l.repoPage(t.Context(), ports.WatchEvent{Event: ev}) | |
| 142 | + | require.NoError(t, err) | |
| 143 | + | notices += len(fs) | |
| 144 | + | } | |
| 145 | + | assert.Equal(t, 1, notices) | |
| 146 | + | } | |
| @@ -133,3 +133,14 @@ func TestIssueTransitionsRefreshMRRail(t *testing.T) { | |||||
| 133 | }) | 133 | }) | ||
| 134 | } | 134 | } | ||
| 135 | } | 135 | } | ||
| 136 | + | ||||
| 137 | + | func TestIssueTransitionsNoticeMROnce(t *testing.T) { | |||
| 138 | + | l := liveStream{repo: "demo", view: "mr", n: "7"} | |||
| 139 | + | var notices int | |||
| 140 | + | for _, ev := range []domain.Event{domain.IssueResolved{}, domain.IssueResolved{}, domain.IssueReopened{}} { | |||
| 141 | + | fs, err := l.repoPage(t.Context(), ports.WatchEvent{Event: ev}) | |||
| 142 | + | require.NoError(t, err) | |||
| 143 | + | notices += len(fs) | |||
| 144 | + | } | |||
| 145 | + | assert.Equal(t, 1, notices) | |||
| 146 | + | } | |||