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

Settings: Add disable whatsnew UI #23381

Merged
merged 10 commits into from
Jul 10, 2023
2 changes: 1 addition & 1 deletion code/lib/core-common/src/utils/get-storybook-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const getRendererInfo = (packageJson: PackageJson) => {

const validConfigExtensions = ['ts', 'js', 'tsx', 'jsx', 'mjs', 'cjs'];

const findConfigFile = (prefix: string, configDir: string) => {
export const findConfigFile = (prefix: string, configDir: string) => {
const filePrefix = path.join(configDir, prefix);
const extension = validConfigExtensions.find((ext: string) =>
fse.existsSync(`${filePrefix}.${ext}`)
Expand Down
4 changes: 4 additions & 0 deletions code/lib/core-events/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ enum events {
REQUEST_WHATS_NEW_DATA = 'requestWhatsNewData',
RESULT_WHATS_NEW_DATA = 'resultWhatsNewData',
SET_WHATS_NEW_CACHE = 'setWhatsNewCache',
TOGGLE_WHATS_NEW_NOTIFICATIONS = 'toggleWhatsNewNotifications',
}

// Enables: `import Events from ...`
Expand Down Expand Up @@ -118,6 +119,7 @@ export const {
REQUEST_WHATS_NEW_DATA,
RESULT_WHATS_NEW_DATA,
SET_WHATS_NEW_CACHE,
TOGGLE_WHATS_NEW_NOTIFICATIONS,
} = events;

// Used to break out of the current render without showing a redbox
Expand All @@ -133,10 +135,12 @@ export type WhatsNewData =
status: 'SUCCESS';
title: string;
url: string;
blogUrl?: string;
publishedAt: string;
excerpt: string;
postIsRead: boolean;
showNotification: boolean;
disableWhatsNewNotifications: boolean;
}
| {
status: 'ERROR';
Expand Down
50 changes: 46 additions & 4 deletions code/lib/core-server/src/presets/common-preset.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { pathExists, readFile } from 'fs-extra';
import { deprecate, logger } from '@storybook/node-logger';
import { telemetry } from '@storybook/telemetry';
import {
findConfigFile,
getDirectoryFromWorkingDir,
getPreviewBodyTemplate,
getPreviewHeadTemplate,
Expand All @@ -15,7 +17,7 @@ import type {
StorybookConfig,
StoryIndexer,
} from '@storybook/types';
import { loadCsf } from '@storybook/csf-tools';
import { loadCsf, readConfig, writeConfig } from '@storybook/csf-tools';
import { join } from 'path';
import { dedent } from 'ts-dedent';
import fetch from 'node-fetch';
Expand All @@ -25,9 +27,11 @@ import {
REQUEST_WHATS_NEW_DATA,
RESULT_WHATS_NEW_DATA,
SET_WHATS_NEW_CACHE,
TOGGLE_WHATS_NEW_NOTIFICATIONS,
} from '@storybook/core-events';
import { parseStaticDir } from '../utils/server-statics';
import { defaultStaticDirs } from '../utils/constants';
import { sendTelemetryError } from '../withTelemetry';

const interpolate = (string: string, data: Record<string, string> = {}) =>
Object.entries(data).reduce((acc, [k, v]) => acc.replace(new RegExp(`%${k}%`, 'g'), v), string);
Expand Down Expand Up @@ -188,7 +192,6 @@ export const features = async (
storyStoreV7: true,
argTypeTargetsV7: true,
legacyDecoratorFileOrder: false,
whatsNewNotifications: false,
});

export const storyIndexers = async (indexers?: StoryIndexer[]) => {
Expand Down Expand Up @@ -246,10 +249,18 @@ const WHATS_NEW_CACHE = 'whats-new-cache';
const WHATS_NEW_URL = 'https://storybook.js.org/whats-new/v1';

// Grabbed from the implementation: https://github.com/storybookjs/dx-functions/blob/main/netlify/functions/whats-new.ts
type WhatsNewResponse = { title: string; url: string; publishedAt: string; excerpt: string };
type WhatsNewResponse = {
title: string;
url: string;
blogUrl?: string;
publishedAt: string;
excerpt: string;
};

// eslint-disable-next-line @typescript-eslint/naming-convention
export const experimental_serverChannel = (channel: Channel, options: Options) => {
export const experimental_serverChannel = async (channel: Channel, options: Options) => {
const coreOptions = await options.presets.apply('core');

channel.on(SET_WHATS_NEW_CACHE, async (data: WhatsNewCache) => {
const cache: WhatsNewCache = await options.cache.get(WHATS_NEW_CACHE).catch((e) => {
logger.verbose(e);
Expand All @@ -266,12 +277,19 @@ export const experimental_serverChannel = (channel: Channel, options: Options) =
throw response;
})) as WhatsNewResponse;

const main = await readConfig(findConfigFile('main', options.configDir));
const disableWhatsNewNotifications = main.getFieldValue([
'core',
'disableWhatsNewNotifications',
]);

const cache: WhatsNewCache = (await options.cache.get(WHATS_NEW_CACHE)) ?? {};
const data = {
...post,
status: 'SUCCESS',
postIsRead: post.url === cache.lastReadPost,
showNotification: post.url !== cache.lastDismissedPost && post.url !== cache.lastReadPost,
disableWhatsNewNotifications,
} satisfies WhatsNewData;
channel.emit(RESULT_WHATS_NEW_DATA, { data });
} catch (e) {
Expand All @@ -282,5 +300,29 @@ export const experimental_serverChannel = (channel: Channel, options: Options) =
}
});

channel.on(
TOGGLE_WHATS_NEW_NOTIFICATIONS,
async ({ disableWhatsNewNotifications }: { disableWhatsNewNotifications: boolean }) => {
const isTelemetryEnabled = coreOptions.disableTelemetry !== true;
try {
const main = await readConfig(findConfigFile('main', options.configDir));
main.setFieldValue(['core', 'disableWhatsNewNotifications'], disableWhatsNewNotifications);
await writeConfig(main);

if (isTelemetryEnabled) {
await telemetry('core-config', { disableWhatsNewNotifications });
}
} catch (error) {
if (isTelemetryEnabled) {
await sendTelemetryError(error, 'core-config', {
cliOptions: options,
presetOptions: { ...options, corePresets: [], overridePresets: [] },
skipPrompt: true,
});
}
}
}
);

return channel;
};
11 changes: 10 additions & 1 deletion code/lib/core-server/src/withTelemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type TelemetryOptions = {
cliOptions: CLIOptions;
presetOptions?: Parameters<typeof loadAllPresets>[0];
printError?: (err: any) => void;
skipPrompt?: boolean;
};

const promptCrashReports = async () => {
Expand All @@ -30,7 +31,11 @@ const promptCrashReports = async () => {

type ErrorLevel = 'none' | 'error' | 'full';

async function getErrorLevel({ cliOptions, presetOptions }: TelemetryOptions): Promise<ErrorLevel> {
async function getErrorLevel({
cliOptions,
presetOptions,
skipPrompt,
}: TelemetryOptions): Promise<ErrorLevel> {
if (cliOptions.disableTelemetry) return 'none';

// If we are running init or similar, we just have to go with true here
Expand All @@ -54,6 +59,10 @@ async function getErrorLevel({ cliOptions, presetOptions }: TelemetryOptions): P
(await cache.get('enableCrashReports')) ?? (await cache.get('enableCrashreports'));
if (valueFromCache !== undefined) return valueFromCache ? 'full' : 'error';

if (skipPrompt) {
return 'error';
}

const valueFromPrompt = await promptCrashReports();
if (valueFromPrompt !== undefined) return valueFromPrompt ? 'full' : 'error';

Expand Down
15 changes: 14 additions & 1 deletion code/lib/manager-api/src/modules/whatsnew.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
REQUEST_WHATS_NEW_DATA,
RESULT_WHATS_NEW_DATA,
SET_WHATS_NEW_CACHE,
TOGGLE_WHATS_NEW_NOTIFICATIONS,
} from '@storybook/core-events';
import type { ModuleFn } from '../index';

Expand All @@ -14,6 +15,7 @@ export type SubState = {
export type SubAPI = {
isWhatsNewUnread(): boolean;
whatsNewHasBeenRead(): void;
toggleWhatsNewNotifications(): void;
};

const WHATS_NEW_NOTIFICATION_ID = 'whats-new';
Expand All @@ -39,6 +41,17 @@ export const init: ModuleFn = ({ fullAPI, store }) => {
fullAPI.clearNotification(WHATS_NEW_NOTIFICATION_ID);
}
},
toggleWhatsNewNotifications() {
if (state.whatsNewData?.status === 'SUCCESS') {
setWhatsNewState({
...state.whatsNewData,
disableWhatsNewNotifications: !state.whatsNewData.disableWhatsNewNotifications,
});
fullAPI.emit(TOGGLE_WHATS_NEW_NOTIFICATIONS, {
disableWhatsNewNotifications: state.whatsNewData.disableWhatsNewNotifications,
});
}
},
};

function getLatestWhatsNewPost(): Promise<WhatsNewData> {
Expand All @@ -63,9 +76,9 @@ export const init: ModuleFn = ({ fullAPI, store }) => {
const isNewStoryBookUser = fullAPI.getUrlState().path.includes('onboarding');

if (
global.FEATURES.whatsNewNotifications &&
!isNewStoryBookUser &&
whatsNewData.status === 'SUCCESS' &&
!whatsNewData.disableWhatsNewNotifications &&
whatsNewData.showNotification
) {
fullAPI.addNotification({
Expand Down
3 changes: 2 additions & 1 deletion code/lib/telemetry/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export type EventType =
| 'canceled'
| 'error'
| 'error-metadata'
| 'version-update';
| 'version-update'
| 'core-config';

export interface Dependency {
version: string | undefined;
Expand Down
10 changes: 5 additions & 5 deletions code/lib/types/src/modules/core-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export interface CoreConfig {
* @see https://storybook.js.org/telemetry
*/
disableTelemetry?: boolean;

/**
* Disables notifications for Storybook updates.
*/
disableWhatsNewNotifications?: boolean;
/**
* Enable crash reports to be sent to Storybook telemetry
* @see https://storybook.js.org/telemetry
Expand Down Expand Up @@ -305,11 +310,6 @@ export interface StorybookConfig {
* Apply decorators from preview.js before decorators from addons or frameworks
*/
legacyDecoratorFileOrder?: boolean;

/**
* Show a notification anytime a What's new? post is published in the Storybook blog.
*/
whatsNewNotifications?: boolean;
};

/**
Expand Down
7 changes: 3 additions & 4 deletions code/ui/manager/src/components/sidebar/Menu.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { ComponentProps } from 'react';
import React from 'react';
import { expect } from '@storybook/jest';
import type { Meta, StoryObj } from '@storybook/react';
import type { ComponentProps } from 'react';

import { TooltipLinkList } from '@storybook/components';
import { styled } from '@storybook/theming';
import { within, userEvent, screen } from '@storybook/testing-library';
import { screen, userEvent, within } from '@storybook/testing-library';
import type { State } from '@storybook/manager-api';
import { SidebarMenu, ToolbarMenu } from './Menu';
import { useMenu } from '../../containers/menu';
Expand Down Expand Up @@ -46,9 +46,8 @@ const DoubleThemeRenderingHack = styled.div({

export const Expanded: Story = {
render: () => {
window.FEATURES.whatsNewNotifications = true;
const menu = useMenu(
{ whatsNewData: { status: 'SUCCESS' } } as State,
{ whatsNewData: { status: 'SUCCESS', disableWhatsNewNotifications: false } } as State,
{
// @ts-expect-error (Converted from ts-ignore)
getShortcutKeys: () => ({}),
Expand Down
4 changes: 2 additions & 2 deletions code/ui/manager/src/containers/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { Badge, Icons } from '@storybook/components';
import type { API, State } from '@storybook/manager-api';
import { shortcutToHumanString } from '@storybook/manager-api';
import { styled, useTheme } from '@storybook/theming';
import { global } from '@storybook/global';

const focusableUIElements = {
storySearchField: 'storybook-explorer-searchfield',
Expand Down Expand Up @@ -69,7 +68,8 @@ export const useMenu = (
[api]
);

const whatsNewNotificationsEnabled = global.FEATURES.whatsNewNotifications;
const whatsNewNotificationsEnabled =
state.whatsNewData?.status === 'SUCCESS' && !state.disableWhatsNewNotifications;
const isWhatsNewUnread = api.isWhatsNewUnread();
const whatsNew = useMemo(
() => ({
Expand Down
6 changes: 4 additions & 2 deletions code/ui/manager/src/containers/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import React from 'react';
import type { Combo, StoriesHash } from '@storybook/manager-api';
import { Consumer } from '@storybook/manager-api';

import { global } from '@storybook/global';
import { Sidebar as SidebarComponent } from '../components/sidebar/Sidebar';
import { useMenu } from './menu';

Expand Down Expand Up @@ -33,6 +32,9 @@ const Sidebar = React.memo(function Sideber() {
enableShortcuts
);

const whatsNewNotificationsEnabled =
state.whatsNewData?.status === 'SUCCESS' && !state.disableWhatsNewNotifications;

return {
title: name,
url,
Expand All @@ -44,7 +46,7 @@ const Sidebar = React.memo(function Sideber() {
refId,
viewMode,
menu,
menuHighlighted: global.FEATURES.whatsNewNotifications && api.isWhatsNewUnread(),
menuHighlighted: whatsNewNotificationsEnabled && api.isWhatsNewUnread(),
enableShortcuts,
};
};
Expand Down
1 change: 1 addition & 0 deletions code/ui/manager/src/globals/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export default {
'STORY_SPECIFIED',
'STORY_THREW_EXCEPTION',
'STORY_UNCHANGED',
'TOGGLE_WHATS_NEW_NOTIFICATIONS',
'UPDATE_GLOBALS',
'UPDATE_QUERY_PARAMS',
'UPDATE_STORY_ARGS',
Expand Down
1 change: 1 addition & 0 deletions code/ui/manager/src/settings/about.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const UpgradeBlock = styled.div(({ theme }) => {
borderRadius: 5,
padding: 20,
margin: 20,
marginTop: 0,
maxWidth: 400,
borderColor: theme.appBorderColor,
fontSize: theme.typography.size.s2,
Expand Down
Loading