forked from MichaelMure/git-bug
-
Notifications
You must be signed in to change notification settings - Fork 3
/
workflow.go
180 lines (164 loc) · 5.73 KB
/
workflow.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package bug
import (
"fmt"
"github.com/daedaleanai/git-ticket/identity"
)
// Invoked to validate if a workflow transition can be taken. Returns an error if the transition is invalid.
type ValidationFunc func(snap *Snapshot, next Status) error
// Invoked to update a ticket as a consequence of a workflow status transition.
type ActionFunc func(b Interface, snapshot *Snapshot, next Status, author identity.Interface, unixTime int64) error
type Transition struct {
start Status
end Status
validationHook []ValidationFunc
actionHook []ActionFunc
}
type Workflow struct {
label Label
initialState Status
transitions []Transition
}
var workflowStore []Workflow
// FindWorkflow searches a list of labels and attempts to match them to a workflow, returning the first found
func FindWorkflow(names []Label) *Workflow {
for _, l := range names {
if l.IsWorkflow() {
for wf := range workflowStore {
if workflowStore[wf].label == l {
return &workflowStore[wf]
}
}
}
}
return nil
}
// GetWorkflowLabels returns a slice of all the available workflow labels
func GetWorkflowLabels() []Label {
var labels []Label
for _, wf := range workflowStore {
labels = append(labels, wf.label)
}
return labels
}
// AllStatuses returns a slice of all possible statuses in the workflow
// for the given one
func (w *Workflow) AllStatuses() []Status {
allStatusesMap := map[Status]struct{}{}
for _, t := range w.transitions {
allStatusesMap[t.start] = struct{}{}
allStatusesMap[t.end] = struct{}{}
}
var allStatuses []Status
for _, s := range AllStatuses() {
if _, ok := allStatusesMap[s]; ok {
allStatuses = append(allStatuses, s)
}
}
return allStatuses
}
// NextStatuses returns a slice of next possible statuses in the workflow
// for the given one
func (w *Workflow) NextStatuses(s Status) []Status {
var validStatuses []Status
for _, t := range w.transitions {
if t.start == s {
validStatuses = append(validStatuses, t.end)
}
}
return validStatuses
}
// ValidateTransition checks if the transition is valid for a given start and end
func (w *Workflow) ValidateTransition(snap *Snapshot, to Status) error {
for _, t := range w.transitions {
if t.start == snap.Status && t.end == to {
if t.validationHook != nil {
for _, v := range t.validationHook {
if err := v(snap, to); err != nil {
return err
}
}
}
return nil
}
}
// invalid transition, return error with list of valid transitions
nextStatuses := w.NextStatuses(snap.Status)
return fmt.Errorf("invalid transition %s->%s, possible next statuses: %s", snap.Status, to, nextStatuses)
}
// ApplyTransitionActions invokes the actionHooks of the transition that was taken
func (w *Workflow) ApplyTransitionActions(b Interface, snap *Snapshot, to Status, author identity.Interface, unixTime int64) error {
for _, t := range w.transitions {
if t.start == snap.Status && t.end == to {
if t.actionHook != nil {
for _, a := range t.actionHook {
if err := a(b, snap, to, author, unixTime); err != nil {
return err
}
}
}
return nil
}
}
return nil
}
func init() {
// Initialise list of workflows
workflowStore = []Workflow{
{label: "workflow:eng",
initialState: ProposedStatus,
transitions: []Transition{
{start: ProposedStatus, end: VettedStatus,
validationHook: []ValidationFunc{ValidateCcb}},
{start: ProposedStatus, end: RejectedStatus, actionHook: []ActionFunc{
ClearAllCcbApprovals}},
{start: VettedStatus, end: InProgressStatus,
validationHook: []ValidationFunc{ValidateAssigneeSet}},
{start: VettedStatus, end: RejectedStatus,
validationHook: []ValidationFunc{ValidateCcb}, actionHook: []ActionFunc{ClearAllCcbApprovals}},
{start: InProgressStatus, end: VettedStatus},
{start: InProgressStatus, end: InReviewStatus},
{start: InProgressStatus, end: RejectedStatus,
validationHook: []ValidationFunc{ValidateCcb}, actionHook: []ActionFunc{ClearAllCcbApprovals}},
{start: InReviewStatus, end: InProgressStatus},
{start: InReviewStatus, end: ReviewedStatus},
{start: InReviewStatus, end: RejectedStatus,
validationHook: []ValidationFunc{ValidateCcb}, actionHook: []ActionFunc{ClearAllCcbApprovals}},
{start: ReviewedStatus, end: InProgressStatus},
{start: ReviewedStatus, end: AcceptedStatus,
validationHook: []ValidationFunc{ValidateCcb,
ValidateChecklistsCompleted}},
{start: ReviewedStatus, end: RejectedStatus,
validationHook: []ValidationFunc{ValidateCcb}, actionHook: []ActionFunc{ClearAllCcbApprovals}},
{start: AcceptedStatus, end: MergedStatus},
{start: AcceptedStatus, end: RejectedStatus,
validationHook: []ValidationFunc{ValidateCcb}, actionHook: []ActionFunc{ClearAllCcbApprovals}},
{start: MergedStatus, end: AcceptedStatus},
{start: RejectedStatus, end: ProposedStatus},
},
},
{label: "workflow:qa",
initialState: ProposedStatus,
transitions: []Transition{
{start: ProposedStatus, end: InProgressStatus,
validationHook: []ValidationFunc{ValidateAssigneeSet}},
{start: ProposedStatus, end: RejectedStatus},
{start: InProgressStatus, end: DoneStatus},
{start: InProgressStatus, end: RejectedStatus},
{start: DoneStatus, end: InProgressStatus},
{start: RejectedStatus, end: ProposedStatus},
},
},
{label: "workflow:change",
initialState: ProposedStatus,
transitions: []Transition{
{start: ProposedStatus, end: InProgressStatus,
validationHook: []ValidationFunc{ValidateAssigneeSet}},
{start: ProposedStatus, end: RejectedStatus},
{start: InProgressStatus, end: DoneStatus},
{start: InProgressStatus, end: RejectedStatus},
{start: DoneStatus, end: InProgressStatus},
{start: RejectedStatus, end: ProposedStatus},
},
},
}
}