Skip to content

Commit beb96d5

Browse files
committed
git cherry-pick -m1 -x 4db60d6
1 parent e3d2dec commit beb96d5

File tree

14 files changed

+786
-57
lines changed

14 files changed

+786
-57
lines changed

code/core/src/core-server/utils/StoryIndexGenerator.test.ts

+206-21
Large diffs are not rendered by default.

code/core/src/core-server/utils/StoryIndexGenerator.ts

+37-15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable no-underscore-dangle */
12
import path from 'node:path';
23
import chalk from 'chalk';
34
import fs from 'fs-extra';
@@ -17,6 +18,7 @@ import type {
1718
StoryIndex,
1819
Indexer,
1920
StorybookConfigRaw,
21+
IndexInputStats,
2022
} from '@storybook/core/types';
2123
import { userOrAutoTitleFromSpecifier, sortStoriesV7 } from '@storybook/core/preview-api';
2224
import { commonGlobOptions, normalizeStoryPath } from '@storybook/core/common';
@@ -27,14 +29,17 @@ import { analyze } from '@storybook/docs-mdx';
2729
import { dedent } from 'ts-dedent';
2830
import { autoName } from './autoName';
2931
import { IndexingError, MultipleIndexingError } from './IndexingError';
32+
import { addStats, type IndexStatsSummary } from './summarizeStats';
3033

