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
Browse files Browse the repository at this point in the history
  • Loading branch information
gnehapk committed Apr 14, 2019
1 parent 59589e3 commit 0fd9058
Show file tree
Hide file tree
Showing 10 changed files with 237 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 @@ -33,6 +33,7 @@
@import './components/ClusterOverview/consumers';
@import './components/CreateBaremetalHostDialog/create-baremetal-host-dialog';
@import './components/StorageOverview/ocs-health';
@import './components/StorageOverview/top-consumer';

/*
TODO: these styles should be backported to the corresponding PF-React package
Expand Down
3 changes: 3 additions & 0 deletions sass/components/StorageOverview/top-consumer.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.kubevirt-storage-consumers-body {
opacity: 1;
}
4 changes: 4 additions & 0 deletions src/components/StorageOverview/StorageOverview.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import OCSHealthConnected from './OCSHealth/Health';
import { CapacityConnected } from './Capacity/Capacity';
import EventsConnected from './Events/Events';
import { UtilizationConnected } from './Utilization/Utilization';
import TopConsumersConnected from './TopConsumers/TopConsumers';

const MainCards = () => (
<GridItem lg={6} md={12} sm={12}>
Expand All @@ -22,6 +23,9 @@ const MainCards = () => (
<GridItem span={6}>
<CapacityConnected />
</GridItem>
<GridItem span={12}>
<TopConsumersConnected />
</GridItem>
</Grid>
</GridItem>
);
Expand Down
96 changes: 96 additions & 0 deletions src/components/StorageOverview/TopConsumers/TopConsumers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
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 = (
<Row>
<Col lg={12} md={12} sm={12} xs={12}>
<LineChart
className="kubevirt-storage-consumers-body"
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>
);
}

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,33 @@
import { TopConsumers } from '../TopConsumers';
import { InlineLoading } from '../../../Loading';

export const TopConsumerStats = {
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,14 @@
import React from 'react';
import { render } from 'enzyme';

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

const testTopConsumersOverview = () => <TopConsumers {...TopConsumerStats} />;

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

exports[`<TopConsumers /> 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="col-lg-12 col-md-12 col-sm-12 col-xs-12"
>
<div
class=" kubevirt-storage-consumers-body"
/>
</div>
</div>
</div>
</div>
</div>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { persistentVolumeClaims } from '../../../tests/mocks/persistentVolumeCla
import { persistentVolumes } from '../../../tests/mocks/persistentVolume';
import { osdDisksCount } from '../../../tests/mocks/disks';

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

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

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

export const getTopConsumerVectorStats = result => {
let maxVal = 0;
let flattenedResult = [];

// returns all the namespace.values in an array
flattenedResult = flatMap(result, namespace => namespace.values);
// returns only the bytes from the namespace.values in an array
flattenedResult = flatMap(flattenedResult, value => parseNumber(value[1]));

maxVal = max(flattenedResult);

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 0fd9058

Please sign in to comment.