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

test: add resize config for easy story and e2e resizing #2234

Merged
merged 3 commits into from
Nov 8, 2023
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
12 changes: 12 additions & 0 deletions e2e/page_objects/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,18 @@ export class CommonPage {
strict: false, // should be true but some stories have multiple charts
});
};

setResizeDimensions = (page: Page) => async (dimensions: { height?: string; width?: string }) => {
const el = page.locator('#story-resize-wrapper');
if (!(await el.isVisible())) {
throw new Error('setResizeDimensions was called when no #story-resize-wrapper exists');
}

await el.evaluate((element, { height, width }) => {
if (height !== undefined) element.style.height = height;
if (width !== undefined) element.style.width = width;
}, dimensions);
};
}

function getSnapshotOptions(options?: ScreenshotDOMElementOptions) {
Expand Down
2 changes: 0 additions & 2 deletions e2e/tests/tooltip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import { test } from '@playwright/test';

import { common } from '../page_objects/common';

process.env.ENV_URL = 'http://localhost:9002/';
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This straggler was causing issues pointing to the wrong port.


test.describe('Tooltip', () => {
test.describe('Chart Types', () => {
test.describe('Cartesian', () => {
Expand Down
29 changes: 19 additions & 10 deletions e2e_server/server/generate/vrt_page_template.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,24 @@ ReactDOM.render(<VRTPage />, document.getElementById('story-root') as HTMLElemen

function pageTemplate(imports, routes, urls) {
return `
import React, { Suspense } from 'react';
import React, { PropsWithChildren, FC, Suspense, CSSProperties } from 'react';
import { EuiProvider } from '@elastic/eui';
import { ThemeIdProvider, BackgroundIdProvider } from '../../storybook/use_base_theme';
import { useGlobalsParameters } from '../server/mocks/use_global_parameters';
import { StoryContext } from '../../storybook/types';

const ResizeWrapper: FC<PropsWithChildren<{ resize?: boolean | CSSProperties }>> = ({ resize, children }) => resize ? (
<div id="story-resize-wrapper" style={resize === true ? {} : resize}>
{ children }
</div>
) : (<>{children}</>)

export function VRTPage() {
const {
themeId,
backgroundId,
toggles,
resize,
setParams,
} = useGlobalsParameters();
const urlParams = new URL(window.location.toString()).searchParams;
Expand Down Expand Up @@ -84,15 +91,17 @@ export function VRTPage() {
}

return (
<EuiProvider colorMode={colorMode}>
<ThemeIdProvider value={themeId as any}>
<BackgroundIdProvider value={backgroundId}>
<Suspense fallback={<div>Loading...</div>}>
${routes.join('\n ')}
</Suspense>
</BackgroundIdProvider>
</ThemeIdProvider>
</EuiProvider>
<ResizeWrapper resize={resize}>
<EuiProvider colorMode={colorMode}>
<ThemeIdProvider value={themeId as any}>
<BackgroundIdProvider value={backgroundId}>
<Suspense fallback={<div>Loading...</div>}>
${routes.join('\n ')}
</Suspense>
</BackgroundIdProvider>
</ThemeIdProvider>
</EuiProvider>
</ResizeWrapper>
);
}

Expand Down
8 changes: 6 additions & 2 deletions e2e_server/server/mocks/use_global_parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@

import { useMemo, useState } from 'react';

import type { StoryGlobals } from './../../../storybook/types';
import type { StoryGlobals, StoryParameters } from './../../../storybook/types';
import { BackgroundParameter } from '../../../storybook/node_modules/storybook-addon-background-toggle';
import { ThemeParameter } from '../../../storybook/node_modules/storybook-addon-theme-toggle';
import { storybookParameters as globalParams } from '../../../storybook/parameters';
import { ThemeId } from '../../../storybook/use_base_theme';

type Parameters = BackgroundParameter & ThemeParameter;
type Parameters = BackgroundParameter & ThemeParameter & Pick<StoryParameters, 'resize'>;

const themeParams = globalParams.theme!;
const backgroundParams = globalParams.background!;
Expand Down Expand Up @@ -48,13 +48,15 @@ interface GlobalParameters {
themeId: StoryGlobals['theme'];
backgroundId: StoryGlobals['background'];
toggles: StoryGlobals['toggles'];
resize: StoryParameters['resize'];
setParams(params: URLSearchParams, parameters?: Parameters): void;
}

export function useGlobalsParameters(): GlobalParameters {
const [themeId, setThemeId] = useState<string>(ThemeId.Light);
const [backgroundId, setBackgroundId] = useState<string | undefined>();
const [togglesJSON, setTogglesJSON] = useState<string>('{}');
const [resize, setResize] = useState<StoryParameters['resize']>(false);

/**
* Handles setting global context values. Stub for theme and background addons
Expand All @@ -67,6 +69,7 @@ export function useGlobalsParameters(): GlobalParameters {
setThemeId(themeIdFromParams);
setTogglesJSON(JSON.stringify(globals.toggles ?? '{}'));
applyThemeCSS(themeIdFromParams);
setResize(parameters?.resize);
}

// using toggles object creates an infinite update loop, thus using JSON state.
Expand All @@ -76,6 +79,7 @@ export function useGlobalsParameters(): GlobalParameters {
themeId,
backgroundId,
toggles,
resize,
setParams,
};
}
Expand Down
65 changes: 30 additions & 35 deletions storybook/stories/metric/1_basic.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,40 +117,35 @@ export const Example: ChartsStory = (_, { title: storyTitle, description }) => {

const configuredData = [[numberTextSwitch ? numericData : textualData]];
return (
<div
style={{
resize: 'both',
padding: '0px',
overflow: 'auto',
height: '200px',
width: '200px',
maxWidth: '100%',
maxHeight: '80vh',
}}
>
<Chart title={storyTitle} description={description}>
<Settings
baseTheme={useBaseTheme()}
onElementClick={([d]) => {
if (isMetricElementEvent(d)) {
const { rowIndex, columnIndex } = d;
onEventClickAction(
`row:${rowIndex} col:${columnIndex} value:${configuredData[rowIndex][columnIndex].value}`,
);
}
}}
onElementOver={([d]) => {
if (isMetricElementEvent(d)) {
const { rowIndex, columnIndex } = d;
onEventOverAction(
`row:${rowIndex} col:${columnIndex} value:${configuredData[rowIndex][columnIndex].value}`,
);
}
}}
onElementOut={() => onEventOutAction('out')}
/>
<Metric id="1" data={configuredData} />
</Chart>
</div>
<Chart title={storyTitle} description={description}>
<Settings
baseTheme={useBaseTheme()}
onElementClick={([d]) => {
if (isMetricElementEvent(d)) {
const { rowIndex, columnIndex } = d;
onEventClickAction(
`row:${rowIndex} col:${columnIndex} value:${configuredData[rowIndex][columnIndex].value}`,
);
}
}}
onElementOver={([d]) => {
if (isMetricElementEvent(d)) {
const { rowIndex, columnIndex } = d;
onEventOverAction(
`row:${rowIndex} col:${columnIndex} value:${configuredData[rowIndex][columnIndex].value}`,
);
}
}}
onElementOut={() => onEventOutAction('out')}
/>
<Metric id="1" data={configuredData} />
</Chart>
);
};

Example.parameters = {
resize: {
height: '200px',
width: '200px',
},
};
81 changes: 35 additions & 46 deletions storybook/stories/test_cases/11_resize_debounce.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,57 +37,46 @@ export const Example: ChartsStory = (_, { title, description }) => {
const resizeDebounce = number('resizeDebounce (ms)', 10, { min: 0, step: 20 });
const cardinality = number('cardinality', 100, { min: 1, max: maxCardinality });
return (
<div
style={{
resize: 'both',
padding: '0px',
overflow: 'auto',
height: '100%',
width: '100%',
maxWidth: '100%',
maxHeight: '100vh',
}}
>
<Chart title={title} description={description}>
<Settings
showLegend
resizeDebounce={resizeDebounce}
onResize={action('onResize')}
onRenderChange={action('onRenderChange')}
baseTheme={useBaseTheme()}
/>
<Axis
id="bottom"
position={Position.Bottom}
style={{
tickLine: { visible: true, size: 0.0001, padding: 4 },
tickLabel: {
alignment: { horizontal: Position.Left, vertical: Position.Bottom },
padding: 0,
offset: { x: 0, y: 0 },
},
}}
timeAxisLayerCount={3}
/>
<Axis id="left" position={Position.Left} />
<Chart title={title} description={description}>
<Settings
showLegend
resizeDebounce={resizeDebounce}
onResize={action('onResize')}
onRenderChange={action('onRenderChange')}
baseTheme={useBaseTheme()}
/>
<Axis
id="bottom"
position={Position.Bottom}
style={{
tickLine: { visible: true, size: 0.0001, padding: 4 },
tickLabel: {
alignment: { horizontal: Position.Left, vertical: Position.Bottom },
padding: 0,
offset: { x: 0, y: 0 },
},
}}
timeAxisLayerCount={3}
/>
<Axis id="left" position={Position.Left} />

<BarSeries
id="Sensor 1"
enableHistogramMode
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor="t"
yAccessors={['v']}
splitSeriesAccessors={['cat']}
stackAccessors={['yes']}
data={data.flatMap(({ t, values }) => values.slice(0, cardinality).map(({ v, cat }) => ({ t, v, cat })))}
/>
</Chart>
</div>
<BarSeries
id="Sensor 1"
enableHistogramMode
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor="t"
yAccessors={['v']}
splitSeriesAccessors={['cat']}
stackAccessors={['yes']}
data={data.flatMap(({ t, values }) => values.slice(0, cardinality).map(({ v, cat }) => ({ t, v, cat })))}
/>
</Chart>
);
};

Example.parameters = {
markdown: `The \`resizeDebounce\` option on the \`Settings\` spec provides control over the eagerness of the chart to re-render upon resize. A value of \`0\` will remove the debounce altogether.
You can play with the cardinality and debounce time to see how the debouncing affects the chart render timing`,
resize: true,
};
35 changes: 24 additions & 11 deletions storybook/story_wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,35 @@
import { EuiProvider, EuiMarkdownFormat, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiText } from '@elastic/eui';
import { DecoratorFunction } from '@storybook/addons';
import classNames from 'classnames';
import React from 'react';
import React, { FC, PropsWithChildren } from 'react';

import { StoryGlobals, StoryParameters } from './types';
import { ThemeId, ThemeIdProvider, BackgroundIdProvider } from './use_base_theme';

const ResizeWrapper: FC<PropsWithChildren<{ resize: StoryParameters['resize'] }>> = ({ resize, children }) =>
resize ? (
<div id="story-resize-wrapper" style={resize === true ? {} : resize}>
{children}
</div>
) : (
<>{children}</>
);

export const StoryWrapper: DecoratorFunction<JSX.Element> = (Story, context) => {
if (!Story) return <div>No Story</div>;

const { globals, parameters } = context;
const globals = (context.globals as StoryGlobals) ?? {};
const parameters = (context.parameters as StoryParameters) ?? {};

const themeId = globals?.theme ?? ThemeId.Light;
const backgroundId = globals?.background;
const themeId = (globals.theme as ThemeId) ?? ThemeId.Light;
const backgroundId = globals.background;
const {
showHeader = false,
showChartTitle = false,
showChartDescription = false,
showChartBoundary = false,
} = globals?.toggles ?? {};
const markdown = parameters?.markdown;
} = globals.toggles ?? {};
const { markdown, resize } = parameters;
const colorMode = themeId.includes('light') ? 'light' : 'dark';

return (
Expand Down Expand Up @@ -56,11 +67,13 @@ export const StoryWrapper: DecoratorFunction<JSX.Element> = (Story, context) =>

<EuiFlexItem grow={false}>
<div id="story-root" className={classNames({ showChartBoundary })}>
<Story
{...context}
title={showChartTitle ? context.kind : undefined}
description={showChartDescription ? context.name : undefined}
/>
<ResizeWrapper resize={resize}>
<Story
{...context}
title={showChartTitle ? context.kind : undefined}
description={showChartDescription ? context.name : undefined}
/>
</ResizeWrapper>
</div>
</EuiFlexItem>
{markdown && (
Expand Down
9 changes: 9 additions & 0 deletions storybook/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,15 @@ body {
}
}

#story-resize-wrapper {
resize: both;
height: 100%;
width: 100%;
overflow: auto;
max-width: 100%;
max-height: 80vh;
}

#story-header {
padding: 20px 40px 16px;
}
Expand Down
8 changes: 6 additions & 2 deletions storybook/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,26 @@

import type { Parameters as SBParameters } from '@storybook/addons';
import { ArgTypes, Args, StoryContext as SBStoryContext } from '@storybook/react';
import { ReactElement } from 'react';
import { CSSProperties, ReactElement } from 'react';
import { StoryBackgroundParameter, BackgroundGlobals } from 'storybook-addon-background-toggle';
import { StoryThemeParameter, ThemeGlobals } from 'storybook-addon-theme-toggle';
import { StoryTogglesParameter, TogglesGlobals } from 'storybook-addon-toggles';

/**
* Parameter accessible at the story level
*/
type StoryParameters = SBParameters &
export type StoryParameters = SBParameters &
StoryThemeParameter &
StoryBackgroundParameter &
StoryTogglesParameter & {
/**
* Renders markdown content below story
*/
markdown?: string;
/**
* Used to enable and style resize wrapper under `#story-root`
*/
resize?: boolean | CSSProperties;
};
export type StoryGlobals = ThemeGlobals & BackgroundGlobals & TogglesGlobals;

Expand Down