Skip to content
This repository has been archived by the owner on Apr 28, 2020. It is now read-only.

Added top consumer card for storage dashboard #349

Merged
merged 1 commit into from
Apr 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sass/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
@import './components/CreateBaremetalHostDialog/create-baremetal-host-dialog';
@import './components/StorageOverview/ocs-health';
@import './components/StorageOverview/data-resiliency';
@import './components/StorageOverview/top-consumer';

/*
TODO: these styles should be backported to the corresponding PF-React package
Expand Down
9 changes: 9 additions & 0 deletions sass/components/StorageOverview/top-consumer.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.kubevirt-top-consumer__line-chart {
opacity: 1;
}

.kubevirt-top-consumer__time-duration {
float: right;
padding-right: 1rem;
font-size: 0.8em;
}
4 changes: 4 additions & 0 deletions src/components/StorageOverview/StorageOverview.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import EventsConnected from './Events/Events';
import { UtilizationConnected } from './Utilization/Utilization';
import { DataResiliencyConnected } from './DataResiliency/DataResiliency';
import { AlertsConnected } from './Alerts/Alerts';
import TopConsumersConnected from './TopConsumers/TopConsumers';

const MainCards = () => (
<GridItem lg={6} md={12} sm={12}>
Expand All @@ -34,6 +35,9 @@ const MainCards = () => (
<GridItem span={6}>
<DataResiliencyConnected />
</GridItem>
<GridItem span={12}>
<TopConsumersConnected />
</GridItem>
</Grid>
</GridItem>
);
Expand Down
101 changes: 101 additions & 0 deletions src/components/StorageOverview/TopConsumers/TopConsumers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React from 'react';
import PropTypes from 'prop-types';

import { LineChart, Row, Col } from 'patternfly-react';

import { InlineLoading } from '../../Loading';
import { formatToShortTime } from '../../../utils';

import {
DashboardCard,
DashboardCardBody,
DashboardCardHeader,
DashboardCardTitle,
} from '../../Dashboard/DashboardCard';
import { StorageOverviewContext } from '../StorageOverviewContext/StorageOverviewContext';
import { getTopConsumerVectorStats } from '../../../selectors/prometheus/storage';

const TopConsumersBody = ({ topConsumerStats }) => {
let results = 'No data available';

if (topConsumerStats.length) {
const columnsConf = getTopConsumerVectorStats(topConsumerStats);
const { columns, unit } = columnsConf;
const formatTime = x => formatToShortTime(x);

results = (
<React.Fragment>
<Row>
<Col className="kubevirt-top-consumer__time-duration">Last 6 hours</Col>
</Row>
<Row>
<Col lg={12} md={12} sm={12} xs={12}>
<LineChart
className="kubevirt-top-consumer__line-chart"
id="line-chart"
data={{
x: 'x',
columns,
type: 'line',
}}
axis={{
y: {
label: {
text: `Used Capacity(${unit})`,
position: 'outer-top',
},
min: 0,
padding: {
bottom: 3,
},
},
x: {
tick: {
format: formatTime,
fit: true,
values: [...columns[0].slice(1)],
},
},
}}
/>
</Col>
</Row>
</React.Fragment>
);
}

return <div>{results}</div>;
};

export const TopConsumers = ({ topConsumerStats, topConsumerLoaded, LoadingComponent }) => (
<DashboardCard>
<DashboardCardHeader>
<DashboardCardTitle>Top Projects by Used Capacity</DashboardCardTitle>
</DashboardCardHeader>
<DashboardCardBody isLoading={!topConsumerLoaded} LoadingComponent={LoadingComponent}>
<TopConsumersBody topConsumerStats={topConsumerStats} />
</DashboardCardBody>
</DashboardCard>
);

TopConsumersBody.propTypes = {
topConsumerStats: PropTypes.array.isRequired,
};

TopConsumers.defaultProps = {
topConsumerStats: [],
topConsumerLoaded: false,
LoadingComponent: InlineLoading,
};

TopConsumers.propTypes = {
topConsumerStats: PropTypes.array,
topConsumerLoaded: PropTypes.bool,
LoadingComponent: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
};

const TopConsumersConnected = () => (
<StorageOverviewContext.Consumer>{props => <TopConsumers {...props} />}</StorageOverviewContext.Consumer>
);

export default TopConsumersConnected;
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { TopConsumers } from '../TopConsumers';
import { InlineLoading } from '../../../Loading';

export const TopConsumerStats = [
{
topConsumerStats: [],
topConsumerLoaded: true,
LoadingComponent: InlineLoading,
},
{
topConsumerStats: [
{
metric: {
namespace: 'openshift-namespace',
},
values: [
[1554797100, '46161920'],
[1554797400, '46161920'],
[1554797700, '46161920'],
[1554798000, '46161920'],
[1554798300, '46161920'],
[1554798600, '46161920'],
],
},
],
topConsumerLoaded: true,
LoadingComponent: InlineLoading,
},
];

export default [
{
component: TopConsumers,
props: { ...TopConsumerStats },
},
{
component: TopConsumers,
name: 'Loading top consumers',
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import { render } from 'enzyme';

import { TopConsumers } from '../TopConsumers';
import { TopConsumerStats } from '../fixtures/TopConsumers.fixture';

describe('<TopConsumers />', () => {
it('no-data-renders-correctly', () => {
const component = render(<TopConsumers {...TopConsumerStats[0]} />);
expect(component).toMatchSnapshot();
});
it('data-renders-correctly', () => {
const component = render(<TopConsumers {...TopConsumerStats[1]} />);
expect(component).toMatchSnapshot();
});
it('render-correctly-on-loading', () => {
const component = render(<TopConsumers />);
expect(component).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<TopConsumers /> data-renders-correctly 1`] = `
<div
class="card-pf kubevirt-dashboard__card"
>
<div
class="card-pf-heading kubevirt-dashboard__card-heading"
>
<h2
class="card-pf-title"
>
Top Projects by Used Capacity
</h2>
</div>
<div
class="card-pf-body"
>
<div>
<div
class="row"
>
<div
class="kubevirt-top-consumer__time-duration"
>
Last 6 hours
</div>
</div>
<div
class="row"
>
<div
class="col-lg-12 col-md-12 col-sm-12 col-xs-12"
>
<div
class=" kubevirt-top-consumer__line-chart"
/>
</div>
</div>
</div>
</div>
</div>
`;

exports[`<TopConsumers /> no-data-renders-correctly 1`] = `
<div
class="card-pf kubevirt-dashboard__card"
>
<div
class="card-pf-heading kubevirt-dashboard__card-heading"
>
<h2
class="card-pf-title"
>
Top Projects by Used Capacity
</h2>
</div>
<div
class="card-pf-body"
>
<div>
No data available
</div>
</div>
</div>
`;

exports[`<TopConsumers /> render-correctly-on-loading 1`] = `
<div
class="card-pf kubevirt-dashboard__card"
>
<div
class="card-pf-heading kubevirt-dashboard__card-heading"
>
<h2
class="card-pf-title"
>
Top Projects by Used Capacity
</h2>
</div>
<div
class="card-pf-body"
>
<div>
<div
class="spinner spinner-md blank-slate-pf-icon"
/>
</div>
</div>
</div>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { persistentVolumes } from '../../../tests/mocks/persistentVolume';
import { osdDisksCount } from '../../../tests/mocks/disks';
import { cephDiskInaccessibleAlert, cephDataRecoveryAlert } from '../Alerts/fixtures/Alerts.fixture';

import { TopConsumerStats } from '../TopConsumers/fixtures/TopConsumers.fixture';

export const nodes = [localhostNode];
export const pvcs = persistentVolumeClaims;
export const pvs = persistentVolumes;
Expand All @@ -42,6 +44,7 @@ export default [
...utilizationStats,
...dataResiliencyData[0],
alertsResponse: [cephDiskInaccessibleAlert, cephDataRecoveryAlert],
...TopConsumerStats[1],
},
},
{
Expand Down
39 changes: 39 additions & 0 deletions src/selectors/prometheus/storage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { flatMap, max } from 'lodash';

import { parseNumber, formatBytes } from '../../utils';

export const getTopConsumerVectorStats = result => {
let maxVal = 0;

const namespaceValues = flatMap(result, namespace => namespace.values);
const allBytes = flatMap(namespaceValues, value => parseNumber(value[1]));

maxVal = max(allBytes);

const maxCapacityConverted = { ...formatBytes(maxVal) };

const yAxisData = result.map(r => [
r.metric.namespace,
...r.values.map(array => formatBytes(array[1], maxCapacityConverted.unit, 2).value),
]);

let largestLengthArray = 0;
let largestLengthArrayIndex = 0;

// timestamps count and values are not same for all the namespaces. Hence, finding the largest length array and taking its timestamps value as x-axis point
result.forEach((namespace, index) => {
const len = namespace.values.length;
if (len > largestLengthArray) {
largestLengthArray = len;
largestLengthArrayIndex = index;
}
});
const xAxisData = ['x', ...result[largestLengthArrayIndex].values.map(array => new Date(array[0] * 1000))];

const stats = {
columns: [xAxisData, ...yAxisData],
unit: maxCapacityConverted.unit,
};

return stats;
};
7 changes: 7 additions & 0 deletions src/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,10 @@ export const getDefaultNetworkBinding = networkType => {
return NETWORK_BINDING_MASQUERADE;
}
};

export const formatToShortTime = timestamp => {
const dt = new Date(timestamp);

// returns in HH:MM format
return dt.toString().substring(16, 21);
};