Skip to content

Add MC stats for import / export to track usage - #983

Merged
bcotrim merged 18 commits into
trunkfrom
add/import_export_tracks
Mar 7, 2025
Merged

Add MC stats for import / export to track usage#983
bcotrim merged 18 commits into
trunkfrom
add/import_export_tracks

Conversation

@bcotrim

@bcotrim bcotrim commented Feb 26, 2025

Copy link
Copy Markdown
Contributor

Related issues

Proposed Changes

  • Add typed constants for stats groups and metrics
  • Replace string literals with typed constants in bump-stats functions
  • Update tests to use the new typed constants
  • Add stats tracking for import and export operations (success/failure)
  • Added tracking for import file types
  • Added tracking for export content types

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:

    • Run the app in development mode and check the console logs for "Would have bumped stat: studio-app-import=success" when importing a site
    • Run the app in development mode and check the console logs for bumped stats when exporting a site. For example
    • Verify that failure stats are logged when operations fail
  • Check that app launch stats continue to work as expected:

    • Run the app in development mode and verify console logs show "Would have bumped stat: studio-app-launch-total=darwin" (or your platform) on startup
    • Verify weekly unique stats are logged with "Would have bumped stat: local-environment-launch-uniques=darwin"
  • Confirm that the TypeScript type safety prevents using invalid stat groups or metrics

Example Testing logs

 Would have bumped stat: studio-app-export=success
 Would have bumped stat: studio-app-export-content=database
 Would have bumped stat: studio-app-export-content=uploads
 Would have bumped stat: studio-app-export-content=plugins
 Would have bumped stat: studio-app-export-content=themes
 Would have bumped stat: studio-app-export-content=mu-plugins
Would have bumped stat: studio-app-import=success
Would have bumped stat: studio-app-import-type=application/zip

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?
@bcotrim
bcotrim requested a review from a team February 26, 2025 13:11
@bcotrim
bcotrim marked this pull request as draft February 26, 2025 13:16
@bcotrim
bcotrim marked this pull request as ready for review February 26, 2025 15:25

@sejas sejas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Comment thread src/ipc-handlers.ts Outdated
Comment on lines +691 to +708
// 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 );
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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?

<Button
onClick={ () => handleExport( exportFullSite ) }
variant="primary"
disabled={ isExportDisabled }
>
{ __( 'Export entire site' ) }
</Button>
<Button
onClick={ () => handleExport( exportDatabase ) }
type="submit"
variant="secondary"
className={ cx(
isExportDisabled ? '' : '!text-a8c-blueberry !shadow-a8c-blueberry'
) }
disabled={ isExportDisabled }
>
{ __( 'Export database' ) }
</Button>

Comment thread src/ipc-handlers.ts Outdated
Comment on lines +124 to +126
bumpStat( STATS_GROUP.STUDIO_IMPORT, STATS_METRIC.SUCCESS );
bumpStat( STATS_GROUP.STUDIO_IMPORT_TYPE, backupFile.type );

@sejas sejas Feb 27, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

@bcotrim
bcotrim force-pushed the add/import_export_tracks branch from d35d9f3 to a7ff1b5 Compare February 28, 2025 13:27

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Heads up that I just landed #988 which modified this file in its previous location. Should be an easy conflict to resolve

@bcotrim
bcotrim force-pushed the add/import_export_tracks branch from b6dfbdc to 6750cca Compare February 28, 2025 15:12
@bcotrim

bcotrim commented Feb 28, 2025

Copy link
Copy Markdown
Contributor Author

@sejas @fredrikekelund Thanks for the reviews!
I've made some changes to address the comments, please let me know what you think!

@bcotrim bcotrim changed the title Add/import export tracks Feb 28, 2025

@fredrikekelund fredrikekelund left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Using typed arguments for bumpStat functions is great, and the code mostly works well, but I left a few we should adress before merging.

Comment thread src/hooks/use-import-export.tsx Outdated

const exportFullSite = useCallback(
async ( selectedSite: SiteDetails ): Promise< string | undefined > => {
getIpcApi().ipcBumpStat( STATS_GROUP.STUDIO_EXPORT, STATS_METRIC.FULL_SITE );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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 ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I moved all the logic to the IPC now.
The following stats should be bumped now.

  • Import - studio-app-import group
    • importer type if the request is successful
    • failure if the request is unsuccessful
  • export - studio-app-export
    • full-site or database-only if 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.

Comment on lines +88 to +89
return await importer.import( site.path, site.id );
const result = await importer.import( site.path, site.id );
return result;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks like a leftover, we could probably revert it 🙂

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for catching that!

Comment thread src/ipc-handlers.ts Outdated
Comment on lines +125 to +126
bumpStat( STATS_GROUP.STUDIO_IMPORT, STATS_METRIC.SUCCESS );
bumpStat( STATS_GROUP.STUDIO_IMPORT, result.importerType || 'unknown' );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do we intentionally use the same group name for both these calls?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done, let me know how it looks!

Comment thread src/lib/bump-stats/types.ts Outdated
@@ -0,0 +1,29 @@
export enum STATS_GROUP {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
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',
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
}
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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
importerType: this.constructor.name,
importerType: StatsMetric.SQL_IMPORTER,

Defining the value by referencing the enum directly seems more understandable.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I like the new approach 👍

@fredrikekelund fredrikekelund left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice! Looks good and works as expected 👍

Comment thread src/ipc-handlers.ts
Comment on lines +689 to +698
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
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
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.

Comment thread src/lib/bump-stats/types.ts Outdated
Comment on lines +45 to +53
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':

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nice, that's perfect! 👍

wpContentDirectory: this.backup.wpContentDirectory,
wpConfig: this.backup.wpConfig,
meta: this.meta,
importerType: this.constructor.name,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I like the new approach 👍

@bcotrim

bcotrim commented Mar 7, 2025

Copy link
Copy Markdown
Contributor Author

@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 sejas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@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
Comment thread src/lib/bump-stats/index.ts Outdated
import { loadUserData, saveUserData } from 'src/storage/user-data';

export type AggregateInterval = 'daily' | 'weekly' | 'monthly';
import { AggregateInterval, StatsGroup, StatsMetric } from './types';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we use an absolute path?

Suggested change
import { AggregateInterval, StatsGroup, StatsMetric } from './types';
import { AggregateInterval, StatsGroup, StatsMetric } from 'src/lib/bump-stats/types';
Comment thread src/lib/bump-stats/types.ts Outdated
Comment on lines +46 to +47
default:
throw new Error( `Unsupported platform: ${ process.platform }` );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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?

Comment thread src/ipc-handlers.ts

const result = await exportBackup( options, onEvent );

if ( result ) {

@sejas sejas Mar 7, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need this result check? Or if we leave it should we consider the else as a failure?

@bcotrim
bcotrim merged commit b336da7 into trunk Mar 7, 2025
@bcotrim
bcotrim deleted the add/import_export_tracks branch March 7, 2025 12:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants