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

Initial skeleton of GIBCT #4803

Merged
merged 7 commits into from
Mar 1, 2017
Merged

Initial skeleton of GIBCT #4803

merged 7 commits into from
Mar 1, 2017

Conversation

ben-USDS-damman
Copy link
Contributor

Since I initially worked on the frontend we've planned design and UX changes. After talking it over with @saneshark on Friday I created a new clean branch off of master, built a skeleton based on the blue button and facility location code, and began layering on my previous work.

As I do this I'm cleaning it up and making sure it comports to the work @melwoodard and others have done on the new design and content. It's mostly copy-paste and then making any necessary changes to form/function.

Seeking feedback on the general code structure and also wondering how I can make this use the new site-wide commonStore instead of its own store. @crwallace? I tried to do it based on blue button but couldn't get it to work. #newb

image

image

@ben-USDS-damman ben-USDS-damman self-assigned this Feb 6, 2017
@va-bot va-bot temporarily deployed to vetsgov-pr-4803 February 6, 2017 15:05 Inactive
bshyong
bshyong previously requested changes Feb 6, 2017
Copy link
Contributor

@bshyong bshyong left a comment

Choose a reason for hiding this comment

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

This generally looks good! I left some notes on how to get the common store working; let me know if you have any questions or issues when trying to get it to work.

Also, AppContent should be removed if users aren't logging in to use GIBCT

return null;
}

const headerDisplacementCSS = `
Copy link
Contributor

Choose a reason for hiding this comment

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

It is possible to use these styles by having them in a class? instead of inline?

Copy link
Contributor Author

@ben-USDS-damman ben-USDS-damman Feb 6, 2017

Choose a reason for hiding this comment

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

Possible, but I wanted to make it explicit that I'm affecting rendering outside the visible boundaries of the component without actually touching DOM nodes outside the component. Not possible.

import Breadcrumbs from '../components/Breadcrumbs';
import AboutThisTool from '../components/AboutThisTool';

function AppContent({ children, isDataAvailable }) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If veterans do not need to be registered to use this tool, we can get rid of the AppContent wrapper. It is meant to be used in conjunction with a RequiredLoginView component that wraps the entire thing (and calls AppContent after the user logs in successfully).

renderProfilePageModals() {
return (
<span>
<Modal onClose={() => this.props.hideModal()} visible={!!this.props.giModals.retention}>
Copy link
Contributor

Choose a reason for hiding this comment

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

for these onClose function with no parameters, I believe you can do onClose={this.props.hideModal}; no need to wrap it in a function

@@ -0,0 +1,8 @@
import { createStore, applyMiddleware, compose } from 'redux';
Copy link
Contributor

Choose a reason for hiding this comment

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

We can use the common store for this instead of having a separate store for GIBCT

import { createHistory } from 'history';
import initReact from '../common/init-react';
import routes from './routes';
import { store } from './store';
Copy link
Contributor

Choose a reason for hiding this comment

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

Use the common store import { commonStore } from '../common/store';

To allow it to work, you also need to go to common/reducers/index.js and import your GIBCT reducers.
https://github.com/department-of-veterans-affairs/vets-website/blob/master/src/js/common/reducers/index.js

Then, in your GIBCT app, in the components that use state you can access them like so (depends on where you put GIBCT in the common store reducer):

This is an example from blue button

const mapStateToProps = (state) => {
  const bbState = state.health.bb;

  return {
    form: bbState.form,
  };
};

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh duh - that's what I was missing - updating common reducers. Thank you!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As stated in other comments we are not going to be using commonStore.

@crwallace
Copy link
Contributor

Hey @ben-USDS-damman -- Do we need to use commonStore for this? Right now we are only moving apps that need login into commonStore.

@ben-USDS-damman
Copy link
Contributor Author

@crwallace Was under the impression we wanted to move over to commonStore when it was practical and I'd like to complete it now vs. defer to "later." (What are we trying to avoid by only switching the apps that require login? Some downside I'm not aware of?)

@crwallace
Copy link
Contributor

We are trying to use the store for attributes that might need to be shared across various apps. Since GIBCT is a stand alone app it doesn't really need to be in the commonStore. We also want to not bloat the store if we don't need to. Is there are future phase where GIBCT uses user data for some kind of custom content?

@typesend
Copy link

typesend commented Feb 6, 2017

Nope. Thanks for clarifying. We won't use commonStore.

@ben-USDS-damman
Copy link
Contributor Author

Addresses previous comments by @bshyong. I'm in the middle of completing the profile page and will be pushing that tonight.

image

image

@bshyong bshyong dismissed their stale review February 22, 2017 22:04

Will review again after profile page and other changes are pushed

@ben-USDS-damman
Copy link
Contributor Author

Please take special note of what I'm doing with reselect as it represents the pattern I'm following for the calculator, outcome graph calculations, and looks like the best way to deal with query param manipulations.


export function fetchConstants() {
const previewVersion = ''; // TODO
const url = `${api.url}/calculator/constants?preview=${previewVersion}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

