From 2e7b9c4b16f7698157e92a7abba1d5f545cb67a3 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Wed, 24 Sep 2025 16:54:43 +0100 Subject: [PATCH] appview/labels: add "subscribe all" button for default labels Change-Id: lxxtrqtnnoxyolnpmupqyrnomlusvowq quickly subscribe to all default labels. Signed-off-by: oppiliappan --- appview/ingester.go | 80 +++++++++++++++++++ appview/labels/labels.go | 3 - appview/pages/pages.go | 19 ++--- .../templates/repo/settings/general.html | 42 ++++++++-- appview/repo/repo.go | 79 +++++++++++++----- appview/state/state.go | 1 + 6 files changed, 187 insertions(+), 37 deletions(-) diff --git a/appview/ingester.go b/appview/ingester.go index 64d51a34..dbde661c 100644 --- a/appview/ingester.go +++ b/appview/ingester.go @@ -5,6 +5,8 @@ import ( "encoding/json" "fmt" "log/slog" + "maps" + "slices" "time" @@ -80,6 +82,8 @@ func (i *Ingester) Ingest() processFunc { 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) } @@ -953,3 +957,79 @@ func (i *Ingester) ingestLabelDefinition(e *jmodels.Event) error { 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 +} diff --git a/appview/labels/labels.go b/appview/labels/labels.go index 309ba1ce..ecd304b4 100644 --- a/appview/labels/labels.go +++ b/appview/labels/labels.go @@ -104,9 +104,6 @@ func (l *Labels) PerformLabelOp(w http.ResponseWriter, r *http.Request) { 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 { diff --git a/appview/pages/pages.go b/appview/pages/pages.go index df37834b..80eee26e 100644 --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -834,15 +834,16 @@ func (p *Pages) RepoSettings(w io.Writer, params RepoSettingsParams) error { } 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 { diff --git a/appview/pages/templates/repo/settings/general.html b/appview/pages/templates/repo/settings/general.html index 2bae37a0..9afae837 100644 --- a/appview/pages/templates/repo/settings/general.html +++ b/appview/pages/templates/repo/settings/general.html @@ -46,12 +46,42 @@ {{ define "defaultLabelSettings" }}
-

Default Labels

-

- 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. -

+
+
+

Default Labels

+

+ 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. +

+

+
+ {{ $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 }} + + {{ end }} + +
+
{{ range .DefaultLabels }}
diff --git a/appview/repo/repo.go b/appview/repo/repo.go index 6390707b..1ff7a7fc 100644 --- a/appview/repo/repo.go +++ b/appview/repo/repo.go @@ -1248,21 +1248,31 @@ func (rp *Repo) SubscribeLabel(w http.ResponseWriter, r *http.Request) { 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) @@ -1286,14 +1296,28 @@ func (rp *Repo) SubscribeLabel(w http.ResponseWriter, r *http.Request) { }, }) - 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) @@ -1311,14 +1335,19 @@ func (rp *Repo) UnsubscribeLabel(w http.ResponseWriter, r *http.Request) { 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 @@ -1328,7 +1357,7 @@ func (rp *Repo) UnsubscribeLabel(w http.ResponseWriter, r *http.Request) { newRepo := f.Repo var updated []string for _, l := range newRepo.Labels { - if l != labelAt { + if !slices.Contains(labelAts, l) { updated = append(updated, l) } } @@ -1359,7 +1388,7 @@ func (rp *Repo) UnsubscribeLabel(w http.ResponseWriter, r *http.Request) { 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) @@ -1927,15 +1956,27 @@ func (rp *Repo) generalSettings(w http.ResponseWriter, r *http.Request) { 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", }) } diff --git a/appview/state/state.go b/appview/state/state.go index 364e1a2e..490de1df 100644 --- a/appview/state/state.go +++ b/appview/state/state.go @@ -103,6 +103,7 @@ func Make(ctx context.Context, config *config.Config) (*State, error) { tangled.RepoIssueNSID, tangled.RepoIssueCommentNSID, tangled.LabelDefinitionNSID, + tangled.LabelOpNSID, }, nil, slog.Default(), -- 2.43.0