Skip to content

Commit

Permalink
Fixes #24011 - Add Patternfly bar chart support
Browse files Browse the repository at this point in the history
  • Loading branch information
lizagilman committed Oct 25, 2018
1 parent 6044a04 commit 5b7dc0f
Show file tree
Hide file tree
Showing 30 changed files with 721 additions and 106 deletions.
10 changes: 10 additions & 0 deletions app/assets/stylesheets/charts.scss
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,16 @@
padding-bottom: 40px;
}

.report-chart {
height: 405px;
}

.host-configuration-chart {
width: 240px;
margin: 25px auto 12px;
padding-bottom: 40px;
}

#host-show {
.statistics-chart {
height: 230px;
Expand Down
6 changes: 3 additions & 3 deletions app/helpers/dashboard_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ def get_overview_json(report, options = {})
].to_json
end

def render_run_distribution(hosts, options = {})
data = count_reports(hosts, options)
flot_bar_chart("run_distribution", _("Minutes Ago"), _("Number Of Clients"), data, options)
def get_run_distribution_data(hosts, options = {})
data = count_reports(hosts, options).to_a
data.map { |label, value| [label.to_s, value] }
end

def searchable_links(name, search, counter)
Expand Down
7 changes: 7 additions & 0 deletions app/helpers/reports_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ def metric(m)
m.round(4) rescue _("N/A")
end

def metrics_table_data(metrics)
metrics.sort.map do |title, value|
metric value
end
metrics.to_a
end

def report_tag(level)
tag = case level
when :notice
Expand Down
46 changes: 10 additions & 36 deletions app/views/config_reports/_metrics.html.erb
Original file line number Diff line number Diff line change
@@ -1,38 +1,12 @@
<% @totaltime = metrics.delete('total') %>
<% metrics.delete_if{ |k,v| v < 0.001 } %>
<div class="row">
<div class="col-md-4">
<div class="stats-well">
<h4 class="ca" ><%= _('Report Metrics') %></h4>
<div id="metrics_chart" class='metrics-chart'></div>
<%= mount_react_component('DonutChart', '#metrics_chart', metrics.to_a.to_json) %>
</div>
</div>
<div class="col-md-4">
<div class="stats-well">
<h4 class="ca" ><%= _('Report Status') %></h4>
<%= flot_bar_chart("status" ,"", _("Number of Events"), status, :class => "statistics-bar")%>
</div>
</div>
<div class="col-md-4">
<table class="<%= table_css_classes %>" style="height: 400px;">
<tbody>
<% metrics.sort.each do |title, value|%>
<tr>
<td class="break-me">
<%= title %>
</td>
<td>
<%= metric value %>
</td>
</tr>
<% end %>
</tbody>
<tfoot>
<tr>
<th><%= _("Total") %></th><th><%= metric (@totaltime || @config_report.runtime) %></th>
</tr>
</tfoot>
</table>
</div>
</div>

<div id="config-reports">
<%= mount_react_component('ConfigReports', '#config-reports', {
metricsChartData: metrics.to_a,
statusChartData: status.to_a,
metricsData: { tableData: metrics_table_data(metrics),
total: metric(@totaltime || @config_report.runtime),
tableClasses: table_css_classes }
}.to_json)
%>
10 changes: 9 additions & 1 deletion app/views/dashboard/_distribution_widget.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,12 @@
<%= N_('Run Distribution Chart') %>
<% end %>
</h4>
<%= render_run_distribution(@data.hosts, :class => 'statistics-bar', :origin => origin) %>
<div id="run_distribution"></div>
<%= mount_react_component('ComponentWrapper', '#run_distribution', {
component: 'BarChart',
componentProps: {
data: get_run_distribution_data(@data.hosts, :origin => origin),
xAxisLabel: _("Minutes Ago"),
yAxisLabel: _("Number Of Clients"),
config: 'small'
}}.to_json) %>
2 changes: 1 addition & 1 deletion app/views/dashboard/_status_chart_widget.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<%= title = _('Host Configuration Chart') %>
<% end %>
</h4>
<div id="<%= widget_chart_id %>">
<div id="<%= widget_chart_id %>" class="host-configuration-chart">
</div>
<%= mount_react_component('DonutChart', "##{widget_chart_id}", get_overview_json(@data.report))%>
<ul class="ui-helper-hidden">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import React from 'react';
import componentRegistry from '../componentRegistry';
import ComponentWrapper from './';

jest.mock('../componentRegistry');

