Add site to Studio when creating preview from CLI#1272
Conversation
fredrikekelund
left a comment
There was a problem hiding this comment.
I haven't tested the changes yet, but judging by the code, this looks like a solid solution 👍
src/stores/new-sites-slice.ts
Outdated
| const state = store.getState() as RootState & { newSites?: NewSitesState }; | ||
|
|
||
| const newSites = payload.newSites; |
There was a problem hiding this comment.
| const state = store.getState() as RootState & { newSites?: NewSitesState }; | |
| const newSites = payload.newSites; | |
| const state = store.getState(); | |
| const newSites = payload.newSites; |
No need for as
src/stores/new-sites-slice.ts
Outdated
| if ( ! state.newSites?.isProcessing && newSites && newSites.length > 0 ) { | ||
| store.dispatch( newSitesSlice.actions.setIsProcessing( true ) ); | ||
|
|
||
| Promise.all( | ||
| newSites.map( async ( site: NewSiteDetails ) => { | ||
| try { | ||
| await getIpcApi().handleNewSite( site ); | ||
| } catch ( error ) { | ||
| console.error( | ||
| `[New Sites Slice] Failed to create site for folder: ${ site.path }`, | ||
| error | ||
| ); | ||
| } | ||
| } ) | ||
| ).finally( () => { | ||
| store.dispatch( newSitesSlice.actions.setIsProcessing( false ) ); | ||
| } ); | ||
| } |
There was a problem hiding this comment.
| if ( ! state.newSites?.isProcessing && newSites && newSites.length > 0 ) { | |
| store.dispatch( newSitesSlice.actions.setIsProcessing( true ) ); | |
| Promise.all( | |
| newSites.map( async ( site: NewSiteDetails ) => { | |
| try { | |
| await getIpcApi().handleNewSite( site ); | |
| } catch ( error ) { | |
| console.error( | |
| `[New Sites Slice] Failed to create site for folder: ${ site.path }`, | |
| error | |
| ); | |
| } | |
| } ) | |
| ).finally( () => { | |
| store.dispatch( newSitesSlice.actions.setIsProcessing( false ) ); | |
| } ); | |
| } | |
| if ( ! state.newSites.isProcessing && newSites ) { | |
| store.dispatch( handleNewSite( newSites ) ); | |
| } |
I would make the event handler smaller and add a handleNewSite thunk like this:
const handleNewSite = createAsyncThunk( 'newSites/handleNewSite', ( sites: NewSiteDetails[] ) => {
return Promise.all(
sites.map( async ( site ) => {
try {
await getIpcApi().handleNewSite( site );
} catch ( error ) {
console.error(
`[New Sites Slice] Failed to create site for folder: ${ site.path }`,
error
);
}
} )
);
} );Lastly, we should update the isProcessing state depending on the thunk state:
extraReducers: ( builder ) => {
builder
.addCase( handleNewSite.pending, ( state ) => {
state.isProcessing = true;
} )
.addCase( handleNewSite.fulfilled, ( state ) => {
state.isProcessing = false;
} )
.addCase( handleNewSite.rejected, ( state ) => {
state.isProcessing = false;
} );
},
cli/lib/appdata.ts
Outdated
| throw new LoggerError( __( 'The specified folder is not added to Studio.' ) ); | ||
| return null; |
There was a problem hiding this comment.
I guess this is ultimately subjective, but I would rather continue throwing an error from this function and instead wrap the call in saveSnapshotToAppdata in a try .. catch
cli/lib/appdata.ts
Outdated
| return site; | ||
| } | ||
|
|
||
| export function getNewSiteAppData( siteFolder: string ): z.infer< typeof siteSchema > { |
There was a problem hiding this comment.
| export function getNewSiteAppData( siteFolder: string ): z.infer< typeof siteSchema > { | |
| export function getNewSitePartial( siteFolder: string ) { |
I feel like we could clarify the name here. I would also let TS automatically intuit the return type.
cli/lib/appdata.ts
Outdated
| const newSiteSchema = z | ||
| .object( { | ||
| id: z.string(), | ||
| path: z.string(), | ||
| } ) | ||
| .passthrough(); | ||
|
|
||
| const userDataSchema = z | ||
| .object( { | ||
| newSites: z.array( newSiteSchema ).optional(), |
There was a problem hiding this comment.
| const newSiteSchema = z | |
| .object( { | |
| id: z.string(), | |
| path: z.string(), | |
| } ) | |
| .passthrough(); | |
| const userDataSchema = z | |
| .object( { | |
| newSites: z.array( newSiteSchema ).optional(), | |
| const userDataSchema = z | |
| .object( { | |
| newSites: z.array( siteSchema ).optional(), |
In my quick testing, it looked like we could use siteSchema here, too. Maybe the current code is a leftover from a previous iteration?
fredrikekelund
left a comment
There was a problem hiding this comment.
I've tested the changes now, and everything works well apart from the fact that the list of sites isn't automatically refreshed. Maybe we could export a refreshSitesList function from src/hooks/use-site-details.tsx to resolve that?
f13a37c to
2c8afdb
Compare
fredrikekelund
left a comment
There was a problem hiding this comment.
Tested again, and it works nicely with auto-refreshing the sites list 👍 Impressively small diff for a relatively complex change, too. Nice job!
It would be good to iron out the question about whether the isProcessing state is truly needed, but other than that, this LGTM.
src/ipc-types.d.ts
Outdated
| type NewSiteDetails = { | ||
| id: string; | ||
| path: string; | ||
| name: string; | ||
| }; |
There was a problem hiding this comment.
| type NewSiteDetails = { | |
| id: string; | |
| path: string; | |
| name: string; | |
| }; | |
| type NewSiteDetails = Pick< SiteDetails, 'id' | 'path' | 'name' >; |
Nit, but this allows us to continue using a canonical type definition for the sites objects.
src/stores/new-sites-slice.ts
Outdated
| const state = store.getState(); | ||
| const newSites = payload.newSites; | ||
|
|
||
| if ( ! state.newSites.isProcessing && newSites ) { |
There was a problem hiding this comment.
I'm curious, what would happen if we didn't have the ! state.newSites.isProcessing check here? It's not immediately clear to me why there might be multiple events in such short succession and why we shouldn't process all of them if that happens.
There was a problem hiding this comment.
Since this event subscription handles all user data changes, and not only newSites ones, I think it is crucial to have some tracking mechanism to avoid site duplication. However I see your point, and issue, that a single flag would probably lead to some unwanted behaviors, like new sites not being picked up if some are currently processing, and staying pending, until another user data event (probably unrelated) gets triggered.
I've updated the implementation to track individual site IDs instead of a single processing flag. Now we filter out only sites already being processed while allowing new ones to proceed immediately.
Let me know what you think about this approach.
There was a problem hiding this comment.
Makes sense. If a single user-data-updated event carried multiple newSites, then this logic will be required to avoid adding duplicate site entries.
The updated solution looks great to me 👍
src/stores/new-sites-slice.ts
Outdated
| }, | ||
| } ); | ||
|
|
||
| export const { addProcessingSites, removeProcessingSites } = newSitesSlice.actions; |
There was a problem hiding this comment.
| export const { addProcessingSites, removeProcessingSites } = newSitesSlice.actions; | |
| export const newSitesActions = newSitesSlice.actions; |
Nit, but it'd be nice to stay consistent with other slices and export the actions inside a "namespace".
| const action = { | ||
| type: 'newSites/addProcessingSites', | ||
| payload: [ 'site-1', 'site-2' ], | ||
| }; |
There was a problem hiding this comment.
| const action = { | |
| type: 'newSites/addProcessingSites', | |
| payload: [ 'site-1', 'site-2' ], | |
| }; | |
| const action = newSitesActions.addProcessingSites( [ 'site-1', 'site-2' ] ); |
If we export the actions in a single namespace, we can create the action in this way.
* add site to studio when create preview from cli * pr comments changes * fix unit tests * fix lint * improve new sites tracking mechanism * address pr feedback
Related issues
Proposed Changes
newSitesfield to user data schema to track sites pending creationTesting Instructions
npm run startand create a new site in Studionpm run cli:buildnode dist/cli/main.js preview create <STUDIO SITE FOLDER>Pre-merge Checklist