Progress reads only a design's head, and says so beside the tracker's tree when even that fails (issue 1947)
This commit is contained in:
@@ -24,8 +24,15 @@ type Design struct {
|
||||
Status string
|
||||
Decisions []string
|
||||
Milestones []Milestone
|
||||
// Unread says why the design's frontmatter and title could not be read, when they could not (novox/hq issue
|
||||
// 1947); the progress answer carries it as a warning beside a tree built from the tracker's milestone records.
|
||||
Unread string
|
||||
}
|
||||
|
||||
// DesignHeadBytes is how much of a design is read: its frontmatter and its title lead it, and its milestones are the
|
||||
// tracker's records, so the rest is never needed.
|
||||
const DesignHeadBytes = 16 << 10
|
||||
|
||||
// Milestone is one milestone of a design, as the issue tracker keeps it.
|
||||
type Milestone struct {
|
||||
ID string
|
||||
@@ -151,11 +158,19 @@ func (k *KB) designs(all bool, number int) ([]*Design, string, error) {
|
||||
}
|
||||
d := c.byPath[p]
|
||||
if d == nil {
|
||||
content, _, err := k.Records.Read(p)
|
||||
var content string
|
||||
var err error
|
||||
if h, ok := k.Records.(HeadReader); ok {
|
||||
content, _, err = h.ReadHead(p, DesignHeadBytes)
|
||||
} else {
|
||||
content, _, err = k.Records.Read(p)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("the records module does not read %s: %v", p, err)
|
||||
d = parseDesign(p, "")
|
||||
d.Unread = fmt.Sprintf("the records module does not read %s (%v): its title and state are not known here", p, err)
|
||||
} else {
|
||||
d = parseDesign(p, content)
|
||||
}
|
||||
d = parseDesign(p, content)
|
||||
c.byPath[p] = d
|
||||
k.progReads++
|
||||
}
|
||||
|
||||
@@ -22,6 +22,12 @@ type Records interface {
|
||||
Read(path string) (content, commit string, err error)
|
||||
}
|
||||
|
||||
// HeadReader is Records that can read only the first part of a document: a caller that needs a frontmatter or a
|
||||
// title reads a long document through it, bounded, never whole (novox/hq issue 1947).
|
||||
type HeadReader interface {
|
||||
ReadHead(path string, bytes int) (content, commit string, err error)
|
||||
}
|
||||
|
||||
// busRecords is Records over the bus: the records module's tools, called as this module.
|
||||
type busRecords struct{}
|
||||
|
||||
@@ -58,8 +64,18 @@ func (busRecords) List(folder string) ([]string, []string, error) {
|
||||
return f.Folders, f.Documents, nil
|
||||
}
|
||||
|
||||
func (busRecords) Read(path string) (string, string, error) {
|
||||
raw, err := stdio.Ask("records.records_read", map[string]any{"path": path})
|
||||
func (busRecords) Read(path string) (string, string, error) { return busRead(path, 0) }
|
||||
|
||||
func (busRecords) ReadHead(path string, bytes int) (string, string, error) {
|
||||
return busRead(path, bytes)
|
||||
}
|
||||
|
||||
func busRead(path string, head int) (string, string, error) {
|
||||
args := map[string]any{"path": path}
|
||||
if head > 0 {
|
||||
args["head"] = head
|
||||
}
|
||||
raw, err := stdio.Ask("records.records_read", args)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
@@ -118,11 +134,19 @@ func (d dirRecords) List(folder string) ([]string, []string, error) {
|
||||
return folders, docs, nil
|
||||
}
|
||||
|
||||
func (d dirRecords) Read(path string) (string, string, error) {
|
||||
func (d dirRecords) Read(path string) (string, string, error) { return d.ReadHead(path, 0) }
|
||||
|
||||
func (d dirRecords) ReadHead(path string, head int) (string, string, error) {
|
||||
raw, err := os.ReadFile(filepath.Join(d.dir, filepath.FromSlash(path)))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if head > 0 && head < len(raw) {
|
||||
raw = raw[:head]
|
||||
if i := strings.LastIndexByte(string(raw), '\n'); i > 0 {
|
||||
raw = raw[:i+1]
|
||||
}
|
||||
}
|
||||
commit, _ := d.Standing()
|
||||
return string(raw), commit, nil
|
||||
}
|
||||
|
||||
@@ -329,6 +329,12 @@ func (k *KB) progressDesign(p *Progress, number int, milestone string) error {
|
||||
return err
|
||||
}
|
||||
d := ds[0]
|
||||
if d.Unread != "" {
|
||||
p.Warnings = append(p.Warnings, d.Unread)
|
||||
if d.Title == "" {
|
||||
d.Title = fmt.Sprintf("design %d", d.Number)
|
||||
}
|
||||
}
|
||||
if len(d.Milestones) == 0 {
|
||||
return fmt.Errorf("design %d has no milestone records in the issue tracker: seed them from its table with "+
|
||||
"issue-tracker.import milestones %d", number, number)
|
||||
|
||||
@@ -142,6 +142,19 @@ func (c *countingRecords) Read(p string) (string, string, error) {
|
||||
return content, c.commit, err
|
||||
}
|
||||
|
||||
func (c *countingRecords) ReadHead(p string, n int) (string, string, error) {
|
||||
c.reads++
|
||||
content, _, err := c.dirRecords.ReadHead(p, n)
|
||||
return content, c.commit, err
|
||||
}
|
||||
|
||||
// brokenRead is Records whose reads fail as the records module's do for a document longer than it answers whole.
|
||||
type brokenRead struct{ dirRecords }
|
||||
|
||||
func (brokenRead) ReadHead(p string, n int) (string, string, error) {
|
||||
return "", "", fmt.Errorf("%s is longer than the records module answers whole", p)
|
||||
}
|
||||
|
||||
// 1947/AC2, as issue 1781 changed it: a design's title, state and decisions are hq's; its milestones are the issue
|
||||
// tracker's records, ordered as its table, an id found with spaces or hyphens, and a closed one holding no issue done.
|
||||
func TestADesignsMilestonesAreTheTrackersRecords(t *testing.T) {
|
||||
@@ -316,6 +329,12 @@ func TestProgressOfADesignMinimalAndDetailed(t *testing.T) {
|
||||
if big, _ := k.Progress(ProgressAsk{Of: "design 47", Bound: 8192}); big.Cut != "" || big.Tree != p.Tree {
|
||||
t.Errorf("a minimal answer at bound 8192 is cut: %q", big.Cut)
|
||||
}
|
||||
// A design whose head the records module cannot read is said in a warning beside the tracker-built tree, never a failure.
|
||||
kb := &KB{Records: brokenRead{progressRecords()}, Seats: newSeatsWorld(t).ask, Now: k.Now}
|
||||
if bp, err := kb.Progress(ProgressAsk{Of: "design 47"}); err != nil || !strings.Contains(strings.Join(bp.Warnings, "\n"), "does not read") ||
|
||||
!strings.Contains(bp.Tree, "design 47") || !strings.Contains(bp.Tree, "R6") {
|
||||
t.Fatalf("an unreadable design: %v %+v", err, bp)
|
||||
}
|
||||
lines := strings.Split(p.Tree, "\n")
|
||||
if !strings.HasPrefix(lines[0], "✗ design 47 · Delivery: from a commit to delivered — ") {
|
||||
t.Fatalf("first line %q", lines[0])
|
||||
|
||||
Reference in New Issue
Block a user