From 69f6598c65f0a83d2457af74e5b048b022593572 Mon Sep 17 00:00:00 2001 From: Will Andrews Date: Tue, 5 May 2026 19:57:11 +0100 Subject: [PATCH] appview: adds issues into timeline Signed-off-by: Will Andrews --- appview/db/timeline.go | 57 +++++++++++++++++++ appview/models/timeline.go | 4 ++ .../timeline/fragments/timeline.html | 55 ++++++++++++++++++ 3 files changed, 116 insertions(+) diff --git a/appview/db/timeline.go b/appview/db/timeline.go index 41bd24a0..a2e2ad28 100644 --- a/appview/db/timeline.go +++ b/appview/db/timeline.go @@ -1,9 +1,12 @@ package db import ( + "fmt" + "slices" "sort" "github.com/bluesky-social/indigo/atproto/syntax" + "tangled.org/core/api/tangled" "tangled.org/core/appview/models" "tangled.org/core/appview/pagination" "tangled.org/core/orm" @@ -47,9 +50,15 @@ func MakeTimeline(e Execer, limit int, loggedInUserDid string, limitToUsersIsFol return nil, err } + issues, err := getTimelineIssues(e, limit, loggedInUserDid, userIsFollowing) + if err != nil { + return nil, err + } + events = append(events, repos...) events = append(events, stars...) events = append(events, follows...) + events = append(events, issues...) sort.Slice(events, func(i, j int) bool { return events[i].EventAt.After(events[j].EventAt) @@ -278,3 +287,51 @@ func getTimelineFollows(e Execer, limit int, loggedInUserDid string, userIsFollo return events, nil } + +func getTimelineIssues(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) { + filters := make([]orm.Filter, 0) + if userIsFollowing != nil { + filters = append(filters, orm.FilterIn("owner_did", userIsFollowing)) + } + filters = append(filters, orm.FilterEq("open", 1)) + + issues, err := GetIssuesPaginated(e, pagination.Page{Limit: limit}, filters...) + if err != nil { + return nil, err + } + + var labels []string + + for _, issue := range issues { + for uri := range issue.Labels.Inner() { + if !slices.Contains(labels, uri) { + labels = append(labels, uri) + } + } + } + + labelDefs, err := GetLabelDefinitions( + e, + orm.FilterIn("at_uri", labels), + orm.FilterContains("scope", tangled.RepoIssueNSID), + ) + if err != nil { + return nil, fmt.Errorf("fetching labels for issues: %w", err) + } + + defs := make(map[string]*models.LabelDefinition) + for _, l := range labelDefs { + defs[l.AtUri().String()] = &l + } + + var events []models.TimelineEvent + for _, issue := range issues { + events = append(events, models.TimelineEvent{ + Issue: &issue, + EventAt: issue.Created, + LabelDefs: defs, + }) + } + + return events, nil +} diff --git a/appview/models/timeline.go b/appview/models/timeline.go index 11f43eaf..8fdbb4a9 100644 --- a/appview/models/timeline.go +++ b/appview/models/timeline.go @@ -6,6 +6,7 @@ type TimelineEvent struct { *Repo *Follow *RepoStar + *Issue EventAt time.Time @@ -20,6 +21,9 @@ type TimelineEvent struct { // optional: populate only if event is Repo IsStarred bool StarCount int64 + + // optional: populate only if event if Issue + LabelDefs map[string]*LabelDefinition } // TimelineGroup is a primary TimelineEvent plus zero or more peer events diff --git a/appview/pages/templates/timeline/fragments/timeline.html b/appview/pages/templates/timeline/fragments/timeline.html index c89e9480..e9ac61f0 100644 --- a/appview/pages/templates/timeline/fragments/timeline.html +++ b/appview/pages/templates/timeline/fragments/timeline.html @@ -19,6 +19,8 @@ {{ template "timeline/fragments/starEvent" (list $ $g) }} {{ else if $primary.Follow }} {{ template "timeline/fragments/followEvent" (list $ $g) }} + {{ else if $primary.Issue }} + {{ template "timeline/fragments/issueEvent" (list $ $g) }} {{ end }} @@ -85,6 +87,59 @@ {{ end }} {{ end }} +{{ define "timeline/fragments/issueEvent" }} + {{ $root := index . 0 }} + {{ $group := index . 1 }} + {{ $event := $group.Primary }} + {{ $issue := $event.Issue }} + {{ with $issue }} + {{ $repo := .Repo}} + {{ $userHandle := resolve .Did }} + {{ $repoOwnerHandle := resolve $repo.Did }} +
+ {{ template "user/fragments/picHandleLink" .Did }} + created an issue in + + {{ $repoOwnerHandle | truncateAt30 }}/{{ $repo.Name }} + + {{ template "repo/fragments/time" .Created }} +
+
+ +
+ {{ $bgColor := "bg-gray-800 dark:bg-gray-700" }} + {{ $icon := "ban" }} + {{ $state := "closed" }} + {{ if .Open }} + {{ $bgColor = "bg-green-600 dark:bg-green-700" }} + {{ $icon = "circle-dot" }} + {{ $state = "open" }} + {{ end }} + + + {{ i $icon "w-3 h-3 mr-1.5 text-white dark:text-white" }} + {{ $state }} + + + + {{ template "repo/fragments/time" .Created }} + + {{ $state := .Labels }} + {{ range $k, $d := $event.LabelDefs }} + {{ range $v, $s := $state.GetValSet $d.AtUri.String }} + {{ template "labels/fragments/label" (dict "def" $d "val" $v "withPrefix" true) }} + {{ end }} + {{ end }} +
+
+ {{ end }} +{{ end }} + {{ define "timeline/fragments/followEvent" }} {{ $root := index . 0 }} {{ $group := index . 1 }} -- 2.51.2