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

Commit

Permalink
Added top consumer card for storage dashboard (#349)
Browse files Browse the repository at this point in the history
  • Loading branch information
gnehapk authored and rawagner committed Apr 15, 2019
1 parent a96be3a commit e59f281
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 0 deletions.
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);
};

0 comments on commit e59f281

Please sign in to comment.