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

DIFM: Manage Purchases: Show extra page price breakdown using price tiers #66559

Merged
merged 15 commits into from
Sep 2, 2022

Conversation

ddc22
Copy link
Contributor

@ddc22 ddc22 commented Aug 15, 2022

Proposed Changes

Adds a proper description and cost breakdown for DIFM purchase receipt view.

Testing Instructions

  • Apply D85709-code
  • Apply D86680-code
  • Sandbox Public API
  • Use the below calypso live link go to /start/do-it-for-me
  • Go through the flow and select more than 5 pages in the page picker step
  • Complete purchase
  • Go to purchases http://calypso.localhost:3000/purchases/subscriptions/<site-slug>
  • Select the DIFM purchase and make sure you see below

image

Pre-merge Checklist

Fixes 937-gh-Automattic/martech

@matticbot
Copy link
Contributor

matticbot commented Aug 15, 2022

Here is how your PR affects size of JS and CSS bundles shipped to the user's browser:

App Entrypoints (~86 bytes added 📈 [gzipped])

name                 parsed_size           gzip_size
entry-stepper             +263 B  (+0.0%)      +86 B  (+0.0%)
entry-gutenboarding        +12 B  (+0.0%)      +14 B  (+0.0%)

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])

name                             parsed_size           gzip_size
site-purchases                       +1822 B  (+0.1%)     +628 B  (+0.2%)
purchases                            +1822 B  (+0.1%)     +628 B  (+0.1%)
jetpack-connect                       +935 B  (+0.1%)     +346 B  (+0.1%)
plans                                 +852 B  (+0.1%)     +302 B  (+0.1%)
jetpack-cloud-pricing                 +852 B  (+0.2%)     +302 B  (+0.2%)
email                                 +852 B  (+0.1%)     +330 B  (+0.2%)
domains                               +852 B  (+0.1%)     +353 B  (+0.1%)
checkout                              +852 B  (+0.1%)     +305 B  (+0.1%)
themes                                +263 B  (+0.0%)      +84 B  (+0.0%)
theme                                 +263 B  (+0.1%)      +78 B  (+0.1%)
settings                              +263 B  (+0.0%)      +76 B  (+0.0%)
plugins                               +263 B  (+0.0%)      +77 B  (+0.0%)
jetpack-cloud-plugin-management       +263 B  (+0.0%)      +78 B  (+0.0%)
home                                  +263 B  (+0.0%)      +77 B  (+0.0%)
backup                                +263 B  (+0.0%)      +76 B  (+0.0%)
add-ons                               +263 B  (+0.1%)      +76 B  (+0.1%)
jetpack-cloud-settings                +251 B  (+0.1%)      +69 B  (+0.1%)
help                                  +251 B  (+0.0%)      +69 B  (+0.0%)
account-close                         +251 B  (+0.1%)      +73 B  (+0.1%)
signup                                 +83 B  (+0.0%)      +44 B  (+0.1%)
accept-invite                          +83 B  (+0.0%)      +44 B  (+0.0%)
woocommerce                            +12 B  (+0.0%)       +6 B  (+0.0%)
stats                                  +12 B  (+0.0%)       +6 B  (+0.0%)
settings-writing                       +12 B  (+0.0%)       +6 B  (+0.0%)
settings-security                      +12 B  (+0.0%)       +6 B  (+0.0%)
settings-performance                   +12 B  (+0.0%)       +6 B  (+0.0%)
scan                                   +12 B  (+0.0%)       +6 B  (+0.0%)
purchase-product                       +12 B  (+0.0%)       +6 B  (+0.0%)
posts-custom                           +12 B  (+0.0%)       +7 B  (+0.0%)
posts                                  +12 B  (+0.0%)       +7 B  (+0.0%)
people                                 +12 B  (+0.0%)       +6 B  (+0.0%)
migrate                                +12 B  (+0.0%)       +6 B  (+0.0%)
media                                  +12 B  (+0.0%)       +8 B  (+0.0%)
marketplace                            +12 B  (+0.0%)       +5 B  (+0.0%)
marketing                              +12 B  (+0.0%)       +6 B  (+0.0%)
jetpack-search                         +12 B  (+0.0%)       +6 B  (+0.0%)
jetpack-cloud-partner-portal           +12 B  (+0.0%)       +6 B  (+0.0%)
jetpack-cloud-agency-dashboard         +12 B  (+0.0%)       +6 B  (+0.0%)
hosting                                +12 B  (+0.0%)       +6 B  (+0.0%)
earn                                   +12 B  (+0.0%)       +6 B  (+0.0%)
activity                               +12 B  (+0.0%)       +6 B  (+0.0%)

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])

name                                                            parsed_size           gzip_size
async-load-calypso-my-sites-checkout-modal                           +852 B  (+0.1%)     +302 B  (+0.1%)
async-load-calypso-blocks-editor-checkout-modal                      +852 B  (+0.1%)     +302 B  (+0.1%)
async-load-signup-steps-plans-atomic-store                           +263 B  (+0.1%)      +79 B  (+0.1%)
async-load-signup-steps-plans                                        +263 B  (+0.1%)      +78 B  (+0.1%)
async-load-design-blocks                                             +263 B  (+0.0%)     +108 B  (+0.0%)
async-load-signup-steps-theme-selection                              +251 B  (+0.2%)      +73 B  (+0.2%)
async-load-calypso-blocks-inline-help-popover                        +251 B  (+0.0%)      +71 B  (+0.0%)
async-load-automattic-help-center                                    +251 B  (+0.0%)      +71 B  (+0.0%)
async-load-signup-steps-woocommerce-install-step-business-info        +12 B  (+0.0%)       +6 B  (+0.0%)
async-load-signup-steps-domains                                       +12 B  (+0.0%)       +6 B  (+0.0%)
async-load-signup-steps-add-ons                                       +12 B  (+0.0%)       +6 B  (+0.0%)
async-load-masterbar-cart-masterbar-cart-wrapper                      +12 B  (+0.0%)       +6 B  (+0.0%)
async-load-design-playground                                          +12 B  (+0.0%)       +6 B  (+0.0%)
async-load-design                                                     +12 B  (+0.0%)       +6 B  (+0.0%)
async-load-calypso-reader-sidebar                                     +12 B  (+0.0%)       +6 B  (+0.0%)
async-load-calypso-post-editor-editor-media-modal                     +12 B  (+0.0%)       +6 B  (+0.0%)
async-load-calypso-my-sites-current-site-notice                       +12 B  (+0.0%)       +6 B  (+0.0%)
async-load-calypso-components-web-preview-component                   +12 B  (+0.0%)       +6 B  (+0.0%)
async-load-calypso-blocks-jitm-templates-sidebar-banner               +12 B  (+0.0%)       +6 B  (+0.0%)
async-load-calypso-blocks-jitm-templates-notice                       +12 B  (+0.0%)       +6 B  (+0.0%)
async-load-calypso-blocks-jitm-templates-default                      +12 B  (+0.0%)       +6 B  (+0.0%)

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.
Gzip Size: Compressed size of the JS and CSS files. This much data needs to be downloaded over network.

Generated by performance advisor bot at iscalypsofastyet.com.

@github-actions
Copy link

github-actions bot commented Aug 15, 2022

@ddc22 ddc22 added the DIFM Express Built By Express Onboarding Pipeline related tasks label Aug 15, 2022
@ddc22 ddc22 self-assigned this Aug 15, 2022
@aneeshd16 aneeshd16 self-requested a review August 23, 2022 11:13
@matticbot matticbot added the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label Aug 23, 2022
@ddc22 ddc22 marked this pull request as ready for review August 23, 2022 11:24
@ddc22 ddc22 requested a review from a team as a code owner August 23, 2022 11:24
@ddc22 ddc22 force-pushed the add/extra-page-reciept-display branch 4 times, most recently from 5ad7fd0 to 235857d Compare August 24, 2022 09:39
@ddc22 ddc22 force-pushed the add/extra-page-reciept-display branch 2 times, most recently from 9865ae0 to 7d79b8f Compare August 26, 2022 12:07
@matticbot
Copy link
Contributor

This PR modifies the release build for editing-toolkit

To test your changes on WordPress.com, run install-plugin.sh editing-toolkit add/extra-page-reciept-display on your sandbox.

To deploy your changes after merging, see the documentation: PCYsg-mMA-p2

Copy link
Contributor

@aneeshd16 aneeshd16 left a 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?

image

  • 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?

image

