|
| 1 | +// Copyright (c) 2026 Uber Technologies, Inc. |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +package entity |
| 16 | + |
| 17 | +import ( |
| 18 | + "fmt" |
| 19 | + |
| 20 | + "github.com/uber/submitqueue/platform/base/id" |
| 21 | +) |
| 22 | + |
| 23 | +// IssueTrackingPhase describes whether one source build's issue updates are pending or complete. |
| 24 | +type IssueTrackingPhase string |
| 25 | + |
| 26 | +const ( |
| 27 | + // IssueTrackingPhaseUnknown is the unset phase. |
| 28 | + IssueTrackingPhaseUnknown IssueTrackingPhase = "" |
| 29 | + // IssueTrackingPhaseRefreshing means reported build issues are being opened or kept open. |
| 30 | + IssueTrackingPhaseRefreshing IssueTrackingPhase = "refreshing" |
| 31 | + // IssueTrackingPhaseRefreshed means a refresh pass completed; later refresh notifications remain eligible. |
| 32 | + IssueTrackingPhaseRefreshed IssueTrackingPhase = "refreshed" |
| 33 | + // IssueTrackingPhaseFinalizing means final opening and closing updates are pending. |
| 34 | + IssueTrackingPhaseFinalizing IssueTrackingPhase = "finalizing" |
| 35 | + // IssueTrackingPhaseFinalized means final reconciliation completed; later refresh notifications are ignored. |
| 36 | + IssueTrackingPhaseFinalized IssueTrackingPhase = "finalized" |
| 37 | +) |
| 38 | + |
| 39 | +// IssueTrackingState is the queue-scoped source-order boundary and resumable reconciliation state. |
| 40 | +type IssueTrackingState struct { |
| 41 | + // RequestID is the newest accepted source request, compared by ingest order. |
| 42 | + RequestID string `json:"request_id"` |
| 43 | + // BuildID identifies the source build whose pass is pending or most recently completed. |
| 44 | + BuildID string `json:"build_id"` |
| 45 | + // NotificationID identifies the message that owns this reconciliation pass. |
| 46 | + NotificationID string `json:"notification_id"` |
| 47 | + // AfterTaskID is the exclusive task cursor after fully processed issue keys. |
| 48 | + // Empty with an empty AfterUnitID starts a pass. |
| 49 | + AfterTaskID string `json:"after_task_id"` |
| 50 | + // AfterUnitID is the unit component of the exclusive cursor; empty is task-wide. |
| 51 | + AfterUnitID string `json:"after_unit_id"` |
| 52 | + // Phase distinguishes pending work from completed refresh or final passes. |
| 53 | + Phase IssueTrackingPhase `json:"phase"` |
| 54 | + // Version is the positive optimistic-lock version of this state. |
| 55 | + Version int32 `json:"version"` |
| 56 | +} |
| 57 | + |
| 58 | +// IsPending reports whether the phase represents unfinished reconciliation. |
| 59 | +func (s IssueTrackingState) IsPending() bool { |
| 60 | + return s.Phase == IssueTrackingPhaseRefreshing || s.Phase == IssueTrackingPhaseFinalizing |
| 61 | +} |
| 62 | + |
| 63 | +// Validate verifies the identity, phase, and version of a persisted state. |
| 64 | +func (s IssueTrackingState) Validate() error { |
| 65 | + if err := id.Validate(s.RequestID); err != nil { |
| 66 | + return fmt.Errorf("invalid issue tracking request ID: %w", err) |
| 67 | + } |
| 68 | + if s.BuildID == "" { |
| 69 | + return fmt.Errorf("issue tracking build ID must not be empty") |
| 70 | + } |
| 71 | + if s.NotificationID == "" { |
| 72 | + return fmt.Errorf("issue tracking notification ID must not be empty") |
| 73 | + } |
| 74 | + if s.AfterTaskID == "" && s.AfterUnitID != "" { |
| 75 | + return fmt.Errorf("issue tracking cursor unit requires a task") |
| 76 | + } |
| 77 | + if s.Version <= 0 { |
| 78 | + return fmt.Errorf("issue tracking version must be positive") |
| 79 | + } |
| 80 | + switch s.Phase { |
| 81 | + case IssueTrackingPhaseRefreshing, IssueTrackingPhaseRefreshed, IssueTrackingPhaseFinalizing, IssueTrackingPhaseFinalized: |
| 82 | + return nil |
| 83 | + default: |
| 84 | + return fmt.Errorf("invalid issue tracking phase %q", s.Phase) |
| 85 | + } |
| 86 | +} |
0 commit comments