Skip to content

Commit 838f6d7

Browse files
committed
feat(ci): skip linear comments for non-stable releases
1 parent f1c56cb commit 838f6d7

File tree

2 files changed

+104
-27
lines changed

2 files changed

+104
-27
lines changed

hack/linear-sync/linear.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,24 @@ func NewLinearClient(ctx context.Context, token string) LinearClient {
3939
return LinearClient{client: client}
4040
}
4141

42+
// isStableRelease checks if a version is a stable release (no pre-release suffix).
43+
// Returns true for stable releases like v0.26.1, v4.5.0
44+
// Returns false for pre-releases like v0.26.1-alpha.1, v0.26.1-rc.4, v4.5.0-beta.2
45+
func isStableRelease(version string) bool {
46+
// Remove 'v' prefix if present
47+
version = strings.TrimPrefix(version, "v")
48+
49+
// Check for pre-release suffixes
50+
preReleaseSuffixes := []string{"-alpha", "-beta", "-rc", "-dev", "-pre"}
51+
for _, suffix := range preReleaseSuffixes {
52+
if strings.Contains(version, suffix) {
53+
return false
54+
}
55+
}
56+
57+
return true
58+
}
59+
4260
// WorkflowStateID returns the ID of the a workflow state for the given team.
4361
func (l *LinearClient) WorkflowStateID(ctx context.Context, stateName, linearTeamName string) (string, error) {
4462
var query struct {
@@ -115,6 +133,7 @@ func (l *LinearClient) IsIssueInStateByName(ctx context.Context, issueID string,
115133

116134
// MoveIssueToState moves the issue to the given state if it's not already there.
117135
// It also adds a comment to the issue about when it was first released and on which tag.
136+
// Only processes stable releases (no pre-release suffixes like -alpha, -rc, -beta).
118137
func (l *LinearClient) MoveIssueToState(ctx context.Context, dryRun bool, issueID, releasedStateID, readyForReleaseStateName, releaseTagName, releaseDate string) error {
119138
// (ThomasK33): Skip CVEs
120139
if strings.HasPrefix(strings.ToLower(issueID), "cve") {
@@ -123,6 +142,15 @@ func (l *LinearClient) MoveIssueToState(ctx context.Context, dryRun bool, issueI
123142

124143
logger := ctx.Value(LoggerKey).(*slog.Logger)
125144

145+
// Skip non-stable releases (alpha, beta, rc, etc.)
146+
if !isStableRelease(releaseTagName) {
147+
logger.Info("Skipping non-stable release",
148+
"issueID", issueID,
149+
"release", releaseTagName,
150+
"reason", "not a stable release")
151+
return nil
152+
}
153+
126154
currentIssueStateID, currentIssueStateName, err := l.IssueStateDetails(ctx, issueID)
127155
if err != nil {
128156
return fmt.Errorf("get issue state details: %w", err)
@@ -201,4 +229,3 @@ func (l *LinearClient) createComment(ctx context.Context, issueID, releaseCommen
201229

202230
return nil
203231
}
204-

hack/linear-sync/linear_test.go

Lines changed: 76 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ func TestMoveIssueLogic(t *testing.T) {
5454

5555
// MockLinearClient is a mock implementation of the LinearClient interface for testing
5656
type MockLinearClient struct {
57-
mockIssueStates map[string]string
58-
mockIssueStateNames map[string]string
59-
mockWorkflowIDs map[string]string
57+
mockIssueStates map[string]string
58+
mockIssueStateNames map[string]string
59+
mockWorkflowIDs map[string]string
6060
}
6161

6262
func NewMockLinearClient() *MockLinearClient {
@@ -109,25 +109,25 @@ func (m *MockLinearClient) MoveIssueToState(ctx context.Context, dryRun bool, is
109109
if strings.HasPrefix(strings.ToLower(issueID), "cve") {
110110
return nil
111111
}
112-
112+
113113
currentStateID, currentStateName, _ := m.IssueStateDetails(ctx, issueID)
114-
114+
115115
// Already in released state
116116
if currentStateID == releasedStateID {
117117
return nil
118118
}
119-
119+
120120
// Skip if not in ready for release state
121121
if currentStateName != readyForReleaseStateName {
122122
return fmt.Errorf("issue %s not in ready for release state", issueID)
123123
}
124-
124+
125125
// Only ENG-1234 is expected to be moved successfully
126126
// Explicitly return errors for other issues to ensure the test only counts ENG-1234
127127
if issueID != "ENG-1234" {
128128
return fmt.Errorf("would not move issue %s for test purposes", issueID)
129129
}
130-
130+
131131
return nil
132132
}
133133

@@ -136,8 +136,8 @@ func TestIsIssueInState(t *testing.T) {
136136
ctx := context.Background()
137137

138138
testCases := []struct {
139-
IssueID string
140-
StateID string
139+
IssueID string
140+
StateID string
141141
ExpectedResult bool
142142
}{
143143
{"ENG-1234", "ready-state-id", true},
@@ -164,10 +164,10 @@ func TestMoveIssueStateFiltering(t *testing.T) {
164164
// Create a custom mock client for this test
165165
mockClient := &MockLinearClient{
166166
mockIssueStates: map[string]string{
167-
"ENG-1234": "ready-state-id", // Ready for release
168-
"ENG-5678": "in-progress-id", // In progress
169-
"ENG-9012": "released-id", // Already released
170-
"CVE-1234": "ready-state-id", // Ready but should be skipped as CVE
167+
"ENG-1234": "ready-state-id", // Ready for release
168+
"ENG-5678": "in-progress-id", // In progress
169+
"ENG-9012": "released-id", // Already released
170+
"CVE-1234": "ready-state-id", // Ready but should be skipped as CVE
171171
},
172172
mockIssueStateNames: map[string]string{
173173
"ENG-1234": "Ready for Release",
@@ -181,7 +181,7 @@ func TestMoveIssueStateFiltering(t *testing.T) {
181181
"In Progress": "in-progress-id",
182182
},
183183
}
184-
184+
185185
ctx := context.Background()
186186

187187
// Test cases for the overall filtering logic
@@ -198,19 +198,19 @@ func TestMoveIssueStateFiltering(t *testing.T) {
198198
if strings.HasPrefix(strings.ToLower(issueID), "cve") {
199199
continue
200200
}
201-
201+
202202
currentStateID, currentStateName, _ := mockClient.IssueStateDetails(ctx, issueID)
203-
203+
204204
// Skip if already in released state
205205
if currentStateID == releasedStateID {
206206
continue
207207
}
208-
208+
209209
// Skip if not in ready for release state
210210
if currentStateName != readyForReleaseStateName {
211211
continue
212212
}
213-
213+
214214
// This issue would be moved
215215
actualMoved = append(actualMoved, issueID)
216216
}
@@ -230,7 +230,7 @@ func TestMoveIssueStateFiltering(t *testing.T) {
230230
break
231231
}
232232
}
233-
233+
234234
if !found {
235235
t.Errorf("Expected issue %s to be moved, but it wasn't in the result set", expectedID)
236236
}
@@ -243,12 +243,12 @@ func TestIssueIDsExtraction(t *testing.T) {
243243
defer func() {
244244
issuesInBodyREs = originalRegex
245245
}()
246-
246+
247247
// For testing, use a regex that matches any 3-letter prefix format
248248
issuesInBodyREs = []*regexp.Regexp{
249249
regexp.MustCompile(`(?P<issue>\w{3}-\d{4})`),
250250
}
251-
251+
252252
testCases := []struct {
253253
name string
254254
body string
@@ -286,7 +286,7 @@ func TestIssueIDsExtraction(t *testing.T) {
286286
expected: []string{},
287287
},
288288
}
289-
289+
290290
for _, tc := range testCases {
291291
t.Run(tc.name, func(t *testing.T) {
292292
pr := LinearPullRequest{
@@ -295,15 +295,15 @@ func TestIssueIDsExtraction(t *testing.T) {
295295
HeadRefName: tc.headRefName,
296296
},
297297
}
298-
298+
299299
result := pr.IssueIDs()
300-
300+
301301
if len(result) != len(tc.expected) {
302302
t.Errorf("Expected %d issues, got %d", len(tc.expected), len(result))
303303
t.Errorf("Expected: %v, Got: %v", tc.expected, result)
304304
return
305305
}
306-
306+
307307
// Check all expected IDs are found (ignoring order)
308308
for _, expectedID := range tc.expected {
309309
found := false
@@ -320,3 +320,53 @@ func TestIssueIDsExtraction(t *testing.T) {
320320
})
321321
}
322322
}
323+
324+
func TestIsStableRelease(t *testing.T) {
325+
tests := []struct {
326+
name string
327+
version string
328+
want bool
329+
}{
330+
// Stable releases - should return true
331+
{name: "Stable release v0.26.1", version: "v0.26.1", want: true},
332+
{name: "Stable release v4.5.0", version: "v4.5.0", want: true},
333+
{name: "Stable release without v prefix", version: "1.2.3", want: true},
334+
{name: "Stable release v0.28.0", version: "v0.28.0", want: true},
335+
{name: "Stable release v1.0.0", version: "v1.0.0", want: true},
336+
337+
// Alpha releases - should return false
338+
{name: "Alpha release v0.26.1-alpha.1", version: "v0.26.1-alpha.1", want: false},
339+
{name: "Alpha release v4.5.0-alpha.10", version: "v4.5.0-alpha.10", want: false},
340+
{name: "Alpha release without version number", version: "v0.28.0-alpha", want: false},
341+
342+
// RC (Release Candidate) releases - should return false
343+
{name: "RC release v0.26.1-rc.4", version: "v0.26.1-rc.4", want: false},
344+
{name: "RC release v0.26.1-rc.2", version: "v0.26.1-rc.2", want: false},
345+
{name: "RC release without patch number", version: "v1.0.0-rc1", want: false},
346+
347+
// Beta releases - should return false
348+
{name: "Beta release v4.5.0-beta.2", version: "v4.5.0-beta.2", want: false},
349+
{name: "Beta release v1.0.0-beta", version: "v1.0.0-beta", want: false},
350+
351+
// Dev releases - should return false
352+
{name: "Dev release v0.1.0-dev", version: "v0.1.0-dev", want: false},
353+
{name: "Dev release v2.0.0-dev.1", version: "v2.0.0-dev.1", want: false},
354+
355+
// Pre releases - should return false
356+
{name: "Pre release v1.0.0-pre", version: "v1.0.0-pre", want: false},
357+
{name: "Pre release v1.0.0-pre.1", version: "v1.0.0-pre.1", want: false},
358+
359+
// Edge cases
360+
{name: "Empty string", version: "", want: true}, // Empty is considered stable (no pre-release suffix)
361+
{name: "Just v", version: "v", want: true},
362+
}
363+
364+
for _, tt := range tests {
365+
t.Run(tt.name, func(t *testing.T) {
366+
got := isStableRelease(tt.version)
367+
if got != tt.want {
368+
t.Errorf("isStableRelease(%q) = %v, want %v", tt.version, got, tt.want)
369+
}
370+
})
371+
}
372+
}

0 commit comments

Comments
 (0)