Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #24011 - Add Patternfly bar chart support #5718

Merged
merged 1 commit into from
Nov 5, 2018

Conversation

lizagilman
Copy link
Member

@lizagilman lizagilman commented Jun 20, 2018

To do:

  • fix legend
  • fix labels
  • add tests
  • add to storybook
  • possibly increase the size of the chart ?
  • align chart
  • move to chartbox
  • add tests to wrapper component

edit:

updated screenshot -

image

@theforeman-bot
Copy link
Member

Issues: #24011

@lizagilman
Copy link
Member Author

@ripcurld @amirfefer

@lizagilman lizagilman force-pushed the bar-chart branch 2 times, most recently from 2d1f6be to 3a5215a Compare June 20, 2018 11:43
Copy link
Member

@amirfefer amirfefer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @lizagilman -
I left some inline comments.

What do you think about adding the bar chart functionality to the ChartBox component? then we could use the chartbox component in the metrics page, and by that we keep the consistency from the Statistics page.

donutChartConfig,
donutLargeChartConfig,
barChartConfig,
} from './ChartService.consts';

const sizeConfig = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a size configuration anymore, could you rename?

@@ -29,6 +34,7 @@ const getChartConfig = ({
data, config, onclick, id = uuidV1(),
}) => {
const chartConfigForType = sizeConfig[config];

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant line

@@ -11,7 +11,8 @@
<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 id="status_chart" class='metrics-chart'></div>
<%= mount_react_component('BarChart', '#status_chart', {barChartData: status.to_a, label: "Number of Events"}.to_json) %>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's another BarChart in the dashboard (run distribution widget)


data: { ...chartConfig.data, type: 'bar' },

legend: { show: true },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the legend default should be false
looks odd with only one category

export const barChartConfig = {
...chartConfig,

data: { ...chartConfig.data, type: 'bar' },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have to add type: 'bar' prop? patternfly BarChart implementation adds it anyway

},
},

categories,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this here as well?


columns.unshift(label);

chartConfig.data.columns = [columns];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like the data structure doesn't support in groups/stacks

Copy link
Member Author

@lizagilman lizagilman Jul 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you referr to grouped bar chart?
Grouped bar chart expects the following structure: [category1, value1, value2, ...], [category2, value3, value4, ...]
Do we have anywhere data that can be used or manipulated to form this structure?
The 'regular' structure ( (DATA, VALUE) ) is good for the single bar (not grouped) chart, which expects only one value [category, value]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure.

@ohadlevy - would we like to support grouped chart?
grouped

