Commit 9002a0bf3f
Verified · cmc
Layout: unified · split
internal/httpd/feed.go → internal/control/feedline.go renamed +36 −9
| @@ -1,4 +1,4 @@ | |||
| 1 | package httpd | 1 | package control |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "encoding/json" | 4 | "encoding/json" |
| @@ -10,8 +10,8 @@ import ( | |||
| 10 | "gitbay.org/gitbay/internal/store" | 10 | "gitbay.org/gitbay/internal/store" |
| 11 | ) | 11 | ) |
| 12 | 12 | ||
| 13 | // feedLine is one activity entry, already phrased and linked. | 13 | // FeedLine is one activity entry, already phrased and linked. |
| 14 | type feedLine struct { | 14 | type FeedLine struct { |
| 15 | Actor string | 15 | Actor string |
| 16 | Verb string // "opened issue", "merged", "ran 2 jobs on" | 16 | Verb string // "opened issue", "merged", "ran 2 jobs on" |
| 17 | Ref string // "#12", "!35", "v0.4.0", a short sha | 17 | Ref string // "#12", "!35", "v0.4.0", a short sha |
| @@ -24,11 +24,11 @@ type feedLine struct { | |||
| 24 | sha string // the commit a build event fired on, for fold-matching | 24 | sha string // the commit a build event fired on, for fold-matching |
| 25 | } | 25 | } |
| 26 | 26 | ||
| 27 | // feedLines turns stored events into readable lines. An unknown kind | 27 | // FeedLines turns stored events into readable lines. An unknown kind |
| 28 | // still shows: the feed says what happened even for events added later. | 28 | // still shows: the feed says what happened even for events added later. |
| 29 | // Build events on the same commit, adjacent in the input, fold into one | 29 | // Build events on the same commit, adjacent in the input, fold into one |
| 30 | // "run" line (D04): its State is the worst of the folded jobs' outcomes, | 30 | // "run" line (D04): its State is the worst of the folded jobs' outcomes, |
| 31 | // via worstStatus — the same rule the builds tab uses for a run's status. | 31 | // via WorstStatus — the same rule the builds tab uses for a run's status. |
| 32 | // A repeated job name ends the line and starts the next, so a scheduled | 32 | // A repeated job name ends the line and starts the next, so a scheduled |
| 33 | // job firing daily on an unchanged tip reads as one line a day rather than | 33 | // job firing daily on an unchanged tip reads as one line a day rather than |
| 34 | // "ran 9 jobs on" one commit (#240). groupRuns separates its runs by | 34 | // "ran 9 jobs on" one commit (#240). groupRuns separates its runs by |
| @@ -36,8 +36,8 @@ type feedLine struct { | |||
| 36 | // per job, so there is no queue moment here to key on. The cost is that | 36 | // per job, so there is no queue moment here to key on. The cost is that |
| 37 | // the oldest scheduled line on a commit folds in the push's jobs, which | 37 | // the oldest scheduled line on a commit folds in the push's jobs, which |
| 38 | // have not been seen yet on that line. | 38 | // have not been seen yet on that line. |
| 39 | func feedLines(events []store.FeedEvent) []feedLine { | 39 | func FeedLines(events []store.FeedEvent) []FeedLine { |
| 40 | out := make([]feedLine, 0, len(events)) | 40 | out := make([]FeedLine, 0, len(events)) |
| 41 | statuses := make([][]string, 0, len(events)) | 41 | statuses := make([][]string, 0, len(events)) |
| 42 | for _, e := range events { | 42 | for _, e := range events { |
| 43 | var d struct { | 43 | var d struct { |
| @@ -58,12 +58,12 @@ func feedLines(events []store.FeedEvent) []feedLine { | |||
| 58 | out[i].Verb = fmt.Sprintf("ran %d jobs on", len(out[i].Jobs)) | 58 | out[i].Verb = fmt.Sprintf("ran %d jobs on", len(out[i].Jobs)) |
| 59 | out[i].Ref = fmt.Sprintf("%.10s", d.SHA) | 59 | out[i].Ref = fmt.Sprintf("%.10s", d.SHA) |
| 60 | out[i].URL = fmt.Sprintf("/%s/commit/%s", e.RepoPath, d.SHA) | 60 | out[i].URL = fmt.Sprintf("/%s/commit/%s", e.RepoPath, d.SHA) |
| 61 | out[i].State = worstStatus(statuses[i]) | 61 | out[i].State = WorstStatus(statuses[i]) |
| 62 | continue | 62 | continue |
| 63 | } | 63 | } |
| 64 | } | 64 | } |
| 65 | 65 | ||
| 66 | l := feedLine{Actor: e.Actor, Repo: e.RepoPath, When: e.CreatedAt, WhenT: parseEventTime(e.CreatedAt)} | 66 | l := FeedLine{Actor: e.Actor, Repo: e.RepoPath, When: e.CreatedAt, WhenT: parseEventTime(e.CreatedAt)} |
| 67 | if l.Actor == "" { | 67 | if l.Actor == "" { |
| 68 | l.Actor = "gitbay" | 68 | l.Actor = "gitbay" |
| 69 | } | 69 | } |
| @@ -100,6 +100,33 @@ func feedLines(events []store.FeedEvent) []feedLine { | |||
| 100 | return out | 100 | return out |
| 101 | } | 101 | } |
| 102 | 102 | ||
| 103 | // runStatusPriority orders WorstStatus's worst-first check: a run reads | ||
| 104 | // as its least finished or least successful build. | ||
| 105 | var runStatusPriority = []string{"failure", "cancelled", "running", "pending"} | ||
| 106 | |||
| 107 | // WorstStatus is the worst of a set of build statuses, success only when | ||
| 108 | // every one of them is. It backs both the builds tab's combinedStatus and | ||
| 109 | // FeedLines' folded build run (D04). A status outside runStatusPriority (a | ||
| 110 | // future state such as "skipped") is still not "success": it is returned | ||
| 111 | // unchanged rather than falling through and reading as green. | ||
| 112 | func WorstStatus(statuses []string) string { | ||
| 113 | has := map[string]bool{} | ||
| 114 | for _, s := range statuses { | ||
| 115 | has[s] = true | ||
| 116 | } | ||
| 117 | for _, s := range runStatusPriority { | ||
| 118 | if has[s] { | ||
| 119 | return s | ||
| 120 | } | ||
| 121 | } | ||
| 122 | for _, s := range statuses { | ||
| 123 | if s != "success" { | ||
| 124 | return s | ||
| 125 | } | ||
| 126 | } | ||
| 127 | return "success" | ||
| 128 | } | ||
| 129 | |||
| 103 | // parseEventTime parses a stored RFC3339 timestamp for ago/whenT | 130 | // parseEventTime parses a stored RFC3339 timestamp for ago/whenT |
| 104 | // rendering; an unparseable value (or none) comes back zero rather than | 131 | // rendering; an unparseable value (or none) comes back zero rather than |
| 105 | // guessing. | 132 | // guessing. |
internal/httpd/feed_test.go → internal/control/feedline_test.go renamed +10 −10
| @@ -1,4 +1,4 @@ | |||
| 1 | package httpd | 1 | package control |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "fmt" | 4 | "fmt" |
| @@ -18,7 +18,7 @@ func TestFeedLinesFoldsBuildRunsBySHA(t *testing.T) { | |||
| 18 | {RepoPath: "alice/app", Actor: "alice", Kind: "build.failure", | 18 | {RepoPath: "alice/app", Actor: "alice", Kind: "build.failure", |
| 19 | Data: `{"number":2,"job":"lint","sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}`}, | 19 | Data: `{"number":2,"job":"lint","sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}`}, |
| 20 | } | 20 | } |
| 21 | lines := feedLines(events) | 21 | lines := FeedLines(events) |
| 22 | if len(lines) != 1 { | 22 | if len(lines) != 1 { |
| 23 | t.Fatalf("feedLines returned %d lines, want 1: %+v", len(lines), lines) | 23 | t.Fatalf("feedLines returned %d lines, want 1: %+v", len(lines), lines) |
| 24 | } | 24 | } |
| @@ -47,7 +47,7 @@ func TestFeedLinesSingleBuildGetsState(t *testing.T) { | |||
| 47 | {RepoPath: "alice/app", Actor: "alice", Kind: "build.success", | 47 | {RepoPath: "alice/app", Actor: "alice", Kind: "build.success", |
| 48 | Data: `{"number":1,"job":"unit","sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}`}, | 48 | Data: `{"number":1,"job":"unit","sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}`}, |
| 49 | } | 49 | } |
| 50 | lines := feedLines(events) | 50 | lines := FeedLines(events) |
| 51 | if len(lines) != 1 { | 51 | if len(lines) != 1 { |
| 52 | t.Fatalf("feedLines returned %d lines, want 1", len(lines)) | 52 | t.Fatalf("feedLines returned %d lines, want 1", len(lines)) |
| 53 | } | 53 | } |
| @@ -68,7 +68,7 @@ func TestFeedLinesDoesNotFoldAcrossDifferentSHAs(t *testing.T) { | |||
| 68 | {RepoPath: "alice/app", Actor: "alice", Kind: "build.success", | 68 | {RepoPath: "alice/app", Actor: "alice", Kind: "build.success", |
| 69 | Data: `{"number":2,"job":"lint","sha":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}`}, | 69 | Data: `{"number":2,"job":"lint","sha":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}`}, |
| 70 | } | 70 | } |
| 71 | lines := feedLines(events) | 71 | lines := FeedLines(events) |
| 72 | if len(lines) != 2 { | 72 | if len(lines) != 2 { |
| 73 | t.Fatalf("feedLines returned %d lines, want 2: %+v", len(lines), lines) | 73 | t.Fatalf("feedLines returned %d lines, want 2: %+v", len(lines), lines) |
| 74 | } | 74 | } |
| @@ -82,7 +82,7 @@ func TestFeedLinesNoSHANeverFolds(t *testing.T) { | |||
| 82 | {RepoPath: "alice/app", Actor: "alice", Kind: "build.success", Data: `{"number":1,"job":"unit"}`}, | 82 | {RepoPath: "alice/app", Actor: "alice", Kind: "build.success", Data: `{"number":1,"job":"unit"}`}, |
| 83 | {RepoPath: "alice/app", Actor: "alice", Kind: "build.success", Data: `{"number":2,"job":"lint"}`}, | 83 | {RepoPath: "alice/app", Actor: "alice", Kind: "build.success", Data: `{"number":2,"job":"lint"}`}, |
| 84 | } | 84 | } |
| 85 | lines := feedLines(events) | 85 | lines := FeedLines(events) |
| 86 | if len(lines) != 2 { | 86 | if len(lines) != 2 { |
| 87 | t.Fatalf("feedLines returned %d lines, want 2: %+v", len(lines), lines) | 87 | t.Fatalf("feedLines returned %d lines, want 2: %+v", len(lines), lines) |
| 88 | } | 88 | } |
| @@ -101,7 +101,7 @@ func TestFeedLinesNonBuildEventBreaksFold(t *testing.T) { | |||
| 101 | {RepoPath: "alice/app", Actor: "alice", Kind: "build.failure", | 101 | {RepoPath: "alice/app", Actor: "alice", Kind: "build.failure", |
| 102 | Data: `{"number":2,"job":"lint","sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}`}, | 102 | Data: `{"number":2,"job":"lint","sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}`}, |
| 103 | } | 103 | } |
| 104 | lines := feedLines(events) | 104 | lines := FeedLines(events) |
| 105 | if len(lines) != 3 { | 105 | if len(lines) != 3 { |
| 106 | t.Fatalf("feedLines returned %d lines, want 3: %+v", len(lines), lines) | 106 | t.Fatalf("feedLines returned %d lines, want 3: %+v", len(lines), lines) |
| 107 | } | 107 | } |
| @@ -127,7 +127,7 @@ func TestFeedLinesRunStatePrecedence(t *testing.T) { | |||
| 127 | events[i] = store.FeedEvent{RepoPath: "alice/app", Actor: "alice", Kind: "build." + s, | 127 | events[i] = store.FeedEvent{RepoPath: "alice/app", Actor: "alice", Kind: "build." + s, |
| 128 | Data: fmt.Sprintf(`{"number":1,"job":"j%d","sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}`, i)} | 128 | Data: fmt.Sprintf(`{"number":1,"job":"j%d","sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}`, i)} |
| 129 | } | 129 | } |
| 130 | lines := feedLines(events) | 130 | lines := FeedLines(events) |
| 131 | if len(lines) != 1 || lines[0].State != tc.want { | 131 | if len(lines) != 1 || lines[0].State != tc.want { |
| 132 | t.Errorf("statuses %v: got %+v, want State %q", tc.statuses, lines, tc.want) | 132 | t.Errorf("statuses %v: got %+v, want State %q", tc.statuses, lines, tc.want) |
| 133 | } | 133 | } |
| @@ -144,7 +144,7 @@ func TestFeedLinesParsesWhenT(t *testing.T) { | |||
| 144 | {RepoPath: "alice/app", Actor: "alice", Kind: "issue.created", | 144 | {RepoPath: "alice/app", Actor: "alice", Kind: "issue.created", |
| 145 | Data: `{"number":2}`, CreatedAt: "not-a-time"}, | 145 | Data: `{"number":2}`, CreatedAt: "not-a-time"}, |
| 146 | } | 146 | } |
| 147 | lines := feedLines(events) | 147 | lines := FeedLines(events) |
| 148 | want, _ := time.Parse(time.RFC3339Nano, "2026-09-10T12:00:00Z") | 148 | want, _ := time.Parse(time.RFC3339Nano, "2026-09-10T12:00:00Z") |
| 149 | if !lines[0].WhenT.Equal(want) { | 149 | if !lines[0].WhenT.Equal(want) { |
| 150 | t.Errorf("WhenT = %v, want %v", lines[0].WhenT, want) | 150 | t.Errorf("WhenT = %v, want %v", lines[0].WhenT, want) |
| @@ -169,7 +169,7 @@ func TestFeedLinesSplitsRepeatedJob(t *testing.T) { | |||
| 169 | events = append(events, store.FeedEvent{RepoPath: "alice/app", Actor: "alice", | 169 | events = append(events, store.FeedEvent{RepoPath: "alice/app", Actor: "alice", |
| 170 | Kind: "build." + status, Data: `{"number":1,"job":"instances","sha":"` + sha + `"}`}) | 170 | Kind: "build." + status, Data: `{"number":1,"job":"instances","sha":"` + sha + `"}`}) |
| 171 | } | 171 | } |
| 172 | lines := feedLines(events) | 172 | lines := FeedLines(events) |
| 173 | if len(lines) != 3 { | 173 | if len(lines) != 3 { |
| 174 | t.Fatalf("feedLines returned %d lines, want 3: %+v", len(lines), lines) | 174 | t.Fatalf("feedLines returned %d lines, want 3: %+v", len(lines), lines) |
| 175 | } | 175 | } |
| @@ -194,7 +194,7 @@ func TestFeedLinesScheduleAbsorbsPushJobs(t *testing.T) { | |||
| 194 | {RepoPath: "alice/app", Actor: "alice", Kind: "build.success", | 194 | {RepoPath: "alice/app", Actor: "alice", Kind: "build.success", |
| 195 | Data: `{"number":1,"job":"lint","sha":"` + sha + `"}`}, | 195 | Data: `{"number":1,"job":"lint","sha":"` + sha + `"}`}, |
| 196 | } | 196 | } |
| 197 | lines := feedLines(events) | 197 | lines := FeedLines(events) |
| 198 | if len(lines) != 2 { | 198 | if len(lines) != 2 { |
| 199 | t.Fatalf("feedLines returned %d lines, want 2: %+v", len(lines), lines) | 199 | t.Fatalf("feedLines returned %d lines, want 2: %+v", len(lines), lines) |
| 200 | } | 200 | } |
internal/httpd/anchors_test.go +2 −1
| @@ -7,6 +7,7 @@ import ( | |||
| 7 | "testing" | 7 | "testing" |
| 8 | 8 | ||
| 9 | "gitbay.org/gitbay/internal/config" | 9 | "gitbay.org/gitbay/internal/config" |
| 10 | "gitbay.org/gitbay/internal/control" | ||
| 10 | "gitbay.org/gitbay/internal/store" | 11 | "gitbay.org/gitbay/internal/store" |
| 11 | "gitbay.org/gitbay/internal/web" | 12 | "gitbay.org/gitbay/internal/web" |
| 12 | ) | 13 | ) |
| @@ -58,7 +59,7 @@ func TestStylesheetURLCarriesTheBuildHash(t *testing.T) { | |||
| 58 | Assigned []store.DashboardItem | 59 | Assigned []store.DashboardItem |
| 59 | MRs []store.DashboardItem | 60 | MRs []store.DashboardItem |
| 60 | Issues []store.DashboardItem | 61 | Issues []store.DashboardItem |
| 61 | Feed []feedLine | 62 | Feed []control.FeedLine |
| 62 | }{base, "dashboard", nil, nil, nil, nil, nil, nil}) | 63 | }{base, "dashboard", nil, nil, nil, nil, nil, nil}) |
| 63 | if err != nil { | 64 | if err != nil { |
| 64 | t.Fatal(err) | 65 | t.Fatal(err) |
internal/httpd/builds.go +1 −28
| @@ -147,10 +147,6 @@ type buildRun struct { | |||
| 147 | Builds []control.BuildOut | 147 | Builds []control.BuildOut |
| 148 | } | 148 | } |
| 149 | 149 | ||
| 150 | // runStatusPriority orders combinedStatus's worst-first check: a run reads | ||
| 151 | // as its least finished or least successful build. | ||
| 152 | var runStatusPriority = []string{"failure", "cancelled", "running", "pending"} | ||
| 153 | |||
| 154 | // combinedStatus is the run's status: the worst of its builds' statuses, | 150 | // combinedStatus is the run's status: the worst of its builds' statuses, |
| 155 | // success only when every one of them is. | 151 | // success only when every one of them is. |
| 156 | func combinedStatus(builds []control.BuildOut) string { | 152 | func combinedStatus(builds []control.BuildOut) string { |
| @@ -158,30 +154,7 @@ func combinedStatus(builds []control.BuildOut) string { | |||
| 158 | for i, b := range builds { | 154 | for i, b := range builds { |
| 159 | statuses[i] = b.Status | 155 | statuses[i] = b.Status |
| 160 | } | 156 | } |
| 161 | return worstStatus(statuses) | 157 | return control.WorstStatus(statuses) |
| 162 | } | ||
| 163 | |||
| 164 | // worstStatus is combinedStatus's ordering rule, factored out so the | ||
| 165 | // dashboard feed can apply the same worst-first precedence to a folded | ||
| 166 | // build run (D04). A status outside runStatusPriority (a future state | ||
| 167 | // such as "skipped") is still not "success": it is returned unchanged | ||
| 168 | // rather than falling through and reading as green. | ||
| 169 | func worstStatus(statuses []string) string { | ||
| 170 | has := map[string]bool{} | ||
| 171 | for _, s := range statuses { | ||
| 172 | has[s] = true | ||
| 173 | } | ||
| 174 | for _, s := range runStatusPriority { | ||
| 175 | if has[s] { | ||
| 176 | return s | ||
| 177 | } | ||
| 178 | } | ||
| 179 | for _, s := range statuses { | ||
| 180 | if s != "success" { | ||
| 181 | return s | ||
| 182 | } | ||
| 183 | } | ||
| 184 | return "success" | ||
| 185 | } | 158 | } |
| 186 | 159 | ||
| 187 | // groupRuns folds consecutive builds of the same commit and the same | 160 | // groupRuns folds consecutive builds of the same commit and the same |
internal/httpd/dashpins_test.go +2 −1
| @@ -4,6 +4,7 @@ import ( | |||
| 4 | "strings" | 4 | "strings" |
| 5 | "testing" | 5 | "testing" |
| 6 | 6 | ||
| 7 | "gitbay.org/gitbay/internal/control" | ||
| 7 | "gitbay.org/gitbay/internal/store" | 8 | "gitbay.org/gitbay/internal/store" |
| 8 | "gitbay.org/gitbay/internal/web" | 9 | "gitbay.org/gitbay/internal/web" |
| 9 | ) | 10 | ) |
| @@ -23,7 +24,7 @@ func TestDashboardTilesAndPins(t *testing.T) { | |||
| 23 | Assigned []store.DashboardItem | 24 | Assigned []store.DashboardItem |
| 24 | MRs []store.DashboardItem | 25 | MRs []store.DashboardItem |
| 25 | Issues []store.DashboardItem | 26 | Issues []store.DashboardItem |
| 26 | Feed []feedLine | 27 | Feed []control.FeedLine |
| 27 | }{base, "dashboard", []pinnedRow{ | 28 | }{base, "dashboard", []pinnedRow{ |
| 28 | {Owner: "krz", Name: "gitbay", Issues: 3, MRs: 0, Build: "success"}, | 29 | {Owner: "krz", Name: "gitbay", Issues: 3, MRs: 0, Build: "success"}, |
| 29 | {Owner: "krz", Name: "old", Build: "cancelled"}, | 30 | {Owner: "krz", Name: "old", Build: "cancelled"}, |
internal/httpd/ownerpage_test.go +2 −2
| @@ -55,7 +55,7 @@ func TestOwnerPageTabOrder(t *testing.T) { | |||
| 55 | // it — not the whole history, which is what the atom feed is for. | 55 | // it — not the whole history, which is what the atom feed is for. |
| 56 | func TestOwnerPageAboutCarriesTheGraphAndLog(t *testing.T) { | 56 | func TestOwnerPageAboutCarriesTheGraphAndLog(t *testing.T) { |
| 57 | d := ownerFixture() | 57 | d := ownerFixture() |
| 58 | d.Log = []feedLine{{Actor: "cmc", Verb: "opened issue", Ref: "#12", Repo: "krz/gitbay", URL: "/krz/gitbay/issues/12"}} | 58 | d.Log = []control.FeedLine{{Actor: "cmc", Verb: "opened issue", Ref: "#12", Repo: "krz/gitbay", URL: "/krz/gitbay/issues/12"}} |
| 59 | out := renderOwner(t, "about", d) | 59 | out := renderOwner(t, "about", d) |
| 60 | for _, want := range []string{"Christian Cleberg", "actgraph", "opened issue", "#12", "/cmc/activity.atom"} { | 60 | for _, want := range []string{"Christian Cleberg", "actgraph", "opened issue", "#12", "/cmc/activity.atom"} { |
| 61 | if !strings.Contains(out, want) { | 61 | if !strings.Contains(out, want) { |
| @@ -149,7 +149,7 @@ type ownerFixtureData struct { | |||
| 149 | Self bool | 149 | Self bool |
| 150 | Snippets int | 150 | Snippets int |
| 151 | Members []control.ProfileMember | 151 | Members []control.ProfileMember |
| 152 | Log []feedLine | 152 | Log []control.FeedLine |
| 153 | Bookmarks []control.BookmarkOut | 153 | Bookmarks []control.BookmarkOut |
| 154 | SnippetRows []snippetRow | 154 | SnippetRows []snippetRow |
| 155 | } | 155 | } |
internal/httpd/web.go +5 −5
| @@ -216,8 +216,8 @@ func (s *Server) dashboard(w http.ResponseWriter, r *http.Request, viewer store. | |||
| 216 | Assigned []store.DashboardItem | 216 | Assigned []store.DashboardItem |
| 217 | MRs []store.DashboardItem | 217 | MRs []store.DashboardItem |
| 218 | Issues []store.DashboardItem | 218 | Issues []store.DashboardItem |
| 219 | Feed []feedLine | 219 | Feed []control.FeedLine |
| 220 | }{s.baseFor(viewer), "dashboard", s.pinnedRows(viewer), reviews, assigned, mrs, issues, feedLines(events)}) | 220 | }{s.baseFor(viewer), "dashboard", s.pinnedRows(viewer), reviews, assigned, mrs, issues, control.FeedLines(events)}) |
| 221 | } | 221 | } |
| 222 | 222 | ||
| 223 | func (s *Server) explore(w http.ResponseWriter, r *http.Request) { | 223 | func (s *Server) explore(w http.ResponseWriter, r *http.Request) { |
| @@ -467,7 +467,7 @@ const profileEvents = 30 | |||
| 467 | // ActivityByDay and OrgActivityByDay respectively — a log that counted | 467 | // ActivityByDay and OrgActivityByDay respectively — a log that counted |
| 468 | // something else would contradict the total printed over it. Only the | 468 | // something else would contradict the total printed over it. Only the |
| 469 | // About tab renders it, so no other tab pays for the query. | 469 | // About tab renders it, so no other tab pays for the query. |
| 470 | func (s *Server) ownerFeed(tab, kind, name string) []feedLine { | 470 | func (s *Server) ownerFeed(tab, kind, name string) []control.FeedLine { |
| 471 | if tab != "about" { | 471 | if tab != "about" { |
| 472 | return nil | 472 | return nil |
| 473 | } | 473 | } |
| @@ -490,7 +490,7 @@ func (s *Server) ownerFeed(tab, kind, name string) []feedLine { | |||
| 490 | if err != nil { | 490 | if err != nil { |
| 491 | return nil | 491 | return nil |
| 492 | } | 492 | } |
| 493 | return feedLines(events) | 493 | return control.FeedLines(events) |
| 494 | } | 494 | } |
| 495 | 495 | ||
| 496 | // ownerPage is what owner.html renders against. It is a named type | 496 | // ownerPage is what owner.html renders against. It is a named type |
| @@ -508,7 +508,7 @@ type ownerPage struct { | |||
| 508 | Orgs []control.ProfileMember | 508 | Orgs []control.ProfileMember |
| 509 | Activity []activityWeek | 509 | Activity []activityWeek |
| 510 | ActivityTotal int | 510 | ActivityTotal int |
| 511 | Log []feedLine | 511 | Log []control.FeedLine |
| 512 | Bookmarks []control.BookmarkOut | 512 | Bookmarks []control.BookmarkOut |
| 513 | SnippetRows []snippetRow | 513 | SnippetRows []snippetRow |
| 514 | SnippetsAll bool | 514 | SnippetsAll bool |