-
Notifications
You must be signed in to change notification settings - Fork 10
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
[RW-4159][risk=no] Terms of Service UI and cleanup of create-account flow. #3075
Conversation
c16aad4
to
7444007
Compare
Hey @blrubenstein and @NehaBroad I'd like to get both of your pairs of eyes on this PR, since it affects code you've recently (or are currently) involved in. I opted not to do a "surgical insertion" of the TOS component here, since I felt there was some low-hanging fruit for cleanup. Recommended flow for reviewing this PR:
|
</div> | ||
</div> | ||
const backgroundImages = StepToImageConfig[this.state.currentStep]; | ||
return <FlexColumn style={styles.signInContainer} data-test-id='sign-in-container'> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dev note: it was quite a challenge to get the sign-in and Terms of Service components to be styled correctly to allow a "flexible" PDF panel with a sticky footer at the bottom to contain the checkboxes & next button. Ultimately, I took an approach of using nested FlexColumn components all the way down... this seemed to be minimally disruptive to the existing styling, though I haven't extensively tested this on very differently-sized screens. A bit of tire-kicking against my staged App Engine version would be very helpful here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't break it - looks pretty nice to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's a first pass of comments, I only made it about half-way through. Will get back to it this afternoon.
ui/src/app/components/flex.tsx
Outdated
// The object destructuring below serves two purposes: (1) allows users of this component to pass | ||
// on styles which will override default FlexRow styles, and (2) allows other props passed to | ||
// FlexRow to be passed onto the inner <div>. We exclude data-test-id from being passed on, since | ||
// passing it onto the <div> would cause both the FlexRow and the <div> component to have the same |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't gel with my understanding of React rendering, or maybe I'm misunderstanding how you're querying the structure via the dataTestId.
The comment here seems to imply that this React component results in two elements being rendered: a "FlexRow" and a "div", when in fact the div really is just the HTML rendering of the FlexRow component - and when rendered it's just a single div. Am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for flagging this! It pushed me to better understand what's going on here.
The Enzyme docs (link) and this issue (link) describe the pain point better: Enzyme searches through both the DOM and the component tree. For components where we splat properties onto the inner DOM element, we end up with Enzyme returning two objects for a given 'data-test-id'.
Now that I read the Enzyme team's understanding of the issue and their reasoning here, I'm realizing that it's not unreasonable to require a test writer to specify whether they want to access the component or the rendered DOM element – in fact, it's better that way.
The trick here will just be around socializing this behavior, and clarifying what our approach should be in writing tests. Effectively, if a bare '[data-test-id="foo"]' search is yielding multiple nodes, the fix is to either (1) prefix with the component name, e.g. 'Checkbox[data-test-id ...', (2) prefix with a node tag, e.g. 'div[data-test-id ...]', or (3) add a call to .hostNodes() to exclude non-DOM nodes from the result (though this breaks when using shallow rendering).
I updated the code & tests here to reflect that. I filed https://precisionmedicineinitiative.atlassian.net/browse/RW-4409 to track further cleanup which would have blown up the scope of this PR too much. I'll also share a quick PSA at the eng sync.
* This sets up the intersection listener which will change state when the user scrolls to the | ||
* end of the document. | ||
*/ | ||
handleLastPageRender() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private? (also for scrollToBottom?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done – sometimes I forget that real private methods are a thing now w/ Typescript...
loading='' | ||
className='terms-of-service__page' | ||
width={Math.max(500, this.props.windowSize.width * .75)} | ||
key={`page_${index + 1}`} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for adding this, I can't take any more log spam
renderAnnotationLayer={false} | ||
renderTextLayer={false} | ||
loading='' | ||
className='terms-of-service__page' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the __ a convention we've used elsewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it's somewhat-standard block-element-modifier approach (https://css-tricks.com/bem-101/). I'm totally willing to back off on that one, maybe switch this to "tos-pdf-page". There's probably not too much to be done in setting a direction there before we loop back on our whole CSS-in-JS versus inline styles story, anyway.
export class AccountCreation extends React.Component<AccountCreationProps, AccountCreationState> { | ||
private usernameCheckTimeout: NodeJS.Timer; | ||
|
||
constructor(props: AccountCreationProps) { | ||
if (props.profile.institutionalAffiliations.length !== 1) { | ||
throw new Error('Profile must be pre-allocated with 1 institutional affiliation.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this precondition. Is this for the new account creation logic, or the old?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for both. I am trying to codify what seemed to be happening in the existing code, around L204 (where a single object was pre-allocated in the institutionalAffiliations array), L232 (where various logic and form-rendering was applied based on the first element of this array), and L347 (old coordinates, where updateInstitutionalAffiliation is updating the first element).
The major change made in this PR is that this component takes the entire Profile object from props and uses that as the initial state value, rather than creating its own version of an empty profile (L185).
I thought this assertion might help clarify that, plus the requirement that an institutional affiliation entry must have been pre-allocated. But maybe it just confuses things more... happy for some guidance on what changes might help a reader better understand what's going on here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this seems fine. I think the only thing that may be confusing here is that typically this profile is going to be passed in an empty state. My first thought on reading this is that perhaps we're expecting the affiliation to already be filled in. A comment here explaining that it will typically be just a placeholder empty affiliation might make this more clear.
this.updateInstitutionAffiliation('role', ''); | ||
this.updateInstitutionAffiliation('institution', ''); | ||
this.updateInstitutionAffiliation('other', ''); | ||
this.setState(currentState => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: In general I think it is best practice to avoid directly mutating the innards of the state object, though this is probably a pretty innocuous case since it's happening in the state callback. I think the preferred approach here would be either lodash set
or via shallow copy:
return {
...currentState.profile.institutionalAffiliations[0],
role: '',
institution: '',
other: ''
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks – I switched to use lodash here, there were a couple other places where I wasn't doing this right too.
(Side note: I'm a little unsatisfied with lodash as our solution for this pattern. I think it's some combination of (1) the painful lack of type safety, and (2) the horrible state of lodash-fp documentation. A quick bit of reading suggests https://github.com/immerjs/immer might offer a type-safe solution with nearly as concise syntax. I may track this as a new-tech opportunity for the future.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My typical flow is:
- Read normal lodash docs
- Google lodash fp playground and poke at it until I can confirm understanding
Sometimes the mapping from lodash to a side-effect free functional interface is straightforward, other times it's very non-obvious.
Also there are some docs here, but I don't remember the quality:
https://gist.github.com/jfmengels/6b973b69c491375117dc
ui/src/app/pages/login/account-creation/account-creation-tos.spec.tsx
Outdated
Show resolved
Hide resolved
|
||
export const StepToImageConfig = { | ||
[SignInStep.LANDING]: { | ||
backgroundImgSrc: '/assets/images/login-group.png', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There used to be images on the account creation page - was removal intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I didn't intend to change any of the images. Is this something you're seeing when testing, or based on the code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind. Existing behavior and it matches the updated mocks.
</div> | ||
</div> | ||
const backgroundImages = StepToImageConfig[this.state.currentStep]; | ||
return <FlexColumn style={styles.signInContainer} data-test-id='sign-in-container'> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't break it - looks pretty nice to me.
…nfig value before rendering.
…rs in spec files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forgot to include: thanks @calbach for the helpful review!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, thanks for the thorough responses.
|
||
export const StepToImageConfig = { | ||
[SignInStep.LANDING]: { | ||
backgroundImgSrc: '/assets/images/login-group.png', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind. Existing behavior and it matches the updated mocks.
ui/src/app/pages/login/sign-in.tsx
Outdated
zipCode: '', | ||
}, | ||
institutionalAffiliations: [ | ||
// We only allow entering a single institutional affiliation from the creat account |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/creat/create/
export class AccountCreation extends React.Component<AccountCreationProps, AccountCreationState> { | ||
private usernameCheckTimeout: NodeJS.Timer; | ||
|
||
constructor(props: AccountCreationProps) { | ||
if (props.profile.institutionalAffiliations.length !== 1) { | ||
throw new Error('Profile must be pre-allocated with 1 institutional affiliation.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this seems fine. I think the only thing that may be confusing here is that typically this profile is going to be passed in an empty state. My first thought on reading this is that perhaps we're expecting the affiliation to already be filled in. A comment here explaining that it will typically be just a placeholder empty affiliation might make this more clear.
The main goal of this PR was to create a Terms of Service acknowledgement UI based on mocks from the linked Jira ticket.
(During review, I'll keep this branch pushed to my gjordan App Engine version.)
The resulting UI looks like this (click "Create Account", then enter invitation key, then you land on this page):
and the "Next" button is enabled once they've read & clicked the checkboxes:
I couldn't help but dig into a bit of clean-up on the overall structure of the sign-in component & the various sub-components it renders. I did a few things to try and clarify the flow of data / control through the components, and improve our ability to adapt the set of sign-up forms over time. (This is partly in anticipation of launch: we're going to add a new "institutional affiliation" step, and remove the "invitation key check" step.)
The clean-up work significantly blew up the size of this PR – sorry about that! In return, I added a bunch of tests.
PR checklist