@@ -52,6 +58,7 @@ const getChartConfig = ({
...chartConfigForType,
id,
data: {
...chartConfigForType.data,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if we'll remove {type: bar} from the config object, then we can remove this line.


columns.unshift(label);

chartConfig.data.columns = [columns];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have mixed feeling about this. looks like we modify columns property in two different places, if the data is calculated differently from donut chart, maybe it would be better to separate the logic between the two.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, isn't this separation (between the two functions) enough?

data,
onclick,
config = 'regularBar',
noDataMsg = __('No data available'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to use defaultProps and Proptypes

Copy link
Member

@amirfefer amirfefer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @lizagilman
please add yarn.lock to gitignore

@@ -67,6 +67,11 @@ def get_overview_json(report, options = {})

def render_run_distribution(hosts, options = {})
data = count_reports(hosts, options)
puts '*********************************************'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forgot to delete this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😅

package.json Outdated
@@ -96,7 +96,7 @@
"uuid": "^3.0.1"
},
"scripts": {
"lint": "./node_modules/.bin/eslint -c .eslintrc webpack/ script/ || exit 0",
"lint": "./node_modules/.bin/eslint -c .eslintrc webpack/ script/",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

@@ -29,7 +29,7 @@ export function templateSelected(item) {
$('#storage_volumes .children_fields >.fields').remove();
$.each(result.volumes, function () {
// Change variable name because 'interface' is a reserved keyword.
this.disk_interface = this['interface'];
this.disk_interface = this.interface;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well I do find this better syntax, but how does it related?

const BarChart = ({
data,
onclick,
noDataMsg = __('No data available'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could it be defined in the defaultProps?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it could be done when your jed pr (5342) is merged

BarChart.propTypes = {
config: PropTypes.string,
noDataMsg: PropTypes.string,
title: PropTypes.object,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use shape instead of just .object


const categories = data.map(dataItem => dataItem[0]);

const columns = data.map(x => x[1]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sharvit This ChartSevice becomes more and more complex, each render this will invoked, even if nothing changed.
what do you think about moving this logic to a selector (memorized if needed) by connect this component to the store, or its parent.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we actually move it into selectors? I think most of the data coming from the data prop and not from the state.
Does it actually lead to performance issue that we must solve in this particular PR?


columns.unshift(label);

chartConfig.data.columns = [columns];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure.

@ohadlevy - would we like to support grouped chart?
grouped

const configForType = {
regularDonut: donutChartConfig,
largeDonut: donutLargeChartConfig,
regularBar: barChartConfig,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, since there is only one size (regular) to BarChart, it is not a good idea to add it to the configForType (previously sizeConfig) object.

If we insist on doing that, maybe keep the name sizeConfig and nested it:

const sizeConfig = {
  donut: {
    regular: ...
    large: ...
  },
  bar: {
    ...
  }
}

const getChartConfig = ({
  ...
  type,
  config
}) = {
  chartConfigForType = sizeConfig[type][config]
  ...
}

...chartConfig,
size: barChartEnums.SIZE.REGULAR,

// data: { ...chartConfig.data, },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you forget to uncomment this? 😃

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😅

@lizagilman lizagilman force-pushed the bar-chart branch 5 times, most recently from cb15536 to 2add6cd Compare July 10, 2018 15:10
@coveralls
Copy link

coveralls commented Jul 10, 2018

Coverage Status

Coverage increased (+0.2%) to 75.891% when pulling 5b7dc0f on lizagilman:bar-chart into 882d451 on theforeman:develop.

@lizagilman lizagilman force-pushed the bar-chart branch 2 times, most recently from 277152f to d5d16b7 Compare July 12, 2018 12:52
def render_run_distribution(hosts, options = {})
data = count_reports(hosts, options)
flot_bar_chart("run_distribution", _("Minutes Ago"), _("Number Of Clients"), data, options)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layout/TrailingWhitespace: Trailing whitespace detected.

@lizagilman
Copy link
Member Author

lizagilman commented Jul 15, 2018

@amirfefer @ripcurld
pushed some updates
still need to work on the look of the charts

image

Copy link
Member

@amirfefer amirfefer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @lizagilman, looks great
Could you use ChartBox in the metrics page?


const BarChart = ({
data,
onclick,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some props missing in the proptype

.gitignore Outdated
@@ -48,3 +48,4 @@ public/webpack
package-lock.json
npm-debug.log
.vscode
yarn.lock
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you using yarn?

@lizagilman
Copy link
Member Author

lizagilman commented Oct 25, 2018

@amirfefer thanks addressed all your comments

@lizagilman
Copy link
Member Author

[test foreman]

@lizagilman
Copy link
Member Author

@sharvit @amirfefer any other comments here?

Copy link
Member

@amirfefer amirfefer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @lizagilman ! 👍
almost there, few minor issues :)

metricsData: { tableData, tableClasses, total },
} = props.data;

const createRow = (metric, i) => (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: please use array destructing here:

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

type="donut"
chart={{ data: metricsChartData, id: 'report-metrics' }}
title="Report Metrics"
id="report-metrics"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find any use of id prop in Chartbox

@@ -79,7 +94,7 @@ class ChartBox extends React.Component {
<Modal.Title>{this.props.title}</Modal.Title>
</Modal.Header>
<Modal.Body>
<Chart {...chartProps} config="large" />;
<Chart {...chartProps} config={this.props.config} />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick:
could you extract the props ?

const { config, title, className, status } = this.props

import PropTypes from 'prop-types';
import componentRegistry from '../componentRegistry';

const ComponentWrapper = (props) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

index.js meant for redux containers (connected components), I prefer to use componentWrapper.js as a name
Plus, shouldn't it be under common directory?

const { component, componentProps } = props.data;

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you add a test for this

}),
};

export default ConfigReports;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here (use ConfigReport.js instead of index.js)

import { translate as __ } from '../../common/I18n';
import { STATUS } from '../../constants';

const ConfigReports = (props) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you add a snapshot test for this component ?

@lizagilman
Copy link
Member Author

@amirfefer
updated, thanks!

@lizagilman
Copy link
Member Author

[test katello]

amirfefer
amirfefer previously approved these changes Oct 29, 2018
Copy link
Member

@amirfefer amirfefer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lizagilman Works and looks great !
thanks 👍

@lizagilman
Copy link
Member Author

@tstrachota can we merge?

Copy link
Member

@tstrachota tstrachota left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found one issue in code. Otherwise it looks good. I still need to test it live though.

@@ -22,6 +22,13 @@ def metric(m)
m.round(4) rescue _("N/A")
end

def metrics_table_data(metrics)
metrics.map do |title, value|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Result of this method is thrown away.

@lizagilman
Copy link
Member Author

thanks @tstrachota , updated

Copy link
Member

@tstrachota tstrachota left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this and it works well.

@tstrachota
Copy link
Member

Test failures are unrelated, merging. Thanks @lizagilman !

@tstrachota tstrachota merged commit 711cbdb into theforeman:develop Nov 5, 2018
@Ron-Lavi
Copy link
Member

Ron-Lavi commented Nov 5, 2018

Hey guys, the component wrapper looks really awesome !

though it seem not to work while the component uses Redux
when props that was received from the server
are named the same as props received from the store.
those props are received to the component as undefined.

my guess is that the mapStateToProps runs after the component wrapper call,
and it fetch data which wasn't initiated yet from the store.

I saw that on regular components it works.. so it seems that something happens when involving Redux mapStateToProps.

any tips how to solve this?
thanks

@amirfefer
Copy link
Member

@LaViro
have you used the wrapper on the unconnected component? I don't think we should wrap a connected component with this wrapper. @lizagilman any thoughts?

@Ron-Lavi
Copy link
Member

Ron-Lavi commented Nov 5, 2018

Tried to pass the unconnected component through componentRegistry and it worked great !
but than the component is not connected to Redux unfortunately..
If it is not the use case of this helper component then sorry for ruining the party 💃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.