Table-mode: sort ips numerically (#2007)

Fix #1746 - sort IPs numerically in the table mode
This commit is contained in:
Filip Barl
2016-11-22 11:05:59 +01:00
committed by GitHub
parent 7b0bf508dd
commit d15e884cb1
8 changed files with 150 additions and 14 deletions
+1
View File
@@ -8,6 +8,7 @@ _obj
_test
.vagrant
releases
tmp
# Architecture specific extensions/prefixes
*.[568vq]
@@ -0,0 +1,106 @@
import React from 'react';
import TestUtils from 'react/lib/ReactTestUtils';
import { Provider } from 'react-redux';
import configureStore from '../../../stores/configureStore';
// need ES5 require to keep automocking off
const NodeDetailsTable = require('../node-details-table.js').default;
describe('NodeDetailsTable', () => {
let nodes, columns, component;
beforeEach(() => {
columns = [
{ id: 'kubernetes_ip', label: 'IP', dataType: 'ip' },
{ id: 'kubernetes_namespace', label: 'Namespace' },
];
nodes = [
{
id: 'node-1',
metadata: [
{ id: 'kubernetes_ip', label: 'IP', value: '10.244.253.24' },
{ id: 'kubernetes_namespace', label: 'Namespace', value: '1111' },
]
}, {
id: 'node-2',
metadata: [
{ id: 'kubernetes_ip', label: 'IP', value: '10.244.253.4' },
{ id: 'kubernetes_namespace', label: 'Namespace', value: '12' },
]
}, {
id: 'node-3',
metadata: [
{ id: 'kubernetes_ip', label: 'IP', value: '10.44.253.255' },
{ id: 'kubernetes_namespace', label: 'Namespace', value: '5' },
]
}, {
id: 'node-4',
metadata: [
{ id: 'kubernetes_ip', label: 'IP', value: '10.244.253.100' },
{ id: 'kubernetes_namespace', label: 'Namespace', value: '00000' },
]
},
];
});
function matchColumnValues(columnLabel, expectedValues) {
// Get the index of the column whose values we want to match.
const columnIndex = columns.findIndex(column => column.id === columnLabel);
// Get all the values rendered in the table.
const values = TestUtils.scryRenderedDOMComponentsWithClass(component, 'node-details-table-node-value').map(d => d.title);
// Since we are interested only in the values that appear in the column `columnIndex`, we drop the rest.
// As `values` are ordered by appearance in the DOM structure (that is, first by row and then by column),
// the indexes we are interested in are of the form columnIndex + n * columns.length, where n >= 0.
// Therefore we take only the values at the index which divided by columns.length gives a reminder columnIndex.
const filteredValues = values.filter((element, index) => index % columns.length === columnIndex);
// Array comparison
expect(filteredValues).toEqual(expectedValues);
}
function clickColumn(title) {
const node = TestUtils.scryRenderedDOMComponentsWithTag(component, 'td').find(d => d.title === title);
TestUtils.Simulate.click(node);
}
describe('kubernetes_ip', () => {
it('sorts by column', () => {
component = TestUtils.renderIntoDocument(
<Provider store={configureStore()}>
<NodeDetailsTable
columns={columns}
sortBy="kubernetes_ip"
nodeIdKey="id"
nodes={nodes}
/>
</Provider>
);
matchColumnValues('kubernetes_ip', ['10.44.253.255', '10.244.253.4', '10.244.253.24', '10.244.253.100']);
clickColumn('IP');
matchColumnValues('kubernetes_ip', ['10.244.253.100', '10.244.253.24', '10.244.253.4', '10.44.253.255']);
clickColumn('IP');
matchColumnValues('kubernetes_ip', ['10.44.253.255', '10.244.253.4', '10.244.253.24', '10.244.253.100']);
});
});
describe('kubernetes_namespace', () => {
it('sorts by column', () => {
component = TestUtils.renderIntoDocument(
<Provider store={configureStore()}>
<NodeDetailsTable
columns={columns}
sortBy="kubernetes_namespace"
nodeIdKey="id"
nodes={nodes}
/>
</Provider>
);
matchColumnValues('kubernetes_namespace', ['00000', '1111', '12', '5']);
clickColumn('Namespace');
matchColumnValues('kubernetes_namespace', ['5', '12', '1111', '00000']);
clickColumn('Namespace');
matchColumnValues('kubernetes_namespace', ['00000', '1111', '12', '5']);
});
});
});
@@ -4,12 +4,19 @@ import classNames from 'classnames';
import ShowMore from '../show-more';
import NodeDetailsTableRow from './node-details-table-row';
import { ipToPaddedString } from '../../utils/string-utils';
function isNumber(data) {
return data.dataType && data.dataType === 'number';
}
function isIP(data) {
return data.dataType && data.dataType === 'ip';
}
const CW = {
XS: '32px',
S: '50px',
@@ -80,7 +87,10 @@ function getNodeValue(node, header) {
let field = _.union(node.metrics, node.metadata).find(f => f.id === fieldId);
if (field) {
if (isNumber(header)) {
if (isIP(header)) {
// Format the IPs so that they are sorted numerically.
return ipToPaddedString(field.value);
} else if (isNumber(header)) {
return parseFloat(field.value);
}
return field.value;
@@ -3,21 +3,30 @@ describe('StringUtils', () => {
const StringUtils = require('../string-utils');
describe('formatMetric', () => {
const formatMetric = StringUtils.formatMetric;
const f = StringUtils.formatMetric;
it('it should render 0', () => {
expect(formatMetric(0)).toBe('0.00');
expect(f(0)).toBe('0.00');
});
});
describe('longestCommonPrefix', () => {
const fun = StringUtils.longestCommonPrefix;
const f = StringUtils.longestCommonPrefix;
it('it should return the longest common prefix', () => {
expect(fun(['interspecies', 'interstellar'])).toBe('inters');
expect(fun(['space', 'space'])).toBe('space');
expect(fun([''])).toBe('');
expect(fun(['prefix', 'suffix'])).toBe('');
expect(f(['interspecies', 'interstellar'])).toBe('inters');
expect(f(['space', 'space'])).toBe('space');
expect(f([''])).toBe('');
expect(f(['prefix', 'suffix'])).toBe('');
});
});
describe('ipToPaddedString', () => {
const f = StringUtils.ipToPaddedString;
it('it should return the formatted IP', () => {
expect(f('10.244.253.4')).toBe('010.244.253.004');
expect(f('0.24.3.4')).toBe('000.024.003.004');
});
});
});
+10
View File
@@ -22,6 +22,11 @@ function renderSvg(text, unit) {
}
function padToThreeDigits(n) {
return `000${n}`.slice(-3);
}
function makeFormatters(renderFn) {
const formatters = {
filesize(value) {
@@ -75,6 +80,11 @@ export function longestCommonPrefix(strArr) {
return (new LCP(strArr)).lcp();
}
// Converts IPs from '10.244.253.4' to '010.244.253.004' format.
export function ipToPaddedString(value) {
return value.match(/\d+/g).map(padToThreeDigits).join('.');
}
// Formats metadata values. Add a key to the `formatters` obj
// that matches the `dataType` of the field. You must return an Object
// with the keys `value` and `title` defined.
+3 -3
View File
@@ -28,7 +28,7 @@ const (
var (
PodMetadataTemplates = report.MetadataTemplates{
State: {ID: State, Label: "State", From: report.FromLatest, Priority: 2},
IP: {ID: IP, Label: "IP", From: report.FromLatest, Priority: 3},
IP: {ID: IP, Label: "IP", From: report.FromLatest, Datatype: "ip", Priority: 3},
report.Container: {ID: report.Container, Label: "# Containers", From: report.FromCounters, Datatype: "number", Priority: 4},
Namespace: {ID: Namespace, Label: "Namespace", From: report.FromLatest, Priority: 5},
Created: {ID: Created, Label: "Created", From: report.FromLatest, Datatype: "datetime", Priority: 6},
@@ -39,8 +39,8 @@ var (
ServiceMetadataTemplates = report.MetadataTemplates{
Namespace: {ID: Namespace, Label: "Namespace", From: report.FromLatest, Priority: 2},
Created: {ID: Created, Label: "Created", From: report.FromLatest, Datatype: "datetime", Priority: 3},
PublicIP: {ID: PublicIP, Label: "Public IP", From: report.FromLatest, Priority: 4},
IP: {ID: IP, Label: "Internal IP", From: report.FromLatest, Priority: 5},
PublicIP: {ID: PublicIP, Label: "Public IP", From: report.FromLatest, Datatype: "ip", Priority: 4},
IP: {ID: IP, Label: "Internal IP", From: report.FromLatest, Datatype: "ip", Priority: 5},
report.Pod: {ID: report.Pod, Label: "# Pods", From: report.FromCounters, Datatype: "number", Priority: 6},
}
+2 -2
View File
@@ -148,7 +148,7 @@ var (
Label: "Services",
Columns: []Column{
{ID: report.Pod, Label: "# Pods", Datatype: "number"},
{ID: kubernetes.IP, Label: "IP"},
{ID: kubernetes.IP, Label: "IP", Datatype: "ip"},
},
},
},
@@ -172,7 +172,7 @@ var (
Columns: []Column{
{ID: kubernetes.State, Label: "State"},
{ID: report.Container, Label: "# Containers", Datatype: "number"},
{ID: kubernetes.IP, Label: "IP"},
{ID: kubernetes.IP, Label: "IP", Datatype: "ip"},
},
},
},
+1 -1
View File
@@ -106,7 +106,7 @@ func TestMakeDetailedHostNode(t *testing.T) {
Columns: []detailed.Column{
{ID: kubernetes.State, Label: "State"},
{ID: report.Container, Label: "# Containers", Datatype: "number"},
{ID: kubernetes.IP, Label: "IP"},
{ID: kubernetes.IP, Label: "IP", Datatype: "ip"},
},
Nodes: []detailed.NodeSummary{podNodeSummary},
},