appview/labels: add "subscribe all" button for default labels #597

merged
opened by oppi.li targeting master from push-lxxtrqtnnoxy

quickly subscribe to all default labels.

Signed-off-by: oppiliappan me@oppi.li

Changed files
+187 -37
appview
labels
pages
templates
repo
settings
repo
state
+80
appview/ingester.go
···
"encoding/json"
"fmt"
"log/slog"
+
"maps"
+
"slices"
"time"
···
err = i.ingestIssueComment(e)
case tangled.LabelDefinitionNSID:
err = i.ingestLabelDefinition(e)
+
case tangled.LabelOpNSID:
+
err = i.ingestLabelOp(e)
}
l = i.Logger.With("nsid", e.Commit.Collection)
}
···
return nil
}
+
+
func (i *Ingester) ingestLabelOp(e *jmodels.Event) error {
+
did := e.Did
+
rkey := e.Commit.RKey
+
+
var err error
+
+
l := i.Logger.With("handler", "ingestLabelOp", "nsid", e.Commit.Collection, "did", did, "rkey", rkey)
+
l.Info("ingesting record")
+
+
ddb, ok := i.Db.Execer.(*db.DB)
+
if !ok {
+
return fmt.Errorf("failed to index label op, invalid db cast")
+
}
+
+
switch e.Commit.Operation {
+
case jmodels.CommitOperationCreate:
+
raw := json.RawMessage(e.Commit.Record)
+
record := tangled.LabelOp{}
+
err = json.Unmarshal(raw, &record)
+
if err != nil {
+
return fmt.Errorf("invalid record: %w", err)
+
}
+
+
subject := syntax.ATURI(record.Subject)
+
collection := subject.Collection()
+
+
var repo *models.Repo
+
switch collection {
+
case tangled.RepoIssueNSID:
+
i, err := db.GetIssues(ddb, db.FilterEq("at_uri", subject))
+
if err != nil || len(i) != 1 {
+
return fmt.Errorf("failed to find subject: %w || subject count %d", err, len(i))
+
}
+
repo = i[0].Repo
+
default:
+
return fmt.Errorf("unsupport label subject: %s", collection)
+
}
+
+
actx, err := db.NewLabelApplicationCtx(ddb, db.FilterIn("at_uri", repo.Labels))
+
if err != nil {
+
return fmt.Errorf("failed to build label application ctx: %w", err)
+
}
+
+
ops := models.LabelOpsFromRecord(did, rkey, record)
+
+
for _, o := range ops {
+
def, ok := actx.Defs[o.OperandKey]
+
if !ok {
+
return fmt.Errorf("failed to find label def for key: %s, expected: %q", o.OperandKey, slices.Collect(maps.Keys(actx.Defs)))
+
}
+
if err := i.Validator.ValidateLabelOp(def, &o); err != nil {
+
return fmt.Errorf("failed to validate labelop: %w", err)
+
}
+
}
+
+
tx, err := ddb.Begin()
+
if err != nil {
+
return err
+
}
+
defer tx.Rollback()
+
+
for _, o := range ops {
+
_, err = db.AddLabelOp(tx, &o)
+
if err != nil {
+
return fmt.Errorf("failed to add labelop: %w", err)
+
}
+
}
+
+
if err = tx.Commit(); err != nil {
+
return err
+
}
+
}
+
+
return nil
+
}
-3
appview/labels/labels.go
···
return
}
-
l.logger.Info("actx", "labels", labelAts)
-
l.logger.Info("actx", "defs", actx.Defs)
-
// calculate the start state by applying already known labels
existingOps, err := db.GetLabelOps(l.db, db.FilterEq("subject", subjectUri))
if err != nil {
+10 -9
appview/pages/pages.go
···
}
type RepoGeneralSettingsParams struct {
-
LoggedInUser *oauth.User
-
RepoInfo repoinfo.RepoInfo
-
Labels []models.LabelDefinition
-
DefaultLabels []models.LabelDefinition
-
SubscribedLabels map[string]struct{}
-
Active string
-
Tabs []map[string]any
-
Tab string
-
Branches []types.Branch
+
LoggedInUser *oauth.User
+
RepoInfo repoinfo.RepoInfo
+
Labels []models.LabelDefinition
+
DefaultLabels []models.LabelDefinition
+
SubscribedLabels map[string]struct{}
+
ShouldSubscribeAll bool
+
Active string
+
Tabs []map[string]any
+
Tab string
+
Branches []types.Branch
}
func (p *Pages) RepoGeneralSettings(w io.Writer, params RepoGeneralSettingsParams) error {
+36 -6
appview/pages/templates/repo/settings/general.html
···
{{ define "defaultLabelSettings" }}
<div class="flex flex-col gap-2">
-
<h2 class="text-sm pb-2 uppercase font-bold">Default Labels</h2>
-
<p class="text-gray-500 dark:text-gray-400">
-
Manage your issues and pulls by creating labels to categorize them. Only
-
repository owners may configure labels. You may choose to subscribe to
-
default labels, or create entirely custom labels.
-
</p>
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 items-center">
+
<div class="col-span-1 md:col-span-2">
+
<h2 class="text-sm pb-2 uppercase font-bold">Default Labels</h2>
+
<p class="text-gray-500 dark:text-gray-400">
+
Manage your issues and pulls by creating labels to categorize them. Only
+
repository owners may configure labels. You may choose to subscribe to
+
default labels, or create entirely custom labels.
+
<p>
+
</div>
+
<form class="col-span-1 md:col-span-1 md:justify-self-end">
+
{{ $title := "Unubscribe from all labels" }}
+
{{ $icon := "x" }}
+
{{ $text := "unsubscribe all" }}
+
{{ $action := "unsubscribe" }}
+
{{ if $.ShouldSubscribeAll }}
+
{{ $title = "Subscribe to all labels" }}
+
{{ $icon = "check-check" }}
+
{{ $text = "subscribe all" }}
+
{{ $action = "subscribe" }}
+
{{ end }}
+
{{ range .DefaultLabels }}
+
<input type="hidden" name="label" value="{{ .AtUri.String }}">
+
{{ end }}
+
<button
+
type="submit"
+
title="{{$title}}"
+
class="btn flex items-center gap-2 group"
+
hx-swap="none"
+
hx-post="/{{ $.RepoInfo.FullName }}/settings/label/{{$action}}"
+
{{ if not .RepoInfo.Roles.IsOwner }}disabled{{ end }}>
+
{{ i $icon "size-4" }}
+
{{ $text }}
+
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
+
</button>
+
</form>
+
</div>
<div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 w-full">
{{ range .DefaultLabels }}
<div id="label-{{.Id}}" class="flex items-center justify-between p-2 pl-4">
+60 -19
appview/repo/repo.go
···
return
+
if err := r.ParseForm(); err != nil {
+
l.Error("invalid form", "err", err)
+
return
+
}
+
errorId := "default-label-operation"
fail := func(msg string, err error) {
l.Error(msg, "err", err)
rp.pages.Notice(w, errorId, msg)
-
labelAt := r.FormValue("label")
-
_, err = db.GetLabelDefinition(rp.db, db.FilterEq("at_uri", labelAt))
+
labelAts := r.Form["label"]
+
_, err = db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", labelAts))
if err != nil {
fail("Failed to subscribe to label.", err)
return
newRepo := f.Repo
-
newRepo.Labels = append(newRepo.Labels, labelAt)
+
newRepo.Labels = append(newRepo.Labels, labelAts...)
+
+
// dedup
+
slices.Sort(newRepo.Labels)
+
newRepo.Labels = slices.Compact(newRepo.Labels)
+
repoRecord := newRepo.AsRecord()
client, err := rp.oauth.AuthorizedClient(r)
···
},
})
-
err = db.SubscribeLabel(rp.db, &models.RepoLabel{
-
RepoAt: f.RepoAt(),
-
LabelAt: syntax.ATURI(labelAt),
-
})
+
tx, err := rp.db.Begin()
if err != nil {
fail("Failed to subscribe to label.", err)
return
+
defer tx.Rollback()
+
+
for _, l := range labelAts {
+
err = db.SubscribeLabel(tx, &models.RepoLabel{
+
RepoAt: f.RepoAt(),
+
LabelAt: syntax.ATURI(l),
+
})
+
if err != nil {
+
fail("Failed to subscribe to label.", err)
+
return
+
}
+
}
+
+
if err := tx.Commit(); err != nil {
+
fail("Failed to subscribe to label.", err)
+
return
+
}
// everything succeeded
rp.pages.HxRefresh(w)
···
return
+
if err := r.ParseForm(); err != nil {
+
l.Error("invalid form", "err", err)
+
return
+
}
+
errorId := "default-label-operation"
fail := func(msg string, err error) {
l.Error(msg, "err", err)
rp.pages.Notice(w, errorId, msg)
-
labelAt := r.FormValue("label")
-
_, err = db.GetLabelDefinition(rp.db, db.FilterEq("at_uri", labelAt))
+
labelAts := r.Form["label"]
+
_, err = db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", labelAts))
if err != nil {
fail("Failed to unsubscribe to label.", err)
return
···
newRepo := f.Repo
var updated []string
for _, l := range newRepo.Labels {
-
if l != labelAt {
+
if !slices.Contains(labelAts, l) {
updated = append(updated, l)
···
err = db.UnsubscribeLabel(
rp.db,
db.FilterEq("repo_at", f.RepoAt()),
-
db.FilterEq("label_at", labelAt),
+
db.FilterIn("label_at", labelAts),
if err != nil {
fail("Failed to unsubscribe label.", err)
···
subscribedLabels[l] = struct{}{}
+
// if there is atleast 1 unsubbed default label, show the "subscribe all" button,
+
// if all default labels are subbed, show the "unsubscribe all" button
+
shouldSubscribeAll := false
+
for _, dl := range defaultLabels {
+
if _, ok := subscribedLabels[dl.AtUri().String()]; !ok {
+
// one of the default labels is not subscribed to
+
shouldSubscribeAll = true
+
break
+
}
+
}
+
rp.pages.RepoGeneralSettings(w, pages.RepoGeneralSettingsParams{
-
LoggedInUser: user,
-
RepoInfo: f.RepoInfo(user),
-
Branches: result.Branches,
-
Labels: labels,
-
DefaultLabels: defaultLabels,
-
SubscribedLabels: subscribedLabels,
-
Tabs: settingsTabs,
-
Tab: "general",
+
LoggedInUser: user,
+
RepoInfo: f.RepoInfo(user),
+
Branches: result.Branches,
+
Labels: labels,
+
DefaultLabels: defaultLabels,
+
SubscribedLabels: subscribedLabels,
+
ShouldSubscribeAll: shouldSubscribeAll,
+
Tabs: settingsTabs,
+
Tab: "general",
})
+1
appview/state/state.go
···
tangled.RepoIssueNSID,
tangled.RepoIssueCommentNSID,
tangled.LabelDefinitionNSID,
+
tangled.LabelOpNSID,
},
nil,
slog.Default(),