Add MC stats for import / export to track usage - #983
Conversation
sejas
left a comment
There was a problem hiding this comment.
Code looks good.
I suggest tracking clicks on the export buttons to make the underlying logic easier to understand. Export entire site vs Export database.
| // Track which content types are being included in the export | ||
| if ( options.includes ) { | ||
| if ( options.includes.database ) { | ||
| bumpStat( STATS_GROUP.STUDIO_EXPORT_CONTENT, STATS_METRIC.DATABASE ); | ||
| } | ||
| if ( options.includes.uploads ) { | ||
| bumpStat( STATS_GROUP.STUDIO_EXPORT_CONTENT, STATS_METRIC.UPLOADS ); | ||
| } | ||
| if ( options.includes.plugins ) { | ||
| bumpStat( STATS_GROUP.STUDIO_EXPORT_CONTENT, STATS_METRIC.PLUGINS ); | ||
| } | ||
| if ( options.includes.themes ) { | ||
| bumpStat( STATS_GROUP.STUDIO_EXPORT_CONTENT, STATS_METRIC.THEMES ); | ||
| } | ||
| if ( options.includes.muPlugins ) { | ||
| bumpStat( STATS_GROUP.STUDIO_EXPORT_CONTENT, STATS_METRIC.MU_PLUGINS ); | ||
| } | ||
| } |
There was a problem hiding this comment.
I don't think we need this granularity in the export.
We have only two buttons in the UI.
One to export the whole site and another to export the database.
What if we expose the bumpStat to ipc-handlers and call it onClick on the UI?
studio/src/components/content-tab-import-export.tsx
Lines 77 to 94 in edf000c
| bumpStat( STATS_GROUP.STUDIO_IMPORT, STATS_METRIC.SUCCESS ); | ||
| bumpStat( STATS_GROUP.STUDIO_IMPORT_TYPE, backupFile.type ); |
There was a problem hiding this comment.
I believe backupFile.type will indicate whether the file is a zip, SQL, or tar.gz format. However, it might be more interesting to differentiate between jetpack, playground, local, etc.
d35d9f3 to
a7ff1b5
Compare
There was a problem hiding this comment.
Heads up that I just landed #988 which modified this file in its previous location. Should be an easy conflict to resolve
b6dfbdc to
6750cca
Compare
|
@sejas @fredrikekelund Thanks for the reviews! |
fredrikekelund
left a comment
There was a problem hiding this comment.
Using typed arguments for bumpStat functions is great, and the code mostly works well, but I left a few we should adress before merging.
|
|
||
| const exportFullSite = useCallback( | ||
| async ( selectedSite: SiteDetails ): Promise< string | undefined > => { | ||
| getIpcApi().ipcBumpStat( STATS_GROUP.STUDIO_EXPORT, STATS_METRIC.FULL_SITE ); |
There was a problem hiding this comment.
Could we submit the bump stat from exportSite in src/ipc-handlers.ts instead? Aside from making it so we don't have to expose another IPC method, there's also an issue with submitting the bump stat here because it happens before the actual export operation. It's probably not very common for users to click the export button and then click Cancel in the Save as… dialog, but if they do, we still submit a bump stat right now.
There was a problem hiding this comment.
Tracking the event in ipc handler was my initial approach, changed later by @sejas suggestion.
My suggestion would be to actually use both. On the UI side we can track the user click and action, on the ipc handler we track the success/failure of the request.
What do you think @sejas @fredrikekelund ?
There was a problem hiding this comment.
Hmm, tracking both operations seems like overkill to me. If we want to understand which errors users encounter (if any), Sentry is a better tool than bump stats.
I think your original implementation is clearer, even if I also agree with @sejas that we don't need that much granularity when submitting the bump stat. It's fine to distinguish only between full site exports and database exports, but we could do that in the exportSite function by comparing the full shape of options.includes
There was a problem hiding this comment.
I moved all the logic to the IPC now.
The following stats should be bumped now.
- Import -
studio-app-importgroup- importer type if the request is successful
- failure if the request is unsuccessful
- export -
studio-app-exportfull-siteordatabase-onlyif successful and depending on the options- failure if the request is unsuccessful
I believe this will allow us to have an idea of usage, while still providing some insights into the number of failed requests.
@sejas @fredrikekelund please let me know what you think about this approach.
| return await importer.import( site.path, site.id ); | ||
| const result = await importer.import( site.path, site.id ); | ||
| return result; |
There was a problem hiding this comment.
This looks like a leftover, we could probably revert it 🙂
There was a problem hiding this comment.
Thanks for catching that!
| bumpStat( STATS_GROUP.STUDIO_IMPORT, STATS_METRIC.SUCCESS ); | ||
| bumpStat( STATS_GROUP.STUDIO_IMPORT, result.importerType || 'unknown' ); |
There was a problem hiding this comment.
Do we intentionally use the same group name for both these calls?
There was a problem hiding this comment.
I did.
I wasn't sure about the best approach for this, should we have everything regarding import in the same group and track success, failure and type, or have a distinct group for each, like import_result and import_type. What do you think it's the best?
There was a problem hiding this comment.
Adding explicit types for bump groups and metrics like this is great 👍 I would argue enums are a great fit here, though.
This seems like a relatively simple change aside from that we have to fix the importSite function in ipc-handlers, where we're currently passing a dynamic value as the second argument to bumpStat. Maybe we could add a bumpStat prop to the ImporterResult type to fix this…
Consider this a recommendation, not a hard blocker.
There was a problem hiding this comment.
Done, let me know how it looks!
| @@ -0,0 +1,29 @@ | |||
| export enum STATS_GROUP { | |||
There was a problem hiding this comment.
| export enum STATS_GROUP { | |
| export enum StatsGroup { |
The nice thing about enums is that we get types and values kind of rolled into one. No need to export a separate StatsGroup type.
| PLAYGROUND_IMPORTER = 'PlaygroundImporter', | ||
| WPRESS_IMPORTER = 'WpressImporter', | ||
| UNKNOWN = 'unknown', | ||
| } |
There was a problem hiding this comment.
| } | |
| DARWIN = 'darwin', | |
| LINUX = 'linux', | |
| WINDOWS = 'win32', | |
| } | |
| export function getPlatform(): StatsMetric { | |
| switch ( process.platform ) { | |
| case 'darwin': | |
| return STATS_METRIC.DARWIN; | |
| case 'linux': | |
| return STATS_METRIC.LINUX; | |
| case 'win32': | |
| return STATS_METRIC.WINDOWS; | |
| default: | |
| throw new Error( `Unsupported platform: ${ process.platform }` ); | |
| } | |
| } |
Something like this would allow us not to export a separate type for StatsMetric. Kind of a nit, but would be nice to address, IMO.
| wpContentDirectory: this.backup.wpContentDirectory, | ||
| wpConfig: this.backup.wpConfig, | ||
| meta: this.meta, | ||
| importerType: this.constructor.name, |
There was a problem hiding this comment.
| importerType: this.constructor.name, | |
| importerType: StatsMetric.SQL_IMPORTER, |
Defining the value by referencing the enum directly seems more understandable.
There was a problem hiding this comment.
I find it strange to reference stats props in the importer class.
What do you think about using a similar approach to the one we implemented for getPlatform ( check getImporterMetric )?
There was a problem hiding this comment.
I like the new approach 👍
fredrikekelund
left a comment
There was a problem hiding this comment.
Nice! Looks good and works as expected 👍
| bumpStat( | ||
| StatsGroup.STUDIO_EXPORT, | ||
| options.includes.database && | ||
| ! options.includes.uploads && | ||
| ! options.includes.plugins && | ||
| ! options.includes.themes && | ||
| ! options.includes.muPlugins | ||
| ? StatsMetric.DATABASE_ONLY | ||
| : StatsMetric.FULL_SITE | ||
| ); |
There was a problem hiding this comment.
| bumpStat( | |
| StatsGroup.STUDIO_EXPORT, | |
| options.includes.database && | |
| ! options.includes.uploads && | |
| ! options.includes.plugins && | |
| ! options.includes.themes && | |
| ! options.includes.muPlugins | |
| ? StatsMetric.DATABASE_ONLY | |
| : StatsMetric.FULL_SITE | |
| ); | |
| const isDatabaseOnly = | |
| options.includes.database && | |
| ! options.includes.uploads && | |
| ! options.includes.plugins && | |
| ! options.includes.themes && | |
| ! options.includes.muPlugins; | |
| bumpStat( | |
| StatsGroup.STUDIO_EXPORT, | |
| isDatabaseOnly ? StatsMetric.DATABASE_ONLY : StatsMetric.FULL_SITE | |
| ); |
Nit, but this would read better if we assigned the condition to a variable.
| case 'JetpackImporter': | ||
| return StatsMetric.JETPACK_IMPORTER; | ||
| case 'LocalImporter': | ||
| return StatsMetric.LOCAL_IMPORTER; | ||
| case 'SQLImporter': | ||
| return StatsMetric.SQL_IMPORTER; | ||
| case 'PlaygroundImporter': | ||
| return StatsMetric.PLAYGROUND_IMPORTER; | ||
| case 'WpressImporter': |
There was a problem hiding this comment.
| case 'JetpackImporter': | |
| return StatsMetric.JETPACK_IMPORTER; | |
| case 'LocalImporter': | |
| return StatsMetric.LOCAL_IMPORTER; | |
| case 'SQLImporter': | |
| return StatsMetric.SQL_IMPORTER; | |
| case 'PlaygroundImporter': | |
| return StatsMetric.PLAYGROUND_IMPORTER; | |
| case 'WpressImporter': | |
| case JetpackImporter.name: | |
| return StatsMetric.JETPACK_IMPORTER; | |
| case LocalImporter.name: | |
| return StatsMetric.LOCAL_IMPORTER; | |
| case SQLImporter.name: | |
| return StatsMetric.SQL_IMPORTER; | |
| case PlaygroundImporter.name: | |
| return StatsMetric.PLAYGROUND_IMPORTER; | |
| case WpressImporter.name: |
Optional nit, but this would help clarify what we're referencing.
There was a problem hiding this comment.
Nice, that's perfect! 👍
| wpContentDirectory: this.backup.wpContentDirectory, | ||
| wpConfig: this.backup.wpConfig, | ||
| meta: this.meta, | ||
| importerType: this.constructor.name, |
There was a problem hiding this comment.
I like the new approach 👍
|
@sejas since you created the original issue and made some suggestion for different approaches I would love to have your feedback. Could I ask you for another review? 🙇 |
sejas
left a comment
There was a problem hiding this comment.
@bcotrim , looks great! thanks for making the changes.
I left a couple of comments, like removing the error in getPlatformMetric and the result check in export site.
I tested it by running npm start and observing the the bumpStat logs:
Would have bumped stat: studio-app-export=full-site
Would have bumped stat: studio-app-export=database-only
Would have bumped stat: studio-app-import=SQLImporter
Would have bumped stat: studio-app-import=JetpackImporter
Would have bumped stat: studio-app-import=PlaygroundImporter
| import { loadUserData, saveUserData } from 'src/storage/user-data'; | ||
|
|
||
| export type AggregateInterval = 'daily' | 'weekly' | 'monthly'; | ||
| import { AggregateInterval, StatsGroup, StatsMetric } from './types'; |
There was a problem hiding this comment.
Should we use an absolute path?
| import { AggregateInterval, StatsGroup, StatsMetric } from './types'; | |
| import { AggregateInterval, StatsGroup, StatsMetric } from 'src/lib/bump-stats/types'; |
| default: | ||
| throw new Error( `Unsupported platform: ${ process.platform }` ); |
There was a problem hiding this comment.
Not sure if this Error could crash the app in some systems. It's not very likely but maybe some unix systems return a different platform string. Maybe returning 'unknown' is safer?
|
|
||
| const result = await exportBackup( options, onEvent ); | ||
|
|
||
| if ( result ) { |
There was a problem hiding this comment.
Why do we need this result check? Or if we leave it should we consider the else as a failure?
Related issues
Proposed Changes
Testing Instructions
Run the test suite to verify that the bump-stats tests pass with the new typed implementation
Verify that stats are properly tracked for import and export operations:
Check that app launch stats continue to work as expected:
Confirm that the TypeScript type safety prevents using invalid stat groups or metrics
Example Testing logs
Pre-merge Checklist