diff --git a/client/app/scripts/components/node-details.js b/client/app/scripts/components/node-details.js
index b8abffd1b7..63a4d9b8a9 100644
--- a/client/app/scripts/components/node-details.js
+++ b/client/app/scripts/components/node-details.js
@@ -203,20 +203,22 @@ class NodeDetails extends React.Component {
))}
- {details.tables && details.tables.length > 0 && details.tables.map((table) => {
- if (table.rows.length > 0) {
+ {details.propertyLists && details.propertyLists.map((propertyList) => {
+ if (propertyList.rows.length > 0) {
return (
-
+
- {table.label}
- {table.truncationCount > 0 && 0 &&
-
+
}
+ rows={propertyList.rows}
+ controls={propertyList.controls}
+ matches={nodeMatches.get('propertyLists')}
+ />
);
}
diff --git a/client/app/scripts/utils/__tests__/search-utils-test.js b/client/app/scripts/utils/__tests__/search-utils-test.js
index 301b431a2c..356562ebe7 100644
--- a/client/app/scripts/utils/__tests__/search-utils-test.js
+++ b/client/app/scripts/utils/__tests__/search-utils-test.js
@@ -17,7 +17,15 @@ describe('SearchUtils', () => {
id: 'metric1',
label: 'Metric 1',
value: 1
- }]
+ }],
+ propertyLists: [{
+ id: 'labels1',
+ rows: [{
+ id: 'label1',
+ label: 'Label 1',
+ value: 'Value 1'
+ }]
+ }],
},
n2: {
id: 'n2',
@@ -28,7 +36,7 @@ describe('SearchUtils', () => {
value: 'value 2'
}],
tables: [{
- id: 'metric1',
+ id: 'table1',
rows: [{
id: 'row1',
label: 'Row 1',
@@ -242,6 +250,14 @@ describe('SearchUtils', () => {
expect(matches.size).toEqual(1);
expect(matches.getIn(['n2', 'tables', 'row1']).text).toBe('Row Value 1');
});
+
+ it('should match on a property lists field', () => {
+ const nodes = nodeSets.someNodes;
+ const matches = fun(nodes, {query: 'Value 1'});
+ expect(matches.size).toEqual(2);
+ expect(matches.getIn(['n2', 'tables', 'row1']).text).toBe('Row Value 1');
+ expect(matches.getIn(['n1', 'propertyLists', 'label1']).text).toBe('Value 1');
+ });
});
describe('updateNodeMatches', () => {
diff --git a/client/app/scripts/utils/search-utils.js b/client/app/scripts/utils/search-utils.js
index b8f74d0471..70299b1707 100644
--- a/client/app/scripts/utils/search-utils.js
+++ b/client/app/scripts/utils/search-utils.js
@@ -148,7 +148,7 @@ export function searchTopology(nodes, { prefix, query, metric, comp, value }) {
});
}
- // tables (envvars and labels)
+ // node tables
const tables = node.get('tables');
if (tables) {
tables.forEach((table) => {
@@ -161,6 +161,20 @@ export function searchTopology(nodes, { prefix, query, metric, comp, value }) {
}
});
}
+
+ // property lists (envvars and labels)
+ const propertyLists = node.get('propertyLists');
+ if (propertyLists) {
+ propertyLists.forEach((propertyList) => {
+ if (propertyList.get('rows')) {
+ propertyList.get('rows').forEach((field) => {
+ const keyPath = [nodeId, 'propertyLists', field.get('id')];
+ nodeMatches = findNodeMatch(nodeMatches, keyPath, field.get('value'),
+ query, prefix, field.get('label'));
+ });
+ }
+ });
+ }
} else if (metric) {
const metrics = node.get('metrics');
if (metrics) {
@@ -267,12 +281,18 @@ export function getSearchableFields(nodes) {
))
), makeSet());
+ const propertyLabels = nodes.reduce((labels, node) => (
+ labels.union(get(node, 'propertyLists').flatMap(t => (t.get('rows') || makeList)
+ .map(f => f.get('label'))
+ ))
+ ), makeSet());
+
const metricLabels = nodes.reduce((labels, node) => (
labels.union(get(node, 'metrics').map(f => f.get('label')))
), makeSet());
return makeMap({
- fields: baseLabels.union(metadataLabels, parentLabels, tableRowLabels)
+ fields: baseLabels.union(metadataLabels, parentLabels, tableRowLabels, propertyLabels)
.map(slugify)
.toList()
.sort(),
diff --git a/probe/docker/container.go b/probe/docker/container.go
index 50856ee0fa..419fb86059 100644
--- a/probe/docker/container.go
+++ b/probe/docker/container.go
@@ -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.AddPrefixPropertyList(LabelPrefix, c.container.Config.Labels)
+ result = result.AddPrefixPropertyList(EnvPrefix, c.env())
return result
}
diff --git a/probe/docker/reporter.go b/probe/docker/reporter.go
index b1e2eb75c8..3d64df6cf1 100644
--- a/probe/docker/reporter.go
+++ b/probe/docker/reporter.go
@@ -47,9 +47,9 @@ var (
report.Container: {ID: report.Container, Label: "# Containers", From: report.FromCounters, Datatype: "number", Priority: 2},
}
- ContainerTableTemplates = report.TableTemplates{
+ ContainerPropertyListTemplates = report.PropertyListTemplates{
ImageTableID: {ID: ImageTableID, Label: "Image",
- FixedRows: map[string]string{
+ FixedProperties: map[string]string{
ImageID: "ID",
ImageName: "Name",
ImageSize: "Size",
@@ -60,7 +60,7 @@ var (
EnvPrefix: {ID: EnvPrefix, Label: "Environment Variables", Prefix: EnvPrefix},
}
- ContainerImageTableTemplates = report.TableTemplates{
+ ContainerImagePropertyListTemplates = report.PropertyListTemplates{
ImageLabelPrefix: {ID: ImageLabelPrefix, Label: "Docker Labels", Prefix: ImageLabelPrefix},
}
@@ -181,7 +181,7 @@ func (r *Reporter) containerTopology(localAddrs []net.IP) report.Topology {
result := report.MakeTopology().
WithMetadataTemplates(ContainerMetadataTemplates).
WithMetricTemplates(ContainerMetricTemplates).
- WithTableTemplates(ContainerTableTemplates)
+ WithPropertyListTemplates(ContainerPropertyListTemplates)
result.Controls.AddControls(ContainerControls)
metadata := map[string]string{report.ControlProbeID: r.probeID}
@@ -244,7 +244,7 @@ func (r *Reporter) containerTopology(localAddrs []net.IP) report.Topology {
func (r *Reporter) containerImageTopology() report.Topology {
result := report.MakeTopology().
WithMetadataTemplates(ContainerImageMetadataTemplates).
- WithTableTemplates(ContainerImageTableTemplates)
+ WithPropertyListTemplates(ContainerImagePropertyListTemplates)
r.registry.WalkImages(func(image docker_client.APIImages) {
imageID := trimImageID(image.ID)
@@ -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.AddPrefixPropertyList(ImageLabelPrefix, image.Labels)
result.AddNode(node)
})
diff --git a/probe/kubernetes/meta.go b/probe/kubernetes/meta.go
index c7db23d0c7..fc8188ca3d 100644
--- a/probe/kubernetes/meta.go
+++ b/probe/kubernetes/meta.go
@@ -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())
+ }).AddPrefixPropertyList(LabelPrefix, m.Labels())
}
diff --git a/probe/kubernetes/reporter.go b/probe/kubernetes/reporter.go
index 44c7c564a3..ef8360cd3e 100644
--- a/probe/kubernetes/reporter.go
+++ b/probe/kubernetes/reporter.go
@@ -67,7 +67,7 @@ var (
ReplicaSetMetricTemplates = PodMetricTemplates
- TableTemplates = report.TableTemplates{
+ PropertyListTemplates = report.PropertyListTemplates{
LabelPrefix: {ID: LabelPrefix, Label: "Kubernetes Labels", Prefix: LabelPrefix},
}
@@ -223,7 +223,7 @@ func (r *Reporter) serviceTopology() (report.Topology, []Service, error) {
result = report.MakeTopology().
WithMetadataTemplates(ServiceMetadataTemplates).
WithMetricTemplates(ServiceMetricTemplates).
- WithTableTemplates(TableTemplates)
+ WithPropertyListTemplates(PropertyListTemplates)
services = []Service{}
)
err := r.client.WalkServices(func(s Service) error {
@@ -258,7 +258,7 @@ func (r *Reporter) deploymentTopology(probeID string) (report.Topology, []Deploy
result = report.MakeTopology().
WithMetadataTemplates(DeploymentMetadataTemplates).
WithMetricTemplates(DeploymentMetricTemplates).
- WithTableTemplates(TableTemplates)
+ WithPropertyListTemplates(PropertyListTemplates)
deployments = []Deployment{}
)
result.Controls.AddControls(ScalingControls)
@@ -276,7 +276,7 @@ func (r *Reporter) replicaSetTopology(probeID string, deployments []Deployment)
result = report.MakeTopology().
WithMetadataTemplates(ReplicaSetMetadataTemplates).
WithMetricTemplates(ReplicaSetMetricTemplates).
- WithTableTemplates(TableTemplates)
+ WithPropertyListTemplates(PropertyListTemplates)
replicaSets = []ReplicaSet{}
selectors = []func(labelledChild){}
)
@@ -355,7 +355,7 @@ func (r *Reporter) podTopology(services []Service, replicaSets []ReplicaSet) (re
pods = report.MakeTopology().
WithMetadataTemplates(PodMetadataTemplates).
WithMetricTemplates(PodMetricTemplates).
- WithTableTemplates(TableTemplates)
+ WithPropertyListTemplates(PropertyListTemplates)
selectors = []func(labelledChild){}
)
pods.Controls.AddControl(report.Control{
diff --git a/probe/overlay/weave.go b/probe/overlay/weave.go
index 819d88eb5a..1087156395 100644
--- a/probe/overlay/weave.go
+++ b/probe/overlay/weave.go
@@ -19,34 +19,34 @@ 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"
+ WeaveConnectionsListPrefix = "weave_connections_list_"
)
var (
@@ -72,16 +72,16 @@ var (
WeaveTrustedSubnets: {ID: WeaveTrustedSubnets, Label: "Trusted Subnets", From: report.FromSets, Priority: 9},
}
- weaveTableTemplates = report.TableTemplates{
+ weavePropertyListTemplates = report.PropertyListTemplates{
WeaveIPAMTableID: {ID: WeaveIPAMTableID, Label: "IPAM",
- FixedRows: map[string]string{
+ FixedProperties: map[string]string{
WeaveIPAMStatus: "Status",
WeaveIPAMRange: "Range",
WeaveIPAMDefaultSubnet: "Default Subnet",
},
},
WeaveDNSTableID: {ID: WeaveDNSTableID, Label: "DNS",
- FixedRows: map[string]string{
+ FixedProperties: map[string]string{
WeaveDNSDomain: "Domain",
WeaveDNSUpstream: "Upstream",
WeaveDNSTTL: "TTL",
@@ -89,21 +89,21 @@ var (
},
},
WeaveProxyTableID: {ID: WeaveProxyTableID, Label: "Proxy",
- FixedRows: map[string]string{
+ FixedProperties: map[string]string{
WeaveProxyStatus: "Status",
WeaveProxyAddress: "Address",
},
},
WeavePluginTableID: {ID: WeavePluginTableID, Label: "Plugin",
- FixedRows: map[string]string{
+ FixedProperties: map[string]string{
WeavePluginStatus: "Status",
WeavePluginDriver: "Driver Name",
},
},
- WeaveConnectionsTablePrefix: {
- ID: WeaveConnectionsTablePrefix,
+ WeaveConnectionsListPrefix: {
+ ID: WeaveConnectionsListPrefix,
Label: "Connections",
- Prefix: WeaveConnectionsTablePrefix,
+ Prefix: WeaveConnectionsListPrefix,
},
}
)
@@ -345,7 +345,7 @@ func (w *Weave) Report() (report.Report, error) {
r := report.MakeReport()
r.Container = r.Container.WithMetadataTemplates(containerMetadata)
- r.Overlay = r.Overlay.WithMetadataTemplates(weaveMetadata).WithTableTemplates(weaveTableTemplates)
+ r.Overlay = r.Overlay.WithMetadataTemplates(weaveMetadata).WithPropertyListTemplates(weavePropertyListTemplates)
// We report nodes for all peers (not just the current node) to highlight peers not monitored by Scope
// (i.e. without a running probe)
@@ -434,13 +434,13 @@ 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.AddPrefixPropertyList(WeaveConnectionsListPrefix, getConnectionsList(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 getConnectionsList(router weave.Router) map[string]string {
const (
outboundArrow = "->"
inboundArrow = "<-"
diff --git a/render/detailed/tables.go b/render/detailed/property_lists.go
similarity index 53%
rename from render/detailed/tables.go
rename to render/detailed/property_lists.go
index 63fe918784..1f08937904 100644
--- a/render/detailed/tables.go
+++ b/render/detailed/property_lists.go
@@ -4,17 +4,16 @@ import (
"github.com/weaveworks/scope/report"
)
-// NodeTables produces a list of tables (to be consumed directly by the UI) based
-// on the report and the node. It uses the report to get the templates for the node's
-// topology.
-func NodeTables(r report.Report, n report.Node) []report.Table {
+// NodePropertyLists produces a list of property lists (to be consumed directly by the UI) based
+// on the report and the node. It uses the report to get the templates for the node's topology.
+func NodePropertyLists(r report.Report, n report.Node) []report.PropertyList {
if _, ok := n.Counters.Lookup(n.Topology); ok {
// This is a group of nodes, so no tables!
return nil
}
if topology, ok := r.Topology(n.Topology); ok {
- return topology.TableTemplates.Tables(n)
+ return topology.PropertyListTemplates.PropertyLists(n)
}
return nil
}
diff --git a/render/detailed/tables_test.go b/render/detailed/property_lists_test.go
similarity index 87%
rename from render/detailed/tables_test.go
rename to render/detailed/property_lists_test.go
index 02f723391f..9ba4019db7 100644
--- a/render/detailed/tables_test.go
+++ b/render/detailed/property_lists_test.go
@@ -11,18 +11,18 @@ import (
"github.com/weaveworks/scope/test/fixture"
)
-func TestNodeTables(t *testing.T) {
+func TestNodePropertyLists(t *testing.T) {
inputs := []struct {
name string
rpt report.Report
node report.Node
- want []report.Table
+ want []report.PropertyList
}{
{
name: "container",
rpt: report.Report{
Container: report.MakeTopology().
- WithTableTemplates(docker.ContainerTableTemplates),
+ WithPropertyListTemplates(docker.ContainerPropertyListTemplates),
},
node: report.MakeNodeWith(fixture.ClientContainerNodeID, map[string]string{
docker.ContainerID: fixture.ClientContainerID,
@@ -31,7 +31,7 @@ func TestNodeTables(t *testing.T) {
}).WithTopology(report.Container).WithSets(report.EmptySets.
Add(docker.ContainerIPs, report.MakeStringSet("10.10.10.0/24", "10.10.10.1/24")),
),
- want: []report.Table{
+ want: []report.PropertyList{
{
ID: docker.EnvPrefix,
Label: "Environment Variables",
@@ -65,7 +65,7 @@ func TestNodeTables(t *testing.T) {
},
}
for _, input := range inputs {
- have := detailed.NodeTables(input.rpt, input.node)
+ have := detailed.NodePropertyLists(input.rpt, input.node)
if !reflect.DeepEqual(input.want, have) {
t.Errorf("%s: %s", input.name, test.Diff(input.want, have))
}
diff --git a/render/detailed/summary.go b/render/detailed/summary.go
index e4ad49e242..6baf7ba707 100644
--- a/render/detailed/summary.go
+++ b/render/detailed/summary.go
@@ -45,19 +45,19 @@ type Column struct {
// NodeSummary is summary information about a child for a Node.
type NodeSummary struct {
- ID string `json:"id"`
- Label string `json:"label"`
- LabelMinor string `json:"labelMinor"`
- Rank string `json:"rank"`
- Shape string `json:"shape,omitempty"`
- Stack bool `json:"stack,omitempty"`
- Linkable bool `json:"linkable,omitempty"` // Whether this node can be linked-to
- Pseudo bool `json:"pseudo,omitempty"`
- Metadata []report.MetadataRow `json:"metadata,omitempty"`
- Parents []Parent `json:"parents,omitempty"`
- Metrics []report.MetricRow `json:"metrics,omitempty"`
- Tables []report.Table `json:"tables,omitempty"`
- Adjacency report.IDList `json:"adjacency,omitempty"`
+ ID string `json:"id"`
+ Label string `json:"label"`
+ LabelMinor string `json:"labelMinor"`
+ Rank string `json:"rank"`
+ Shape string `json:"shape,omitempty"`
+ Stack bool `json:"stack,omitempty"`
+ Linkable bool `json:"linkable,omitempty"` // Whether this node can be linked-to
+ Pseudo bool `json:"pseudo,omitempty"`
+ Metadata []report.MetadataRow `json:"metadata,omitempty"`
+ Parents []Parent `json:"parents,omitempty"`
+ Metrics []report.MetricRow `json:"metrics,omitempty"`
+ PropertyLists []report.PropertyList `json:"propertyLists,omitempty"`
+ Adjacency report.IDList `json:"adjacency,omitempty"`
}
var renderers = map[string]func(NodeSummary, report.Node) (NodeSummary, bool){
@@ -106,14 +106,14 @@ func (n NodeSummary) SummarizeMetrics() NodeSummary {
func baseNodeSummary(r report.Report, n report.Node) NodeSummary {
t, _ := r.Topology(n.Topology)
return NodeSummary{
- ID: n.ID,
- Shape: t.GetShape(),
- Linkable: true,
- Metadata: NodeMetadata(r, n),
- Metrics: NodeMetrics(r, n),
- Parents: Parents(r, n),
- Tables: NodeTables(r, n),
- Adjacency: n.Adjacency,
+ ID: n.ID,
+ Shape: t.GetShape(),
+ Linkable: true,
+ Metadata: NodeMetadata(r, n),
+ Metrics: NodeMetrics(r, n),
+ Parents: Parents(r, n),
+ PropertyLists: NodePropertyLists(r, n),
+ Adjacency: n.Adjacency,
}
}
diff --git a/report/property_list.go b/report/property_list.go
new file mode 100644
index 0000000000..027f417754
--- /dev/null
+++ b/report/property_list.go
@@ -0,0 +1,191 @@
+package report
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+ "time"
+
+ log "github.com/Sirupsen/logrus"
+ "github.com/weaveworks/common/mtime"
+)
+
+// MaxPropertyListSize sets the limit on the size of the property list to render.
+// TODO: this won't be needed once we send reports incrementally
+const (
+ MaxPropertyListSize = 20
+ TruncationCountPrefix = "property_list_truncation_count_"
+)
+
+// AddPrefixPropertyList appends arbitrary key-value pairs to the Node, returning a new node.
+func (node Node) AddPrefixPropertyList(prefix string, properties map[string]string) Node {
+ count := 0
+ for key, value := range properties {
+ if count >= MaxPropertyListSize {
+ break
+ }
+ node = node.WithLatest(prefix+key, mtime.Now(), value)
+ count++
+ }
+ if len(properties) > MaxPropertyListSize {
+ truncationCount := fmt.Sprintf("%d", len(properties)-MaxPropertyListSize)
+ node = node.WithLatest(TruncationCountPrefix+prefix, mtime.Now(), truncationCount)
+ }
+ return node
+}
+
+// ExtractPropertyList returns the key-value pairs to build a property list from this node
+func (node Node) ExtractPropertyList(template PropertyListTemplate) (rows map[string]string, truncationCount int) {
+ rows = map[string]string{}
+ truncationCount = 0
+ node.Latest.ForEach(func(key string, _ time.Time, value string) {
+ if label, ok := template.FixedProperties[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
+}
+
+// PropertyList is the type for a property list (labels) in the UI.
+type PropertyList struct {
+ ID string `json:"id"`
+ Label string `json:"label"`
+ Rows []MetadataRow `json:"rows"`
+ TruncationCount int `json:"truncationCount,omitempty"`
+}
+
+type propertyListsByID []PropertyList
+
+func (t propertyListsByID) Len() int { return len(t) }
+func (t propertyListsByID) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
+func (t propertyListsByID) Less(i, j int) bool { return t[i].ID < t[j].ID }
+
+// Copy returns a copy of the PropertyList.
+func (t PropertyList) Copy() PropertyList {
+ result := PropertyList{
+ ID: t.ID,
+ Label: t.Label,
+ Rows: make([]MetadataRow, 0, len(t.Rows)),
+ }
+ for _, row := range t.Rows {
+ result.Rows = append(result.Rows, row)
+ }
+ return result
+}
+
+// PropertyListTemplate describes how to render a property list for the UI.
+type PropertyListTemplate struct {
+ ID string `json:"id"`
+ Label string `json:"label"`
+ Prefix string `json:"prefix"`
+ // FixedProperties indicates what predetermined properties to render each entry is
+ // indexed by the key to extract the row value is mapped to the row
+ // label
+ FixedProperties map[string]string `json:"fixedProperties"`
+}
+
+// Copy returns a value-copy of the PropertyListTemplate
+func (t PropertyListTemplate) Copy() PropertyListTemplate {
+ FixedPropertiesCopy := make(map[string]string, len(t.FixedProperties))
+ for key, value := range t.FixedProperties {
+ FixedPropertiesCopy[key] = value
+ }
+ t.FixedProperties = FixedPropertiesCopy
+ return t
+}
+
+// Merge other into t, returning a fresh copy. Does fieldwise max -
+// whilst this isn't particularly meaningful, at least it idempotent,
+// commutativite and associative.
+func (t PropertyListTemplate) Merge(other PropertyListTemplate) PropertyListTemplate {
+ max := func(s1, s2 string) string {
+ if s1 > s2 {
+ return s1
+ }
+ return s2
+ }
+
+ fixedProperties := t.FixedProperties
+ if len(other.FixedProperties) > len(fixedProperties) {
+ fixedProperties = other.FixedProperties
+ }
+
+ return PropertyListTemplate{
+ ID: max(t.ID, other.ID),
+ Label: max(t.Label, other.Label),
+ Prefix: max(t.Prefix, other.Prefix),
+ FixedProperties: fixedProperties,
+ }
+}
+
+// PropertyListTemplates is a mergeable set of PropertyListTemplate
+type PropertyListTemplates map[string]PropertyListTemplate
+
+// PropertyLists renders the PropertyListTemplates for a given node.
+func (t PropertyListTemplates) PropertyLists(node Node) []PropertyList {
+ var result []PropertyList
+ for _, template := range t {
+ rows, truncationCount := node.ExtractPropertyList(template)
+ propertyList := PropertyList{
+ ID: template.ID,
+ Label: template.Label,
+ Rows: []MetadataRow{},
+ TruncationCount: truncationCount,
+ }
+ keys := make([]string, 0, len(rows))
+ for k := range rows {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ for _, key := range keys {
+ propertyList.Rows = append(propertyList.Rows, MetadataRow{
+ ID: "label_" + key,
+ Label: key,
+ Value: rows[key],
+ })
+ }
+ result = append(result, propertyList)
+ }
+ sort.Sort(propertyListsByID(result))
+ return result
+}
+
+// Copy returns a value copy of the PropertyListTemplates
+func (t PropertyListTemplates) Copy() PropertyListTemplates {
+ if t == nil {
+ return nil
+ }
+ result := PropertyListTemplates{}
+ for k, v := range t {
+ result[k] = v.Copy()
+ }
+ return result
+}
+
+// Merge merges two sets of PropertyListTemplates
+func (t PropertyListTemplates) Merge(other PropertyListTemplates) PropertyListTemplates {
+ if t == nil && other == nil {
+ return nil
+ }
+ result := make(PropertyListTemplates, len(t))
+ for k, v := range t {
+ result[k] = v
+ }
+ for k, v := range other {
+ if existing, ok := result[k]; ok {
+ result[k] = v.Merge(existing)
+ } else {
+ result[k] = v
+ }
+ }
+ return result
+}
diff --git a/report/table_test.go b/report/property_list_test.go
similarity index 70%
rename from report/table_test.go
rename to report/property_list_test.go
index 72b6d94cd4..1a73168d62 100644
--- a/report/table_test.go
+++ b/report/property_list_test.go
@@ -16,8 +16,8 @@ func TestPrefixTables(t *testing.T) {
}
nmd := report.MakeNode("foo1")
- nmd = nmd.AddPrefixTable("foo_", want)
- have, truncationCount := nmd.ExtractTable(report.TableTemplate{Prefix: "foo_"})
+ nmd = nmd.AddPrefixPropertyList("foo_", want)
+ have, truncationCount := nmd.ExtractPropertyList(report.PropertyListTemplate{Prefix: "foo_"})
if truncationCount != 0 {
t.Error("Table shouldn't had been truncated")
@@ -38,13 +38,13 @@ func TestFixedTables(t *testing.T) {
"foo2key": "bar2",
})
- template := report.TableTemplate{FixedRows: map[string]string{
+ template := report.PropertyListTemplate{FixedProperties: map[string]string{
"foo1key": "foo1",
"foo2key": "foo2",
},
}
- have, _ := nmd.ExtractTable(template)
+ have, _ := nmd.ExtractPropertyList(template)
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
@@ -54,7 +54,7 @@ func TestFixedTables(t *testing.T) {
func TestTruncation(t *testing.T) {
wantTruncationCount := 1
want := map[string]string{}
- for i := 0; i < report.MaxTableRows+wantTruncationCount; i++ {
+ for i := 0; i < report.MaxPropertyListSize+wantTruncationCount; i++ {
key := fmt.Sprintf("key%d", i)
value := fmt.Sprintf("value%d", i)
want[key] = value
@@ -62,8 +62,8 @@ func TestTruncation(t *testing.T) {
nmd := report.MakeNode("foo1")
- nmd = nmd.AddPrefixTable("foo_", want)
- _, truncationCount := nmd.ExtractTable(report.TableTemplate{Prefix: "foo_"})
+ nmd = nmd.AddPrefixPropertyList("foo_", want)
+ _, truncationCount := nmd.ExtractPropertyList(report.PropertyListTemplate{Prefix: "foo_"})
if truncationCount != wantTruncationCount {
t.Error(
diff --git a/report/table.go b/report/table.go
deleted file mode 100644
index 74cbc1a71f..0000000000
--- a/report/table.go
+++ /dev/null
@@ -1,198 +0,0 @@
-package report
-
-import (
- "fmt"
- "sort"
- "strings"
- "time"
-
- log "github.com/Sirupsen/logrus"
- "github.com/weaveworks/common/mtime"
-)
-
-// MaxTableRows sets the limit on the table size to render
-// TODO: this won't be needed once we send reports incrementally
-const (
- MaxTableRows = 20
- TruncationCountPrefix = "table_truncation_count_"
-)
-
-// AddPrefixTable appends arbitrary key-value pairs to the Node, returning a new node.
-func (node Node) AddPrefixTable(prefix string, labels map[string]string) Node {
- count := 0
- for key, value := range labels {
- if count >= MaxTableRows {
- break
- }
- node = node.WithLatest(prefix+key, mtime.Now(), value)
- count++
- }
- if len(labels) > MaxTableRows {
- truncationCount := fmt.Sprintf("%d", len(labels)-MaxTableRows)
- node = node.WithLatest(TruncationCountPrefix+prefix, mtime.Now(), truncationCount)
- }
- 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{}
- 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
-}
-
-// 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"`
-}
-
-type tablesByID []Table
-
-func (t tablesByID) Len() int { return len(t) }
-func (t tablesByID) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
-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)),
- }
- for _, row := range t.Rows {
- result.Rows = append(result.Rows, row)
- }
- 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"`
- // 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
- FixedRows map[string]string `json:"fixedRows"`
-}
-
-// Copy returns a value-copy of the TableTemplate
-func (t TableTemplate) Copy() TableTemplate {
- fixedRowsCopy := make(map[string]string, len(t.FixedRows))
- for key, value := range t.FixedRows {
- fixedRowsCopy[key] = value
- }
- t.FixedRows = fixedRowsCopy
- return t
-}
-
-// Merge other into t, returning a fresh copy. Does fieldwise max -
-// whilst this isn't particularly meaningful, at least it idempotent,
-// commutativite and associative.
-func (t TableTemplate) Merge(other TableTemplate) TableTemplate {
- max := func(s1, s2 string) string {
- if s1 > s2 {
- return s1
- }
- return s2
- }
-
- fixedRows := t.FixedRows
- if len(other.FixedRows) > len(fixedRows) {
- fixedRows = other.FixedRows
- }
-
- return TableTemplate{
- ID: max(t.ID, other.ID),
- Label: max(t.Label, other.Label),
- Prefix: max(t.Prefix, other.Prefix),
- FixedRows: fixedRows,
- }
-}
-
-// TableTemplates is a mergeable set of TableTemplate
-type TableTemplates map[string]TableTemplate
-
-// Tables renders the TableTemplates for a given node.
-func (t TableTemplates) Tables(node Node) []Table {
- var result []Table
- for _, template := range t {
- rows, truncationCount := node.ExtractTable(template)
- table := Table{
- ID: template.ID,
- Label: template.Label,
- Rows: []MetadataRow{},
- 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
-}
-
-// Copy returns a value copy of the TableTemplates
-func (t TableTemplates) Copy() TableTemplates {
- if t == nil {
- return nil
- }
- result := TableTemplates{}
- for k, v := range t {
- result[k] = v.Copy()
- }
- return result
-}
-
-// Merge merges two sets of TableTemplates
-func (t TableTemplates) Merge(other TableTemplates) TableTemplates {
- if t == nil && other == nil {
- return nil
- }
- result := make(TableTemplates, len(t))
- for k, v := range t {
- result[k] = v
- }
- for k, v := range other {
- if existing, ok := result[k]; ok {
- result[k] = v.Merge(existing)
- } else {
- result[k] = v
- }
- }
- return result
-}
diff --git a/report/topology.go b/report/topology.go
index 1beba4f79d..6fa58321bb 100644
--- a/report/topology.go
+++ b/report/topology.go
@@ -10,14 +10,14 @@ import (
// EdgeMetadatas and Nodes respectively. Edges are directional, and embedded
// in the Node struct.
type Topology struct {
- Shape string `json:"shape,omitempty"`
- Label string `json:"label,omitempty"`
- LabelPlural string `json:"label_plural,omitempty"`
- Nodes `json:"nodes"`
- Controls `json:"controls,omitempty"`
- MetadataTemplates `json:"metadata_templates,omitempty"`
- MetricTemplates `json:"metric_templates,omitempty"`
- TableTemplates `json:"table_templates,omitempty"`
+ Shape string `json:"shape,omitempty"`
+ Label string `json:"label,omitempty"`
+ LabelPlural string `json:"label_plural,omitempty"`
+ Nodes `json:"nodes"`
+ Controls `json:"controls,omitempty"`
+ MetadataTemplates `json:"metadata_templates,omitempty"`
+ MetricTemplates `json:"metric_templates,omitempty"`
+ PropertyListTemplates `json:"property_list_templates,omitempty"`
}
// MakeTopology gives you a Topology.
@@ -32,14 +32,14 @@ func MakeTopology() Topology {
// returning a new topology.
func (t Topology) WithMetadataTemplates(other MetadataTemplates) Topology {
return Topology{
- Shape: t.Shape,
- Label: t.Label,
- LabelPlural: t.LabelPlural,
- Nodes: t.Nodes.Copy(),
- Controls: t.Controls.Copy(),
- MetadataTemplates: t.MetadataTemplates.Merge(other),
- MetricTemplates: t.MetricTemplates.Copy(),
- TableTemplates: t.TableTemplates.Copy(),
+ Shape: t.Shape,
+ Label: t.Label,
+ LabelPlural: t.LabelPlural,
+ Nodes: t.Nodes.Copy(),
+ Controls: t.Controls.Copy(),
+ MetadataTemplates: t.MetadataTemplates.Merge(other),
+ MetricTemplates: t.MetricTemplates.Copy(),
+ PropertyListTemplates: t.PropertyListTemplates.Copy(),
}
}
@@ -47,57 +47,57 @@ func (t Topology) WithMetadataTemplates(other MetadataTemplates) Topology {
// returning a new topology.
func (t Topology) WithMetricTemplates(other MetricTemplates) Topology {
return Topology{
- Shape: t.Shape,
- Label: t.Label,
- LabelPlural: t.LabelPlural,
- Nodes: t.Nodes.Copy(),
- Controls: t.Controls.Copy(),
- MetadataTemplates: t.MetadataTemplates.Copy(),
- MetricTemplates: t.MetricTemplates.Merge(other),
- TableTemplates: t.TableTemplates.Copy(),
+ Shape: t.Shape,
+ Label: t.Label,
+ LabelPlural: t.LabelPlural,
+ Nodes: t.Nodes.Copy(),
+ Controls: t.Controls.Copy(),
+ MetadataTemplates: t.MetadataTemplates.Copy(),
+ MetricTemplates: t.MetricTemplates.Merge(other),
+ PropertyListTemplates: t.PropertyListTemplates.Copy(),
}
}
-// WithTableTemplates merges some table templates into this topology,
+// WithPropertyListTemplates merges some table templates into this topology,
// returning a new topology.
-func (t Topology) WithTableTemplates(other TableTemplates) Topology {
+func (t Topology) WithPropertyListTemplates(other PropertyListTemplates) Topology {
return Topology{
- Shape: t.Shape,
- Label: t.Label,
- LabelPlural: t.LabelPlural,
- Nodes: t.Nodes.Copy(),
- Controls: t.Controls.Copy(),
- MetadataTemplates: t.MetadataTemplates.Copy(),
- MetricTemplates: t.MetricTemplates.Copy(),
- TableTemplates: t.TableTemplates.Merge(other),
+ Shape: t.Shape,
+ Label: t.Label,
+ LabelPlural: t.LabelPlural,
+ Nodes: t.Nodes.Copy(),
+ Controls: t.Controls.Copy(),
+ MetadataTemplates: t.MetadataTemplates.Copy(),
+ MetricTemplates: t.MetricTemplates.Copy(),
+ PropertyListTemplates: t.PropertyListTemplates.Merge(other),
}
}
// WithShape sets the shape of nodes from this topology, returning a new topology.
func (t Topology) WithShape(shape string) Topology {
return Topology{
- Shape: shape,
- Label: t.Label,
- LabelPlural: t.LabelPlural,
- Nodes: t.Nodes.Copy(),
- Controls: t.Controls.Copy(),
- MetadataTemplates: t.MetadataTemplates.Copy(),
- MetricTemplates: t.MetricTemplates.Copy(),
- TableTemplates: t.TableTemplates.Copy(),
+ Shape: shape,
+ Label: t.Label,
+ LabelPlural: t.LabelPlural,
+ Nodes: t.Nodes.Copy(),
+ Controls: t.Controls.Copy(),
+ MetadataTemplates: t.MetadataTemplates.Copy(),
+ MetricTemplates: t.MetricTemplates.Copy(),
+ PropertyListTemplates: t.PropertyListTemplates.Copy(),
}
}
// WithLabel sets the label terminology of this topology, returning a new topology.
func (t Topology) WithLabel(label, labelPlural string) Topology {
return Topology{
- Shape: t.Shape,
- Label: label,
- LabelPlural: labelPlural,
- Nodes: t.Nodes.Copy(),
- Controls: t.Controls.Copy(),
- MetadataTemplates: t.MetadataTemplates.Copy(),
- MetricTemplates: t.MetricTemplates.Copy(),
- TableTemplates: t.TableTemplates.Copy(),
+ Shape: t.Shape,
+ Label: label,
+ LabelPlural: labelPlural,
+ Nodes: t.Nodes.Copy(),
+ Controls: t.Controls.Copy(),
+ MetadataTemplates: t.MetadataTemplates.Copy(),
+ MetricTemplates: t.MetricTemplates.Copy(),
+ PropertyListTemplates: t.PropertyListTemplates.Copy(),
}
}
@@ -125,14 +125,14 @@ func (t Topology) GetShape() string {
// Copy returns a value copy of the Topology.
func (t Topology) Copy() Topology {
return Topology{
- Shape: t.Shape,
- Label: t.Label,
- LabelPlural: t.LabelPlural,
- Nodes: t.Nodes.Copy(),
- Controls: t.Controls.Copy(),
- MetadataTemplates: t.MetadataTemplates.Copy(),
- MetricTemplates: t.MetricTemplates.Copy(),
- TableTemplates: t.TableTemplates.Copy(),
+ Shape: t.Shape,
+ Label: t.Label,
+ LabelPlural: t.LabelPlural,
+ Nodes: t.Nodes.Copy(),
+ Controls: t.Controls.Copy(),
+ MetadataTemplates: t.MetadataTemplates.Copy(),
+ MetricTemplates: t.MetricTemplates.Copy(),
+ PropertyListTemplates: t.PropertyListTemplates.Copy(),
}
}
@@ -148,14 +148,14 @@ func (t Topology) Merge(other Topology) Topology {
label, labelPlural = other.Label, other.LabelPlural
}
return Topology{
- Shape: shape,
- Label: label,
- LabelPlural: labelPlural,
- Nodes: t.Nodes.Merge(other.Nodes),
- Controls: t.Controls.Merge(other.Controls),
- MetadataTemplates: t.MetadataTemplates.Merge(other.MetadataTemplates),
- MetricTemplates: t.MetricTemplates.Merge(other.MetricTemplates),
- TableTemplates: t.TableTemplates.Merge(other.TableTemplates),
+ Shape: shape,
+ Label: label,
+ LabelPlural: labelPlural,
+ Nodes: t.Nodes.Merge(other.Nodes),
+ Controls: t.Controls.Merge(other.Controls),
+ MetadataTemplates: t.MetadataTemplates.Merge(other.MetadataTemplates),
+ MetricTemplates: t.MetricTemplates.Merge(other.MetricTemplates),
+ PropertyListTemplates: t.PropertyListTemplates.Merge(other.PropertyListTemplates),
}
}