Created generic table model on backend

Replaced MetadataRow with generic Row in Table model

Sending through multicolumn tables from the backend
This commit is contained in:
Filip Barl
2017-01-16 12:22:10 +01:00
parent 92b3cbe952
commit 31be525bd2
10 changed files with 209 additions and 109 deletions
@@ -6,7 +6,6 @@ import MatchedText from '../matched-text';
import NodeDetailsControlButton from './node-details-control-button';
import ShowMore from '../show-more';
const Controls = controls => (
<div className="node-details-labels-controls">
{sortBy(controls, 'rank').map(control => <NodeDetailsControlButton
@@ -52,11 +51,11 @@ export default class NodeDetailsLabels extends React.Component {
<div className="node-details-labels-field" key={field.id}>
<div
className="node-details-labels-field-label truncate"
title={field.label} key={field.id}>
{field.label}
title={field.entries.label} key={field.id}>
{field.entries.label}
</div>
<div className="node-details-labels-field-value truncate" title={field.value}>
<MatchedText text={field.value} match={matches.get(field.id)} />
<div className="node-details-labels-field-value truncate" title={field.entries.value}>
<MatchedText text={field.entries.value} match={matches.get(field.id)} />
</div>
</div>
))}
@@ -30,9 +30,17 @@ describe('SearchUtils', () => {
tables: [{
id: 'metric1',
rows: [{
id: 'row1',
label: 'Row 1',
value: 'Row Value 1'
entries: {
id: 'row1',
label: 'Row 1',
value: 'Row Value 1'
}
}, {
entries: {
id: 'row2',
label: 'Row 2',
value: 'Row Value 2'
}
}]
}],
},
+3 -2
View File
@@ -154,9 +154,10 @@ export function searchTopology(nodes, { prefix, query, metric, comp, value }) {
tables.forEach((table) => {
if (table.get('rows')) {
table.get('rows').forEach((field) => {
const entries = field.get('entries');
const keyPath = [nodeId, 'tables', field.get('id')];
nodeMatches = findNodeMatch(nodeMatches, keyPath, field.get('value'),
query, prefix, field.get('label'));
nodeMatches = findNodeMatch(nodeMatches, keyPath, entries.get('value'),
query, prefix, entries.get('label'));
});
}
});
+2 -2
View File
@@ -379,8 +379,8 @@ func (c *container) getBaseNode() report.Node {
}).WithParents(report.EmptySets.
Add(report.ContainerImage, report.MakeStringSet(report.MakeContainerImageNodeID(c.Image()))),
)
result = result.AddPrefixTable(LabelPrefix, c.container.Config.Labels)
result = result.AddPrefixTable(EnvPrefix, c.env())
result = result.AddPrefixLabels(LabelPrefix, c.container.Config.Labels)
result = result.AddPrefixLabels(EnvPrefix, c.env())
return result
}
+1 -1
View File
@@ -258,7 +258,7 @@ func (r *Reporter) containerImageTopology() report.Topology {
}
nodeID := report.MakeContainerImageNodeID(imageID)
node := report.MakeNodeWith(nodeID, latests)
node = node.AddPrefixTable(ImageLabelPrefix, image.Labels)
node = node.AddPrefixLabels(ImageLabelPrefix, image.Labels)
result.AddNode(node)
})
+1 -1
View File
@@ -56,5 +56,5 @@ func (m meta) MetaNode(id string) report.Node {
Name: m.Name(),
Namespace: m.Namespace(),
Created: m.Created(),
}).AddPrefixTable(LabelPrefix, m.Labels())
}).AddPrefixLabels(LabelPrefix, m.Labels())
}
+63 -40
View File
@@ -19,34 +19,35 @@ import (
// Keys for use in Node
const (
WeavePeerName = "weave_peer_name"
WeavePeerNickName = "weave_peer_nick_name"
WeaveDNSHostname = "weave_dns_hostname"
WeaveMACAddress = "weave_mac_address"
WeaveVersion = "weave_version"
WeaveEncryption = "weave_encryption"
WeaveProtocol = "weave_protocol"
WeavePeerDiscovery = "weave_peer_discovery"
WeaveTargetCount = "weave_target_count"
WeaveConnectionCount = "weave_connection_count"
WeavePeerCount = "weave_peer_count"
WeaveTrustedSubnets = "weave_trusted_subnet_count"
WeaveIPAMTableID = "weave_ipam_table"
WeaveIPAMStatus = "weave_ipam_status"
WeaveIPAMRange = "weave_ipam_range"
WeaveIPAMDefaultSubnet = "weave_ipam_default_subnet"
WeaveDNSTableID = "weave_dns_table"
WeaveDNSDomain = "weave_dns_domain"
WeaveDNSUpstream = "weave_dns_upstream"
WeaveDNSTTL = "weave_dns_ttl"
WeaveDNSEntryCount = "weave_dns_entry_count"
WeaveProxyTableID = "weave_proxy_table"
WeaveProxyStatus = "weave_proxy_status"
WeaveProxyAddress = "weave_proxy_address"
WeavePluginTableID = "weave_plugin_table"
WeavePluginStatus = "weave_plugin_status"
WeavePluginDriver = "weave_plugin_driver"
WeaveConnectionsTablePrefix = "weave_connections_table_"
WeavePeerName = "weave_peer_name"
WeavePeerNickName = "weave_peer_nick_name"
WeaveDNSHostname = "weave_dns_hostname"
WeaveMACAddress = "weave_mac_address"
WeaveVersion = "weave_version"
WeaveEncryption = "weave_encryption"
WeaveProtocol = "weave_protocol"
WeavePeerDiscovery = "weave_peer_discovery"
WeaveTargetCount = "weave_target_count"
WeaveConnectionCount = "weave_connection_count"
WeavePeerCount = "weave_peer_count"
WeaveTrustedSubnets = "weave_trusted_subnet_count"
WeaveIPAMTableID = "weave_ipam_table"
WeaveIPAMStatus = "weave_ipam_status"
WeaveIPAMRange = "weave_ipam_range"
WeaveIPAMDefaultSubnet = "weave_ipam_default_subnet"
WeaveDNSTableID = "weave_dns_table"
WeaveDNSDomain = "weave_dns_domain"
WeaveDNSUpstream = "weave_dns_upstream"
WeaveDNSTTL = "weave_dns_ttl"
WeaveDNSEntryCount = "weave_dns_entry_count"
WeaveProxyTableID = "weave_proxy_table"
WeaveProxyStatus = "weave_proxy_status"
WeaveProxyAddress = "weave_proxy_address"
WeavePluginTableID = "weave_plugin_table"
WeavePluginStatus = "weave_plugin_status"
WeavePluginDriver = "weave_plugin_driver"
WeaveConnectionsTablePrefix = "weave_connections_table_"
WeaveConnectionsMulticolumnTablePrefix = "weave_connections_multicolumn_table_"
)
var (
@@ -73,14 +74,20 @@ var (
}
weaveTableTemplates = report.TableTemplates{
WeaveIPAMTableID: {ID: WeaveIPAMTableID, Label: "IPAM",
WeaveIPAMTableID: {
ID: WeaveIPAMTableID,
Label: "IPAM",
Type: report.PropertyListType,
FixedRows: map[string]string{
WeaveIPAMStatus: "Status",
WeaveIPAMRange: "Range",
WeaveIPAMDefaultSubnet: "Default Subnet",
},
},
WeaveDNSTableID: {ID: WeaveDNSTableID, Label: "DNS",
WeaveDNSTableID: {
ID: WeaveDNSTableID,
Label: "DNS",
Type: report.PropertyListType,
FixedRows: map[string]string{
WeaveDNSDomain: "Domain",
WeaveDNSUpstream: "Upstream",
@@ -88,13 +95,19 @@ var (
WeaveDNSEntryCount: "Entries",
},
},
WeaveProxyTableID: {ID: WeaveProxyTableID, Label: "Proxy",
WeaveProxyTableID: {
ID: WeaveProxyTableID,
Label: "Proxy",
Type: report.PropertyListType,
FixedRows: map[string]string{
WeaveProxyStatus: "Status",
WeaveProxyAddress: "Address",
},
},
WeavePluginTableID: {ID: WeavePluginTableID, Label: "Plugin",
WeavePluginTableID: {
ID: WeavePluginTableID,
Label: "Plugin",
Type: report.PropertyListType,
FixedRows: map[string]string{
WeavePluginStatus: "Status",
WeavePluginDriver: "Driver Name",
@@ -103,8 +116,15 @@ var (
WeaveConnectionsTablePrefix: {
ID: WeaveConnectionsTablePrefix,
Label: "Connections",
Type: report.PropertyListType,
Prefix: WeaveConnectionsTablePrefix,
},
WeaveConnectionsMulticolumnTablePrefix: {
ID: WeaveConnectionsMulticolumnTablePrefix,
Label: "Connections",
Type: report.MulticolumnTableType,
Prefix: WeaveConnectionsMulticolumnTablePrefix,
},
}
)
@@ -434,28 +454,31 @@ func (w *Weave) addCurrentPeerInfo(latests map[string]string, node report.Node)
latests[WeavePluginStatus] = "running"
latests[WeavePluginDriver] = "weave"
}
node = node.AddPrefixTable(WeaveConnectionsTablePrefix, getConnectionsTable(w.statusCache.Router))
node = node.AddPrefixTable(WeaveConnectionsMulticolumnTablePrefix, getConnectionsTable(w.statusCache.Router))
node = node.WithParents(report.EmptySets.Add(report.Host, report.MakeStringSet(w.hostID)))
return latests, node
}
func getConnectionsTable(router weave.Router) map[string]string {
func getConnectionsTable(router weave.Router) []report.Row {
const (
outboundArrow = "->"
inboundArrow = "<-"
)
table := make(map[string]string, len(router.Connections))
table := make([]report.Row, len(router.Connections))
for _, conn := range router.Connections {
arrow := inboundArrow
if conn.Outbound {
arrow = outboundArrow
}
// TODO: we should probably use a multicolumn table for this
// but there is no mechanism to support it yet.
key := fmt.Sprintf("%s %s", arrow, conn.Address)
value := fmt.Sprintf("%s, %s", conn.State, conn.Info)
table[key] = value
table = append(table, report.Row{
ID: conn.Address,
Entries: map[string]string{
"ip": fmt.Sprintf("%s %s", arrow, conn.Address),
"state": conn.State,
"info": conn.Info,
},
})
}
return table
}
+8 -6
View File
@@ -35,23 +35,25 @@ func TestNodeTables(t *testing.T) {
{
ID: docker.EnvPrefix,
Label: "Environment Variables",
Rows: []report.MetadataRow{},
Rows: []report.Row{},
},
{
ID: docker.LabelPrefix,
Label: "Docker Labels",
Rows: []report.MetadataRow{
Rows: []report.Row{
{
ID: "label_label1",
Label: "label1",
Value: "label1value",
Entries: map[string]string{
"id": "label_label1",
"label": "label1",
"value": "label1value",
},
},
},
},
{
ID: docker.ImageTableID,
Label: "Image",
Rows: []report.MetadataRow{},
Rows: []report.Row{},
},
},
},
+114 -47
View File
@@ -15,10 +15,33 @@ import (
const (
MaxTableRows = 20
TruncationCountPrefix = "table_truncation_count_"
MulticolumnTableType = "multicolumn-table"
PropertyListType = "property-list"
)
// AddPrefixTable appends arbitrary key-value pairs to the Node, returning a new node.
func (node Node) AddPrefixTable(prefix string, labels map[string]string) Node {
// AddPrefixTable appends arbitrary rows to the Node, returning a new node.
func (node Node) AddPrefixTable(prefix string, rows []Row) Node {
count := 0
for _, row := range rows {
if count >= MaxTableRows {
break
}
// TODO: Figure a more natural way of storing rows
for column, value := range row.Entries {
key := fmt.Sprintf("%s %s", row.ID, column)
node = node.WithLatest(prefix+key, mtime.Now(), value)
}
count++
}
if len(rows) > MaxTableRows {
truncationCount := fmt.Sprintf("%d", len(rows)-MaxTableRows)
node = node.WithLatest(TruncationCountPrefix+prefix, mtime.Now(), truncationCount)
}
return node
}
// AddPrefixLabels appends arbitrary key-value pairs to the Node, returning a new node.
func (node Node) AddPrefixLabels(prefix string, labels map[string]string) Node {
count := 0
for key, value := range labels {
if count >= MaxTableRows {
@@ -34,33 +57,86 @@ func (node Node) AddPrefixTable(prefix string, labels map[string]string) Node {
return node
}
// ExtractTable returns the key-value pairs to build a table from this node
func (node Node) ExtractTable(template TableTemplate) (rows map[string]string, truncationCount int) {
rows = map[string]string{}
// ExtractTable returns the rows to build a table from this node
func (node Node) ExtractTable(template TableTemplate) (rows []Row, truncationCount int) {
rows = []Row{}
switch template.Type {
case MulticolumnTableType:
keyRows := map[string]Row{}
node.Latest.ForEach(func(key string, _ time.Time, value string) {
if len(template.Prefix) > 0 && strings.HasPrefix(key, template.Prefix) {
rowID, column := "", ""
fmt.Sscanf(key[len(template.Prefix):], "%s %s", &rowID, &column)
if _, ok := keyRows[rowID]; !ok {
keyRows[rowID] = Row{
ID: rowID,
Entries: map[string]string{},
}
}
keyRows[rowID].Entries[column] = value
}
})
for _, row := range keyRows {
rows = append(rows, row)
}
// By default assume it's a property list (for backward compatibility)
default:
keyValues := map[string]string{}
node.Latest.ForEach(func(key string, _ time.Time, value string) {
if label, ok := template.FixedRows[key]; ok {
keyValues[label] = value
}
if len(template.Prefix) > 0 && strings.HasPrefix(key, template.Prefix) {
label := key[len(template.Prefix):]
keyValues[label] = value
}
})
labels := make([]string, 0, len(rows))
for label := range keyValues {
labels = append(labels, label)
}
sort.Strings(labels)
for _, label := range labels {
rows = append(rows, Row{
ID: "label_" + label,
Entries: map[string]string{
"label": label,
"value": keyValues[label],
},
})
}
}
truncationCount = 0
node.Latest.ForEach(func(key string, _ time.Time, value string) {
if label, ok := template.FixedRows[key]; ok {
rows[label] = value
}
if len(template.Prefix) > 0 && strings.HasPrefix(key, template.Prefix) {
label := key[len(template.Prefix):]
rows[label] = value
}
})
if str, ok := node.Latest.Lookup(TruncationCountPrefix + template.Prefix); ok {
if n, err := fmt.Sscanf(str, "%d", &truncationCount); n != 1 || err != nil {
log.Warn("Unexpected truncation count format %q", str)
}
}
return rows, truncationCount
}
type Column struct {
ID string `json:"id"`
Label string `json:"label"`
DataType string `json:"dataType"`
Alignment string `json:"alignment"`
}
type Row struct {
ID string `json:"id"`
Entries map[string]string `json:"entries"`
}
// Table is the type for a table in the UI.
type Table struct {
ID string `json:"id"`
Label string `json:"label"`
Rows []MetadataRow `json:"rows"`
TruncationCount int `json:"truncationCount,omitempty"`
ID string `json:"id"`
Label string `json:"label"`
Type string `json:"type"`
Columns []Column `json:"columns"`
Rows []Row `json:"rows"`
TruncationCount int `json:"truncationCount,omitempty"`
}
type tablesByID []Table
@@ -72,9 +148,14 @@ func (t tablesByID) Less(i, j int) bool { return t[i].ID < t[j].ID }
// Copy returns a copy of the Table.
func (t Table) Copy() Table {
result := Table{
ID: t.ID,
Label: t.Label,
Rows: make([]MetadataRow, 0, len(t.Rows)),
ID: t.ID,
Label: t.Label,
Type: t.Type,
Columns: make([]Column, 0, len(t.Columns)),
Rows: make([]Row, 0, len(t.Rows)),
}
for _, column := range t.Columns {
result.Columns = append(result.Columns, column)
}
for _, row := range t.Rows {
result.Rows = append(result.Rows, row)
@@ -82,18 +163,13 @@ func (t Table) Copy() Table {
return result
}
// FixedRow describes a row which is part of a TableTemplate and whose value is extracted
// from a predetermined key
type FixedRow struct {
Label string `json:"label"`
Key string `json:"key"`
}
// TableTemplate describes how to render a table for the UI.
type TableTemplate struct {
ID string `json:"id"`
Label string `json:"label"`
Prefix string `json:"prefix"`
ID string `json:"id"`
Label string `json:"label"`
Prefix string `json:"prefix"`
Type string `json:"type"`
Columns []Column `json:"columns"`
// FixedRows indicates what predetermined rows to render each entry is
// indexed by the key to extract the row value is mapped to the row
// label
@@ -126,10 +202,13 @@ func (t TableTemplate) Merge(other TableTemplate) TableTemplate {
fixedRows = other.FixedRows
}
// TODO: Refactor the merging logic, as mixing
// the types now might result in invalid tables.
return TableTemplate{
ID: max(t.ID, other.ID),
Label: max(t.Label, other.Label),
Prefix: max(t.Prefix, other.Prefix),
Type: max(t.Type, other.Type),
FixedRows: fixedRows,
}
}
@@ -142,25 +221,13 @@ func (t TableTemplates) Tables(node Node) []Table {
var result []Table
for _, template := range t {
rows, truncationCount := node.ExtractTable(template)
table := Table{
result = append(result, Table{
ID: template.ID,
Label: template.Label,
Rows: []MetadataRow{},
Type: template.Type,
Rows: rows,
TruncationCount: truncationCount,
}
keys := make([]string, 0, len(rows))
for k := range rows {
keys = append(keys, k)
}
sort.Strings(keys)
for _, key := range keys {
table.Rows = append(table.Rows, MetadataRow{
ID: "label_" + key,
Label: key,
Value: rows[key],
})
}
result = append(result, table)
})
}
sort.Sort(tablesByID(result))
return result
+2 -2
View File
@@ -16,7 +16,7 @@ func TestPrefixTables(t *testing.T) {
}
nmd := report.MakeNode("foo1")
nmd = nmd.AddPrefixTable("foo_", want)
nmd = nmd.AddPrefixLabels("foo_", want)
have, truncationCount := nmd.ExtractTable(report.TableTemplate{Prefix: "foo_"})
if truncationCount != 0 {
@@ -62,7 +62,7 @@ func TestTruncation(t *testing.T) {
nmd := report.MakeNode("foo1")
nmd = nmd.AddPrefixTable("foo_", want)
nmd = nmd.AddPrefixLabels("foo_", want)
_, truncationCount := nmd.ExtractTable(report.TableTemplate{Prefix: "foo_"})
if truncationCount != wantTruncationCount {