Skip to content

Refactor Studio to depend on CLI for preview site creation #1201

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

Merged
merged 28 commits into from
Apr 22, 2025

Conversation

fredrikekelund
Copy link
Contributor

@fredrikekelund fredrikekelund commented Apr 14, 2025

Related issues

Proposed Changes

  • Add a Redux slice to hold state related to preview site CRUD operations.
  • Refactor the preview site creation functionality to depend on the CLI.
  • Add a getSnapshotUsage query to src/stores/wpcom-api.ts and update the UI to use this hook in the relevant places.
  • Remove the useArchiveSite hook.

Note

Snapshot state is now split between the new Redux reducer and the old SnapshotProvider. This means updating and deleting snapshots does not update the UI as expected. We will address this in separate PRs (potentially branching off this PR instead of merging this to trunk first).

Testing Instructions

  1. Ensure that creating a preview site works as expected.
  2. Ensure that the Preview sites counter in the settings modal works as expected.

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?

@fredrikekelund fredrikekelund requested a review from a team April 14, 2025 14:58
@fredrikekelund fredrikekelund self-assigned this Apr 14, 2025
@fredrikekelund fredrikekelund changed the title [WIP] Refactor Studio to depend on CLI for preview site creation Refactor Studio to depend on CLI for preview site creation Apr 15, 2025
@fredrikekelund fredrikekelund marked this pull request as ready for review April 15, 2025 12:53
Copy link
Contributor

@bcotrim bcotrim left a comment

Choose a reason for hiding this comment

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

I don't really like the progress bar behavior for this scenario.
What happens on all my tests is that the bar progresses quickly until the last step, so it just gets stuck on this state for a while:
image

In my opinion, we could either use an indeterminate progress indicator (like a continuous progress bar) or display the same information we show on the CLI (Creating archive, Uploading archive, etc...) which seems far more valuable for our users compared to a stuck progress bar.

Code-wise I like this approach, looks clean and it's easy to read.
One minor comment would be to clear up the naming a bit. At least for me, the distinction between previews and snapshots seem confusing. Is there a benefit for keeping the 2, could we pick one?

Another thing on my mind is how we are currently cleaning up operations. Operations that fail, timeout, or are hanging for other reasons could potentially go on existing forever in our state, correct? I don't think this alone will be an issue, but it's something we should be mindful of, if this is going to be our approach for operations in Studio moving forward.

Is it possible to use a feature flag for this during the development?

@@ -0,0 +1,28 @@
// Store the actions in a separate file to avoid Webpack issues when importing them in the Studio
Copy link
Contributor

Choose a reason for hiding this comment

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

At some point I believe it would be better for us to start building a common folder for types and functions that are used in both CLI and Studio. I don't think it will make any difference behavior wise, but will make it more intentional and easier to read.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea 👍

@fredrikekelund
Copy link
Contributor Author

Is it possible to use a feature flag for this during the development?

This PR touches so many parts of the code, so I was thinking it might be easier to branch off of this PR for the related tasks in https://linear.app/a8c/issue/STU-235

@@ -17,12 +17,15 @@ import nodePath from 'path';
import * as Sentry from '@sentry/electron/main';
import { __, LocaleData, defaultI18n } from '@wordpress/i18n';
import archiver from 'archiver';
import { z } from 'zod';
import { CreateLoggerAction } from 'cli/commands/preview/logger-actions';
Copy link
Contributor

@youknowriad youknowriad Apr 15, 2025

Choose a reason for hiding this comment

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

If you're importing code from the "cli" app, why not import the command and run it as a function instead of a subprocess. I don't have a fully formed opinion at the moment but I think right now we're doing a bit of both which is kind of weird.

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 suggested this approach of keeping the business logic squarely in the CLI when planning the project. It definitely adds complexity, but the upside is that it lets us avoid double bundling of PHP-WASM dependencies (once we get to moving that functionality from Studio to the CLI), which would increase the bundle size by ~700MB in the short term and ~400MB in the long term.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As for code sharing, the idea is to only share modules without side effects. @bcotrim suggested moving those to a common folder for added clarity, which I think is a good idea.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good to me ;)

@fredrikekelund
Copy link
Contributor Author

progress bar