Adding a note to remember to remove the temporary hack in config.js later on



/**
* A form checkbox with a label that can display error messages.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to use the common component for Errorable Checkbox here instead of a specific one for GIBCT?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The Checkbox and RadioButtons components were copied here to make that possible in a future iteration, but my needs were simpler and significantly different such that I didn't want to tackle generalizing common components right away. (Aside from making it easy to diff to what the diffs are between the common and gibct components.) I thought about using inheritance but that seemed less practical and would've introduced a dependency on the common components that I'm not sure is a good idea.

Copy link
Contributor Author

@ben-USDS-damman ben-USDS-damman Feb 24, 2017

Choose a reason for hiding this comment

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

  • the concepts of required fields and error messages do not apply to GIBCT
  • click handlers on the common components also seemed like overkill and I frankly didn't understand why they were implemented the way they are, i.e. {value: x, dirty: true}. I'm sure there's a good reason but it felt like the reason was maybe only well-suited to more complicated forms and schema forms elsewhere on the site.

Copy link
Contributor

@jbalboni jbalboni Feb 25, 2017

Choose a reason for hiding this comment

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

For what it's worth, the value and dirty pattern is there to support showing validation errors only after a field has been interacted with (i.e. it's dirty). Definitely not useful if you don't have required or error messages.

Those components were originally built for HCA and forms like what the Rainbows team is building, but we're moving away from using these particular components now (the schema form work). So there's probably some room in the future to have a simpler set of components that fit more cleanly in apps that just have a few input fields.


import ToolTip from '../../common/components/form-elements/ToolTip';
import ExpandingGroup from '../../common/components/form-elements/ExpandingGroup';

Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to use the common Radio Button component?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(The Checkbox and RadioButtons components were copied here to make that possible in a future iteration, but my needs were simpler and significantly different such that I didn't want to tackle generalizing common components right away. (Aside from making it easy to diff to what the diffs are between the common and gibct components.) I thought about using inheritance but that seemed less practical and would've introduced a dependency on the common components that I'm not sure is a good idea.)


const getEligibilityDetails = (state) => {
const details = Object.assign({}, state.eligibility);
delete details.dropdowns;
Copy link
Contributor

Choose a reason for hiding this comment

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

why is the dropdowns attribute deleted? no change needed, just wondering

Copy link
Contributor

Choose a reason for hiding this comment

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

The use of reselect looks fine to me. Because some of the attribute calculations are fairly complicated, reselect lets us only recalculate when needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

details.dropdowns holds the shown/not-shown state of the eligibility interdependent dropdowns and isn't used in the calculation. IOW the code for the visible/hidden presentation of the eligibility dropdowns is separate from the code that determines what chosen values become inputs to the calculation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My approach is to try to make inputs as explicit/obvious as possible - like I did for getRequiredAttributes in the estimator.js selector.

Copy link
Contributor

@U-DON U-DON left a comment

Choose a reason for hiding this comment

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

Just noticed a few places that are probably failing the lint and the use of certain components. Some minor things.

* `onValueChange` - a function with this prototype: (newValue)
* `required` - boolean. Render marker indicating field is required.
*/
class Checkbox extends React.Component {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why create a duplicate class rather than just reuse the already existing ErrorableCheckbox? Seems virtually the same except for the renamed onValueChange prop.

* `value` - string. Value of the select field.
* `onValueChange` - a function with this prototype: (newValue)
*/
class RadioButtons extends React.Component {
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar question about this as the Checkbox component. Seems like a copy of ErrorableRadioButtons with a few changes. It seems like this is to avoid using field objects in the form of { value: <string>, dirty: <boolean> } created by the makeField function.

import { connect } from 'react-redux';
import * as actions from '../../actions';

import If from '../If';
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the linter will fail if you're not using things you import. In this case, doesn't seem like you're using If.

};

const mapStateToProps = (state) => state;
const mapDispatchToProps = (dispatch) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

There's a shorthand for this where you don't have to manually map each action's name to a dispatch-wrapped version of itself. Also, since you only needed exitPreviewMode, you import it by itself.

import { exitPreviewMode } from '../../actions';

...

const mapDispatchToProps = {
  exitPreviewMode
};

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wow, nice of redux to make that work!

@@ -0,0 +1,24 @@
import React from 'react';

class If extends React.Component {
Copy link
Contributor

Choose a reason for hiding this comment

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

Rather than create an If component, I think it's more idiomatic to either (1) conditionally assign JSX to variables or (2) just inline the conditions. https://facebook.github.io/react/docs/conditional-rendering.html

// Conditionally assigning JSX.

let conditionalSection;

if (condition) {
  conditionalSection = <div>This section appears because of condition.</div>;
}

return (
  <div>
    <span>This always appears.</span>
    {conditionalSection}
  </div>
);
// Inlining conditional sections.

function renderConditionalSection() {
  return <div>This section appears because of condition.</div>;
}

function render() {
  return (
    <div>
      <span>This always appears.</span>
      {condition && renderConditionalSection()}
    </div>
  );
}

From this SO answer: "As for the If component you asked about, one problem it has is that in its current form it will evaluate its children even if the condition is false. This can lead to errors when the body of the If only makes sense if the condition is true."

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I seriously hate this If component, but it was a necessity when porting over legacy code.

return (
<div className="search-result">
<div className="outer">
{this.cautionFlag.bind(this)()}
Copy link
Contributor

Choose a reason for hiding this comment

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

Usually we just bind every method in the constructor and don't have to deal with fixing references to this later.

constructor(props) {
  super(props);
  this.cautionFlag = this.cautionFlag.bind(this);
  this.place = this.place.bind(this);
  this.renderEstimate = this.renderEstimate.bind(this);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -0,0 +1,21 @@
import environment from '../common/helpers/environment';
Copy link
Contributor

Choose a reason for hiding this comment

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

This file doesn't need the jsx suffix, since it doesn't use any React, so can it just be config.js.

inProgress: false,
previewVersion: null,
search_term: '',
facility_code: null,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are these two keys snake_case while the first two are camelCase? In any case, linter will fail on the snake_cased keys, so they should be changed to camelCase.

If it's because the data received from the API would need to be transformed into the proper casing, you can actually just add the header X-Key-Inflection: 'camel' to the requests.

const getRequiredAttributes = (_state, props) => {
const { type, bah } = props;
return { type, bah };
};
Copy link
Contributor

Choose a reason for hiding this comment

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

If the two above get* functions are getting used in a few different modules, could they be in utils/helpers.js and imported to where they're needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are truly private functions that will be different across selectors.

// The monthly benefit rate for non-chapter 33 benefits
const isOJT = its.type === 'ojt';
const isFlight = its.type === 'flight';
const n = Number(your.number_of_dependents);
Copy link
Contributor

Choose a reason for hiding this comment

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

Here's a neat little shorthand for Number conversions:

const n = +your.number_of_dependents.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice!

@typesend
Copy link

typesend commented Feb 25, 2017 via email

Copy link
Contributor

@U-DON U-DON left a comment

Choose a reason for hiding this comment

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

Left a few comments, but otherwise looks good. I notice similar things as my last review for things that would trip the linter. Be sure to run npm run lint to capture all the errors to fix before the tests pass.

const expanded = this.state.expanded;
return (
<li>
<button onClick={this.toggle.bind(this)} className="usa-accordion-button" aria-expanded={expanded} aria-controls="amendment-1">
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 bound in the constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

return (
<p>
<strong>Veterans tuition policy:&nbsp;</strong>
<a href={`http://${it.vet_tuition_policy_url}`} target="_blank">View policy</a>
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to just always use https here, or is it just not available?

}
}

const mapStateToProps = (state, props) => state;
Copy link
Contributor

Choose a reason for hiding this comment

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

Typically if the component only needs a part of the state, you want to pare down the parts of the store you want to translate to the component's props. In this case, you're only going to be using profile.attributes.

<tspan x="220" y={y}>{text}</tspan>
</text>
);
const AverageMark = ({percent, text}) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Other than ValueBar, these components don't directly use Graph's props. They could probably be defined outside of the class, so that they don't get initialized on each render. Then for ValueBar, this.props.max could just be passed in as an arg.

function normalizedAttributes(attributes) {
const name = attributes.name ? attributes.name.toUpperCase() : attributes.name;
const city = attributes.city ? attributes.city.toUpperCase() : attributes.city;
const state = attributes.state ? attributes.state.toUpperCase() : attributes.state;
Copy link
Contributor

Choose a reason for hiding this comment

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

For these, you could just do something like:

const name = attributes.name && attributes.name.toUpperCase().

@va-bot va-bot temporarily deployed to dev February 28, 2017 10:35 Inactive
@va-bot va-bot temporarily deployed to dev February 28, 2017 11:15 Inactive
@va-bot va-bot temporarily deployed to dev February 28, 2017 20:15 Inactive
Copy link
Contributor

@U-DON U-DON left a comment

Choose a reason for hiding this comment

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

LGTM. The main things that can be fixed after are moving away from commonStore and usage of some components.

@typesend
Copy link

This code does NOT use commonStore at all. To be clear.

@saneshark
Copy link
Contributor

Looks like the build is passing. @ben-USDS-damman do you still need to add profile updates or are those in? Since this unauthed can we triple check and make sure it is behind a feature toggle and not publicly visible unless you know the URL before we merge?

@typesend
Copy link

typesend commented Mar 1, 2017

It's in the ignoreList AND the rev proxy is sending traffic to the old rails app still anyway - something we need to change for dev/staging if we want to look at this there.

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

Successfully merging this pull request may close these issues.

None yet

8 participants