-
Notifications
You must be signed in to change notification settings - Fork 2k
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
DIFM: Manage Purchases: Show extra page price breakdown using price tiers #66559
Conversation
Here is how your PR affects size of JS and CSS bundles shipped to the user's browser: App Entrypoints (~86 bytes added 📈 [gzipped])
Common code that is always downloaded and parsed every time the app is loaded, no matter which route is used. Sections (~2041 bytes added 📈 [gzipped])
Sections contain code specific for a given set of routes. Is downloaded and parsed only when a particular route is navigated to. Async-loaded Components (~699 bytes added 📈 [gzipped])
React components that are loaded lazily, when a certain part of UI is displayed for the first time. Legend What is parsed and gzip size?Parsed Size: Uncompressed size of the JS and CSS files. This much code needs to be parsed and stored in memory. Generated by performance advisor bot at iscalypsofastyet.com. |
5ad7fd0
to
235857d
Compare
9865ae0
to
7d79b8f
Compare
This PR modifies the release build for editing-toolkit To test your changes on WordPress.com, run To deploy your changes after merging, see the documentation: PCYsg-mMA-p2 |
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.
Works well with new purchases. Great job on the backend changes! 👍
I have left some code comments and some feedback below:
- This change should also handle past purchases. The price for past purchases is shown incorrectly. Maybe we can show the new text only if tier data is available on the purchase object?
- The purchase description does seem a bit confusing on this page (Manage Purchases). The user has already "hired" a professional. Maybe this could be something like "Website built by a WordPress.com professional. This build contains 9 pages". What do you think, @mikeicode?
f7aa023
to
6b63120
Compare
d14f192
to
fcfc5b6
Compare
fcfc5b6
to
cfb28e9
Compare
cfb28e9
to
19080a2
Compare
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.
Tests well! I left some code feedback below.
client/lib/purchases/types.ts
Outdated
maximumPriceDisplay?: string | null | undefined; | ||
} | ||
|
||
export interface RawPurchasdPriceTierEntry extends PriceTierEntry { |
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.
Typo :) Should be RawPurchasePriceTierEntry
.
@@ -988,6 +1017,7 @@ function PurchasesQueryComponent( { isSiteLevel, selectedSiteId } ) { | |||
export default connect( | |||
( state, props ) => { | |||
const purchase = getByPurchaseId( state, props.purchaseId ); | |||
const difmTieredPurchaseDetails = getDIFMTieredPurchaseDetails( state, props.purchaseId ); |
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 like the refactor to extract this into a function! Can you please explain why this function needs to fetch the purchase
object from the state again? The purchase
object is already available near both usages of the function. The new function is not using state
for anything other than to fetch the purchase
object, so it seems redundant to pass in the state when the purchase object is already available. Can we directly pass this object to the new function? This function signature could look like getDIFMTieredPurchaseDetails( purchase ) => <return value>
.
This new function could live in client/lib/purchases/index.ts
, where almost all functions have a similar signature. ( fn( purchase ) => return value
). Admittedly, this file is getting quite large, but since the functions seem to be pure, it should be easy to refactor in 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.
Actually I was modelling it as a REDUX selector so it can be used anywhere that state slice needs to be calculated. I actually don't understand why it needs to go in a separate lib directory when this is clearly a calculation of existing state 🤔.
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.
The calculation is on the purchase
object, which could come from different places - it could be from the global redux state (like here), from some other store accessed by @wordpress/data
(example usage), or directly from the results of an API call (since a lot of implementations are moving to react-query
). The state
is not used for anything other than to fetch the purchase object. Converting this to a lib function also makes writing automated tests easier.
Also, before calling this function, we are relying on the fact that the code has checked if this is a DIFM purchase. Otherwise, the output of this function does not make sense for non-DIFM purchases.
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.
The calculation is on the purchase object, which could come from different places - it could be from the global redux state (like here), from some other store accessed by @wordpress/data (example usage), or directly from the results of an API call. The state is not used for anything other than to fetch the purchase object.
Actually the intention of this piece of code was to extract the purchase details from the REDUX state slice which holds the information cached from /purchases endpoint. Nothing else. What your explaining sounds familiar to the util method anti pattern similar to util class but for functions. Or am I wrong? This function does one thing. Transforms and calculates DIFM related price tier details if available in the identified purchase of the purchases slice of the supplied redux tree and will return null if not.
Also, before calling this function, we are relying on the fact that the code has checked if this is a DIFM purchase. Otherwise, the output of this function does not make sense for non-DIFM purchases.
Yes the code anticipates that it will be call on any variation of the universal state slice. And it will handle all those variations as appropriate :). We can possibly check the purchase Id to see if it's actually DIFM an reject the result but I don't think that is necessary. If someone wants to reuse this logic for something identical they can refactor the name when they find this function. But for others this encapsulates the price tier's extraction logic for a DIFM purchase so long as you provide the proper purchase ID of a difm purchase you will get the proper details back.
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 added some changes to improve the clarity of the selector 👍
7a245a2
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.
Transforms and calculates DIFM related price tier details if available in the identified purchase of the purchases slice of the supplied redux tree and will return null if not.
I understand that this function is currently designed to pick the purchase object from the state slice. However, IMO, this function is better suited to work on the purchase
object itself. My reasoning:
- The function currently does not use any other part of the state to calculate the result. It only picks the finds the
purchase
object again, which we already have access to when we call this function. - This function is executed for all types of purchases since it is being called in the
connect
function here, which seems redundant to me. - The function is tightly coupled to the redux state, and assumes that the
purchase
object will always be in the redux state. However, it may not be the case as:- The recommended approach for data is using react-query. For future changes that involve fetching purchase data from the backend, this function will have to be refactored to work on the
purchase
object. - Not all data is stored in the global redux state. In the stepper framework for example, the data is stored in
@wordpress/data
stores. (link)
- The recommended approach for data is using react-query. For future changes that involve fetching purchase data from the backend, this function will have to be refactored to work on the
According to the article you linked to, I don't this is an anti-pattern as the lib/purchases
directory has a clearly defined boundary (purchase
object functions) and it is not a part of general app-wide util module.
However, let's keep this PR moving forward 🙂 If we do decide to keep the function as it is, I have added one more bit of feedback for the change in client/me/purchases/manage-purchase/purchase-meta.jsx
.
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.
Oh I wasn't aware of the move to react-query TIL! . I had no idea we were evolving the data layer to something different!
In which case trying to maintain a set of clean set of selectors is a non starter anyway (which was my main motivation)!
I will move this to the lib folder as you recommended! :)
oneTimeFee, | ||
}, | ||
components: { | ||
period: <span className="manage-purchase__time-period" />, |
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 class has no styles in the code base, so maybe we can remove the span
element here?
@@ -224,11 +226,48 @@ function PurchaseMetaOwner( { owner } ) { | |||
|
|||
function PurchaseMetaPrice( { purchase } ) { | |||
const translate = useTranslate(); | |||
const overallState = useSelector( ( state ) => state ); |
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.
We might as well use useSelector( state => getDIFMTieredPurchaseDetails( state, purchase.id )
here. Otherwise, this component will be re-rendered if any part of the global state changes.
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.
LGTM! Great work!
This Pull Request is now available for translation here: https://translate.wordpress.com/deliverables/7508317 Thank you @jdc91 for including a screenshot in the description! This is really helpful for our translators. |
Hi all! Any chance w can add a plural to the string In some languages, a separate translation is needed if you have more than one page. Also, I think there should be hyphen between the number and the word "page", such as "A professionally built 2-page website." There is also an extra space before the colon in this string:
Thanks! |
Hey @emilyaudela thanks for this feedback. Technically there might not be a scenario where a single page is provided as the default offering but theoretically it is a possibility. So I am going to include a version with the word "single". Just in case. The extra space before the colon is also added here which I will fix. wp-calypso/client/me/purchases/manage-purchase/purchase-meta.jsx Lines 253 to 254 in ed1f87d
Is there a reason or best practice for not adding a space before a colon ( English is not my first language 😄 ). I just added it because it looked more pleasing to the eye 😁 |
Thanks for looking into this!
Awesome! Thanks! 👍
In English, there aren't usually spaces before punctuation marks. 😄 |
…iers (#66559) * Added custom text and price breakdown * Reciept display tweaks * Review fixes * Fix price formatting * Price tier changes * Fixed issues related to untiered price legacy viewing, and non extra page views * Review fixes * Review fixes * Unit test fixes * review fixes * Bug fixes * Review fixes * Add clarity to DIFM price tier selector * Refactor difm purchase details function * Minor bug fix
Translation for this Pull Request has now been finished. |
Proposed Changes
Adds a proper description and cost breakdown for DIFM purchase receipt view.
Testing Instructions
/start/do-it-for-me
http://calypso.localhost:3000/purchases/subscriptions/<site-slug>
Pre-merge Checklist
Fixes 937-gh-Automattic/martech