That's fair. I guess the current behavior in this PR would easily be perceived as a UX regression. I'll look into improving this.

clear up the naming

That's fair. We could probably stick with "snapshots", since that's what we use in the code already.

how we are currently cleaning up operations. Operations that fail, timeout, or are hanging for other reasons could potentially go on existing forever in our state, correct?

They could indeed. That's not unique for this architecture, though, even if I realize that I still need to add some error handling to this PR.

Comment on lines 14 to 22
name: z.string().optional(),
name: z.string(),
userId: z.number().optional(),
} );

const siteSchema = z
.object( {
id: z.string(),
path: z.string(),
name: z.string().optional(),
name: z.string(),
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 don't believe name props are actually optional for sites or snapshots. I made this change to accommodate the reportKeyValuePair logging of the snapshot name.

return;
}
const snapshot: z.infer< typeof snapshotSchema > = {
const site = await getSiteFromFolder( siteFolder );
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change means that this function now throws if the site isn't found in the appdata file.

@fredrikekelund fredrikekelund requested a review from bcotrim April 16, 2025 10:03
Copy link
Contributor

@bcotrim bcotrim left a comment

Choose a reason for hiding this comment

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

LGTM 👍
Code and UI are looking great!

Copy link
Contributor

@nightnei nightnei left a comment

Choose a reason for hiding this comment

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

I was testing and:

  1. Counter looks broken for me
  2. Deleting also broken (maybe it's expected for this PR and will be solved in taht separate PR for handling deletion of previews, but wanted to flag in case if it's not expected)

@bcotrim coukd you please test it one more time, to understand is it my local issue or it's indeed exist.

Recording of the issues: https://drive.google.com/file/d/1xd71MGjY8bU0ZFdEcbzrRKjNqounppAp/view?usp=sharing

@bcotrim
Copy link
Contributor

bcotrim commented Apr 18, 2025

@nightnei I see that same issue but I assumed it was expected based on

Note

Snapshot state is now split between the new Redux reducer and the old SnapshotProvider. This means updating and deleting snapshots does not update the UI as expected. We will address this in separate PRs (potentially branching off this PR instead of merging this to trunk first).

If you reload the app after creating a preview site, the count is updated as expected.

@nightnei
Copy link
Contributor

nightnei commented Apr 18, 2025

If you reload the app after creating a preview site, the count is updated as expected.

It wasn't menthioned in testing instructions, I see only "... works es expected". But it doesn't work as expected.

Also, here (p1744904341127939-slack-C04GESRBWKW) he menthioned "I opted for this multi-PR approach to not break the preview site functionality on trunk while still making the PR size relatively manageable.", so looks liek it should work well even after merging just this one PR, w/o other PRs.

Let's wait for Fredrik's response to clarify things.

UPDATE:
I tested with the help of "Refactor preview site deleting to depend on CLI #1230" (it's chain of PRs) - we indeed have bugs:

  1. Delete previeew site - counter isn't updated
  2. Delete all preview sites - doesn't have any effect to UI, but under teh hood looks liek everything works well, so necessary to fix UI handling.

if ( value > animatedValue ) {
setAnimatedValue( value );
}
// eslint-disable-next-line react-hooks/exhaustive-deps
Copy link
Contributor

@nightnei nightnei Apr 18, 2025

Choose a reason for hiding this comment

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

Let's add some description, otherwise anotehr A12N can make mistake with adding it animatedValue. And it will save time for A12S in the future with debugging to understand teh reason and logic.


useEffect( () => {
const maximumAutoIncrementValue = maxValue * 0.95;

const interval = setInterval( () => {
Copy link
Contributor

@nightnei nightnei Apr 18, 2025

Choose a reason for hiding this comment

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

It seems it should be setTimeout, since we run this effect every time when we update animatedValue.
Theoreticaly, it works even now, since we clear interval every time when we rerun the effect, but a safer and correct way would be to use setTimeout.
Right or am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call 👍

Comment on lines +80 to +81
.filter( ( operation ) => operation.status === 'pending' )
.find( ( operation ) => operation.siteId === siteId ),
Copy link
Contributor

@nightnei nightnei Apr 18, 2025

Choose a reason for hiding this comment

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

Suggested change
.filter( ( operation ) => operation.status === 'pending' )
.find( ( operation ) => operation.siteId === siteId ),
.filter( ( operation ) => operation.status === 'pending' && operation.siteId === siteId )

Copy link
Contributor

Choose a reason for hiding this comment

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

},
selectors: {
selectSnapshots: ( state ) => state.snapshots,
selectSnapshotsCount: ( state ) => state.snapshots.length,
Copy link
Contributor

@nightnei nightnei Apr 18, 2025

Choose a reason for hiding this comment

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

We use it only in 1 place.
Also it looks a bit redundant to create extra selector for length if we already have selector for teh list.
Also, it even simlifies using it and readign code:

BEFORE:
	const allSnapshots = useRootSelector( snapshotSelectors.selectSnapshots );
	const snapshotsCount = useRootSelector( snapshotSelectors.selectSnapshotsCount );
AFTER:
	const allSnapshots = useRootSelector( snapshotSelectors.selectSnapshots );
	const snapshotsCount = allSnapshots.length; // But actually, I think we can even avoid creating this extra variable and use "allSnapshots.length" directly in code
Suggested change
selectSnapshotsCount: ( state ) => state.snapshots.length,

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 agree. I actually removed this in #1230.

@nightnei
Copy link
Contributor

At some point I believe it would be better for us to start building a common folder for types and functions that are used in both CLI and Studio.

It's great idea 👍
But also, when we proceed with it - necessary to take into account that we have 3 things: backend, cli and frontend. We already have mix of frontend and backend and one day I belive we should split it. So if we create kinda common folder, then necessary to think about more specific name and / or location, to avoid bounding all 3 things. Otherwise, it will be very messy.

@bcotrim
Copy link
Contributor

bcotrim commented Apr 18, 2025

At some point I believe it would be better for us to start building a common folder for types and functions that are used in both CLI and Studio.

It's great idea 👍 But also, when we proceed with it - necessary to take into account that we have 3 things: backend, cli and frontend. We already have mix of frontend and backend and one day I belive we should split it. So if we create kinda common folder, then necessary to think about more specific name and / or location, to avoid bounding all 3 things. Otherwise, it will be very messy.

We already merged a change with the initial version of that: #1222
Let me know if you have any feedback, and I can address it in a follow-up

} );

child.on( 'exit', ( code: number | null ) => {
console.log( 'Preview site creation completed with code', code );
Copy link
Contributor

Choose a reason for hiding this comment

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

While reviewing #1223 I noticed that we will have a specific execute command for previews. It will likely make sense to remove this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point 👍 I've removed it in #1230

@fredrikekelund
Copy link
Contributor Author

Delete previeew site - counter isn't updated

Good catch. We do actually invalidate the query correctly, but it looks like the deletion hasn't taken effect yet on the back-end when we do that, because the API still returns the old site count. Maybe we should delay the query invalidation to solve this…

Delete all preview sites - doesn't have any effect to UI, but under teh hood looks liek everything works well, so necessary to fix UI handling.

Good catch. Looking into this 👍

@fredrikekelund fredrikekelund requested a review from nightnei April 22, 2025 08:35
@fredrikekelund
Copy link
Contributor Author

I've addressed your comments, @nightnei. You should probably re-review #1230, but this PR needs an approval before merging.

Copy link
Contributor

@nightnei nightnei left a comment

Choose a reason for hiding this comment

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

LGTM 👍

@fredrikekelund fredrikekelund merged commit f93e22e into trunk Apr 22, 2025
8 checks passed
@fredrikekelund fredrikekelund deleted the f26d/depend-on-cli-preview-site-creation branch April 22, 2025 10:12
bgrgicak pushed a commit that referenced this pull request Apr 23, 2025
* [WIP] Refactor Studio to depend on CLI for preview site creation

* More changes

* Fix

* Use node's child_process module to fork CLI process

* Tests

* useGetSnapshotUsage

* Build CLI before starting dev version of app

* Tweaks

* Fixes

* preview ➡️ snapshot

* Improved progress bar

* Display notice when operation completes

* Update zod

* Remove useArchiveSite hook

* Fix test

* Modify exitCode in Logger::reportError

* Set exit code on error and fix outputFormat parsing

* Shorten

* Fix outputFormat parsing

* Clarify naming

* Tweaks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants