Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core: Handle v3 index in composition #18498

Merged
merged 6 commits into from
Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions lib/api/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,20 +146,27 @@ export const combineParameters = (...parameterSets: Parameters[]) =>
return undefined;
});

export type ModuleFn<APIType = unknown, StateType = unknown> = (
m: ModuleArgs
) => Module<APIType, StateType>;

interface Module<APIType = unknown, StateType = unknown> {
init?: () => void;
api?: APIType;
state?: StateType;
interface ModuleWithInit<APIType = unknown, StateType = unknown> {
init: () => void | Promise<void>;
api: APIType;
state: StateType;
}

type ModuleWithoutInit<APIType = unknown, StateType = unknown> = Omit<
ModuleWithInit<APIType, StateType>,
'init'
>;

export type ModuleFn<APIType = unknown, StateType = unknown, HasInit = false> = (
m: ModuleArgs
) => HasInit extends true
? ModuleWithInit<APIType, StateType>
: ModuleWithoutInit<APIType, StateType>;

class ManagerProvider extends Component<ManagerProviderProps, State> {
api: API = {} as API;

modules: Module[];
modules: (ModuleWithInit | ModuleWithoutInit)[];

static displayName = 'Manager';

Expand Down Expand Up @@ -261,9 +268,9 @@ class ManagerProvider extends Component<ManagerProviderProps, State> {
initModules = () => {
// Now every module has had a chance to set its API, call init on each module which gives it
// a chance to do things that call other modules' APIs.
this.modules.forEach(({ init }) => {
if (init) {
init();
this.modules.forEach((module) => {
if ('init' in module) {
module.init();
}
});
};
Expand Down
35 changes: 33 additions & 2 deletions lib/api/src/lib/stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
import deprecate from 'util-deprecate';
import dedent from 'ts-dedent';
import mapValues from 'lodash/mapValues';
import countBy from 'lodash/countBy';
import global from 'global';
import type {
StoryId,
Expand Down Expand Up @@ -137,9 +138,13 @@ export type StoryIndexEntry = BaseIndexEntry & {
type?: 'story';
};

interface V3IndexEntry extends BaseIndexEntry {
parameters?: Parameters;
}

export interface StoryIndexV3 {
v: 3;
stories: Record<StoryId, Omit<StoryIndexEntry, 'type'>>;
stories: Record<StoryId, V3IndexEntry>;
}

export type DocsIndexEntry = BaseIndexEntry & {
Expand Down Expand Up @@ -247,6 +252,28 @@ const transformSetStoriesStoryDataToPreparedStoryIndex = (
return { v: 4, entries };
};

const transformStoryIndexV3toV4 = (index: StoryIndexV3): PreparedStoryIndex => {
const countByTitle = countBy(Object.values(index.stories), 'title');
return {
v: 4,
entries: Object.values(index.stories).reduce((acc, entry) => {
let type: IndexEntry['type'] = 'story';
if (
entry.parameters?.docsOnly ||
(entry.name === 'Page' && countByTitle[entry.title] === 1)
) {
type = 'docs';
}
acc[entry.id] = {
type,
...(type === 'docs' && { storiesImports: [] }),
...entry,
};
return acc;
}, {} as PreparedStoryIndex['entries']),
};
};

export const transformStoryIndexToStoriesHash = (
index: PreparedStoryIndex,
{
Expand All @@ -255,7 +282,11 @@ export const transformStoryIndexToStoriesHash = (
provider: Provider;
}
): StoriesHash => {
const entryValues = Object.values(index.entries);
if (!index.v) throw new Error('Composition: Missing stories.json version');

const v4Index = index.v === 4 ? index : transformStoryIndexV3toV4(index as any);

const entryValues = Object.values(v4Index.entries);
const { sidebar = {}, showRoots: deprecatedShowRoots } = provider.getConfig();
const { showRoots = deprecatedShowRoots, collapsedRoots = [], renderLabel } = sidebar;
const usesOldHierarchySeparator = entryValues.some(({ title }) => title.match(/\.|\|/)); // dot or pipe
Expand Down
9 changes: 4 additions & 5 deletions lib/api/src/modules/addons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,9 @@ type Panels = Collection<Addon>;

type StateMerger<S> = (input: S) => S;

interface StoryInput {
parameters: {
[parameterName: string]: any;
};
export interface SubState {
selectedPanel: string;
addons: Record<string, never>;
}

export interface SubAPI {
Expand Down Expand Up @@ -96,7 +95,7 @@ export function ensurePanel(panels: Panels, selectedPanel?: string, currentPanel
return currentPanel;
}

export const init: ModuleFn = ({ provider, store, fullAPI }) => {
export const init: ModuleFn<SubAPI, SubState> = ({ provider, store, fullAPI }) => {
const api: SubAPI = {
getElements: (type) => provider.getElements(type),
getPanels: () => api.getElements(types.PANEL),
Expand Down
6 changes: 4 additions & 2 deletions lib/api/src/modules/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ export interface SubAPI {
expandAll: () => void;
}

export const init: ModuleFn = ({ provider }) => {
export type SubState = Record<string, never>;

export const init: ModuleFn<SubAPI, SubState> = ({ provider }) => {
const api: SubAPI = {
getChannel: () => provider.channel,
on: (type, cb) => {
Expand All @@ -33,5 +35,5 @@ export const init: ModuleFn = ({ provider }) => {
api.emit(STORIES_EXPAND_ALL);
},
};
return { api };
return { api, state: {} };
};
3 changes: 2 additions & 1 deletion lib/api/src/modules/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ export interface SubAPI {
renderPreview?: Provider['renderPreview'];
}

export const init: ModuleFn = ({ provider, fullAPI }) => {
export const init: ModuleFn<SubAPI, {}, true> = ({ provider, fullAPI }) => {
return {
api: provider.renderPreview ? { renderPreview: provider.renderPreview } : {},
state: {},
init: () => {
provider.handleAPI(fullAPI);
},
Expand Down
78 changes: 42 additions & 36 deletions lib/api/src/modules/refs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import {
SetStoriesStory,
StoriesHash,
transformStoryIndexToStoriesHash,
StoryIndexEntry,
SetStoriesStoryData,
StoryIndex,
} from '../lib/stories';

import type { ModuleFn, StoryId } from '../index';
import type { ModuleFn } from '../index';

const { location, fetch } = global;

Expand All @@ -20,9 +20,9 @@ export interface SubState {
type Versions = Record<string, string>;

export type SetRefData = Partial<
Omit<ComposedRef, 'stories'> & {
v: number;
stories?: SetStoriesStoryData;
ComposedRef & {
setStoriesData: SetStoriesStoryData;
storyIndex: StoryIndex;
}
>;

Expand Down Expand Up @@ -99,14 +99,24 @@ const addRefIds = (input: StoriesHash, ref: ComposedRef): StoriesHash => {
}, {} as StoriesHash);
};

const handle = async (request: Response | false): Promise<SetRefData> => {
if (request) {
return Promise.resolve(request)
.then((response) => (response.ok ? response.json() : {}))
.catch((error) => ({ error }));
async function handleRequest(request: Response | false): Promise<SetRefData> {
if (!request) return {};

try {
const response = await request;
if (!response.ok) return {};

const json = await response.json();

if (json.stories) {
return { storyIndex: json };
}

return json as SetRefData;
} catch (error) {
return { error };
}
return {};
};
}

const map = (
input: SetStoriesStoryData,
Expand All @@ -122,7 +132,10 @@ const map = (
return input;
};

export const init: ModuleFn = ({ store, provider, singleStory }, { runCheck = true } = {}) => {
export const init: ModuleFn<SubAPI, SubState, void> = (
{ store, provider, singleStory },
{ runCheck = true } = {}
) => {
const api: SubAPI = {
findRef: (source) => {
const refs = api.getRefs();
Expand Down Expand Up @@ -190,9 +203,9 @@ export const init: ModuleFn = ({ store, provider, singleStory }, { runCheck = tr
`,
} as Error;
} else if (storiesFetch.ok) {
const [stories, metadata] = await Promise.all([
handle(storiesFetch),
handle(
const [storyIndex, metadata] = await Promise.all([
handleRequest(storiesFetch),
handleRequest(
fetch(`${url}/metadata.json${query}`, {
headers: {
Accept: 'application/json',
Expand All @@ -203,7 +216,7 @@ export const init: ModuleFn = ({ store, provider, singleStory }, { runCheck = tr
),
]);

Object.assign(loadedData, { ...stories, ...metadata });
Object.assign(loadedData, { ...storyIndex, ...metadata });
}

const versions =
Expand All @@ -214,8 +227,7 @@ export const init: ModuleFn = ({ store, provider, singleStory }, { runCheck = tr
url,
...loadedData,
...(versions ? { versions } : {}),
error: loadedData.error,
type: !loadedData.stories ? 'auto-inject' : 'lazy',
type: !loadedData.storyIndex ? 'auto-inject' : 'lazy',
});
},

Expand All @@ -225,29 +237,23 @@ export const init: ModuleFn = ({ store, provider, singleStory }, { runCheck = tr
return refs;
},

setRef: (id, { stories, v, ...rest }, ready = false) => {
setRef: (id, { storyIndex, setStoriesData, ...rest }, ready = false) => {
if (singleStory) return;
const { storyMapper = defaultStoryMapper } = provider.getConfig();
const ref = api.getRefs()[id];

let storiesHash: StoriesHash;

if (stories) {
if (v === 2) {
storiesHash = transformSetStoriesStoryDataToStoriesHash(
map(stories, ref, { storyMapper }),
{
provider,
}
);
} else if (!v) {
throw new Error('Composition: Missing stories.json version');
} else {
const index = stories as unknown as Record<StoryId, StoryIndexEntry>;
storiesHash = transformStoryIndexToStoriesHash({ v, entries: index }, { provider });
}
storiesHash = addRefIds(storiesHash, ref);
if (setStoriesData) {
storiesHash = transformSetStoriesStoryDataToStoriesHash(
map(setStoriesData, ref, { storyMapper }),
{
provider,
}
);
} else if (storyIndex) {
storiesHash = transformStoryIndexToStoriesHash(storyIndex, { provider });
}
if (storiesHash) storiesHash = addRefIds(storiesHash, ref);

api.updateRef(id, { stories: storiesHash, ...rest, ready });
},
Expand Down
6 changes: 2 additions & 4 deletions lib/api/src/modules/release-notes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export interface SubState {
releaseNotesViewed: string[];
}

export const init: ModuleFn = ({ store }) => {
export const init: ModuleFn<SubAPI, SubState> = ({ store }) => {
const releaseNotesData = getReleaseNotesData();
const getReleaseNotesViewed = () => {
const { releaseNotesViewed: persistedReleaseNotesViewed } = store.getState();
Expand Down Expand Up @@ -58,7 +58,5 @@ export const init: ModuleFn = ({ store }) => {
},
};

const initModule = () => {};

return { init: initModule, api };
return { state: { releaseNotesViewed: [] }, api };
};
8 changes: 2 additions & 6 deletions lib/api/src/modules/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface SubState {
settings: Settings;
}

export const init: ModuleFn = ({ store, navigate, fullAPI }) => {
export const init: ModuleFn<SubAPI, SubState> = ({ store, navigate, fullAPI }) => {
const isSettingsScreenActive = () => {
const { path } = fullAPI.getUrlState();
return !!(path || '').match(/^\/settings/);
Expand Down Expand Up @@ -49,9 +49,5 @@ export const init: ModuleFn = ({ store, navigate, fullAPI }) => {
},
};

const initModule = async () => {
await store.setState({ settings: { lastTrackedStoryId: null } });
};

return { init: initModule, api };
return { state: { settings: { lastTrackedStoryId: null } }, api };
};
Loading