diff --git a/code/core/src/core-events/index.ts b/code/core/src/core-events/index.ts index 3ab4b36fb3a1..f2acd7739990 100644 --- a/code/core/src/core-events/index.ts +++ b/code/core/src/core-events/index.ts @@ -58,6 +58,8 @@ enum events { // A global was just updated GLOBALS_UPDATED = 'globalsUpdated', REGISTER_SUBSCRIPTION = 'registerSubscription', + // Preview initialized for first-load-event + PREVIEW_INITIALIZED = 'previewInitialized', // Tell the manager that the user pressed a key in the preview PREVIEW_KEYDOWN = 'previewKeydown', // Tell the preview that the builder is in progress @@ -114,6 +116,7 @@ export const { PLAY_FUNCTION_THREW_EXCEPTION, UNHANDLED_ERRORS_WHILE_PLAYING, PRELOAD_ENTRIES, + PREVIEW_INITIALIZED, PREVIEW_BUILDER_PROGRESS, PREVIEW_KEYDOWN, REGISTER_SUBSCRIPTION, diff --git a/code/core/src/core-server/presets/common-preset.ts b/code/core/src/core-server/presets/common-preset.ts index fe09e8647688..1bbdb9cca171 100644 --- a/code/core/src/core-server/presets/common-preset.ts +++ b/code/core/src/core-server/presets/common-preset.ts @@ -33,6 +33,7 @@ import { resolvePackageDir } from '../../shared/utils/module'; import { initCreateNewStoryChannel } from '../server-channel/create-new-story-channel'; import { initFileSearchChannel } from '../server-channel/file-search-channel'; import { initOpenInEditorChannel } from '../server-channel/open-in-editor-channel'; +import { initPreviewInitializedChannel } from '../server-channel/preview-initialized-channel'; import { defaultFavicon, defaultStaticDirs } from '../utils/constants'; import { initializeSaveStory } from '../utils/save-story/save-story'; import { parseStaticDir } from '../utils/server-statics'; @@ -258,6 +259,7 @@ export const experimental_serverChannel = async ( initFileSearchChannel(channel, options, coreOptions); initCreateNewStoryChannel(channel, options, coreOptions); initOpenInEditorChannel(channel, options, coreOptions); + initPreviewInitializedChannel(channel, options, coreOptions); return channel; }; diff --git a/code/core/src/core-server/server-channel/preview-initialized-channel.ts b/code/core/src/core-server/server-channel/preview-initialized-channel.ts new file mode 100644 index 000000000000..1a0ad5e595da --- /dev/null +++ b/code/core/src/core-server/server-channel/preview-initialized-channel.ts @@ -0,0 +1,31 @@ +import type { Channel } from 'storybook/internal/channels'; +import { PREVIEW_INITIALIZED } from 'storybook/internal/core-events'; +import { telemetry } from 'storybook/internal/telemetry'; +import type { CoreConfig, Options } from 'storybook/internal/types'; + +import { getLastEvents } from '../../telemetry/event-cache'; +import { getSessionId } from '../../telemetry/session-id'; + +export function initPreviewInitializedChannel( + channel: Channel, + options: Options, + _coreConfig: CoreConfig +) { + channel.on(PREVIEW_INITIALIZED, async ({ userAgent }) => { + if (!options.disableTelemetry) { + try { + const sessionId = await getSessionId(); + const lastEvents = await getLastEvents(); + const lastInit = lastEvents.init; + const lastPreviewFirstLoad = lastEvents['preview-first-load']; + if (!lastPreviewFirstLoad) { + const isInitSession = lastInit?.body.sessionId === sessionId; + const timeSinceInit = lastInit ? Date.now() - lastInit.body.timestamp : undefined; + telemetry('preview-first-load', { timeSinceInit, isInitSession, userAgent }); + } + } catch (e) { + // do nothing + } + } + }); +} diff --git a/code/core/src/manager/globals/exports.ts b/code/core/src/manager/globals/exports.ts index b717d6296d56..dbfcca072246 100644 --- a/code/core/src/manager/globals/exports.ts +++ b/code/core/src/manager/globals/exports.ts @@ -567,6 +567,7 @@ export default { 'PLAY_FUNCTION_THREW_EXCEPTION', 'PRELOAD_ENTRIES', 'PREVIEW_BUILDER_PROGRESS', + 'PREVIEW_INITIALIZED', 'PREVIEW_KEYDOWN', 'REGISTER_SUBSCRIPTION', 'REQUEST_WHATS_NEW_DATA', diff --git a/code/core/src/preview-api/modules/preview-web/Preview.tsx b/code/core/src/preview-api/modules/preview-web/Preview.tsx index f7ac363d1205..60b1892c17e8 100644 --- a/code/core/src/preview-api/modules/preview-web/Preview.tsx +++ b/code/core/src/preview-api/modules/preview-web/Preview.tsx @@ -9,6 +9,7 @@ import { FORCE_REMOUNT, FORCE_RE_RENDER, GLOBALS_UPDATED, + PREVIEW_INITIALIZED, RESET_STORY_ARGS, type RequestData, type ResponseData, @@ -128,6 +129,9 @@ export class Preview { const projectAnnotations = await this.getProjectAnnotationsOrRenderError(); await this.runBeforeAllHook(projectAnnotations); await this.initializeWithProjectAnnotations(projectAnnotations); + // eslint-disable-next-line compat/compat + const userAgent = globalThis?.navigator?.userAgent; + await this.channel.emit(PREVIEW_INITIALIZED, { userAgent }); } catch (err) { this.rejectStoreInitializationPromise(err as Error); } diff --git a/code/core/src/telemetry/event-cache.ts b/code/core/src/telemetry/event-cache.ts index c40d7254aefc..0f25b1c14579 100644 --- a/code/core/src/telemetry/event-cache.ts +++ b/code/core/src/telemetry/event-cache.ts @@ -24,8 +24,12 @@ export const set = async (eventType: EventType, body: any) => { }; export const get = async (eventType: EventType) => { - const lastEvents = await cache.get('lastEvents'); - return lastEvents?.[eventType]; + const lastEvents = await getLastEvents(); + return lastEvents[eventType]; +}; + +export const getLastEvents = async () => { + return (await cache.get('lastEvents')) || {}; }; const upgradeFields = (event: any): UpgradeSummary => { diff --git a/code/core/src/telemetry/telemetry.ts b/code/core/src/telemetry/telemetry.ts index 747906c41926..064596a84a75 100644 --- a/code/core/src/telemetry/telemetry.ts +++ b/code/core/src/telemetry/telemetry.ts @@ -109,17 +109,13 @@ export async function sendTelemetry( try { request = prepareRequest(data, context, options); tasks.push(request); - if (options.immediate) { - await Promise.all(tasks); - } else { - await request; - } const sessionId = await getSessionId(); const eventId = nanoid(); const body = { ...rest, eventType, eventId, sessionId, metadata, payload, context }; - await saveToCache(eventType, body); + const waitFor = options.immediate ? tasks : [request]; + await Promise.all([...waitFor, saveToCache(eventType, body)]); } catch (err) { // } finally { diff --git a/code/core/src/telemetry/types.ts b/code/core/src/telemetry/types.ts index de36f4af5fb1..e6f221da277e 100644 --- a/code/core/src/telemetry/types.ts +++ b/code/core/src/telemetry/types.ts @@ -32,7 +32,8 @@ export type EventType = | 'test-run' | 'addon-onboarding' | 'onboarding-survey' - | 'mocking'; + | 'mocking' + | 'preview-first-load'; export interface Dependency { version: string | undefined;