-
Notifications
You must be signed in to change notification settings - Fork 5
/
roadmap.go
252 lines (197 loc) · 7.8 KB
/
roadmap.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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
package roadmap
import (
"crypto/sha256"
_ "embed"
"encoding/json"
"fmt"
"regexp"
"strings"
"time"
"github.com/pkg/errors"
"github.com/santhosh-tekuri/jsonschema/v5"
"gopkg.in/yaml.v3"
)
//go:embed roadmap.schema.json
var roamdapSchema string
// Schema will return the raw JSON Schema used to validate a road map file
// for this version of the road map library.
func Schema() string {
return roamdapSchema
}
// Parse will convert a provided input buffer into a roadmap structure.
func Parse(in []byte) (*Roadmap, error) {
r := Roadmap{}
if err := yaml.Unmarshal(in, &r); err != nil {
return nil, errors.Wrap(err, "roadmap: unable to parse roadmap ")
}
r.ensureDefaults()
return &r, nil
}
// A Roadmap describes a series of milestones and important dates which combine to
// outline a planned sequence of execution for a given project or team.
type Roadmap struct {
// The Title is used to briefly introduce the road map to a reader
Title string `json:"title"`
// The Description is a markdown formatted body of text explaining the road map
// and any context that a reader should have when consuming it.
Description string `json:"description,omitempty"`
// The Authors list provides the names and contact details for each of the individuals
// or teams involved in defining the road map. They act as points of contact should questions
// about the road map arise in future.
Authors []*Author `json:"authors,omitempty"`
// The Objectives list contains the high level goals that the team is working towards
// and should inform both the content and prioritization of deliverables in each milestone.
Objectives []*Objective `json:"objectives,omitempty"`
// The Timeline lists important dates which are of relevance to the execution of this
// road map. They are intentionally separated from milestones as we expect that milestones
// are executed in sequence and might be delayed or accelerated based on external factors.
Timeline []*TimelineEntry `json:"timeline,omitempty"`
// The Milestones act as a series of high-level progress indicators. These allow a team
// to visualize where they are on the road map without being overly constrained to the
// specific execution.
Milestones []*Milestone `json:"milestones,omitempty"`
}
// Validate will check whether the provided road map is valid according to the embedded
// JSON Schema.
func (r *Roadmap) Validate() error {
compiler := jsonschema.NewCompiler()
compiler.Draft = jsonschema.Draft2020
compiler.AddResource("roadmap.schema.json", strings.NewReader(Schema()))
schema, err := compiler.Compile("roadmap.schema.json")
if err != nil {
return err
}
j, err := json.Marshal(r)
if err != nil {
return err
}
var raw map[string]interface{}
err = json.Unmarshal(j, &raw)
if err != nil {
return err
}
err = schema.Validate(raw)
if err != nil {
return err
}
return nil
}
func (r *Roadmap) ensureDefaults() {
for _, a := range r.Authors {
a.ensureDefaults()
}
for _, t := range r.Timeline {
t.ensureDefaults()
}
for _, m := range r.Milestones {
m.ensureDefaults()
}
}
// An Author is an individual or team which has contributed to the road map and which
// may be able to provide additional context if required.
type Author struct {
// The Name of an individual or team which has contributed to this road map.
Name string `json:"name"`
// The Contact details for the individual or team, should a reader wish to contact them.
// This may be an email address, IM handle or other method of contact.
Contact string `json:"contact,omitempty"`
}
// ID will return a deterministic identifier for this author which may be used to uniquely identify them.
func (a *Author) ID() string {
return id(a.Name, a.Contact)
}
func (a *Author) ensureDefaults() {
}
// A TimelineEntry describes an important date which is relevant to the road map. This
// may be a delivery date, quarterly milestone or any other point-in-time reference.
type TimelineEntry struct {
// The Date associated with this timeline entry.
Date time.Time `json:"date"`
// The Title provides a brief name for the timeline entry to succinctly convey meaning to a reader.
Title string `json:"title"`
// The Description is a markdown formatted body of text providing additional context
// on this timeline entry to any reader who needs it.
Description string `json:"description,omitempty"`
}
// ID will return a deterministic identifier for this timeline entry which is based on its title and date.
func (t *TimelineEntry) ID() string {
return id(t.Title, t.Date)
}
func (t *TimelineEntry) ensureDefaults() {
}
// A Milestone is used to describe a high-level progress indicator for a team or project.
// It is composed of a series of deliverables and represents a shift in the delivered
// value at a level which can be understood by the business and customers.
type Milestone struct {
// The Title provides a brief name for this milestone.
Title string `json:"title"`
// The Description is a markdown formatted body of text which provides additional
// information about the milestone.
Description string `json:"description,omitempty"`
// The Deliverables are concrete tasks which combine to achieve a given milestone.
Deliverables []*Deliverable `json:"deliverables,omitempty"`
}
// ID will return a deterministic identifier for this milestone which is based on its title and deliverables.
func (m *Milestone) ID() string {
dids := make([]interface{}, len(m.Deliverables))
for i, d := range m.Deliverables {
dids[i] = d.ID()
}
return id(m.Title, dids...)
}
func (m *Milestone) ensureDefaults() {
for _, d := range m.Deliverables {
d.ensureDefaults()
}
}
// An Objective describes a high level goal for the team. It is usually something that
// will be worked towards over several milestones and might not have a clear definition of done.
type Objective struct {
// The Title provides a brief name for this objective.
Title string `json:"title"`
// The Description is a markdown formatted body of text that which provides additional
// context on the objective.
Description string `json:"description,omitempty"`
}
// ID returns a deterministic identifier for this deliverable which is based on its title and reference
func (o *Objective) ID() string {
return id(o.Title)
}
// A Deliverable is a concrete task which may be delivered as part of a broader milestone.
// Deliverables can state their RFC2119 requirement level to indicate how their delivery
// impacts the milestone.
type Deliverable struct {
// The Title provides a brief name for this deliverable.
Title string `json:"title"`
// The Description is a markdown formatted body of text which provides additional
// context on this deliverable.
Description string `json:"description,omitempty"`
// The Reference is a URL at which additional information about the deliverable can
// be found. This may be documentation, a tracking ticket or a PR.
Reference string `json:"reference,omitempty"`
// The Requirement specifies, using RFC2119 form, the impact that delivery has on
// the milestone (MUST, SHOULD, MAY).
Requirement string `json:"requirement,omitempty"`
// The State of the deliverable may be one of TODO, DOING, DONE, SKIP.
State string `json:"state,omitempty"`
}
// ID returns a deterministic identifier for this deliverable which is based on its title and reference
func (d *Deliverable) ID() string {
return id(d.Title, d.Reference)
}
func (d *Deliverable) ensureDefaults() {
if d.Requirement == "" {
d.Requirement = "SHOULD"
}
if d.State == "" {
d.State = "TODO"
}
}
// The id function generates a deterministic identifier based on the provided seeds.
func id(key string, hash ...interface{}) string {
prefix := string(regexp.MustCompile("[^a-zA-Z0-9_]").ReplaceAll([]byte(key), []byte("_")))
h := sha256.New()
h.Write([]byte(fmt.Sprint(hash...)))
suffix := fmt.Sprintf("%x", h.Sum(nil))
return prefix + "_" + suffix[:8]
}