fix monitoring elasticsearch nodes listing sort and filtering (#20321)

closes https://github.com/elastic/kibana/issues/20132
This commit is contained in:
Tim Sullivan 2018-07-02 10:11:44 -07:00 committed by GitHub
parent e1778497b9
commit f7070890d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 328 additions and 40 deletions

View file

@ -33,14 +33,14 @@ const metricVal = (metric, format, isPercent) => {
return formatMetric(metric, format);
};
function MetricCell({ isOnline, metric = {}, isPercent }) {
function MetricCell({ isOnline, metric = {}, isPercent, ...props }) {
if (isOnline) {
const { lastVal, maxVal, minVal, slope } = get(metric, 'summary', {});
const format = get(metric, 'metric.format');
return (
<KuiTableRowCell>
<div className="monitoringTableCell__MetricCell__metric">
<div className="monitoringTableCell__MetricCell__metric" data-test-subj={props['data-test-subj']}>
{ metricVal(lastVal, format, isPercent) }
</div>
<span className={`monitoringTableCell__MetricCell__slopeArrow fa fa-long-arrow-${getSlopeArrow(slope)}`} />

View file

@ -15,23 +15,23 @@ import { MetricCell, OfflineCell } from './cells';
import { EuiLink, EuiToolTip } from '@elastic/eui';
import { KuiTableRowCell, KuiTableRow } from '@kbn/ui-framework/components';
const filterFields = ['node.name', 'status', 'type', 'transport_address'];
const filterFields = ['name'];
const getColumns = showCgroupMetricsElasticsearch => {
const cols = [];
cols.push({ title: 'Name', sortKey: 'node.name', sortOrder: SORT_ASCENDING });
cols.push({ title: 'Status', sortKey: 'online' });
cols.push({ title: 'Name', sortKey: 'name', sortOrder: SORT_ASCENDING });
cols.push({ title: 'Status', sortKey: 'isOnline' });
if (showCgroupMetricsElasticsearch) {
cols.push({ title: 'CPU Usage', sortKey: 'node_cgroup_quota.lastVal' });
cols.push({ title: 'CPU Usage', sortKey: 'node_cgroup_quota' });
cols.push({
title: 'CPU Throttling',
sortKey: 'node_cgroup_throttled.lastVal'
sortKey: 'node_cgroup_throttled'
});
} else {
cols.push({ title: 'CPU Usage', sortKey: 'node_cpu_utilization.lastVal' });
cols.push({ title: 'Load Average', sortKey: 'node_load_average.lastVal' });
cols.push({ title: 'CPU Usage', sortKey: 'node_cpu_utilization' });
cols.push({ title: 'Load Average', sortKey: 'node_load_average' });
}
cols.push({ title: 'JVM Memory', sortKey: 'node_jvm_mem_percent.lastVal' });
cols.push({ title: 'Disk Free Space', sortKey: 'node_free_space.lastVal' });
cols.push({ title: 'JVM Memory', sortKey: 'node_jvm_mem_percent' });
cols.push({ title: 'Disk Free Space', sortKey: 'node_free_space' });
cols.push({ title: 'Shards', sortKey: 'shardCount' });
return cols;
};
@ -55,12 +55,14 @@ const nodeRowFactory = showCgroupMetricsElasticsearch => {
isOnline={isOnline}
metric={get(this.props, 'node_cgroup_quota')}
isPercent={true}
data-test-subj="cpuQuota"
/>,
<MetricCell
key="cpuCol2"
isOnline={isOnline}
metric={get(this.props, 'node_cgroup_throttled')}
isPercent={false}
data-test-subj="cpuThrottled"
/>
];
}
@ -70,12 +72,14 @@ const nodeRowFactory = showCgroupMetricsElasticsearch => {
isOnline={isOnline}
metric={get(this.props, 'node_cpu_utilization')}
isPercent={true}
data-test-subj="cpuUsage"
/>,
<MetricCell
key="cpuCol2"
isOnline={isOnline}
metric={get(this.props, 'node_load_average')}
isPercent={false}
data-test-subj="loadAverage"
/>
];
}
@ -84,7 +88,7 @@ const nodeRowFactory = showCgroupMetricsElasticsearch => {
if (this.isOnline()) {
return (
<KuiTableRowCell>
<div className="monitoringTableCell__number">
<div className="monitoringTableCell__number" data-test-subj="shards">
{get(this.props, 'shardCount')}
</div>
</KuiTableRowCell>
@ -108,12 +112,14 @@ const nodeRowFactory = showCgroupMetricsElasticsearch => {
<span className={`fa ${this.props.nodeTypeClass}`} />
</EuiToolTip>
&nbsp;
<EuiLink
href={`#/elasticsearch/nodes/${this.props.resolver}`}
data-test-subj={`nodeLink-${this.props.resolver}`}
>
{this.props.name}
</EuiLink>
<span data-test-subj="name">
<EuiLink
href={`#/elasticsearch/nodes/${this.props.resolver}`}
data-test-subj={`nodeLink-${this.props.resolver}`}
>
{this.props.name}
</EuiLink>
</span>
</div>
<div className="monitoringTableCell__transportAddress">
{extractIp(this.props.transport_address)}
@ -133,11 +139,13 @@ const nodeRowFactory = showCgroupMetricsElasticsearch => {
isOnline={isOnline}
metric={get(this.props, 'node_jvm_mem_percent')}
isPercent={true}
data-test-subj="jvmMemory"
/>
<MetricCell
isOnline={isOnline}
metric={get(this.props, 'node_free_space')}
isPercent={false}
data-test-subj="diskFreeSpace"
/>
{this.getShardCount()}
</KuiTableRow>

View file

@ -13,34 +13,204 @@ export default function ({ getService, getPageObjects }) {
const esClusterSummaryStatus = getService('monitoringElasticsearchSummaryStatus');
describe('Elasticsearch nodes listing', () => {
const { setup, tearDown } = getLifecycleMethods(getService, getPageObjects);
describe('with offline node', () => {
const { setup, tearDown } = getLifecycleMethods(getService, getPageObjects);
before(async () => {
await setup('monitoring/singlecluster-three-nodes-shard-relocation', {
from: '2017-10-05 20:31:48.354',
to: '2017-10-05 20:35:12.176',
before(async () => {
await setup('monitoring/singlecluster-three-nodes-shard-relocation', {
from: '2017-10-05 20:28:28.475',
to: '2017-10-05 20:34:38.341',
});
// go to nodes listing
await overview.clickEsNodes();
expect(await nodesList.isOnListing()).to.be(true);
});
// go to nodes listing
await overview.clickEsNodes();
expect(await nodesList.isOnListing()).to.be(true);
after(async () => {
await tearDown();
});
it('should have an Elasticsearch Cluster Summary Status with correct info', async () => {
expect(await esClusterSummaryStatus.getContent()).to.eql({
nodesCount: 'Nodes: 2',
indicesCount: 'Indices: 20',
memory: 'Memory: 696.6 MB / 1.3 GB',
totalShards: 'Total Shards: 79',
unassignedShards: 'Unassigned Shards: 7',
documentCount: 'Documents: 25,758',
dataSize: 'Data: 100.0 MB',
health: 'Health: yellow',
});
});
it('should have a nodes table with correct rows with default sorting', async () => {
const rows = await nodesList.getRows();
expect(rows.length).to.be(3);
const nodesAll = await nodesList.getNodesAll();
const tableData = [
{ name: 'whatever-01', status: 'Status: Online', cpu: '0%', load: '3.28', memory: '39%', disk: '173.9 GB', shards: '38', },
{ name: 'whatever-02', status: 'Status: Online', cpu: '2%', load: '3.28', memory: '25%', disk: '173.9 GB', shards: '38', },
{ name: 'whatever-03', status: 'Status: Offline' },
];
nodesAll.forEach((obj, node) => {
expect(nodesAll[node].name).to.be(tableData[node].name);
expect(nodesAll[node].status).to.be(tableData[node].status);
expect(nodesAll[node].cpu).to.be(tableData[node].cpu);
expect(nodesAll[node].load).to.be(tableData[node].load);
expect(nodesAll[node].memory).to.be(tableData[node].memory);
expect(nodesAll[node].disk).to.be(tableData[node].disk);
expect(nodesAll[node].shards).to.be(tableData[node].shards);
});
});
it('should sort by name', async () => {
await nodesList.clickNameCol();
await nodesList.clickNameCol();
const nodesAll = await nodesList.getNodesAll();
const tableData = [
{ name: 'whatever-01' },
{ name: 'whatever-02' },
{ name: 'whatever-03' },
];
nodesAll.forEach((obj, node) => {
expect(nodesAll[node].name).to.be(tableData[node].name);
});
});
it('should sort by status', async () => {
await nodesList.clickStatusCol();
await nodesList.clickStatusCol();
const nodesAll = await nodesList.getNodesAll();
const tableData = [
{ status: 'Status: Online' },
{ status: 'Status: Online' },
{ status: 'Status: Offline' },
];
nodesAll.forEach((obj, node) => {
expect(nodesAll[node].status).to.be(tableData[node].status);
});
});
it('should sort by cpu', async () => {
await nodesList.clickCpuCol();
await nodesList.clickCpuCol();
const nodesAll = await nodesList.getNodesAll();
const tableData = [{ cpu: '0%' }, { cpu: '2%' }, { cpu: undefined }];
nodesAll.forEach((obj, node) => {
expect(nodesAll[node].cpu).to.be(tableData[node].cpu);
});
});
it('should sort by load average', async () => {
await nodesList.clickLoadCol();
await nodesList.clickLoadCol();
const nodesAll = await nodesList.getNodesAll();
const tableData = [
{ load: '3.28' },
{ load: '3.28' },
{ load: undefined },
];
nodesAll.forEach((obj, node) => {
expect(nodesAll[node].load).to.be(tableData[node].load);
});
});
it('should sort by memory', async () => {
await nodesList.clickMemoryCol();
await nodesList.clickMemoryCol();
const nodesAll = await nodesList.getNodesAll();
const tableData = [
{ memory: '39%' },
{ memory: '25%' },
{ memory: undefined },
];
nodesAll.forEach((obj, node) => {
expect(nodesAll[node].memory).to.be(tableData[node].memory);
});
});
it('should sort by disk', async () => {
await nodesList.clickDiskCol();
await nodesList.clickDiskCol();
const nodesAll = await nodesList.getNodesAll();
const tableData = [
{ disk: '173.9 GB' },
{ disk: '173.9 GB' },
{ disk: undefined },
];
nodesAll.forEach((obj, node) => {
expect(nodesAll[node].disk).to.be(tableData[node].disk);
});
});
it('should sort by shards', async () => {
await nodesList.clickShardsCol();
await nodesList.clickShardsCol();
const nodesAll = await nodesList.getNodesAll();
const tableData = [
{ shards: '38' },
{ shards: '38' },
{ shards: undefined },
];
nodesAll.forEach((obj, node) => {
expect(nodesAll[node].shards).to.be(tableData[node].shards);
});
});
});
after(async () => {
await tearDown();
});
describe('with only online nodes', () => {
const { setup, tearDown } = getLifecycleMethods(getService, getPageObjects);
it('should have an Elasticsearch Cluster Summary Status with correct info', async () => {
expect(await esClusterSummaryStatus.getContent()).to.eql({
nodesCount: 'Nodes: 3',
indicesCount: 'Indices: 20',
memory: 'Memory: 575.3 MB / 2.0 GB',
totalShards: 'Total Shards: 80',
unassignedShards: 'Unassigned Shards: 5',
documentCount: 'Documents: 25,927',
dataSize: 'Data: 101.6 MB',
health: 'Health: yellow',
before(async () => {
await setup('monitoring/singlecluster-three-nodes-shard-relocation', {
from: '2017-10-05 20:31:48.354',
to: '2017-10-05 20:35:12.176',
});
// go to nodes listing
await overview.clickEsNodes();
expect(await nodesList.isOnListing()).to.be(true);
});
after(async () => {
await tearDown();
});
it('should have an Elasticsearch Cluster Summary Status with correct info', async () => {
expect(await esClusterSummaryStatus.getContent()).to.eql({
nodesCount: 'Nodes: 3',
indicesCount: 'Indices: 20',
memory: 'Memory: 575.3 MB / 2.0 GB',
totalShards: 'Total Shards: 80',
unassignedShards: 'Unassigned Shards: 5',
documentCount: 'Documents: 25,927',
dataSize: 'Data: 101.6 MB',
health: 'Health: yellow',
});
});
it('should filter for specific indices', async () => {
await nodesList.setFilter('01');
const rows = await nodesList.getRows();
expect(rows.length).to.be(1);
await nodesList.clearFilter();
});
it('should filter for non-existent index', async () => {
await nodesList.setFilter('foobar');
await nodesList.assertNoData();
await nodesList.clearFilter();
});
});
});
}

View file

@ -4,12 +4,36 @@
* you may not use this file except in compliance with the Elastic License.
*/
export function MonitoringElasticsearchNodesProvider({ getService/*, getPageObjects */ }) {
import { range } from 'lodash';
export function MonitoringElasticsearchNodesProvider({ getService, getPageObjects }) {
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['monitoring']);
const retry = getService('retry');
const SUBJ_LISTING_PAGE = 'elasticsearchNodesListingPage';
const SUBJ_TABLE_CONTAINER = 'elasticsearchNodesTableContainer';
const SUBJ_TABLE_NO_DATA = `${SUBJ_TABLE_CONTAINER} monitoringTableNoData`;
const SUBJ_SEARCH_BAR = `${SUBJ_TABLE_CONTAINER} monitoringTableToolBar`;
const SUBJ_TABLE_SORT_NAME_COL = `${SUBJ_TABLE_CONTAINER} tableHeaderCell-name`;
const SUBJ_TABLE_SORT_STATUS_COL = `${SUBJ_TABLE_CONTAINER} tableHeaderCell-status`;
const SUBJ_TABLE_SORT_CPU_COL = `${SUBJ_TABLE_CONTAINER} tableHeaderCell-cpuUsage`;
const SUBJ_TABLE_SORT_LOAD_COL = `${SUBJ_TABLE_CONTAINER} tableHeaderCell-loadAverage`;
const SUBJ_TABLE_SORT_MEM_COL = `${SUBJ_TABLE_CONTAINER} tableHeaderCell-jvmMemory`;
const SUBJ_TABLE_SORT_DISK_COL = `${SUBJ_TABLE_CONTAINER} tableHeaderCell-diskFreeSpace`;
const SUBJ_TABLE_SORT_SHARDS_COL = `${SUBJ_TABLE_CONTAINER} tableHeaderCell-shards`;
const SUBJ_TABLE_BODY = 'elasticsearchNodesTableBody';
const SUBJ_NODES_NAMES = `${SUBJ_TABLE_BODY} name`;
const SUBJ_NODES_STATUSES = `${SUBJ_TABLE_BODY} statusIcon`;
const SUBJ_NODES_CPUS = `${SUBJ_TABLE_BODY} cpuUsage`;
const SUBJ_NODES_LOADS = `${SUBJ_TABLE_BODY} loadAverage`;
const SUBJ_NODES_MEMS = `${SUBJ_TABLE_BODY} jvmMemory`;
const SUBJ_NODES_DISKS = `${SUBJ_TABLE_BODY} diskFreeSpace`;
const SUBJ_NODES_SHARDS = `${SUBJ_TABLE_BODY} shards`;
const SUBJ_NODE_LINK_PREFIX = `${SUBJ_TABLE_BODY} nodeLink-`;
return new class ElasticsearchIndices {
@ -22,5 +46,91 @@ export function MonitoringElasticsearchNodesProvider({ getService/*, getPageObje
return testSubjects.click(SUBJ_NODE_LINK_PREFIX + nodeResolver);
}
async clickNameCol() {
const headerCell = await testSubjects.find(SUBJ_TABLE_SORT_NAME_COL);
const button = await headerCell.findByTagName('button');
return button.click();
}
async clickStatusCol() {
const headerCell = await testSubjects.find(SUBJ_TABLE_SORT_STATUS_COL);
const button = await headerCell.findByTagName('button');
return button.click();
}
async clickCpuCol() {
const headerCell = await testSubjects.find(SUBJ_TABLE_SORT_CPU_COL);
const button = await headerCell.findByTagName('button');
return button.click();
}
async clickLoadCol() {
const headerCell = await testSubjects.find(SUBJ_TABLE_SORT_LOAD_COL);
const button = await headerCell.findByTagName('button');
return button.click();
}
async clickMemoryCol() {
const headerCell = await testSubjects.find(SUBJ_TABLE_SORT_MEM_COL);
const button = await headerCell.findByTagName('button');
return button.click();
}
async clickDiskCol() {
const headerCell = await testSubjects.find(SUBJ_TABLE_SORT_DISK_COL);
const button = await headerCell.findByTagName('button');
return button.click();
}
async clickShardsCol() {
const headerCell = await testSubjects.find(SUBJ_TABLE_SORT_SHARDS_COL);
const button = await headerCell.findByTagName('button');
return button.click();
}
getRows() {
return PageObjects.monitoring.tableGetRows(SUBJ_TABLE_BODY);
}
setFilter(text) {
return PageObjects.monitoring.tableSetFilter(SUBJ_SEARCH_BAR, text);
}
clearFilter() {
return PageObjects.monitoring.tableClearFilter(SUBJ_SEARCH_BAR);
}
assertNoData() {
return PageObjects.monitoring.assertTableNoData(SUBJ_TABLE_NO_DATA);
}
async getNodesAll() {
const names = await testSubjects.getVisibleTextAll(SUBJ_NODES_NAMES);
const statuses = await testSubjects.getPropertyAll(SUBJ_NODES_STATUSES, 'alt');
const cpus = await testSubjects.getVisibleTextAll(SUBJ_NODES_CPUS);
const loads = await testSubjects.getVisibleTextAll(SUBJ_NODES_LOADS);
const memories = await testSubjects.getVisibleTextAll(SUBJ_NODES_MEMS);
const disks = await testSubjects.getVisibleTextAll(SUBJ_NODES_DISKS);
const shards = await testSubjects.getVisibleTextAll(SUBJ_NODES_SHARDS);
// tuple-ize the icons and texts together into an array of objects
const tableRows = await this.getRows();
const iterator = range(tableRows.length);
return iterator.reduce((all, current) => {
return [
...all,
{
name: names[current],
status: statuses[current],
cpu: cpus[current],
load: loads[current],
memory: memories[current],
disk: disks[current],
shards: shards[current],
}
];
}, []);
}
};
}