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

Themes: Refactor themes-selection #2444

Merged
merged 1 commit into from
Jan 27, 2016
Merged

Themes: Refactor themes-selection #2444

merged 1 commit into from
Jan 27, 2016

Conversation

ockham
Copy link
Contributor

@ockham ockham commented Jan 14, 2016

tl;dr: Instead of passing down a gradually more bound getButtonOptions() function, invoke it fully inside main.jsx already, and make its getUrl and action properties functions that accept theme as an argument. Then, invoke those functions inside the Theme and MoreButton components, i.e. only at the very bottom of the component hierarchy. Also, use the return value from getButtonOptions() to pass some other props more cleanly. Why? Been an illegible mess on the brink of unmaintenable.


Removes the theme argument from getButtonOptions(), as returned from theme-options. This way, getButtonOptions() returns an object that is much easier to work with than the bound functions it previously returned. Instead, that object's (nested) getUrl and action properties are now bound functions that accept theme and are invoked at the very leaves of the component hierarchy -- in the 'More' button menu, when the latter's entries are rendered. (If this turns out to affect performance negatively, we might want to tackle #1599 as a follow-up to this PR.)

This means no more expensive JSX .bind()s along that chain. It also means we have to pass an entire theme object down to the 'More' button, instead of just select attributes, as getUrl and action functions rely on helpers that use all different theme object properties. Note that we also had a hack in place that re-created a theme object from individual properties, and that this PR now also gets rid of.

Furthermore, that bundled information on theme actions is used inside main.jsx to pass data more cleanly down to descendant components. For example, action and label for the theme screenshot are now set there (instead of passing down customize and togglePreview actions as props individually, and letting the Theme component itself decide on the labelling. Ugh.).

No visual changes. To test -- check that the theme showcase works as before. Also check the logged-out signup flow since it also uses ThemesList.

Some more possible refactoring items for future PRs:

  • For components that are meant for use as elements in a list (such as Theme inside ThemesList), avoiding JSX .bind()s quite naturally leads to a pattern where the list (parent) component passes two different kind of data to the element (child) component: 1 -- the individual element data (the theme object in this case), and 2 -- a bunch of functions that accept the element data as an argument (e.g. onScreenshotClick), and are invoked by the element component. However, this is still a bit inconsistent as of this PR, as some of those functions are already invoked at ThemesList's level.
  • Instead of passing actions to theme-options, we might import the actions module there, and only pass dispatch from main.
  • We might want to give ThemesSiteSelectorModal an isVisible prop (instead of conditionally rendering it via { this.isThemeOrActionSet() && <ThemesSiteSelectorModal ... /> }). There used to be some issues with this approach in the past (as ThemesSiteSelectorModal renders Theme, so possibly due to missing props), but we might want to re-visit this.
  • One neat way to assign screenshot action/label/URL at Theme component level would be by just picking the appropriate action object from buttonOptions, i.e. by passing the action name (e.g. 'preview') as a string for key lookup. This would somewhat minimize duplicated props. (For the special case of Theme components that don't need a more button menu (signup flow!), we could pass just one option in buttonOptions.) However, we can't currently do this due to different analytics events that we are recording.
  • We are using names like getButtonOptions, getOptions, buttonContents etc for essentially the same thing, so we should probably settle on a unique name for these things.
  • To get rid of some redundant logic, we might want to use theme-options for CurrentThemeCard (we can re-use isHidden in order to hide the 'Support' button on Jetpack sites, and for free themes).
  • We might want a cleaner approach to tracking.
  • WebPreview (and how its Button is wired to an action and given a label) could still use some more makeover.

@ockham ockham added [Feature Group] Appearance & Themes Features related to the appearance of sites. [Status] In Progress labels Jan 14, 2016
@ockham ockham self-assigned this Jan 14, 2016
@ockham ockham added this to the Themes: Showcase M3-LoggedOut milestone Jan 14, 2016
@ockham ockham force-pushed the refactor/themes-selection branch from 29d0185 to 0f20e03 Compare January 14, 2016 23:35
@ehg ehg removed this from the Themes: Showcase M3-LoggedOut milestone Jan 15, 2016
@ockham ockham force-pushed the refactor/themes-selection branch from 0f20e03 to a7b156d Compare January 15, 2016 17:12
// Theme ID (theme-slug)
id: React.PropTypes.string.isRequired,
// Index of theme in results list, if any
theme: React.PropTypes.shape( {
Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting, this PropType is too complex to repeat in various places. Should we factor out this PropType somewhere so it can be shared? Might be better just to have it in one place and expect a PropType of object elsewhere.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I'd like to see a Calypso lib of PropTypes somewhere. Maybe something following the client/state hierarchy?

@ockham ockham force-pushed the refactor/themes-selection branch from 061155b to 5cb693f Compare January 15, 2016 18:16

export function getButtonOptions( site, theme, isLoggedOut, actions, setSelectedTheme, togglePreview ) {
let options = pick( buttonOptions, option => ! ( option.isHidden && option.isHidden( site, theme, isLoggedOut ) ) );
let options = pick( buttonOptions, option => ! option.isHidden );
options = mapValues( options, appendLabelAndHeader );
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can we use lodash's merge or similar here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can we use lodash's merge or similar here?

62b7335

@ockham ockham added [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. and removed [Status] In Progress labels Jan 19, 2016
@seear seear force-pushed the refactor/themes-selection branch from 62b7335 to 96829a1 Compare January 19, 2016 16:57
@seear
Copy link
Contributor

seear commented Jan 19, 2016

I've rebased to master to do some Chrome Timeline runs.

@seear
Copy link
Contributor

seear commented Jan 19, 2016

Running the Chrome Timeline, performance between this branch and master look to be roughly comparable, with slow frames of similar duration and frequency.

This PR shows encouraging signs in terms of which functions are most expensive, with renderTheme and getButtonOptions dropping out of the list of worst offenders.

(Compare the percentages between these two shots, not the times)

This Branch
screen shot 2016-01-19 at 20 53 58

Master
screen shot 2016-01-19 at 21 10 10

@ockham
Copy link
Contributor Author

ockham commented Jan 19, 2016

@seear Great, thanks for looking into this!

@seear
Copy link
Contributor

seear commented Jan 27, 2016

Code looks good to me after the walkthrough session and subsequent tweaks, so I'll give a 👍

I have not run any kind of rigorous testing, so it might be worth running either the e2e tests or going through a full set of manual tests before merge if you haven't already done so.

Instead of passing down a gradually more bound getButtonOptions() function,
invoke it fully inside main.jsx already, and make its `getUrl` and `action`
properties functions that accept theme as an argument. Then, invoke those
functions inside the `Theme` and `MoreButton` components, i.e. only at the very
bottom of the component hierarchy. Also, use the return value from
`getButtonOptions()` to pass some other props more cleanly.

* Pass theme arg to getUrl() only in ThemeMoreButton instead of binding early
  If we want to invoke our actions and URL generators inside the more button instead
  of binding to theme (too) early, we need to pass the entire object, since all
  action and URL helpers require different theme attributes.
* Get rid of JSX bind()s (for performance)
* theme-options:
 * Move buttonOptions back inside getButtonOptions()
 * s/hideForSite/isHidden/
 * s/setSelectedTheme/letUserSelectSite/
 * s/togglePreview/showPreview/
 * Inline `appendUrl()` and `appendAction()`
   The functional approach made things less legible here IMO -- e.g. it had a
   3-clause if/elseif branch that actually acted on 4 items. I find it clearer
   as it is now.
 * Move the remaining `appendActionTracking()` function out of
  `getButtonOptions()`.
* Add tracking to 'More' button actions inside main.jsx
* main.jsx: Leverage `buttonOptions` to simplify the WebPreview button action
* components/theme/test: Remove obsolete isSelected prop
* Fix Theme component tests
* ThemesSelection: Instead of customize and preview, accept unified
  `onScreenshotClick` and `screenshotLabel` props
* themes/main: s/setSelectedTheme/showSiteSelectorModal/
@ockham ockham force-pushed the refactor/themes-selection branch from 9ed2d24 to 1c74c53 Compare January 27, 2016 12:49
@ockham
Copy link
Contributor Author

ockham commented Jan 27, 2016

Thanks @seear!

Squashed, and ran e2e tests. Will merge once CircleCI finished.

ockham added a commit that referenced this pull request Jan 27, 2016
@ockham ockham merged commit da102d7 into master Jan 27, 2016
@ockham ockham deleted the refactor/themes-selection branch January 27, 2016 13:22
@scruffian scruffian removed the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label Jan 27, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature Group] Appearance & Themes Features related to the appearance of sites.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants