-
Notifications
You must be signed in to change notification settings - Fork 10.7k
Add one-click installation for recommended extensions in multichannel Marketing page #35542
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
Conversation
The approach implemented here is similar to plugins/woocommerce-admin/client/tasks/fills/Marketing/index.tsx.
This is so that we don't need to make network request and display loading progress.
Test Results SummaryCommit SHA: 90677fd
To view the full API test report, click here. To view the full E2E test report, click here. To view all test reports, visit the WooCommerce Test Reports Dashboard. |
|
About the failing GitHub check https://github.com/woocommerce/woocommerce/actions/runs/3439480519/jobs/5736827762, the failed e2e tests are not related to the changes in this PR. |
joshuatf
left a comment
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 the changes here, @ecgan! Code looks good overall. Left a few comments and questions, but nothing too major.
I think skipping the introduction of removeRecommendedPlugin might be a good idea if we can rely on installed plugins to hide this item from the bottom list.
| }; | ||
|
|
||
| export const usePlugins = (): UsePluginsType => { | ||
| export const useInstalledPlugins = (): UsePluginsType => { |
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.
Any particular reason for this name change? It looks like this returns more than just installed plugins and the previous name may be more accurate.
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.
In this one I'm going to agree with @ecgan solution since for me it seems that the return is only related to the installed plugins. usePlugins seems more generic for me and is not really clear.
💅 If we are going to refactor it tho... besides the name it would be even more helpful to add a JSDOC at the top :)
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 think in this case, the logic in here does not just deal with "installed" plugins. It also deals with plugins that are in the process of activation, activating plugins, and "loading" plugins after installation. While this semantically nitpicky, the semantics here are important.
I don't want to dig into this too much in this PR since I know it was not the intention of this PR to open this can of worms, but I don't think a unique data store should have been created around plugin activation specific to marketing. We have a data store for plugins in WooCommerce that would work well here. We should strive to not reinvent the wheel and consolidate efforts behind existing packages.
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.
Any particular reason for this name change?
The main reason is because we already have a useRecommendedPlugins for the "Discover more marketing tools" card. If we continue to have usePlugin, it feels confusing.
I renamed it to useInstalledPlugins because it is used in the "Installed extensions" card.
I think in this case, the logic in here does not just deal with "installed" plugins. It also deals with plugins that are in the process of activation, activating plugins, and "loading" plugins after installation. While this semantically nitpicky, the semantics here are important.
Yeah, I thought about that too, but I couldn't think of a nice hook name for it.
In the end, I named it useInstalledPlugins because it is mainly used in the "Installed extensions" card (although it is also used in the DiscoverTools component).
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 think a unique data store should have been created around plugin activation specific to marketing. We have a data store for plugins in WooCommerce that would work well here. We should strive to not reinvent the wheel and consolidate efforts behind existing packages.
@joshuatf , I agree with you on this, that's why in this PR, I looked into the data store for plugins and I am using the installAndActivatePlugins method, to use the more standardized approach in WooCommerce.
To give a bit more context, the "unique data store specific to marketing" was created in March 2020 or earlier (see woocommerce/woocommerce-admin#3953), while the @woocommerce/data package was first released in April 2020, so I guess there probably wasn't a choice back then.
...ins/woocommerce-admin/client/marketing/overview-multichannel/DiscoverTools/DiscoverTools.tsx
Outdated
Show resolved
Hide resolved
...commerce-admin/client/marketing/overview-multichannel/DiscoverTools/useRecommendedPlugins.ts
Outdated
Show resolved
Hide resolved
...ins/woocommerce-admin/client/marketing/overview-multichannel/DiscoverTools/DiscoverTools.tsx
Outdated
Show resolved
Hide resolved
...commerce-admin/client/marketing/overview-multichannel/DiscoverTools/useRecommendedPlugins.ts
Outdated
Show resolved
Hide resolved
...ins/woocommerce-admin/client/marketing/overview-multichannel/DiscoverTools/DiscoverTools.tsx
Outdated
Show resolved
Hide resolved
...ins/woocommerce-admin/client/marketing/overview-multichannel/DiscoverTools/DiscoverTools.tsx
Outdated
Show resolved
Hide resolved
...ins/woocommerce-admin/client/marketing/overview-multichannel/DiscoverTools/DiscoverTools.tsx
Outdated
Show resolved
Hide resolved
isResolving is not accurate for the use of isLoading.
For consistency with the hook name.
@joshuatf , I'm not very keen of the idea. If we have that, then imagine the following scenario:
I think it makes things a bit more complicated, and it would make maintainers to take more time to figure out:
By having |
I don't see anything particularly wrong with this scenario and there are plenty of cases where we don't display all items returned from an endpoint in the UI.
If this is a real issue, what you're referring to here is your data store becoming out of sync with the endpoint, in which case, you can solve by 1 of 2 ways:
What you appear to be attempting is option 2, however you've added the removal logic in your client-side instead of your data store. Dispatching actions in Redux should be akin to dispatching an event.
By using There are quite a few cases of incorrectly using setters in this data store and across WooCommerce, but one reason I think this one may be more confusing is that someone calling |
|
@joshuatf , thanks for the thorough explanation! 😃
Got it, that makes sense. 👍
That was actually what I did in the first place, but I removed that because I thought it would be good not to make a network request just for that, and I didn't want to display the loading indicator after invalidate resolution but I couldn't figure out how to make it work at that time. I have removed |
joshuatf
left a comment
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 making all the changes on this, @ecgan! This is testing well for me. Left one optional suggestion, but will leave that up to your discretion and pre-approving 😄
I didn't want to display the loading indicator after invalidate resolution but I couldn't figure out how to make it work at that time.
I think the suggestion I made on isLoading may help to discern between the initialization and refetching states. But let me know if I misunderstood this!
That was actually what I did in the first place, but I removed that because I thought it would be good not to make a network request just for that
I think the answer is that it kind of depends. If you need the local data store to be in sync with the network (which I think you suggested by wanting the UI and data store to match) then we need to invalidate.
But you could both invalidate and remove (or filter the item based on "active" plugin status) so that it both gets removed immediately and that the data store is in sync with the server changes.
| [ category ] | ||
| ); | ||
| return { | ||
| isLoading: |
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.
After install there's a very brief moment where the activated plugin remains in the list and buttons are no longer disabled.
Optionally, I think that creating separate isLoading and isInitializing states may help differentiate and disable the UI while being refetched. Something like:
isInitializing: ! hasFinishedResolution( selector, [ category ] ) &&
! plugins.length,
isLoading: ! hasFinishedResolution( selector, [ category ] );
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 @joshuatf , I think that's a good idea, I have made the change in 4db827e (#35542).
This is to have different loading state when first loading and when refetching after installing an extension.
This is so that the installed recommended plugin gets removed immediately and the data store is in sync with the server changes. This is similar to the previous removeRecommendedPlugin but with different naming following redux style guide.
@joshuatf , I try to achieve the above in |
|
On second thought, |
joshuatf
left a comment
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.
Testing well and no longer see the delay in the item being moved.
The action naming is better, but you're also right about it being possibly confusing. Left one more comment about this, but you can decide whether or not you want to implement. I've already held you up quite a bit on this PR 😅
| } | ||
| } | ||
|
|
||
| export function* installAndActivateRecommendedPlugin( |
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 agree with your above comment that this name could be confusing, but I'll leave this up to you to decide whether or not you want to change it.
I still think the easiest fix for this might be to rely on the plugins data store to see if something is being installed and activated as opposed to removing it manually. Letting the data flow downstream (kicking off the install action and filtering in your view based on plugins that are installed/activated) is always going to be easier than handling state manually upstream (manually removing a plugin after kicking off an action in another data store).
The latter is prone to becoming out of sync and could errantly remove a recommended item when a plugin install or activation fails.
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 am actually thinking about removing the marketing data store and have those marketing data (including the recommended plugins data) as local component state. That can be done in the future, possibly after Multichannel Marketing becomes the default experience and it replaces the current marketing page. When that happens, then it makes sense to have the data flow downstream and filter them in the view, and things are much simpler to see and understand without another overlapping data store. 🙂 👍
All Submissions:
Changes proposed in this Pull Request:
Closes #35578.
This PR depends on woocommerce.com API changes in PR https://github.com/Automattic/woocommerce.com/pull/14729.
In this PR, we add one-click installation feature to recommended extensions in Marketing page (beta multichannel view only; this PR does not affect the normal Marketing page).
In the API response, recommended extensions will have a property
direct_installto indicate whether it supports one-click installation.direct_installistrueif the extension is available in the WordPress.org plugin listings;falseotherwise.If
direct_installistrue, we will display a "Install plugin" button. Clicking it will automatically install and activate the plugin. The plugin will then be removed from the "Discover more marketing tools" card and will appear in the "Installed extensions" card.If
direct_installisfalse, the plugin will have a "View details" button. Clicking it will navigate to the product page on WooCommerce.com. This is same as existing behavior; we just changed the button text from "Get started" to "View details".The code for "automatic install and activate" is based on existing prior art in the code base, e.g.:
woocommerce/plugins/woocommerce-admin/client/tasks/fills/Marketing/index.tsx
Lines 131 to 156 in 0b52411
Other technical notes:
removeRecommendedPlugin. This is called after "automatic install and activate", to remove the extension from "Discover more marketing tools".hooksandtypesfor the shared hooks and types.usePluginshas been renamed touseInstalledPluginsfor better clarity.📹 Demo video of the feature with my voice annotation:
demo-pr-one-click-installation.mp4
How to test the changes in this Pull Request:
Pre-condition:
/wp-admin/admin.php?page=wc-settings&tab=advanced§ion=features).Test instructions:
/wp-admin/admin.php?page=wc-admin&path=%2Fmarketing.Other information:
pnpm --filter=<project> changelog add?FOR PR REVIEWER ONLY: