Skip to content

Commit

Permalink
feature: add Custom columns labels support (derailed#605)
Browse files Browse the repository at this point in the history
  • Loading branch information
crossRT committed Nov 10, 2024
1 parent 99d47ab commit 9c1d72a
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 22 deletions.
33 changes: 31 additions & 2 deletions internal/model1/fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,44 @@

package model1

import "reflect"
import (
"fmt"
"reflect"
"regexp"
"strings"
)

// Fields represents a collection of row fields.
type Fields []string

// Customize returns a subset of fields.
func (f Fields) Customize(cols []int, out Fields) {
func (f Fields) Customize(cols []int, out Fields, extractionInfoBag ExtractionInfoBag) {
for i, c := range cols {
if c < 0 {

// If current index can retrieve an extractionInfo from extractionInfoBag,
// meaning this column has to retrieve the actual value from other field.
// For example: `LABELS[kubernetes.io/hostname]` needs to extract the value from column `LABELS`
if extractionInfo, ok := extractionInfoBag[i]; ok {
idxInFields := extractionInfo.IdxInFields
key := extractionInfo.Key

// Escape dots from the key
// For example: `kubernetes.io/hostname` needs to be escaped to `kubernetes\.io/hostname`
escapedKey := strings.ReplaceAll(key, ".", "\\.")

// Extract the value by using regex
pattern := fmt.Sprintf(`%s=([^ ]+)`, escapedKey)
regex := regexp.MustCompile(pattern)

// Find the value in the field that store original values
matches := regex.FindStringSubmatch(f[idxInFields])
if len(matches) > 1 {
out[i] = matches[1]
continue
}
}

out[i] = NAValue
continue
}
Expand Down
43 changes: 40 additions & 3 deletions internal/model1/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,23 @@ package model1

import (
"reflect"
"regexp"

"github.com/rs/zerolog/log"
)

const ageCol = "AGE"

// ExtractionInfo stores data for a field to extract value from another field
type ExtractionInfo struct {
IdxInFields int
HeaderName string
Key string
}

// ExtractionInfoBag store ExtractionInfo by using the index of the column
type ExtractionInfoBag map[int]ExtractionInfo

// HeaderColumn represent a table header.
type HeaderColumn struct {
Name string
Expand Down Expand Up @@ -65,18 +76,44 @@ func (h Header) Labelize(cols []int, labelCol int, rr *RowEvents) Header {
}

// MapIndices returns a collection of mapped column indices based of the requested columns.
func (h Header) MapIndices(cols []string, wide bool) []int {
// And the extraction information used to extract a value for a field from another field.
func (h Header) MapIndices(cols []string, wide bool) ([]int, ExtractionInfoBag) {
ii := make([]int, 0, len(cols))
cc := make(map[int]struct{}, len(cols))

eib := make(ExtractionInfoBag)
regex, _ := regexp.Compile(`^(?<HEADER_NAME>.*)\[(?<KEY>.*)\]$`)

for _, col := range cols {
idx, ok := h.IndexOf(col, true)
if !ok {
log.Warn().Msgf("Column %q not found on resource", col)
}
ii, cc[idx] = append(ii, idx), struct{}{}

// If the column already found OR the it doesn't match the regex
if ok || !regex.MatchString(col) {
continue
}

// Check if the column matches the pattern
// For example: `LABELS[beta.kubernetes.io/os]` will match
matches := regex.FindStringSubmatch(col)
headerName := matches[1]
key := matches[2]

// now only support LABELS
if headerName != "LABELS" {
continue
}

currentIdx := len(ii) - 1
idxInFields, _ := h.IndexOf(headerName, true)
eib[currentIdx] = ExtractionInfo{idxInFields, headerName, key}
}

if !wide {
return ii
return ii, eib
}

for i := range h {
Expand All @@ -85,7 +122,7 @@ func (h Header) MapIndices(cols []string, wide bool) []int {
}
ii = append(ii, i)
}
return ii
return ii, eib
}

// Customize builds a header from custom col definitions.
Expand Down
22 changes: 19 additions & 3 deletions internal/model1/header_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,49 @@ func TestHeaderMapIndices(t *testing.T) {
cols []string
wide bool
e []int
eib model1.ExtractionInfoBag
}{
"all": {
h1: makeHeader(),
cols: []string{"A", "B", "C"},
e: []int{0, 1, 2},
eib: model1.ExtractionInfoBag{},
},
"reverse": {
h1: makeHeader(),
cols: []string{"C", "B", "A"},
e: []int{2, 1, 0},
eib: model1.ExtractionInfoBag{},
},
"missing": {
h1: makeHeader(),
cols: []string{"Duh", "B", "A"},
e: []int{-1, 1, 0},
eib: model1.ExtractionInfoBag{},
},
"skip": {
h1: makeHeader(),
cols: []string{"C", "A"},
e: []int{2, 0},
eib: model1.ExtractionInfoBag{},
},
"labels": {
h1: makeHeader(),
cols: []string{"A", "LABELS[kubernetes.io/hostname]", "B", "LABELS[topology.kubernetes.io/region]"},
e: []int{0, -1, 1, -1},
eib: model1.ExtractionInfoBag{
1: model1.ExtractionInfo{3, "LABELS", "kubernetes.io/hostname"},
3: model1.ExtractionInfo{3, "LABELS", "topology.kubernetes.io/region"},
},
},
}

for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
ii := u.h1.MapIndices(u.cols, u.wide)
ii, eib := u.h1.MapIndices(u.cols, u.wide)
assert.Equal(t, u.e, ii)
assert.Equal(t, u.eib, eib)
})
}
}
Expand Down Expand Up @@ -264,11 +279,11 @@ func TestHeaderColumns(t *testing.T) {
},
"regular": {
h: makeHeader(),
e: []string{"A", "C"},
e: []string{"A", "C", "LABELS"},
},
"wide": {
h: makeHeader(),
e: []string{"A", "B", "C"},
e: []string{"A", "B", "C", "LABELS"},
wide: true,
},
}
Expand Down Expand Up @@ -314,5 +329,6 @@ func makeHeader() model1.Header {
model1.HeaderColumn{Name: "A"},
model1.HeaderColumn{Name: "B", Wide: true},
model1.HeaderColumn{Name: "C"},
model1.HeaderColumn{Name: "LABELS"},
}
}
4 changes: 2 additions & 2 deletions internal/model1/row.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ func (r Row) Labelize(cols []int, labelCol int, labels []string) Row {
}

// Customize returns a row subset based on given col indices.
func (r Row) Customize(cols []int) Row {
func (r Row) Customize(cols []int, extractionInfoBag ExtractionInfoBag) Row {
out := NewRow(len(cols))
r.Fields.Customize(cols, out.Fields)
r.Fields.Customize(cols, out.Fields, extractionInfoBag)
out.ID = r.ID

return out
Expand Down
8 changes: 4 additions & 4 deletions internal/model1/row_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (r RowEvent) Clone() RowEvent {
}

// Customize returns a new subset based on the given column indices.
func (r RowEvent) Customize(cols []int) RowEvent {
func (r RowEvent) Customize(cols []int, extractionInfoBag ExtractionInfoBag) RowEvent {
delta := r.Deltas
if !r.Deltas.IsBlank() {
delta = make(DeltaRow, len(cols))
Expand All @@ -57,7 +57,7 @@ func (r RowEvent) Customize(cols []int) RowEvent {
return RowEvent{
Kind: r.Kind,
Deltas: delta,
Row: r.Row.Customize(cols),
Row: r.Row.Customize(cols, extractionInfoBag),
}
}

Expand Down Expand Up @@ -158,10 +158,10 @@ func (r *RowEvents) Labelize(cols []int, labelCol int, labels []string) *RowEven
}

// Customize returns custom row events based on columns layout.
func (r *RowEvents) Customize(cols []int) *RowEvents {
func (r *RowEvents) Customize(cols []int, extractionInfoBag ExtractionInfoBag) *RowEvents {
ee := make([]RowEvent, 0, len(cols))
for _, re := range r.events {
ee = append(ee, re.Customize(cols))
ee = append(ee, re.Customize(cols, extractionInfoBag))
}

return NewRowEventsWithEvts(ee...)
Expand Down
6 changes: 4 additions & 2 deletions internal/model1/row_event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ func TestRowEventCustomize(t *testing.T) {
uu := map[string]struct {
re1, e model1.RowEvent
cols []int
eib model1.ExtractionInfoBag
}{
"empty": {
re1: model1.RowEvent{
Expand Down Expand Up @@ -101,7 +102,7 @@ func TestRowEventCustomize(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, u.re1.Customize(u.cols))
assert.Equal(t, u.e, u.re1.Customize(u.cols, u.eib))
})
}
}
Expand Down Expand Up @@ -297,6 +298,7 @@ func TestRowEventsCustomize(t *testing.T) {
uu := map[string]struct {
re, e *model1.RowEvents
cols []int
eib model1.ExtractionInfoBag
}{
"same": {
re: model1.NewRowEventsWithEvts(
Expand Down Expand Up @@ -355,7 +357,7 @@ func TestRowEventsCustomize(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, u.re.Customize(u.cols))
assert.Equal(t, u.e, u.re.Customize(u.cols, u.eib))
})
}
}
Expand Down
21 changes: 17 additions & 4 deletions internal/model1/row_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ import (
func BenchmarkRowCustomize(b *testing.B) {
row := model1.Row{ID: "fred", Fields: model1.Fields{"f1", "f2", "f3"}}
cols := []int{0, 1, 2}
eib := model1.ExtractionInfoBag{}
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
_ = row.Customize(cols)
_ = row.Customize(cols, eib)
}
}

Expand All @@ -28,6 +29,7 @@ func TestFieldCustomize(t *testing.T) {
fields model1.Fields
cols []int
e model1.Fields
eib model1.ExtractionInfoBag
}{
"empty": {
fields: model1.Fields{},
Expand All @@ -49,13 +51,22 @@ func TestFieldCustomize(t *testing.T) {
cols: []int{10, 0},
e: model1.Fields{"", "f1"},
},
"labels": {
fields: model1.Fields{"f1", "f2", "f3", "kubernetes.io/os=linux kubernetes.io/hostname=node1"},
cols: []int{0, -1, -1},
e: model1.Fields{"f1", "node1", "linux"},
eib: model1.ExtractionInfoBag{
1: model1.ExtractionInfo{3, "LABELS", "kubernetes.io/hostname"},
2: model1.ExtractionInfo{3, "LABELS", "kubernetes.io/os"},
},
},
}

for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
ff := make(model1.Fields, len(u.cols))
u.fields.Customize(u.cols, ff)
u.fields.Customize(u.cols, ff, u.eib)
assert.Equal(t, u.e, ff)
})
}
Expand All @@ -74,6 +85,7 @@ func TestRowlabelize(t *testing.T) {
row model1.Row
cols []int
e model1.Row
eib model1.ExtractionInfoBag
}{
"empty": {
row: model1.Row{},
Expand All @@ -95,7 +107,7 @@ func TestRowlabelize(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
row := u.row.Customize(u.cols)
row := u.row.Customize(u.cols, u.eib)
assert.Equal(t, u.e, row)
})
}
Expand All @@ -106,6 +118,7 @@ func TestRowCustomize(t *testing.T) {
row model1.Row
cols []int
e model1.Row
eib model1.ExtractionInfoBag
}{
"empty": {
row: model1.Row{},
Expand All @@ -127,7 +140,7 @@ func TestRowCustomize(t *testing.T) {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
row := u.row.Customize(u.cols)
row := u.row.Customize(u.cols, u.eib)
assert.Equal(t, u.e, row)
})
}
Expand Down
4 changes: 2 additions & 2 deletions internal/model1/table_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,8 +338,8 @@ func (t *TableData) Customize(vs *config.ViewSetting, sc SortColumn, manual, wid
namespace: t.namespace,
header: t.header.Customize(cols, wide),
}
ids := t.header.MapIndices(cols, wide)
cdata.rowEvents = t.rowEvents.Customize(ids)
ids, extractionInfoBag := t.header.MapIndices(cols, wide)
cdata.rowEvents = t.rowEvents.Customize(ids, extractionInfoBag)
if manual || vs == nil {
return &cdata, sc
}
Expand Down

0 comments on commit 9c1d72a

Please sign in to comment.