Skip to content

Commit

Permalink
feat: support native chart title and description (#2002)
Browse files Browse the repository at this point in the history
  • Loading branch information
markov00 authored Jun 13, 2023
1 parent 558eddc commit 341a990
Show file tree
Hide file tree
Showing 398 changed files with 1,939 additions and 1,145 deletions.
1 change: 1 addition & 0 deletions e2e/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const groupsToSkip: Set<string> = new Set(['Components/Tooltip']);
const storiesToSkip: Map<string, string[]> = new Map(
Object.entries({
'Test Cases': ['noSeries'],
Interactions: ['multiChartCursorSync'],
}),
);

Expand Down
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 60 additions & 0 deletions e2e/tests/chart.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { test } from '@playwright/test';

import { Position } from '../constants';
import { pwEach } from '../helpers';
import { common } from '../page_objects/common';

test.describe('Chart', () => {
test.describe('Sizing', () => {
test('should accommodate chart title only', async ({ page }) => {
await common.expectChartAtUrlToMatchScreenshot(page)(
'http://localhost:9002/?path=/story/legend--positioning&knob-position=bottom&globals=toggles.showChartTitle:true',
);
});

test('should accommodate chart description only', async ({ page }) => {
await common.expectChartAtUrlToMatchScreenshot(page)(
'http://localhost:9002/?path=/story/legend--positioning&knob-position=bottom&globals=toggles.showChartDescription:true',
);
});

test('should render multiple charts with titles', async ({ page }) => {
await common.expectChartAtUrlToMatchScreenshot(page)(
'http://localhost:9001/?path=/story/interactions--multi-chart-cursor-sync',
{ screenshotSelector: '#story-root' },
);
});

pwEach.test<Position>(['bottom', 'left', 'right', 'top'])(
(position) => `should accommodate chart title and description - legend ${position}`,
async (page, position) => {
await common.expectChartAtUrlToMatchScreenshot(page)(
`http://localhost:9002/?path=/story/legend--positioning&globals=toggles.showChartTitle:true;toggles.showChartDescription:true&knob-position=${position}`,
);
},
);

pwEach.test<[type: string, url: string]>([
['Cartesian', 'http://localhost:9001/?path=/story/mixed-charts--areas-and-bars'],
['Partition', 'http://localhost:9001/?path=/story/sunburst--sunburst-with-three-layers'],
['Heatmap', 'http://localhost:9001/?path=/story/heatmap-alpha--basic'],
['WordCloud', 'http://localhost:9001/?path=/story/wordcloud-alpha--simple-wordcloud'],
['Metric SM', 'http://localhost:9001/?path=/story/metric-alpha--grid'],
])(
([type]) => `should accommodate chart title and description - ${type}`,
async (page, [, url]) => {
await common.expectChartAtUrlToMatchScreenshot(page)(
`${url}&globals=toggles.showChartTitle:true;toggles.showChartDescription:true`,
);
},
);
});
});
32 changes: 19 additions & 13 deletions e2e/tests/legend_stories.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,22 +101,28 @@ test.describe('Legend stories', () => {

test.describe('Tooltip placement with legend', () => {
test('should render tooltip with left legend', async ({ page }) => {
await common.expectChartWithMouseAtUrlToMatchScreenshot(page)('http://localhost:9001/?path=/story/legend--left', {
bottom: 190,
left: 310,
});
await common.expectChartWithMouseAtUrlToMatchScreenshot(page)(
'http://localhost:9001/?path=/story/legend--positioning&knob-position=left',
{
bottom: 190,
left: 310,
},
);
});

test('should render tooltip with top legend', async ({ page }) => {
await common.expectChartWithMouseAtUrlToMatchScreenshot(page)('http://localhost:9001/?path=/story/legend--top', {
top: 150,
left: 320,
});
await common.expectChartWithMouseAtUrlToMatchScreenshot(page)(
'http://localhost:9001/?path=/story/legend--positioning&knob-position=top',
{
top: 150,
left: 320,
},
);
});

test('should render tooltip with right legend', async ({ page }) => {
await common.expectChartWithMouseAtUrlToMatchScreenshot(page)(
'http://localhost:9001/?path=/story/legend--right',
'http://localhost:9001/?path=/story/legend--positioning&knob-position=right',
{
bottom: 180,
left: 330,
Expand All @@ -126,7 +132,7 @@ test.describe('Legend stories', () => {

test('should render tooltip with bottom legend', async ({ page }) => {
await common.expectChartWithMouseAtUrlToMatchScreenshot(page)(
'http://localhost:9001/?path=/story/legend--bottom',
'http://localhost:9001/?path=/story/legend--positioning&knob-position=bottom',
{
top: 150,
left: 320,
Expand All @@ -140,7 +146,7 @@ test.describe('Legend stories', () => {
// puts mouse to the bottom left
await common.moveMouse(page)(0, 0);
await common.expectChartWithKeyboardEventsAtUrlToMatchScreenshot(page)(
'http://localhost:9001/?path=/story/legend--right',
'http://localhost:9001/?path=/story/legend--positioning&knob-position=right',
[
{
key: 'tab',
Expand All @@ -155,7 +161,7 @@ test.describe('Legend stories', () => {
});
test('should change aria label to hidden when clicked', async ({ page }) => {
await common.loadElementFromURL(page)(
'http://localhost:9001/?path=/story/legend--right',
'http://localhost:9001/?path=/story/legend--positioning&knob-position=right',
'.echLegendItem__label',
);
await common.clickMouseRelativeToDOMElement(page)(
Expand Down Expand Up @@ -262,7 +268,7 @@ test.describe('Legend stories', () => {
test.describe('Custom width', () => {
// eslint-disable-next-line unicorn/consistent-function-scoping
const getUrl = (position: string, size: number) =>
`http://localhost:9001/?path=/story/legend--${position}&knob-enable legend size=true&knob-legend size=${size}`;
`http://localhost:9001/?path=/story/legend--positioning&knob-position=${position}&knob-enable legend size=true&knob-legend size=${size}`;

pwEach.describe(['top', 'right', 'bottom', 'left'])(
(p) => `position ${p}`,
Expand Down
1 change: 1 addition & 0 deletions e2e_server/server/generate/extract_examples.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ function extractExamples(exampleRelativePath = 'storybook/stories') {
name,
filename,
url,
groupTitle,
filePath: path.join(path.relative(process.cwd(), path.dirname(groupFile)), filename),
};
});
Expand Down
11 changes: 8 additions & 3 deletions e2e_server/server/generate/import_template.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@
* Side Public License, v 1.
*/

module.exports = function lazyImportTemplate(index, path) {
const { capitalCase } = require('change-case');

module.exports = function lazyImportTemplate({ filePath, groupTitle, name }, index) {
return `
const Component${index} = React.lazy(() => {
return import('../../${path}').then((module) => {
return import('../../${filePath}').then((module) => {
setParams(urlParams, (module.Example as any).parameters);
return { default: module.Example };
const Component = module.Example.bind(module.Example, {}, getStoryContext('${groupTitle}', '${capitalCase(
name,
)}'))
return { default: Component };
});
});`;
};
2 changes: 1 addition & 1 deletion e2e_server/server/generate/route_template.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
* Side Public License, v 1.
*/

module.exports = function routeComponentTemplate(index, url) {
module.exports = function routeComponentTemplate({ url }, index) {
return `{path === '${url}' && <Component${index} />}`;
};
24 changes: 18 additions & 6 deletions e2e_server/server/generate/vrt_page_template.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,26 @@ import React, { Suspense } 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';
export function VRTPage() {
const {
themeId,
backgroundId,
toggles,
setParams,
} = useGlobalsParameters();
const urlParams = new URL(window.location.toString()).searchParams;
const colorMode = themeId.includes('light') ? 'light' : 'dark';
const colorMode = (themeId ?? '').includes('light') ? 'light' : 'dark';
const getStoryContext = (title, name): StoryContext => ({
globals: {
theme: themeId,
background: backgroundId,
toggles,
},
title: toggles.showChartTitle ? title : undefined,
description: toggles.showChartDescription ? name : undefined,
});
${imports.join('\n ')}
const path = urlParams.get('path');
Expand All @@ -71,12 +82,13 @@ export function VRTPage() {
</ul>
</>);
}
return (
<EuiProvider colorMode={colorMode}>
<ThemeIdProvider value={themeId as any}>
<BackgroundIdProvider value={backgroundId}>
<Suspense fallback={<div>Loading...</div>}>
${routes.join('\n ')}
${routes.join('\n ')}
</Suspense>
</BackgroundIdProvider>
</ThemeIdProvider>
Expand All @@ -93,10 +105,10 @@ function compileVRTPage(examples) {
return acc;
}, []);
const { imports, routes, urls } = flatExamples.reduce(
(acc, { filePath, url }, index) => {
acc.imports.push(compileImportTemplate(index, filePath));
acc.routes.push(compileRouteTemplate(index, url));
acc.urls.push(url);
(acc, example, index) => {
acc.imports.push(compileImportTemplate(example, index));
acc.routes.push(compileRouteTemplate(example, index));
acc.urls.push(example.url);
return acc;
},
{ imports: [], routes: [], urls: [] },
Expand Down
46 changes: 35 additions & 11 deletions e2e_server/server/mocks/use_global_parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,14 @@
* Side Public License, v 1.
*/

import { useState } from 'react';
import { useMemo, useState } from 'react';

import type { StoryGlobals } 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';

interface Globals {
theme?: string;
background?: string;
}

type Parameters = BackgroundParameter & ThemeParameter;

const themeParams = globalParams.theme!;
Expand Down Expand Up @@ -48,30 +44,58 @@ function applyThemeCSS(themeId: string) {
}
}

export function useGlobalsParameters() {
interface GlobalParameters {
themeId: StoryGlobals['theme'];
backgroundId: StoryGlobals['background'];
toggles: StoryGlobals['toggles'];
setParams(params: URLSearchParams, parameters?: Parameters): void;
}

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

/**
* Handles setting global context values. Stub for theme and background addons
*/
function setParams(params: URLSearchParams, parameters?: Parameters) {
const globals = getGlobalParams(params) as Globals;
const globals = getGlobalParams(params);
const backgroundIdFromParams = globals.background ?? parameters?.background?.default ?? backgroundParams.default;
setBackgroundId(backgroundIdFromParams);
const themeIdFromParams = globals.theme ?? parameters?.theme?.default ?? themeParams.default ?? ThemeId.Light;
setThemeId(themeIdFromParams);
setTogglesJSON(JSON.stringify(globals.toggles ?? '{}'));
applyThemeCSS(themeIdFromParams);
}

// using toggles object creates an infinite update loop, thus using JSON state.
const toggles = useMemo<StoryGlobals['toggles']>(() => JSON.parse(togglesJSON), [togglesJSON]);

return {
themeId,
backgroundId,
toggles,
setParams,
};
}

function getGlobalParams(params: URLSearchParams) {
const globals = params.get('globals') ?? '';
return Object.fromEntries(globals.split(';').map((pair: string) => pair.split(':')));
function getGlobalParams(params: URLSearchParams): StoryGlobals {
const rawGlobals = params.get('globals') ?? '';
const globalsArr = rawGlobals.split(';').map((pair: string) => pair.split(':'));
return globalsArr.reduce((acc, [key, value]) => {
const [k1, k2] = key?.split('.') ?? [];

if (k1 && k2) {
if (!acc[k1]) acc[k1] = {};

// capture nested object globals (i.e. toggles.showHeader:true)
try {
acc[k1][k2] = value && JSON.parse(value);
} catch {
acc[k1][k2] = value;
}
} else if (k1) acc[k1] = value;
return acc;
}, {} as any);
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"lint:it": "NODE_ENV=production eslint --quiet --ext .tsx,.ts,.js",
"lint:fix:it": "yarn lint:it --fix",
"prettier:check": "prettier --check \"**/*.{json,html,css,scss}\"",
"prettier:fix": "prettier --w \"**/*.{json,html,css,scss}\"",
"playground": "cd playground && RNG_SEED='elastic-charts' webpack serve",
"pq": "pretty-quick",
"semantic-release": "semantic-release --debug",
Expand Down Expand Up @@ -107,6 +108,7 @@
"autoprefixer": "^9.0.0",
"backport": "^5.6.6",
"commitizen": "^4.2.3",
"change-case": "^4.1.2",
"cross-env": "^7.0.2",
"cz-conventional-changelog": "^3.3.0",
"enzyme": "^3.11.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/charts/api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,8 @@ export class Chart extends React_2.Component<ChartProps, ChartState> {
// (undocumented)
componentDidMount(): void;
// (undocumented)
componentDidUpdate({ title, description }: Readonly<ChartProps>): void;
// (undocumented)
componentWillUnmount(): void;
// (undocumented)
static defaultProps: ChartProps;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class FlameState implements InternalChartState {
isBrushAvailable = () => false;
isBrushing = () => false;
isChartEmpty = () => false;
canDisplayChartTitles = () => false;
getLegendItemsLabels = () => [];
getLegendItems = () => [];
getLegendExtraValues = () => new Map<SeriesKey, LegendItemExtraValues>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,7 @@ export class GoalState implements InternalChartState {
smVDomain: [],
};
}

// TODO enable for small multiples
canDisplayChartTitles = () => false;
}
2 changes: 2 additions & 0 deletions packages/charts/src/chart_types/heatmap/state/chart_state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,6 @@ export class HeatmapState implements InternalChartState {
this.onBrushEndCaller(globalState);
this.onPointerUpdate(globalState);
}

canDisplayChartTitles = () => true;
}
4 changes: 4 additions & 0 deletions packages/charts/src/chart_types/metric/renderer/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
border-right: 1px solid #343741;
}

&--topBorder {
border-top: 1px solid #343741;
}

&--bottomBorder {
border-bottom: 1px solid #343741;
}
Expand Down
Loading

0 comments on commit 341a990

Please sign in to comment.