+80
appview/ingester.go
+80
appview/ingester.go
···
5
5
"encoding/json"
6
6
"fmt"
7
7
"log/slog"
8
+
"maps"
9
+
"slices"
8
10
9
11
"time"
10
12
···
80
82
err = i.ingestIssueComment(e)
81
83
case tangled.LabelDefinitionNSID:
82
84
err = i.ingestLabelDefinition(e)
85
+
case tangled.LabelOpNSID:
86
+
err = i.ingestLabelOp(e)
83
87
}
84
88
l = i.Logger.With("nsid", e.Commit.Collection)
85
89
}
···
953
957
954
958
return nil
955
959
}
960
+
961
+
func (i *Ingester) ingestLabelOp(e *jmodels.Event) error {
962
+
did := e.Did
963
+
rkey := e.Commit.RKey
964
+
965
+
var err error
966
+
967
+
l := i.Logger.With("handler", "ingestLabelOp", "nsid", e.Commit.Collection, "did", did, "rkey", rkey)
968
+
l.Info("ingesting record")
969
+
970
+
ddb, ok := i.Db.Execer.(*db.DB)
971
+
if !ok {
972
+
return fmt.Errorf("failed to index label op, invalid db cast")
973
+
}
974
+
975
+
switch e.Commit.Operation {
976
+
case jmodels.CommitOperationCreate:
977
+
raw := json.RawMessage(e.Commit.Record)
978
+
record := tangled.LabelOp{}
979
+
err = json.Unmarshal(raw, &record)
980
+
if err != nil {
981
+
return fmt.Errorf("invalid record: %w", err)
982
+
}
983
+
984
+
subject := syntax.ATURI(record.Subject)
985
+
collection := subject.Collection()
986
+
987
+
var repo *models.Repo
988
+
switch collection {
989
+
case tangled.RepoIssueNSID:
990
+
i, err := db.GetIssues(ddb, db.FilterEq("at_uri", subject))
991
+
if err != nil || len(i) != 1 {
992
+
return fmt.Errorf("failed to find subject: %w || subject count %d", err, len(i))
993
+
}
994
+
repo = i[0].Repo
995
+
default:
996
+
return fmt.Errorf("unsupport label subject: %s", collection)
997
+
}
998
+
999
+
actx, err := db.NewLabelApplicationCtx(ddb, db.FilterIn("at_uri", repo.Labels))
1000
+
if err != nil {
1001
+
return fmt.Errorf("failed to build label application ctx: %w", err)
1002
+
}
1003
+
1004
+
ops := models.LabelOpsFromRecord(did, rkey, record)
1005
+
1006
+
for _, o := range ops {
1007
+
def, ok := actx.Defs[o.OperandKey]
1008
+
if !ok {
1009
+
return fmt.Errorf("failed to find label def for key: %s, expected: %q", o.OperandKey, slices.Collect(maps.Keys(actx.Defs)))
1010
+
}
1011
+
if err := i.Validator.ValidateLabelOp(def, &o); err != nil {
1012
+
return fmt.Errorf("failed to validate labelop: %w", err)
1013
+
}
1014
+
}
1015
+
1016
+
tx, err := ddb.Begin()
1017
+
if err != nil {
1018
+
return err
1019
+
}
1020
+
defer tx.Rollback()
1021
+
1022
+
for _, o := range ops {
1023
+
_, err = db.AddLabelOp(tx, &o)
1024
+
if err != nil {
1025
+
return fmt.Errorf("failed to add labelop: %w", err)
1026
+
}
1027
+
}
1028
+
1029
+
if err = tx.Commit(); err != nil {
1030
+
return err
1031
+
}
1032
+
}
1033
+
1034
+
return nil
1035
+
}
-3
appview/labels/labels.go
-3
appview/labels/labels.go
···
104
104
return
105
105
}
106
106
107
-
l.logger.Info("actx", "labels", labelAts)
108
-
l.logger.Info("actx", "defs", actx.Defs)
109
-
110
107
// calculate the start state by applying already known labels
111
108
existingOps, err := db.GetLabelOps(l.db, db.FilterEq("subject", subjectUri))
112
109
if err != nil {
+10
-9
appview/pages/pages.go
+10
-9
appview/pages/pages.go
···
834
834
}
835
835
836
836
type RepoGeneralSettingsParams struct {
837
-
LoggedInUser *oauth.User
838
-
RepoInfo repoinfo.RepoInfo
839
-
Labels []models.LabelDefinition
840
-
DefaultLabels []models.LabelDefinition
841
-
SubscribedLabels map[string]struct{}
842
-
Active string
843
-
Tabs []map[string]any
844
-
Tab string
845
-
Branches []types.Branch
837
+
LoggedInUser *oauth.User
838
+
RepoInfo repoinfo.RepoInfo
839
+
Labels []models.LabelDefinition
840
+
DefaultLabels []models.LabelDefinition
841
+
SubscribedLabels map[string]struct{}
842
+
ShouldSubscribeAll bool
843
+
Active string
844
+
Tabs []map[string]any
845
+
Tab string
846
+
Branches []types.Branch
846
847
}
847
848
848
849
func (p *Pages) RepoGeneralSettings(w io.Writer, params RepoGeneralSettingsParams) error {
+36
-6
appview/pages/templates/repo/settings/general.html
+36
-6
appview/pages/templates/repo/settings/general.html
···
46
46
47
47
{{ define "defaultLabelSettings" }}
48
48
<div class="flex flex-col gap-2">
49
-
<h2 class="text-sm pb-2 uppercase font-bold">Default Labels</h2>
50
-
<p class="text-gray-500 dark:text-gray-400">
51
-
Manage your issues and pulls by creating labels to categorize them. Only
52
-
repository owners may configure labels. You may choose to subscribe to
53
-
default labels, or create entirely custom labels.
54
-
</p>
49
+
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 items-center">
50
+
<div class="col-span-1 md:col-span-2">
51
+
<h2 class="text-sm pb-2 uppercase font-bold">Default Labels</h2>
52
+
<p class="text-gray-500 dark:text-gray-400">
53
+
Manage your issues and pulls by creating labels to categorize them. Only
54
+
repository owners may configure labels. You may choose to subscribe to
55
+
default labels, or create entirely custom labels.
56
+
<p>
57
+
</div>
58
+
<form class="col-span-1 md:col-span-1 md:justify-self-end">
59
+
{{ $title := "Unubscribe from all labels" }}
60
+
{{ $icon := "x" }}
61
+
{{ $text := "unsubscribe all" }}
62
+
{{ $action := "unsubscribe" }}
63
+
{{ if $.ShouldSubscribeAll }}
64
+
{{ $title = "Subscribe to all labels" }}
65
+
{{ $icon = "check-check" }}
66
+
{{ $text = "subscribe all" }}
67
+
{{ $action = "subscribe" }}
68
+
{{ end }}
69
+
{{ range .DefaultLabels }}
70
+
<input type="hidden" name="label" value="{{ .AtUri.String }}">
71
+
{{ end }}
72
+
<button
73
+
type="submit"
74
+
title="{{$title}}"
75
+
class="btn flex items-center gap-2 group"
76
+
hx-swap="none"
77
+
hx-post="/{{ $.RepoInfo.FullName }}/settings/label/{{$action}}"
78
+
{{ if not .RepoInfo.Roles.IsOwner }}disabled{{ end }}>
79
+
{{ i $icon "size-4" }}
80
+
{{ $text }}
81
+
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
82
+
</button>
83
+
</form>
84
+
</div>
55
85
<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">
56
86
{{ range .DefaultLabels }}
57
87
<div id="label-{{.Id}}" class="flex items-center justify-between p-2 pl-4">
+60
-19
appview/repo/repo.go
+60
-19
appview/repo/repo.go
···
1248
1248
return
1249
1249
}
1250
1250
1251
+
if err := r.ParseForm(); err != nil {
1252
+
l.Error("invalid form", "err", err)
1253
+
return
1254
+
}
1255
+
1251
1256
errorId := "default-label-operation"
1252
1257
fail := func(msg string, err error) {
1253
1258
l.Error(msg, "err", err)
1254
1259
rp.pages.Notice(w, errorId, msg)
1255
1260
}
1256
1261
1257
-
labelAt := r.FormValue("label")
1258
-
_, err = db.GetLabelDefinition(rp.db, db.FilterEq("at_uri", labelAt))
1262
+
labelAts := r.Form["label"]
1263
+
_, err = db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", labelAts))
1259
1264
if err != nil {
1260
1265
fail("Failed to subscribe to label.", err)
1261
1266
return
1262
1267
}
1263
1268
1264
1269
newRepo := f.Repo
1265
-
newRepo.Labels = append(newRepo.Labels, labelAt)
1270
+
newRepo.Labels = append(newRepo.Labels, labelAts...)
1271
+
1272
+
// dedup
1273
+
slices.Sort(newRepo.Labels)
1274
+
newRepo.Labels = slices.Compact(newRepo.Labels)
1275
+
1266
1276
repoRecord := newRepo.AsRecord()
1267
1277
1268
1278
client, err := rp.oauth.AuthorizedClient(r)
···
1286
1296
},
1287
1297
})
1288
1298
1289
-
err = db.SubscribeLabel(rp.db, &models.RepoLabel{
1290
-
RepoAt: f.RepoAt(),
1291
-
LabelAt: syntax.ATURI(labelAt),
1292
-
})
1299
+
tx, err := rp.db.Begin()
1293
1300
if err != nil {
1294
1301
fail("Failed to subscribe to label.", err)
1295
1302
return
1296
1303
}
1304
+
defer tx.Rollback()
1305
+
1306
+
for _, l := range labelAts {
1307
+
err = db.SubscribeLabel(tx, &models.RepoLabel{
1308
+
RepoAt: f.RepoAt(),
1309
+
LabelAt: syntax.ATURI(l),
1310
+
})
1311
+
if err != nil {
1312
+
fail("Failed to subscribe to label.", err)
1313
+
return
1314
+
}
1315
+
}
1316
+
1317
+
if err := tx.Commit(); err != nil {
1318
+
fail("Failed to subscribe to label.", err)
1319
+
return
1320
+
}
1297
1321
1298
1322
// everything succeeded
1299
1323
rp.pages.HxRefresh(w)
···
1311
1335
return
1312
1336
}
1313
1337
1338
+
if err := r.ParseForm(); err != nil {
1339
+
l.Error("invalid form", "err", err)
1340
+
return
1341
+
}
1342
+
1314
1343
errorId := "default-label-operation"
1315
1344
fail := func(msg string, err error) {
1316
1345
l.Error(msg, "err", err)
1317
1346
rp.pages.Notice(w, errorId, msg)
1318
1347
}
1319
1348
1320
-
labelAt := r.FormValue("label")
1321
-
_, err = db.GetLabelDefinition(rp.db, db.FilterEq("at_uri", labelAt))
1349
+
labelAts := r.Form["label"]
1350
+
_, err = db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", labelAts))
1322
1351
if err != nil {
1323
1352
fail("Failed to unsubscribe to label.", err)
1324
1353
return
···
1328
1357
newRepo := f.Repo
1329
1358
var updated []string
1330
1359
for _, l := range newRepo.Labels {
1331
-
if l != labelAt {
1360
+
if !slices.Contains(labelAts, l) {
1332
1361
updated = append(updated, l)
1333
1362
}
1334
1363
}
···
1359
1388
err = db.UnsubscribeLabel(
1360
1389
rp.db,
1361
1390
db.FilterEq("repo_at", f.RepoAt()),
1362
-
db.FilterEq("label_at", labelAt),
1391
+
db.FilterIn("label_at", labelAts),
1363
1392
)
1364
1393
if err != nil {
1365
1394
fail("Failed to unsubscribe label.", err)
···
1927
1956
subscribedLabels[l] = struct{}{}
1928
1957
}
1929
1958
1959
+
// if there is atleast 1 unsubbed default label, show the "subscribe all" button,
1960
+
// if all default labels are subbed, show the "unsubscribe all" button
1961
+
shouldSubscribeAll := false
1962
+
for _, dl := range defaultLabels {
1963
+
if _, ok := subscribedLabels[dl.AtUri().String()]; !ok {
1964
+
// one of the default labels is not subscribed to
1965
+
shouldSubscribeAll = true
1966
+
break
1967
+
}
1968
+
}
1969
+
1930
1970
rp.pages.RepoGeneralSettings(w, pages.RepoGeneralSettingsParams{
1931
-
LoggedInUser: user,
1932
-
RepoInfo: f.RepoInfo(user),
1933
-
Branches: result.Branches,
1934
-
Labels: labels,
1935
-
DefaultLabels: defaultLabels,
1936
-
SubscribedLabels: subscribedLabels,
1937
-
Tabs: settingsTabs,
1938
-
Tab: "general",
1971
+
LoggedInUser: user,
1972
+
RepoInfo: f.RepoInfo(user),
1973
+
Branches: result.Branches,
1974
+
Labels: labels,
1975
+
DefaultLabels: defaultLabels,
1976
+
SubscribedLabels: subscribedLabels,
1977
+
ShouldSubscribeAll: shouldSubscribeAll,
1978
+
Tabs: settingsTabs,
1979
+
Tab: "general",
1939
1980
})
1940
1981
}
1941
1982