client/me/purchases/manage-purchase/index.jsx Outdated Show resolved Hide resolved
client/me/purchases/manage-purchase/purchase-meta.jsx Outdated Show resolved Hide resolved
client/me/purchases/manage-purchase/purchase-meta.jsx Outdated Show resolved Hide resolved
client/me/purchases/manage-purchase/purchase-meta.jsx Outdated Show resolved Hide resolved
client/lib/purchases/assembler.ts Outdated Show resolved Hide resolved
client/lib/purchases/types.ts Outdated Show resolved Hide resolved
client/lib/purchases/types.ts Outdated Show resolved Hide resolved
client/me/purchases/manage-purchase/index.jsx Outdated Show resolved Hide resolved
client/me/purchases/manage-purchase/index.jsx Outdated Show resolved Hide resolved
client/me/purchases/manage-purchase/purchase-meta.jsx Outdated Show resolved Hide resolved
@ddc22 ddc22 force-pushed the add/extra-page-reciept-display branch from f7aa023 to 6b63120 Compare August 31, 2022 15:50
@ddc22 ddc22 requested a review from aneeshd16 August 31, 2022 15:52
client/lib/purchases/types.ts Outdated Show resolved Hide resolved
client/lib/signup/step-actions/index.js Outdated Show resolved Hide resolved
client/me/purchases/manage-purchase/index.jsx Outdated Show resolved Hide resolved
@ddc22 ddc22 force-pushed the add/extra-page-reciept-display branch 2 times, most recently from d14f192 to fcfc5b6 Compare September 1, 2022 09:21
@ddc22 ddc22 force-pushed the add/extra-page-reciept-display branch from fcfc5b6 to cfb28e9 Compare September 1, 2022 09:59
@ddc22 ddc22 force-pushed the add/extra-page-reciept-display branch from cfb28e9 to 19080a2 Compare September 1, 2022 10:49
@ddc22 ddc22 requested a review from aneeshd16 September 1, 2022 11:22
Copy link
Contributor

@aneeshd16 aneeshd16 left a 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.

maximumPriceDisplay?: string | null | undefined;
}

export interface RawPurchasdPriceTierEntry extends PriceTierEntry {
Copy link
Contributor

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 );
Copy link
Contributor

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.

Copy link
Contributor Author

@ddc22 ddc22 Sep 1, 2022

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 🤔.

Copy link
Contributor

@aneeshd16 aneeshd16 Sep 1, 2022

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.

Copy link
Contributor Author

@ddc22 ddc22 Sep 1, 2022

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.

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 added some changes to improve the clarity of the selector 👍
7a245a2

Copy link
Contributor

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)

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.

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 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" />,
Copy link
Contributor

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?

@ddc22 ddc22 requested a review from aneeshd16 September 1, 2022 14:43
@@ -224,11 +226,48 @@ function PurchaseMetaOwner( { owner } ) {

function PurchaseMetaPrice( { purchase } ) {
const translate = useTranslate();
const overallState = useSelector( ( state ) => state );
Copy link
Contributor

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.

@ddc22 ddc22 requested a review from aneeshd16 September 2, 2022 08:54
@ddc22 ddc22 changed the title Add/extra page receipt display DIFM: Manage Purchases: Show extra page price breakdown using price tiers Sep 2, 2022
Copy link
Contributor

@aneeshd16 aneeshd16 left a comment

Choose a reason for hiding this comment

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

LGTM! Great work!

@ddc22 ddc22 merged commit ed1f87d into trunk Sep 2, 2022
@ddc22 ddc22 deleted the add/extra-page-reciept-display branch September 2, 2022 09:04
@github-actions github-actions bot removed the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label Sep 2, 2022
@a8ci18n
Copy link

a8ci18n commented Sep 2, 2022

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.

@emilyaudela
Copy link
Contributor

emilyaudela commented Sep 2, 2022

Hi all! Any chance w can add a plural to the string

A professionally built %(numberOfIncludedPages)d page website in 4 business days or less

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:

Service : %(oneTimeFee)s (one-time)

Thanks!

@ddc22
Copy link
Contributor Author

ddc22 commented Sep 5, 2022

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.
A professionally built single page website in 4 business days or less

The extra space before the colon is also added here which I will fix.

'%(extraPageCount)d extra page : %(costOfExtraPages)s (one-time)',
'%(extraPageCount)d extra pages : %(costOfExtraPages)s (one-time)',

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 😁

@emilyaudela
Copy link
Contributor

Thanks for looking into this!

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.
A professionally built single page website in 4 business days or less

Awesome! Thanks! 👍

Is there a reason or best practice for not adding a space before a colon.

In English, there aren't usually spaces before punctuation marks. 😄

eoigal pushed a commit that referenced this pull request Sep 5, 2022
…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
@a8ci18n
Copy link

a8ci18n commented Sep 8, 2022

Translation for this Pull Request has now been finished.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
DIFM Express Built By Express Onboarding Pipeline related tasks
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants