Skip to content

Commit

Permalink
Merge pull request #20084 from storybookjs/shilman/ugrade-success-tel…
Browse files Browse the repository at this point in the history
…emetry

Telemetry: Add precedingUpgrade data to dev/build events
  • Loading branch information
shilman authored Dec 5, 2022
2 parents db68fa9 + 4d0ffcf commit 3b97e7e
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 16 deletions.
15 changes: 9 additions & 6 deletions code/lib/core-server/src/build-static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { dedent } from 'ts-dedent';
import global from 'global';

import { logger } from '@storybook/node-logger';
import { telemetry } from '@storybook/telemetry';
import { telemetry, getPrecedingUpgrade } from '@storybook/telemetry';
import type {
BuilderOptions,
CLIOptions,
Expand Down Expand Up @@ -173,11 +173,14 @@ export async function buildStaticStandalone(
effects.push(
initializedStoryIndexGenerator.then(async (generator) => {
const storyIndex = await generator?.getIndex();
const payload = storyIndex
? {
storyIndex: summarizeIndex(storyIndex),
}
: undefined;
const payload = {
precedingUpgrade: await getPrecedingUpgrade('build'),
};
if (storyIndex) {
Object.assign(payload, {
storyIndex: summarizeIndex(storyIndex),
});
}
await telemetry('build', payload, { configDir: options.configDir });
})
);
Expand Down
18 changes: 11 additions & 7 deletions code/lib/core-server/src/dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {

import { normalizeStories, logConfig } from '@storybook/core-common';

import { telemetry } from '@storybook/telemetry';
import { telemetry, getPrecedingUpgrade } from '@storybook/telemetry';
import { getMiddleware } from './utils/middleware';
import { getServerAddresses } from './utils/server-address';
import { getServer } from './utils/server-init';
Expand Down Expand Up @@ -156,12 +156,16 @@ async function doTelemetry(
initializedStoryIndexGenerator.then(async (generator) => {
const storyIndex = await generator?.getIndex();
const { versionCheck, versionUpdates } = options;
const payload = storyIndex
? {
versionStatus: versionUpdates ? versionStatus(versionCheck) : 'disabled',
storyIndex: summarizeIndex(storyIndex),
}
: undefined;
const payload = {
precedingUpgrade: await getPrecedingUpgrade('dev'),
};
if (storyIndex) {
Object.assign(payload, {
versionStatus: versionUpdates ? versionStatus(versionCheck) : 'disabled',
storyIndex: summarizeIndex(storyIndex),
});
}

telemetry('dev', payload, { configDir: options.configDir });
});
}
Expand Down
146 changes: 146 additions & 0 deletions code/lib/telemetry/src/event-cache.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { getPrecedingUpgrade } from './event-cache';

expect.addSnapshotSerializer({
print: (val: any) => JSON.stringify(val, null, 2),
test: (val) => typeof val !== 'string',
});

describe('event-cache', () => {
const init = { body: { eventType: 'init', eventId: 'init' }, timestamp: 1 };
const upgrade = { body: { eventType: 'upgrade', eventId: 'upgrade' }, timestamp: 2 };
const dev = { body: { eventType: 'dev', eventId: 'dev' }, timestamp: 3 };

describe('data handling', () => {
it('errors', async () => {
const preceding = await getPrecedingUpgrade('dev', {
init: { timestamp: 1, body: { ...init.body, error: {} } },
});
expect(preceding).toMatchInlineSnapshot(`
{
"timestamp": 1,
"eventType": "init",
"eventId": "init"
}
`);
});

it('session IDs', async () => {
const preceding = await getPrecedingUpgrade('dev', {
init: { timestamp: 1, body: { ...init.body, sessionId: 100 } },
});
expect(preceding).toMatchInlineSnapshot(`
{
"timestamp": 1,
"eventType": "init",
"eventId": "init",
"sessionId": 100
}
`);
});

it('extra fields', async () => {
const preceding = await getPrecedingUpgrade('dev', {
init: { timestamp: 1, body: { ...init.body, foobar: 'baz' } },
});
expect(preceding).toMatchInlineSnapshot(`
{
"timestamp": 1,
"eventType": "init",
"eventId": "init"
}
`);
});
});

describe('no intervening dev events', () => {
it('no upgrade events', async () => {
const preceding = await getPrecedingUpgrade('dev', {});
expect(preceding).toBeUndefined();
});

it('init', async () => {
const preceding = await getPrecedingUpgrade('dev', { init });
expect(preceding).toMatchInlineSnapshot(`
{
"timestamp": 1,
"eventType": "init",
"eventId": "init"
}
`);
});

it('upgrade', async () => {
const preceding = await getPrecedingUpgrade('dev', { upgrade });
expect(preceding).toMatchInlineSnapshot(`
{
"timestamp": 2,
"eventType": "upgrade",
"eventId": "upgrade"
}
`);
});

it('both init and upgrade', async () => {
const preceding = await getPrecedingUpgrade('dev', { init, upgrade });
expect(preceding).toMatchInlineSnapshot(`
{
"timestamp": 2,
"eventType": "upgrade",
"eventId": "upgrade"
}
`);
});
});

describe('intervening dev events', () => {
it('no upgrade events', async () => {
const preceding = await getPrecedingUpgrade('dev', { dev });
expect(preceding).toBeUndefined();
});

it('init', async () => {
const preceding = await getPrecedingUpgrade('dev', { init, dev });
expect(preceding).toBeUndefined();
});

it('upgrade', async () => {
const preceding = await getPrecedingUpgrade('dev', { upgrade, dev });
expect(preceding).toBeUndefined();
});

it('init followed by upgrade', async () => {
const preceding = await getPrecedingUpgrade('dev', { init, upgrade, dev });
expect(preceding).toBeUndefined();
});

it('both init and upgrade with intervening dev', async () => {
const secondUpgrade = {
body: { eventType: 'upgrade', eventId: 'secondUpgrade' },
timestamp: 4,
};
const preceding = await getPrecedingUpgrade('dev', { init, dev, upgrade: secondUpgrade });
expect(preceding).toMatchInlineSnapshot(`
{
"timestamp": 4,
"eventType": "upgrade",
"eventId": "secondUpgrade"
}
`);
});

it('both init and upgrade with non-intervening dev', async () => {
const earlyDev = {
body: { eventType: 'dev', eventId: 'earlyDev' },
timestamp: -1,
};
const preceding = await getPrecedingUpgrade('dev', { dev: earlyDev, init, upgrade });
expect(preceding).toMatchInlineSnapshot(`
{
"timestamp": 2,
"eventType": "upgrade",
"eventId": "upgrade"
}
`);
});
});
});
39 changes: 39 additions & 0 deletions code/lib/telemetry/src/event-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { cache } from '@storybook/core-common';
import type { EventType } from './types';

export const set = async (eventType: EventType, body: any) => {
const lastEvents = (await cache.get('lastEvents')) || {};
lastEvents[eventType] = { body, timestamp: Date.now() };
await cache.set('lastEvents', lastEvents);
};

export const get = async (eventType: EventType) => {
const lastEvents = await cache.get('lastEvents');
return lastEvents?.[eventType];
};

const upgradeFields = (event: any) => {
const { body, timestamp } = event;
return {
timestamp,
eventType: body?.eventType,
eventId: body?.eventId,
sessionId: body?.sessionId,
};
};

export const getPrecedingUpgrade = async (eventType: EventType, events: any = undefined) => {
const lastEvents = events || (await cache.get('lastEvents'));
const init = lastEvents?.init;
let precedingUpgrade = init;
const upgrade = lastEvents?.upgrade;
if (upgrade && (!precedingUpgrade || upgrade.timestamp > precedingUpgrade?.timestamp)) {
precedingUpgrade = upgrade;
}
if (!precedingUpgrade) return undefined;

const lastEventOfType = lastEvents?.[eventType];
return !lastEventOfType?.timestamp || precedingUpgrade.timestamp > lastEventOfType.timestamp
? upgradeFields(precedingUpgrade)
: undefined;
};
2 changes: 2 additions & 0 deletions code/lib/telemetry/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export * from './types';

export { getStorybookCoreVersion } from './package-json';

export { getPrecedingUpgrade } from './event-cache';

export const telemetry = async (
eventType: EventType,
payload: Payload = {},
Expand Down
11 changes: 8 additions & 3 deletions code/lib/telemetry/src/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import retry from 'fetch-retry';
import { nanoid } from 'nanoid';
import type { Options, TelemetryData } from './types';
import { getAnonymousProjectId } from './anonymous-id';
import { set as saveToCache } from './event-cache';

const URL = process.env.STORYBOOK_TELEMETRY_URL || 'https://storybook.js.org/event-log';

Expand All @@ -24,16 +25,17 @@ export async function sendTelemetry(
// the server actually gets the request and stores it anyway.

// flatten the data before we send it
const { payload, metadata, ...rest } = data;
const { eventType, payload, metadata, ...rest } = data;
const context = options.stripMetadata
? {}
: {
anonymousId: getAnonymousProjectId(),
inCI: Boolean(process.env.CI),
};
const eventId = nanoid();
const body = { ...rest, eventId, sessionId, metadata, payload, context };
const body = { ...rest, eventType, eventId, sessionId, metadata, payload, context };
let request: Promise<any>;
let cache: Promise<any>;

try {
request = fetch(URL, {
Expand All @@ -49,15 +51,18 @@ export async function sendTelemetry(
: 1000),
});
tasks.push(request);
cache = saveToCache(eventType, body);
tasks.push(cache);

if (options.immediate) {
await Promise.all(tasks);
} else {
await request;
await cache;
}
} catch (err) {
//
} finally {
tasks = tasks.filter((task) => task !== request);
tasks = tasks.filter((task) => task !== request && task !== cache);
}
}

0 comments on commit 3b97e7e

Please sign in to comment.