3134
// Extended type to keep track of the csf meta id so we know the component id when referencing docs in `extractDocs`
32-
type StoryIndexEntryWithMetaId = StoryIndexEntry & { metaId?: string };
35+
type StoryIndexEntryWithExtra = StoryIndexEntry & {
36+
extra: { metaId?: string; stats: IndexInputStats };
37+
};
3338
/** A .mdx file will produce a docs entry */
3439
type DocsCacheEntry = DocsIndexEntry;
3540
/** A *.stories.* file will produce a list of stories and possibly a docs entry */
3641
type StoriesCacheEntry = {
37-
entries: (StoryIndexEntryWithMetaId | DocsIndexEntry)[];
42+
entries: (StoryIndexEntryWithExtra | DocsIndexEntry)[];
3843
dependents: Path[];
3944
type: 'stories';
4045
};
@@ -104,6 +109,9 @@ export class StoryIndexGenerator {
104109
// - the preview changes [not yet implemented]
105110
private lastIndex?: StoryIndex | null;
106111

112+
// Cache the last value stats calculation, mirroring lastIndex
113+
private lastStats?: IndexStatsSummary;
114+
107115
// Same as the above but for the error case
108116
private lastError?: Error | null;
109117

@@ -222,7 +230,7 @@ export class StoryIndexGenerator {
222230
projectTags,
223231
}: {
224232
projectTags?: Tag[];
225-
}): Promise<(IndexEntry | ErrorEntry)[]> {
233+
}): Promise<{ entries: (IndexEntry | ErrorEntry)[]; stats: IndexStatsSummary }> {
226234
// First process all the story files. Then, in a second pass,
227235
// process the docs files. The reason for this is that the docs
228236
// files may use the `<Meta of={XStories} />` syntax, which requires
@@ -237,7 +245,8 @@ export class StoryIndexGenerator {
237245
this.extractDocs(specifier, absolutePath, projectTags)
238246
);
239247

240-
return this.specifiers.flatMap((specifier) => {
248+
const statsSummary = {} as IndexStatsSummary;
249+
const entries = this.specifiers.flatMap((specifier) => {
241250
const cache = this.specifierToCache.get(specifier);
242251
invariant(
243252
cache,
@@ -252,12 +261,17 @@ export class StoryIndexGenerator {
252261

253262
return entry.entries.map((item) => {
254263
if (item.type === 'docs') return item;
255-
// Drop the meta id as it isn't part of the index, we just used it for record keeping in `extractDocs`
256-
const { metaId, ...existing } = item;
264+
265+
addStats(item.extra.stats, statsSummary);
266+
267+
// Drop extra data used for internal bookkeeping
268+
const { extra, ...existing } = item;
257269
return existing;
258270
});
259271
});
260272
});
273+
274+
return { entries, stats: statsSummary };
261275
}
262276

263277
findDependencies(absoluteImports: Path[]) {
@@ -341,22 +355,24 @@ export class StoryIndexGenerator {
341355
]);
342356
}
343357

344-
const entries: ((StoryIndexEntryWithMetaId | DocsCacheEntry) & { tags: Tag[] })[] =
358+
const entries: ((StoryIndexEntryWithExtra | DocsCacheEntry) & { tags: Tag[] })[] =
345359
indexInputs.map((input) => {
346360
const name = input.name ?? storyNameFromExport(input.exportName);
347361
const componentPath =
348362
input.rawComponentPath &&
349363
this.resolveComponentPath(input.rawComponentPath, absolutePath, matchPath);
350364
const title = input.title ?? defaultMakeTitle();
351365

352-
// eslint-disable-next-line no-underscore-dangle
353366
const id = input.__id ?? toId(input.metaId ?? title, storyNameFromExport(input.exportName));
354367
const tags = combineTags(...projectTags, ...(input.tags ?? []));
355368

356369
return {
357370
type: 'story',
358371
id,
359-
metaId: input.metaId,
372+
extra: {
373+
metaId: input.metaId,
374+
stats: input.__stats ?? {},
375+
},
360376
name,
361377
title,
362378
importPath,
@@ -428,12 +444,12 @@ export class StoryIndexGenerator {
428444

429445
// Also, if `result.of` is set, it means that we're using the `<Meta of={XStories} />` syntax,
430446
// so find the `title` defined the file that `meta` points to.
431-
let csfEntry: StoryIndexEntryWithMetaId | undefined;
447+
let csfEntry: StoryIndexEntryWithExtra | undefined;
432448
if (result.of) {
433449
const absoluteOf = makeAbsolute(result.of, normalizedPath, this.options.workingDir);
434450
dependencies.forEach((dep) => {
435451
if (dep.entries.length > 0) {
436-
const first = dep.entries.find((e) => e.type !== 'docs') as StoryIndexEntryWithMetaId;
452+
const first = dep.entries.find((e) => e.type !== 'docs') as StoryIndexEntryWithExtra;
437453

438454
if (
439455
path
@@ -475,7 +491,7 @@ export class StoryIndexGenerator {
475491
result.name ||
476492
(csfEntry ? autoName(importPath, csfEntry.importPath, defaultName) : defaultName);
477493

478-
const id = toId(csfEntry?.metaId || title, name);
494+
const id = toId(csfEntry?.extra.metaId || title, name);
479495

480496
const tags = combineTags(
481497
...projectTags,
@@ -598,15 +614,20 @@ export class StoryIndexGenerator {
598614
}
599615

600616
async getIndex() {
601-
if (this.lastIndex) return this.lastIndex;
617+
return (await this.getIndexAndStats()).storyIndex;
618+
}
619+
620+
async getIndexAndStats(): Promise<{ storyIndex: StoryIndex; stats: IndexStatsSummary }> {
621+
if (this.lastIndex && this.lastStats)
622+
return { storyIndex: this.lastIndex, stats: this.lastStats };
602623
if (this.lastError) throw this.lastError;
603624

604625
const previewCode = await this.getPreviewCode();
605626
const projectTags = this.getProjectTags(previewCode);
606627

607628
// Extract any entries that are currently missing
608629
// Pull out each file's stories into a list of stories, to be composed and sorted
609-
const storiesList = await this.ensureExtracted({ projectTags });
630+
const { entries: storiesList, stats } = await this.ensureExtracted({ projectTags });
610631

611632
try {
612633
const errorEntries = storiesList.filter((entry) => entry.type === 'error');
@@ -635,12 +656,13 @@ export class StoryIndexGenerator {
635656
previewCode && getStorySortParameter(previewCode)
636657
);
637658

659+
this.lastStats = stats;
638660
this.lastIndex = {
639661
v: 5,
640662
entries: sorted,
641663
};
642664

643-
return this.lastIndex;
665+
return { storyIndex: this.lastIndex, stats: this.lastStats };
644666
} catch (err) {
645667
this.lastError = err == null || err instanceof Error ? err : undefined;
646668
invariant(this.lastError);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const component = {};
2+
export default {
3+
component,
4+
};
5+
6+
export const WithPlay = {
7+
play: async () => {},
8+
};
9+
10+
export const WithStoryFn = () => {};
11+
12+
export const WithRender = {
13+
render: () => {},
14+
};
15+
16+
export const WithTest = {
17+
beforeEach: async () => {},
18+
play: async ({ mount }) => {
19+
await mount();
20+
},
21+
};
22+
23+
export const WithCSF1 = {
24+
parameters: {},
25+
decorators: [],
26+
loaders: [],
27+
};

code/core/src/core-server/utils/__tests__/index-extraction.test.ts

+48-12
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,12 @@ describe('story extraction', () => {
6262
"entries": [
6363
{
6464
"componentPath": undefined,
65+
"extra": {
66+
"metaId": "a",
67+
"stats": {},
68+
},
6569
"id": "a--story-one",
6670
"importPath": "./src/A.stories.js",
67-
"metaId": "a",
6871
"name": "Story One",
6972
"tags": [
7073
"story-tag-from-indexer",
@@ -74,9 +77,12 @@ describe('story extraction', () => {
7477
},
7578
{
7679
"componentPath": undefined,
80+
"extra": {
81+
"metaId": "custom-id",
82+
"stats": {},
83+
},
7784
"id": "some-fully-custom-id",
7885
"importPath": "./src/A.stories.js",
79-
"metaId": "custom-id",
8086
"name": "Another Story Name",
8187
"tags": [
8288
"story-tag-from-indexer",
@@ -118,9 +124,12 @@ describe('story extraction', () => {
118124
"entries": [
119125
{
120126
"componentPath": undefined,
127+
"extra": {
128+
"metaId": undefined,
129+
"stats": {},
130+
},
121131
"id": "f--story-one",
122132
"importPath": "./src/first-nested/deeply/F.stories.js",
123-
"metaId": undefined,
124133
"name": "Story One",
125134
"tags": [],
126135
"title": "F",
@@ -164,9 +173,12 @@ describe('story extraction', () => {
164173
"entries": [
165174
{
166175
"componentPath": undefined,
176+
"extra": {
177+
"metaId": "a",
178+
"stats": {},
179+
},
167180
"id": "a--story-one",
168181
"importPath": "./src/first-nested/deeply/F.stories.js",
169-
"metaId": "a",
170182
"name": "Story One",
171183
"tags": [
172184
"story-tag-from-indexer",
@@ -212,9 +224,12 @@ describe('story extraction', () => {
212224
"entries": [
213225
{
214226
"componentPath": undefined,
227+
"extra": {
228+
"metaId": "a",
229+
"stats": {},
230+
},
215231
"id": "a--story-one",
216232
"importPath": "./src/A.stories.js",
217-
"metaId": "a",
218233
"name": "Story One",
219234
"tags": [
220235
"story-tag-from-indexer",
@@ -278,9 +293,12 @@ describe('story extraction', () => {
278293
"entries": [
279294
{
280295
"componentPath": undefined,
296+
"extra": {
297+
"metaId": undefined,
298+
"stats": {},
299+
},
281300
"id": "a--story-one",
282301
"importPath": "./src/A.stories.js",
283-
"metaId": undefined,
284302
"name": "Story One",
285303
"tags": [
286304
"story-tag-from-indexer",
@@ -290,9 +308,12 @@ describe('story extraction', () => {
290308
},
291309
{
292310
"componentPath": undefined,
311+
"extra": {
312+
"metaId": undefined,
313+
"stats": {},
314+
},
293315
"id": "custom-title--story-two",
294316
"importPath": "./src/A.stories.js",
295-
"metaId": undefined,
296317
"name": "Custom Name For Second Story",
297318
"tags": [
298319
"story-tag-from-indexer",
@@ -302,9 +323,12 @@ describe('story extraction', () => {
302323
},
303324
{
304325
"componentPath": undefined,
326+
"extra": {
327+
"metaId": "custom-meta-id",
328+
"stats": {},
329+
},
305330
"id": "custom-meta-id--story-three",
306331
"importPath": "./src/A.stories.js",
307-
"metaId": "custom-meta-id",
308332
"name": "Story Three",
309333
"tags": [
310334
"story-tag-from-indexer",
@@ -347,9 +371,12 @@ describe('story extraction', () => {
347371
"entries": [
348372
{
349373
"componentPath": undefined,
374+
"extra": {
375+
"metaId": undefined,
376+
"stats": {},
377+
},
350378
"id": "a--story-one",
351379
"importPath": "./src/A.stories.js",
352-
"metaId": undefined,
353380
"name": "Story One",
354381
"tags": [
355382
"story-tag-from-indexer",
@@ -397,9 +424,12 @@ describe('docs entries from story extraction', () => {
397424
"entries": [
398425
{
399426
"componentPath": undefined,
427+
"extra": {
428+
"metaId": undefined,
429+
"stats": {},
430+
},
400431
"id": "a--story-one",
401432
"importPath": "./src/A.stories.js",
402-
"metaId": undefined,
403433
"name": "Story One",
404434
"tags": [
405435
"story-tag-from-indexer",
@@ -457,9 +487,12 @@ describe('docs entries from story extraction', () => {
457487
},
458488
{
459489
"componentPath": undefined,
490+
"extra": {
491+
"metaId": undefined,
492+
"stats": {},
493+
},
460494
"id": "a--story-one",
461495
"importPath": "./src/A.stories.js",
462-
"metaId": undefined,
463496
"name": "Story One",
464497
"tags": [
465498
"autodocs",
@@ -506,9 +539,12 @@ describe('docs entries from story extraction', () => {
506539
"entries": [
507540
{
508541
"componentPath": undefined,
542+
"extra": {
543+
"metaId": undefined,
544+
"stats": {},
545+
},
509546
"id": "a--story-one",
510547
"importPath": "./src/A.stories.js",
511-
"metaId": undefined,
512548
"name": "Story One",
513549
"tags": [
514550
"autodocs",

code/core/src/core-server/utils/doTelemetry.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ export async function doTelemetry(
1515
) {
1616
if (!core?.disableTelemetry) {
1717
initializedStoryIndexGenerator.then(async (generator) => {
18-
let storyIndex: StoryIndex | undefined;
18+
let indexAndStats;
1919
try {
20-
storyIndex = await generator?.getIndex();
20+
indexAndStats = await generator?.getIndexAndStats();
2121
} catch (err) {
2222
// If we fail to get the index, treat it as a recoverable error, but send it up to telemetry
2323
// as if we crashed. In the future we will revisit this to send a distinct error
@@ -36,10 +36,11 @@ export async function doTelemetry(
3636
const payload = {
3737
precedingUpgrade: await getPrecedingUpgrade(),
3838
};
39-
if (storyIndex) {
39+
if (indexAndStats) {
4040
Object.assign(payload, {
4141
versionStatus: versionUpdates && versionCheck ? versionStatus(versionCheck) : 'disabled',
42-
storyIndex: summarizeIndex(storyIndex),
42+
storyIndex: summarizeIndex(indexAndStats.storyIndex),
43+
storyStats: indexAndStats.stats,
4344
});
4445
}
4546
telemetry('dev', payload, { configDir: options.configDir });

0 commit comments

Comments
 (0)