Skip to content

Commit

Permalink
Merge pull request #10373 from storybookjs/10361-normalize-parameters
Browse files Browse the repository at this point in the history
Core: Normalize parameters in store/channel
  • Loading branch information
shilman authored Apr 29, 2020
2 parents 2d78535 + e2d54f6 commit 94c6e1d
Show file tree
Hide file tree
Showing 14 changed files with 446 additions and 166 deletions.
30 changes: 30 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- [Rolling back](#rolling-back)
- [New addon presets](#new-addon-presets)
- [Removed Deprecated APIs](#removed-deprecated-apis)
- [New setStories event](#new-setstories-event)
- [Client API changes](#client-api-changes)
- [Removed Legacy Story APIs](#removed-legacy-story-apis)
- [Can no longer add decorators/parameters after stories](#can-no-longer-add-decoratorsparameters-after-stories)
Expand Down Expand Up @@ -295,6 +296,31 @@ See the migration guides for further details:
- [Unified docs preset](#unified-docs-preset)
- [Addon centered decorator deprecated](#addon-centered-decorator-deprecated)

### New setStories event

The `setStories`/`SET_STORIES` event has changed and now denormalizes global and kind-level parameters. The new format of the event data is:

```js
{
globalParameters: { p: 'q' },
kindParameters: { kind: { p: 'q' } },
stories: /* as before but with only story-level parameters */
}
```

If you want the full denormalized parameters for a story, you can do something like:

```js
import { combineParameters } from '@storybook/api';

const story = data.stories[storyId];
const parameters = combineParameters(
data.globalParameters,
data.kindParameters[story.kind],
story.parameters
);
```

### Client API changes

#### Removed Legacy Story APIs
Expand Down Expand Up @@ -347,6 +373,10 @@ _You cannot set parameters from decorators_

Parameters are intended to be statically set at story load time. So setting them via a decorator doesn't quite make sense. If you were using this to control the rendering of a story, chances are using the new `args` feature is a more idiomatic way to do this.

_You can only set storySort globally_

If you want to change the ordering of stories, use `export const parameters = { options: { storySort: ... } }` in `preview.js`.

### Simplified Render Context

The `RenderContext` that is passed to framework rendering layers in order to render a story has been simplified, dropping a few members that were not used by frameworks to render stories. In particular, the following have been removed:
Expand Down
23 changes: 8 additions & 15 deletions docs/src/pages/configurations/options-parameter/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,10 @@ addons.setConfig({

### showRoots

Import and use `addParameters` with the `options` key in your `preview.js` file.
Export `parameters` with the `options` key in your `preview.js` file.

```js
import { addParameters } from '@storybook/react';

addParameters({
export const parameters = {
options: {
/**
* display the top-level grouping as a "root" in the sidebar
Expand All @@ -100,46 +98,41 @@ By default, stories are sorted in the order in which they were imported. This ca
The most powerful method of sorting is to provide a function to `storySort`. Any custom sorting can be achieved with this method.

```js
import { addParameters } from '@storybook/react';

addParameters({
export const parameters = {
options: {
storySort: (a, b) =>
a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, undefined, { numeric: true }),
},
});
};
```

The `storySort` can also accept a configuration object.

```js
import { addParameters, configure } from '@storybook/react';

addParameters({
export parameters = {
options: {
storySort: {
method: 'alphabetical', // Optional, defaults to 'configure'.
order: ['Intro', 'Components'], // Optional, defaults to [].
locales: 'en-US', // Optional, defaults to system locale.
},
},
});
};
```

To sort your stories alphabetically, set `method` to `'alphabetical'` and optionally set the `locales` string. To sort your stories using a custom list, use the `order` array; stories that don't match an item in the `order` list will appear after the items in the list.

The `order` array can accept a nested array in order to sort 2nd-level story kinds. For example:

```js
import { addParameters, configure } from '@storybook/react';

addParameters({
export parameters = {
options: {
storySort: {
order: ['Intro', 'Pages', ['Home', 'Login', 'Admin'], 'Components'],
},
},
});
};
```

Which would result in this story ordering:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ exports[`Storyshots Core/Parameters passed to story 1`] = `
"globalParameter": "globalParameter",
"framework": "angular",
"chapterParameter": "chapterParameter",
"argTypes": {},
"storyParameter": "storyParameter",
"__id": "core-parameters--passed-to-story"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ exports[`Storyshots Core/Parameters passed to story 1`] = `
"globalParameter": "globalParameter",
"framework": "riot",
"chapterParameter": "chapterParameter",
"argTypes": {},
"__id": "core-parameters--passed-to-story",
"storyParameter": "storyParameter",
"id": "root",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ exports[`Storyshots Core/Parameters passed to story 1`] = `
"globalParameter": "globalParameter",
"framework": "vue",
"chapterParameter": "chapterParameter",
"argTypes": {},
"storyParameter": "storyParameter",
"__id": "core-parameters--passed-to-story"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ exports[`Storyshots Custom/Decorator for Vue With Data 1`] = `
"parameters": {
"globalParameter": "globalParameter",
"framework": "vue",
"argTypes": {},
"__id": "custom-decorator-for-vue--with-data"
},
"args": {},
Expand Down
21 changes: 20 additions & 1 deletion lib/api/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import React, {
useMemo,
useRef,
} from 'react';
import { mergeWith } from 'lodash';

import {
SET_STORIES,
STORY_CHANGED,
SHARED_STATE_CHANGED,
SHARED_STATE_SET,
SET_STORIES,
} from '@storybook/core-events';
import { RenderData as RouterData } from '@storybook/router';
import { Listener } from '@storybook/channels';
Expand Down Expand Up @@ -93,9 +94,14 @@ export type ManagerProviderProps = RouterData &
children: ReactNode | ((props: Combo) => ReactNode);
};

// These types are duplicated in addons.
export type StoryId = string;
export type StoryKind = string;

export interface Args {
[key: string]: any;
}

export interface ArgType {
name?: string;
description?: string;
Expand All @@ -107,6 +113,19 @@ export interface ArgTypes {
[key: string]: ArgType;
}

export interface Parameters {
[key: string]: any;
}

// This is duplicated from @storybook/client-api for the reasons mentioned in lib-addons/types.js
export const combineParameters = (...parameterSets: Parameters[]) =>
mergeWith({}, ...parameterSets, (objValue: any, srcValue: any) => {
// Treat arrays as scalars:
if (Array.isArray(srcValue)) return srcValue;

return undefined;
});

export type ModuleFn = (m: ModuleArgs) => Module;

interface Module {
Expand Down
38 changes: 34 additions & 4 deletions lib/api/src/lib/stories.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import deprecate from 'util-deprecate';
import dedent from 'ts-dedent';
import { sanitize, parseKind } from '@storybook/csf';
import { mapValues } from 'lodash';

import { Args } from '../index';
import { StoryId, StoryKind, Args, Parameters, combineParameters } from '../index';
import merge from './merge';
import { Provider } from '../modules/provider';
import { ViewMode } from '../modules/addons';

export type StoryId = string;
export { StoryId };

export interface Root {
id: StoryId;
Expand Down Expand Up @@ -48,7 +49,7 @@ export interface Story {
depth: number;
parent: StoryId;
name: string;
kind: string;
kind: StoryKind;
refId?: string;
children?: StoryId[];
isComponent: boolean;
Expand All @@ -73,7 +74,7 @@ export interface StoryInput {
id: StoryId;
name: string;
refId?: string;
kind: string;
kind: StoryKind;
children: string[];
parameters: {
fileName: string;
Expand Down Expand Up @@ -103,6 +104,20 @@ export interface StoriesRaw {
[id: string]: StoryInput;
}

export interface SetStoriesPayload {
v?: number;
stories: StoriesRaw;
}

export interface SetStoriesPayloadV2 extends SetStoriesPayload {
v: 2;
globalParameters: Parameters;
kindParameters: {
[kind: string]: Parameters;
};
stories: StoriesRaw;
}

const warnUsingHierarchySeparatorsAndShowRoots = deprecate(
() => {},
dedent`
Expand Down Expand Up @@ -135,6 +150,21 @@ const toGroup = (name: string) => ({
id: toKey(name),
});

export const denormalizeStoryParameters = ({
globalParameters,
kindParameters,
stories,
}: SetStoriesPayloadV2): StoriesRaw => {
return mapValues(stories, (storyData) => ({
...storyData,
parameters: combineParameters(
globalParameters,
kindParameters[storyData.kind],
(storyData.parameters as unknown) as Parameters
),
}));
};

export const transformStoriesRawToStoriesHash = (
input: StoriesRaw,
base: StoriesHash,
Expand Down
33 changes: 21 additions & 12 deletions lib/api/src/modules/stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,24 @@ import {
UPDATE_STORY_ARGS,
STORY_ARGS_UPDATED,
STORY_CHANGED,
SET_STORIES,
SELECT_STORY,
SET_STORIES,
} from '@storybook/core-events';

import { logger } from '@storybook/client-logger';
import {
denormalizeStoryParameters,
transformStoriesRawToStoriesHash,
StoriesHash,
Story,
Group,
StoriesRaw,
SetStoriesPayload,
StoryId,
isStory,
Root,
isRoot,
StoriesRaw,
SetStoriesPayloadV2,
} from '../lib/stories';

import { Args, ModuleFn } from '../index';
Expand Down Expand Up @@ -302,30 +305,36 @@ export const init: ModuleFn = ({
}
});

fullAPI.on(SET_STORIES, function handleSetStories(data: { stories: StoriesRaw }) {
fullAPI.on(SET_STORIES, function handleSetStories(data: SetStoriesPayload) {
// the event originates from an iframe, event.source is the iframe's location origin + pathname
const { storyId } = store.getState();
const { source }: { source: string } = this;
const [sourceType, sourceLocation] = getSourceType(source);

// TODO: what is the mechanism where we warn here?
if (data.v && data.v > 2)
// eslint-disable-next-line no-console
console.warn(`Received SET_STORIES event with version ${data.v}, we'll try and handle it`);

const stories = data.v
? denormalizeStoryParameters(data as SetStoriesPayloadV2)
: data.stories;

switch (sourceType) {
// if it's a local source, we do nothing special
case 'local': {
fullAPI.setStories(data.stories);
const options = storyId
? fullAPI.getParameters(storyId, 'options')
: fullAPI.getParameters(Object.keys(data.stories)[0], 'options');
fullAPI.setOptions(options);
if (!data.v) throw new Error('Unexpected legacy SET_STORIES event from local source');

fullAPI.setStories(stories);

fullAPI.setOptions((data as SetStoriesPayloadV2).globalParameters.options);
break;
}

// if it's a ref, we need to map the incoming stories to a prefixed version, so it cannot conflict with others
case 'external': {
const ref = fullAPI.findRef(sourceLocation);

if (ref) {
console.log('ref2', ref);
fullAPI.setRef(ref.id, { ...ref, ...data }, true);
fullAPI.setRef(ref.id, { ...ref, ...data, stories }, true);
break;
}
}
Expand Down
Loading

0 comments on commit 94c6e1d

Please sign in to comment.