fix(09): WR-07 keep relation column order from the YAML itself, not a fixed indentation

This commit is contained in:
Jakub Zych
2026-10-01 21:10:09 +02:00
parent 889250c8fb
commit b026aec162
3 changed files with 80 additions and 22 deletions

View File

@@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"io/fs" "io/fs"
"reflect" "reflect"
"sort"
"strings" "strings"
"git.golem15.com/golem15/summercms/modules/lagoon" "git.golem15.com/golem15/summercms/modules/lagoon"
@@ -212,7 +211,7 @@ func compileRelations(pluginID string, ctl pact.AdminController, fsys fs.FS, for
return nil, bootErr(pluginID, ctl.ID(), file, err) return nil, bootErr(pluginID, ctl.ID(), file, err)
} }
var root yaml.MapSlice var root yaml.MapSlice
if err := decodeStrict(raw, &root); err != nil { if err := decodeStrictOrdered(raw, &root); err != nil {
return nil, bootErr(pluginID, ctl.ID(), file, err) return nil, bootErr(pluginID, ctl.ID(), file, err)
} }
out := map[string]*CompiledRelation{} out := map[string]*CompiledRelation{}
@@ -247,8 +246,6 @@ func compileRelations(pluginID string, ctl pact.AdminController, fsys fs.FS, for
if err != nil { if err != nil {
return nil, bootErr(pluginID, ctl.ID(), file, fmt.Errorf("relation %s manage: %w", name, err)) return nil, bootErr(pluginID, ctl.ID(), file, fmt.Errorf("relation %s manage: %w", name, err))
} }
orderRelationColumns(raw, &view)
orderRelationColumns(raw, &manage)
schema := &RelationSchema{Name: name, Label: doc.Label, View: view, Manage: manage} schema := &RelationSchema{Name: name, Label: doc.Label, View: view, Manage: manage}
if doc.Messages != nil { if doc.Messages != nil {
schema.messageKeys = *doc.Messages schema.messageKeys = *doc.Messages
@@ -274,24 +271,6 @@ func compileRelations(pluginID string, ctl pact.AdminController, fsys fs.FS, for
return out, nil return out, nil
} }
func orderRelationColumns(raw []byte, panel *RelationPanel) {
if panel == nil || len(panel.List.Columns) < 2 {
return
}
source := string(raw)
sort.SliceStable(panel.List.Columns, func(i, j int) bool {
left := strings.Index(source, "\n "+panel.List.Columns[i].Key+":")
right := strings.Index(source, "\n "+panel.List.Columns[j].Key+":")
if left < 0 {
left = len(source)
}
if right < 0 {
right = len(source)
}
return left < right
})
}
func compileRelationPanel(doc relationPanelDocument, contract RelationContract, view bool) (RelationPanel, error) { func compileRelationPanel(doc relationPanelDocument, contract RelationContract, view bool) (RelationPanel, error) {
cols := make([]RelationColumn, 0, len(doc.List.Columns)) cols := make([]RelationColumn, 0, len(doc.List.Columns))
seen := map[string]struct{}{} seen := map[string]struct{}{}

View File

@@ -115,6 +115,77 @@ func TestRelationDefaultSort(t *testing.T) {
} }
} }
// TestRelationColumnOrderIsIndependentOfIndentation pins WR-07: column order
// is the file's order in each panel, whatever the indentation, and the view and
// manage panels do not share one order.
func TestRelationColumnOrderIsIndependentOfIndentation(t *testing.T) {
const twoSpace = `editors:
label: demo.editors
view:
list:
columns:
username:
label: demo.username
email:
label: demo.email
toolbarButtons: link|unlink
manage:
list:
columns:
username:
label: demo.username
email:
label: demo.email
`
for name, yamlText := range map[string]string{"two-space": twoSpace, "four-space": validRelationYAML} {
t.Run(name, func(t *testing.T) {
ctl := relationTestController{}
fsys := relationTestFS(yamlText)
form, err := CompileForm("acme.demo", ctl, fsys)
if err != nil {
t.Fatal(err)
}
relations, err := compileRelations("acme.demo", ctl, fsys, form)
if err != nil {
t.Fatal(err)
}
schema := relations["editors"].Schema
for panel, cols := range map[string][]RelationColumn{"view": schema.View.List.Columns, "manage": schema.Manage.List.Columns} {
if len(cols) != 2 || cols[0].Key != "username" || cols[1].Key != "email" {
t.Fatalf("%s columns = %+v, want username then email (file order, not alphabetical)", panel, cols)
}
}
})
}
const reversed = `editors:
view:
list:
columns:
username: {label: demo.username}
email: {label: demo.email}
manage:
list:
columns:
email: {label: demo.email}
username: {label: demo.username}
`
ctl := relationTestController{}
fsys := relationTestFS(reversed)
form, err := CompileForm("acme.demo", ctl, fsys)
if err != nil {
t.Fatal(err)
}
relations, err := compileRelations("acme.demo", ctl, fsys, form)
if err != nil {
t.Fatal(err)
}
schema := relations["editors"].Schema
if v, m := schema.View.List.Columns, schema.Manage.List.Columns; v[0].Key != "username" || m[0].Key != "email" {
t.Fatalf("view=%+v manage=%+v, want each panel in its own file order", v, m)
}
}
func TestRelationCandidateExclusions(t *testing.T) { func TestRelationCandidateExclusions(t *testing.T) {
contract := relationTestController{}.AdminRelationContracts()[0] contract := relationTestController{}.AdminRelationContracts()[0]
ids, err := contract.ExcludedRelatedIDs(&relationTestOwner{OwnerID: 41}) ids, err := contract.ExcludedRelatedIDs(&relationTestOwner{OwnerID: 41})

View File

@@ -18,6 +18,14 @@ func decodeStrict(raw []byte, dest any) error {
return nil return nil
} }
// decodeStrictOrdered is decodeStrict with every nested mapping kept as an
// ordered yaml.MapSlice, so a file that is decoded, re-marshalled per entry and
// decoded again keeps its key order at any depth and any indentation.
func decodeStrictOrdered(raw []byte, dest any) error {
dec := yaml.NewDecoder(bytes.NewReader(raw), yaml.DisallowUnknownField(), yaml.UseOrderedMap())
return dec.Decode(dest)
}
func readAsset(fsys fs.FS, name string) ([]byte, error) { func readAsset(fsys fs.FS, name string) ([]byte, error) {
name = path.Clean(name) name = path.Clean(name)
if name == "." || strings.HasPrefix(name, "..") || strings.Contains(name, "..") { if name == "." || strings.HasPrefix(name, "..") || strings.Contains(name, "..") {