describe('ComponentWrapper', () => {
it('should render core component', () => {
componentRegistry.getComponent = jest.fn(() => ({ type: 'AwesomeComponent' }));

const wrapper = shallow(<ComponentWrapper data={{ component: 'AwesomeComponent' }} />);

expect(toJson(wrapper)).toMatchSnapshot();
});

it('should not render unregistered component', () => {
const render = () => {
componentRegistry.getComponent = jest.fn(() => undefined);
shallow(<ComponentWrapper data={{ component: 'NotAwesomeComponent' }} />);
};

expect(render).toThrow(Error);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ComponentWrapper should render core component 1`] = `<AwesomeComponent />`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import componentRegistry from '../componentRegistry';

const ComponentWrapper = (props) => {
const { component, componentProps } = props.data;

if (component === 'ComponentWrapper') {
throw new Error('Cannot wrap componenet wrapper');
}

const registeredComponent = componentRegistry.getComponent(component);

if (!registeredComponent) {
throw new Error('Component name is missing!');
}

const Component = registeredComponent.type;

return <Component {...componentProps} />;
};

ComponentWrapper.propTypes = {
data: PropTypes.shape({
componentProps: PropTypes.object,
component: PropTypes.string.isRequired,
}).isRequired,
};

export default ComponentWrapper;
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Row, Col } from 'patternfly-react';
import cx from 'classnames';
import ChartBox from '../statistics/ChartBox';
import { translate as __ } from '../../common/I18n';
import { STATUS } from '../../constants';

const ConfigReports = (props) => {
const { metricsChartData, statusChartData, metricsData } = props.data;

const createRow = (metric, i) => (
<tr key={i}>
<td className="break-me">{metric[0]}</td>
<td>{metric[1]}</td>
</tr>
);

const chartBoxProps = {
className: 'report-chart',
noDataMsg: __('No data available'),
status: STATUS.RESOLVED,
config: 'medium',
};

return (
<Row>
<Col md={4}>
<ChartBox
{...chartBoxProps}
type="donut"
chart={{ data: metricsChartData, id: 'report-metrics' }}
title="Report Metrics"
id="report-metrics"
/>
</Col>

<Col md={4}>
<ChartBox
{...chartBoxProps}
type="bar"
chart={{ data: statusChartData, id: 'report-status' }}
title="Report Status"
id="report-status"
/>
</Col>
<Col md={4}>
<table className={cx(metricsData.tableClasses, 'report-chart')}>
<tbody>{metricsData.tableData.map((metric, i) => createRow(metric, i))}</tbody>
<tfoot>
<tr>
<th>{__('Total')}</th>
<th>{metricsData.total}</th>
</tr>
</tfoot>
</table>
</Col>
</Row>
);
};

ConfigReports.propTypes = {
data: PropTypes.shape({
metricsChartData: PropTypes.array,
statusChartData: PropTypes.array,
metricsData: PropTypes.shape({
tableData: PropTypes.array,
total: PropTypes.number,
tableClasses: PropTypes.string,
}),
}),
};

export default ConfigReports;
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
export const barChartData = {
data: [
['Fedora 21', 3],
['Ubuntu 14.04', 4],
['Centos 7', 2],
['Debian 8', 1],
],
xAxisLabel: 'OS',
yAxisLabel: 'COUNT',
};

export const barChartConfig = {
data: {
columns: [['Number of Events', 3, 4, 2, 1]],
type: 'bar',
},
color: {
pattern: ['#0088ce', '#ec7a08', '#3f9c35', '#005c66', 'f9d67a', '#703fec'],
},
tooltip: {
format: {},
},
legend: {
show: true,
},
padding: null,
size: {
width: 240,
height: 240,
},
id: 'operatingsystem',
axis: {
x: {
categories: ['Fedora 21', 'Ubuntu 14.04', 'Centos 7', 'Debian 8'],
type: 'category',
},
},
categories: ['Fedora 21', 'Ubuntu 14.04', 'Centos 7', 'Debian 8'],
};

export const barChartConfigWithEmptyData = {
...barChartConfig,
data: {
columns: [],
type: 'bar',
},
};

export const emptyData = null;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import BarChart from './';
import { barChartData } from './BarChart.fixtures';

storiesOf('Components/Charts', module)
.add('Bar Chart', () => <BarChart {...barChartData} />);
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import React from 'react';
import BarChart from './';
import * as chartService from '../../../../../services/charts/BarChartService';
import { barChartConfig, barChartData, emptyData, barChartConfigWithEmptyData } from './BarChart.fixtures';

jest.unmock('./');
describe('renders BarChart', () => {
it('render bar chart', () => {
chartService.getBarChartConfig = jest.fn(() => barChartConfig);
const wrapper = shallow(<BarChart data={barChartData.data} />);

expect(toJson(wrapper)).toMatchSnapshot();
});

it('render empty state', () => {
chartService.getBarChartConfig = jest.fn(() => barChartConfigWithEmptyData);
const wrapper = shallow(<BarChart data={emptyData} />);

expect(toJson(wrapper)).toMatchSnapshot();
});
});
Loading

0 comments on commit 5b7dc0f

Please sign in to comment.