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

TextualSummaries: part of client side rendering with React. #3420

Merged
merged 4 commits into from
May 28, 2018

Conversation

martinpovolny
Copy link
Member

@martinpovolny martinpovolny commented Feb 16, 2018

The goal is to implement a top level component for client-side rendering of textual summaries. Such component can then consume a JSON endpoint and later the API.

Extracted PRs

TODO

  • tests (snapshot testing)

  • links

  • images in general (see e.g. http://localhost:3000/ems_cluster/show/10000000000032 missing img)

  • (will be fixed in a separate PR) PDF rendering: replace with full-screen display with browser print?

  • (dropped) preprocess inlined images (needed in PDF rendering, so we may drop it)

  • TextualCustom: 2x in physical server: textual_firmware_table, textual_network_adapter_table

  • TextualCustom: in app/helpers/vm_helper/textual_summary.rb: textual_normal_operating_ranges

  • TextualListview: in ops/ vmdb tables

  • TextualMultilabel -- seems to be a superset of TextualTable with sorting and value expanding
    (expanding used in app/helpers/container_build_helper/textual_summary.rb)

  • TextualMultilink
    app/helpers/ems_cluster_helper/textual_summary.rb: ret.blank? ? nil :
    TextualMultilink.new(("OpenStack Status"), :items => ret)
    app/helpers/host_helper/textual_summary.rb: TextualMultilink.new(
    ("OpenStack Service Status"),
    :items => textual_generate_openstack_status)
    (FIXME: specs)

  • TextualTable (andible playbok job plays, security group firewall rules) (scurity group, network router)

  • (dropped) :spinner => true used only once in app/helpers/vm_helper/textual_summary.rb in textual_genealogy can we drop it?

  • :no_value => true -- only used in GO, can we drop it? (introduced in Follow-up for 2846 #2852 I don't understand that change)

Next (separate PRs)

Copy link
Member

@ohadlevy ohadlevy left a comment

Choose a reason for hiding this comment

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

Thanks @martinpovolny - I just did a really quick code review - more inline:


$(function () {
const summary = $('#textual_summary').data('summary');
ReactDOM.render(<TextualSummary summary={summary} />, document.getElementById('textual_summary'));
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 the mounter helper instead ?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, will do that

Copy link
Member Author

Choose a reason for hiding this comment

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

cannot be done right now, unless the component uses only data:

https://github.com/ManageIQ/manageiq-ui-classic/blob/master/app/javascript/react/componentRegistry.js#L43

will wait with this change until that is fixed

Copy link
Contributor

Choose a reason for hiding this comment

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

Should be resolved in #3228, reviewing tomorrow..

@@ -0,0 +1,30 @@
import * as React from 'react';
Copy link
Member

Choose a reason for hiding this comment

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

as a conversion, please just use .js, no need for .jsx extension imho.

Copy link
Member Author

Choose a reason for hiding this comment

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

Well. I am not sure if this is a good idea. @himdel, @karelhala, @skateman, @Hyperkid123: what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

To be honest I like to use .jsx for react specific code, that way you can immediately see that it's react component and nothing else.

I would even recommend using this ${cmp_name}.component.jsx for components, ${action_name}.action.js for redux actions and ${reducer_name}.reducer.js for reducers, but that is my preference and no need to do it that way.

Copy link
Contributor

@himdel himdel Feb 19, 2018

Choose a reason for hiding this comment

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

Same here, better explicit than implicit :)

Copy link
Member

Choose a reason for hiding this comment

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

I disagree :) but thats OK.. there is an ongoing discussion in the foreman
community about how to structure this (and redux etc) at
theforeman/foreman#5240

Copy link
Contributor

@Hyperkid123 Hyperkid123 Feb 19, 2018

Choose a reason for hiding this comment

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

If we were using just React i would be fine with .js, but i think it would be better to use .jsx in our case.

import PropTypes from 'prop-types';
import GenericTableRow from './generic_table_row';

export default class GenericGroup extends React.Component {
Copy link
Member

Choose a reason for hiding this comment

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

this can probably be written as a const, e.g.:

const GenericGroup = ({ title, items }) => (
  <table className="table table-bordered table-hover table-striped table-summary-screen">
    <thead>
      <tr>
        <th colSpan="2" align="left">
          {title}
        </th>
      </tr>
    </thead>
    <tbody>{items.map((item, i) => <GenericTableRow key={i} item={item} />)}</tbody>
  </table>
);

export default GenericGroup;

and probably consider using patternfly-react table component.

Copy link
Member Author

@martinpovolny martinpovolny Feb 19, 2018

Choose a reason for hiding this comment

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

this can probably be written as a const,

yes, the ones where there'll be just the render and no other code I'll convert to functions. (I guess you mean "use function rather than class").

and probably consider using patternfly-react table component.

not in the repo at this point, but yes, I will take a look if it's usable in this context


renderIcon() {
// %i{:class => item[:icon], :title => item[:title]}
return <i className={this.props.icon} title={this.props.title} />;
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 patternfly-react icon component.

Copy link
Member Author

Choose a reason for hiding this comment

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

Not now, later when we include patternfly-react in manageiq, we can do it.

Copy link
Member

Choose a reason for hiding this comment

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

TBH I think we should just add it, no point in writing the code twice...

Copy link
Member Author

Choose a reason for hiding this comment

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

"writing the code twice..." -- you mean the single line ;-).

I will take a look at a later point. It's not a 5 minutes job. After 5 minutes of searching I found the component in patternfly-react. Figured it's not in the storybook https://rawgit.com/patternfly/patternfly-react/gh-pages/index.html. Have to figure what that means, hopefully it does not mean it should not be used.

Then I figured it's actually 2 components: https://github.com/patternfly/patternfly-react/blob/master/src/components/Icon/Icon.js and the resulting markup seems different. So have to check if it renders that same.

All that work is to prevent writing <i className={this.props.icon} title={this.props.title} />; Probably not. That does not match my sense of effective work.

In this PR I am trying to start this task by moving the rendering to the client side while making sure that the textual summaries render as they did before. W/o extra changes if possible.

Later we can change the markup and/or styling. In a separate PR or at least a separate commit.

Copy link
Member

Choose a reason for hiding this comment

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

I've added patternfly/patternfly-react#234 to track the missing storybook.

the two components are simply because we support two type of components (patternfly icons and font-awesome)
using i assume its only font-awesome?

to use it you can simply do

import { Icon } from 'patternfly-react';

...
export default = () => (
  <Icon type='fa' name='react' />
);

image: null,
icon: null,
};

Copy link
Member

Choose a reason for hiding this comment

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

can be written as:

import * as React from 'react';
import PropTypes from 'prop-types';

/**
 * Render icon or an image with a title
 */
const IconOrImage = ({ icon, title, image }) => {
  const renderIcon = () => <i className={icon} />;

  const renderImage = () => <img src={image} alt={title} title={title} />;

  return image ? renderIcon() : renderImage();
};

IconOrImage.propTypes = {
  title: PropTypes.string,
  image: PropTypes.string,
  icon: PropTypes.string,
};

IconOrImage.defaultProps = {
  title: null,
  image: null,
  icon: null,
};

export default IconOrImage;

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, I will move the methods inside the class (I did that in some places already).

But the line return image ? renderIcon() : renderImage(); is not the same as my original code. This simplification would not work.

Copy link
Member

Choose a reason for hiding this comment

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

sorry i did not test the code, what did not work? how about

return (image === null ?  renderIcon() : renderImage());

Copy link
Member Author

Choose a reason for hiding this comment

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

no ;-)

in case of both not present I need an empty string. And nested ternary is discouraged so I have that if

package.json Outdated
@@ -54,6 +54,8 @@
"babel-polyfill": "^6.23.0",
"babel-preset-env": "~1.4.0",
"babel-preset-react": "^6.24.1",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-1": "^6.24.1",
Copy link
Member

Choose a reason for hiding this comment

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

are those required?

Copy link
Member Author

@martinpovolny martinpovolny Feb 19, 2018

Choose a reason for hiding this comment

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

const renderIcon = () => <i className={icon} />;

Yes. W/o those 2 lines (and the change to .babelrc) this code would not compile ^^^

Copy link
Member

Choose a reason for hiding this comment

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

what was the error? stage-1 should normally not be required.

Copy link
Member Author

Choose a reason for hiding this comment

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

    at transpile (/home/martin/Projects/manageiq-ui-classic/node_modules/babel-loader/lib/index.js:63:13)
    at Object.module.exports (/home/martin/Projects/manageiq-ui-classic/node_modules/babel-loader/lib/index.js:163:20)
 @ ./app/javascript/react/textual_summary/textual_group.jsx 20:17-39
 @ ./app/javascript/react/textual_summary/textual_row.jsx
 @ ./app/javascript/react/textual_summary/index.jsx
 @ ./app/javascript/packs/application-common.js

ERROR in ./app/javascript/react/textual_summary/icon_or_image.jsx
Module build failed: SyntaxError: Unexpected token (12:13)

  10 |   }
  11 | 
> 12 |   renderIcon = () => <i className={this.props.icon} title={this.props.title} />;
     |              ^

diff --git a/.babelrc b/.babelrc
index 9d3ea8b..936c5c8 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,5 +1,6 @@
 {
-  "presets": [["env", { "modules": false }], "react", "es2015", "stage-1"],
+  //"presets": [["env", { "modules": false }], "react", "es2015", "stage-1"],
+  "presets": [["env", { "modules": false }], "react", "es2015"],
   "env": {
     "test": {
       "plugins": ["transform-es2015-modules-commonjs"]

Copy link
Member Author

Choose a reason for hiding this comment

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

The problem is it's inside the class, so I need class fields. I can change the code to use methods instead or make it a function, not a class.

Copy link
Contributor

Choose a reason for hiding this comment

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

The missing feature is called class fields: https://github.com/tc39/proposal-class-fields

class Foo {
  renderIcon = 5;
}

Currently at stage3 .. so maybe we just need to bump es2015 to a newer version..

Copy link
Contributor

@himdel himdel Feb 19, 2018

Choose a reason for hiding this comment

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

Looks like maybe we should just be using env (and react) with some settings to target IE9+,
and maybe include this particual feature..

Copy link
Member Author

@martinpovolny martinpovolny Feb 19, 2018

Choose a reason for hiding this comment

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

We need to figure the best way ;-)

Figuring the best way, I have some notes:

I like some of the poweful expressivity of the functional notation. But there are some parts that are really ugly inside JSX:

  • the contional inclusion of markup: (condition && (<your_crap_here>))
            {(index === 0) && (                                                                     
              <td rowSpan={item.value.length} className="label">{item.label}</td>                   
            )}   
  • case construct (hash + lookup):
      {{                                                                                            
          'shared/summary/textual': (                                                                 
            <GenericGroup items={props.group.items} title={props.group.title} />                      
          ),                                                                                          
          'shared/summary/textual_tags': (                                                            
            <TagGroup items={props.group.items} title={props.group.title} />                          
          ),                                                                                          
          'shared/summary/textual_table': (                                                           
            <SimpleTable items={props.group.items} title={props.group.title} />                       
          ),                                                                                          
        }[props.group.partial]} 
  • I am glad for the <React.Fragment>. That helps.

Overall the JSX seems to be just another way to write markup and not particularly better than HAML. How many do we need?

@ohadlevy
Copy link
Member

ohadlevy commented Feb 19, 2018 via email

@martinpovolny
Copy link
Member Author

martinpovolny commented Feb 28, 2018

@AparnaKarve : can you, please, tell me, why the :no_value key was added in
#2852 ? What was the problem before that PR? Thx!

@AparnaKarve
Copy link
Contributor

no_value is to render a table row with a single cell with no associated column or value.
So far it has been used only in the Generic Objects context.

In the image below, notice the Attributes, Associations and Methods table, that do not have a value.
All we need is a single cell that displays - No Attributes defined

screen shot 2018-02-28 at 4 16 44 pm

@martinpovolny
Copy link
Member Author

no_value is to render a table row with a single cell with no associated column or value.
So far it has been used only in the Generic Objects context.

Thanks, @AparnaKarve. I saw that much it the code ;-) But I don't understand why we need a separate attribute :no_value. Why don't we just check :value for emptyness. Can you explain that, please?

Thanks!

@martinpovolny
Copy link
Member Author

@AparnaKarve : to be more explicit: What was the motivation behind the PR that introduced the new attribute? Why testing just :value was not enough?

I'd like to remove this feature if it's not really needed, but there's no test that would fail if I do nor do I understand it when reading the PR.

Thx!

@himdel
Copy link
Contributor

himdel commented Mar 1, 2018

@martinpovolny the babel stuff ..

diff --git a/.babelrc b/.babelrc
index 9d3ea8b30..83a481bc7 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,5 +1,6 @@
 {
-  "presets": [["env", { "modules": false }], "react", "es2015", "stage-1"],
+  "presets": [["env", { "modules": false }], "react"],
+  "plugins": ["transform-class-properties", "transform-object-rest-spread"],
   "env": {
     "test": {
       "plugins": ["transform-es2015-modules-commonjs"]
diff --git a/package.json b/package.json
index 394df1c49..77e5ed098 100644
--- a/package.json
+++ b/package.json
@@ -54,12 +54,11 @@
     "babel-jest": "^22.0.0",
     "babel-loader": "~7.0",
     "babel-plugin-syntax-dynamic-import": "^6.18.0",
-    "babel-plugin-transform-class-properties": "^6.24.1",
+    "babel-plugin-transform-class-properties": "~6.24.1",
+    "babel-plugin-transform-object-rest-spread": "~6.26.0",
     "babel-polyfill": "^6.23.0",
-    "babel-preset-env": "~1.4.0",
-    "babel-preset-es2015": "^6.24.1",
+    "babel-preset-env": "^1.6.1",
     "babel-preset-react": "^6.24.1",
-    "babel-preset-stage-1": "^6.24.1",
     "coffee-loader": "~0.7.3",
     "coffee-script": "~1.12.5",
     "compression-webpack-plugin": "~0.4.0",

(and making a TODO for myself to update to babel7)

@AparnaKarve
Copy link
Contributor

AparnaKarve commented Mar 2, 2018

Why don't we just check :value for emptyness

That's what I thought too and even adjusted the code to check for existence of :value
5e2b5a6 (PR #2846)

But turns out that a nil value or a blank value could be a valid value in certain cases - in which case we need to render the blank value cell.
(I think the Foreman configured systems summary screen has such examples)

@martinpovolny
Copy link
Member Author

martinpovolny commented Mar 2, 2018

@AparnaKarve : Can we have a nil value render nothing and an empty string to be rendered as empty string?

The 5e2b5a6 that you referenced is what turned out not to work? If so then it is pretty counter-intuitive that it does not work. I would suggest having it documented in a test or at least add a source code comment. Can you remember what the problem was?

Taking it from the other side: why cannot the GO behave the same way, why is GO the only entity that needs a special behavior? Would it be a problem if the behavior was consistent?

I mean before you changes we already had 2 behaviors for handling empty values:

  1. empty groups are not rendered at all so instead of a header "Associations" and text "No Associations defined" there would simply be nothing. That would be consistent with all the other summary pages.

  2. The helper functions that render links to association also can handle a link to an empty list.

For some reason, it seems that you implemented a 3rd way:

  def textual_group_associations                                                                    
    if @record.property_associations.count > 0                                                      
      TextualGroup.new(_("Associations"), associations)                                             
    else                                                                                            
      TextualGroup.new(_("Associations"), %i(associations_none))                                    
    end                                                                                             
  end 

So this is completely inconsistend with all the other textual summaries. It's a new type of behavior.

If I accept that then looking at the code path that one has to go to display:

+----------------------------------------+
| Associations...........................|
+----------------------------------------+
| No Associations defined................|
+----------------------------------------+

Is pretty obscure. I mean the functionality to display this is added to the most complex path in the TextualSummary rendering.

So what next?

I suggest that:

  1. I remove the functionality.
  2. You then help me understand what where the requirements or reasons behind the different behavior of GO compared to all the other places.
  3. If it really cannot behave the same as the other places, I get it working again.
  4. or you show me the places that get broken (somewhere under Foreman)

This way we should end up with less code, more tests and more consistend UX.

@martinpovolny
Copy link
Member Author

martinpovolny commented Mar 2, 2018

@AparnaKarve : I also need you help with this: #3492

It's another area where the behavior is different to the other summaries. It's hard for me to fix that, because I have no data for the physical infra. Given you reviewed it, can you show me how you tested/debugged? I mean do you have a gist or script or database, that I could use? I don't see those two special partials covered in any tests so I need some help.

@miq-bot
Copy link
Member

miq-bot commented Mar 2, 2018

This pull request is not mergeable. Please rebase and repush.

@AparnaKarve
Copy link
Contributor

I remove the functionality.
You then help me understand what where the requirements or reasons behind the different behavior of GO compared to all the other places.

GO instances need to display Attributes, Associations and Methods.
If these don't exist, then we need to explicitly say so.

Not showing data because it does not exist can be confusing for the user - I think that's what the BZ was about.
The No Attributes defined text clearly conveys that there are no Attributes defined for the GO instance.

or you show me the places that get broken (somewhere under Foreman)

With 5e2b5a6, certain tables in Foreman Configured Systems summary page looked like this without the value column, which is incorrect.

(Bad)
screen shot 2018-03-02 at 4 16 39 pm

Compare the above with this -

(Good)
screen shot 2018-03-02 at 4 23 37 pm


because I have no data for the physical infra

6_0_dc_MBU_demo_01202018.backup from Dan's folder should have all the data for physical infra.
Look up record# 55 (/physical_server/show/55)

@martinpovolny
Copy link
Member Author

@AparnaKarve : I have redone the empty groups a bit. I created a trivial component to display those, rather than having that behavior mixed onto the complex table.

I found that the behavior is different in GO vs GO definitions. A am not sure that is desired. In my eyes it's yet another inconsistency.

I did some extra fixing:

  • I fixed the broken i18n, (but meanwhile @mzazrivec fixed that in master so I effectively removed those changes when rebasing).
  • I removed some methods, 2 where broken anyway (returning identity where each was used in place of map).

Overall it should work better that before and your review is welcome.

@AparnaKarve
Copy link
Contributor

I created a trivial component to display those

Thanks. I was going to suggest that as well.

the behavior is different in GO vs GO definitions

Non-admin users cannot view a GO definition.
Hence for them, information like "No Attributes/Associations/Methods defined" can be quite useful.

Admins, on the other hand, can view the GO definitions.

However, for the sake of consistency, we could also add "No Attributes/Associations/Methods defined" for GO definitions.

@martinpovolny martinpovolny force-pushed the reactive_textual branch 2 times, most recently from 91ae5f6 to ae83ad6 Compare March 12, 2018 08:55
@martinpovolny
Copy link
Member Author

martinpovolny commented Mar 12, 2018

@AparnaKarve : I figured one of the Physical summary files is unused and the 2nd can be trivially replaced with a generic one: Please, check the 2 that mention "physical" close to the end.
p.s. It should also fix the i18n in the summary.

@AparnaKarve
Copy link
Contributor

AparnaKarve commented Mar 13, 2018

The TextualTable implementation looks nice and clean.

The unused one is a result of this PR #3398.
The network adapters (aka guest devices) now have a view of their own, which means they don't need to be displayed in a table in the Physical Server Summary screen anymore.

@miq-bot
Copy link
Member

miq-bot commented Mar 13, 2018

This pull request is not mergeable. Please rebase and repush.

@miq-bot
Copy link
Member

miq-bot commented May 16, 2018

This pull request is not mergeable. Please rebase and repush.

@skateman
Copy link
Member

When you open an openstack computenode's (infra -> hosts) summary screen, the boolean value isn't properly displayed for the introspected field:
Before:
screenshot from 2018-05-24 14-35-23
After:
screenshot from 2018-05-24 14-34-58

@skateman
Copy link
Member

With configuration profiles this one:
screenshot from 2018-05-24 15-38-51

@skateman
Copy link
Member

And configured systems:
screenshot from 2018-05-24 15-40-12

@miq-bot
Copy link
Member

miq-bot commented May 28, 2018

Checked commits martinpovolny/manageiq-ui-classic@89fcb55~...f90b7f3 with ruby 2.3.3, rubocop 0.52.1, haml-lint 0.20.0, and yamllint 1.10.0
54 files checked, 1 offense detected

**

  • 💣 💥 🔥 🚒 - Linter/Haml - Linter::Haml STDERR:
warning: parser/current is loading parser/ruby23, which recognizes
warning: 2.3.6-compliant syntax, but you are running 2.3.3.
warning: please see https://github.com/whitequark/parser#compatibility-with-ruby-mri.

Copy link
Member

@skateman skateman left a comment

Choose a reason for hiding this comment

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

The Seal of Approval

@martinpovolny
Copy link
Member Author

ping @mzazrivec, @himdel : please, merge

(this is going to break already broken printing even more, but that must be fixed separately)

@skateman
Copy link
Member

I'm working on the printing right now, let's get this in so I can test it properly 😉

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.

9 participants