[fleet] Introduce Storybook to Fleet (#112611) (#112665)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Clint Andrew Hall <clint.hall@elastic.co>
This commit is contained in:
Kibana Machine 2021-09-21 11:45:51 -04:00 committed by GitHub
parent 94b4239a85
commit a7d00cff7c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 463 additions and 2 deletions

View file

@ -13,6 +13,7 @@ const STORYBOOKS = [
'dashboard_enhanced',
'data_enhanced',
'embeddable',
'fleet',
'infra',
'security_solution',
'ui_actions_enhanced',

View file

@ -24,6 +24,7 @@ export const storybookAliases = {
expression_reveal_image: 'src/plugins/expression_reveal_image/.storybook',
expression_shape: 'src/plugins/expression_shape/.storybook',
expression_tagcloud: 'src/plugins/chart_expressions/expression_tagcloud/.storybook',
fleet: 'x-pack/plugins/fleet/storybook',
infra: 'x-pack/plugins/infra/.storybook',
security_solution: 'x-pack/plugins/security_solution/.storybook',
ui_actions_enhanced: 'x-pack/plugins/ui_actions_enhanced/.storybook',

View file

@ -0,0 +1,55 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { AssetsFacetGroup as Component } from './assets_facet_group';
export default {
component: Component,
title: 'Sections/EPM/Assets Facet Group',
};
interface Args {
width: number;
}
const args: Args = {
width: 250,
};
export const AssetsFacetGroup = ({ width }: Args) => {
return (
<div style={{ width }}>
<Component
assets={{
kibana: {
dashboard: [],
visualization: [],
index_pattern: [],
search: [],
map: [],
lens: [],
security_rule: [],
ml_module: [],
},
elasticsearch: {
component_template: [],
data_stream_ilm_policy: [],
data_stream: [],
ilm_policy: [],
index_template: [],
ingest_pipeline: [],
transform: [],
},
}}
/>
</div>
);
};
AssetsFacetGroup.args = args;

View file

@ -0,0 +1,79 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import type { SavedObject } from 'src/core/public';
import type { Installation } from '../../../../../../common';
import type { PackageCardProps } from './package_card';
import { PackageCard } from './package_card';
export default {
title: 'Sections/EPM/Package Card',
description: 'A card representing a package available in Fleet',
};
type Args = Omit<PackageCardProps, 'status'> & { width: number };
const args: Args = {
width: 250,
title: 'Title',
description: 'Description',
name: 'beats',
release: 'ga',
id: 'id',
version: '1.0.0',
download: '/',
path: 'path',
};
const argTypes = {
release: {
control: {
type: 'radio',
options: ['ga', 'beta', 'experimental'],
},
},
};
export const NotInstalled = ({ width, ...props }: Args) => (
<div style={{ width }}>
<PackageCard {...props} status="not_installed" />
</div>
);
export const Installed = ({ width, ...props }: Args) => {
const savedObject: SavedObject<Installation> = {
id: props.id,
type: props.type || '',
attributes: {
name: props.name,
version: props.version,
install_version: props.version,
es_index_patterns: {},
installed_kibana: [],
installed_es: [],
install_status: 'installed',
install_source: 'registry',
install_started_at: '2020-01-01T00:00:00.000Z',
},
references: [],
};
return (
<div style={{ width }}>
<PackageCard {...props} status="installed" savedObject={savedObject} />
</div>
);
};
NotInstalled.args = args;
NotInstalled.argTypes = argTypes;
Installed.args = args;
Installed.argTypes = argTypes;

View file

@ -15,7 +15,7 @@ import { PackageIcon } from '../../../components';
import { RELEASE_BADGE_LABEL, RELEASE_BADGE_DESCRIPTION } from './release_badge';
type PackageCardProps = PackageListItem;
export type PackageCardProps = PackageListItem;
// adding the `href` causes EuiCard to use a `a` instead of a `button`
// `a` tags use `euiLinkColor` which results in blueish Badge text

View file

@ -0,0 +1,138 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { SavedObject } from 'src/core/public';
import type { Installation } from '../../../../../../common';
import type { ListProps } from './package_list_grid';
import { PackageListGrid } from './package_list_grid';
export default {
component: PackageListGrid,
title: 'Sections/EPM/Package List Grid',
};
type Args = Pick<ListProps, 'title' | 'isLoading' | 'showMissingIntegrationMessage'>;
const args: Args = {
title: 'Installed integrations',
isLoading: false,
showMissingIntegrationMessage: false,
};
const savedObject: SavedObject<Installation> = {
id: 'id',
type: 'integration',
attributes: {
name: 'savedObject',
version: '1.2.3',
install_version: '1.2.3',
es_index_patterns: {},
installed_kibana: [],
installed_es: [],
install_status: 'installed',
install_source: 'registry',
install_started_at: '2020-01-01T00:00:00.000Z',
},
references: [],
};
export const EmptyList = (props: Args) => (
<PackageListGrid
list={[]}
onSearchChange={action('onSearchChange')}
setSelectedCategory={action('setSelectedCategory')}
{...props}
/>
);
export const List = (props: Args) => (
<PackageListGrid
list={[
{
title: 'Package One',
description: 'Not Installed Description',
name: 'beats',
release: 'ga',
id: 'id',
version: '1.0.0',
download: '/',
path: 'path',
status: 'not_installed',
},
{
title: 'Package Two',
description: 'Not Installed Description',
name: 'aws',
release: 'beta',
id: 'id',
version: '1.0.0',
download: '/',
path: 'path',
status: 'not_installed',
},
{
title: 'Package Three',
description: 'Not Installed Description',
name: 'azure',
release: 'experimental',
id: 'id',
version: '1.0.0',
download: '/',
path: 'path',
status: 'not_installed',
},
{
title: 'Package Four',
description: 'Installed Description',
name: 'elastic',
release: 'ga',
id: 'id',
version: '1.0.0',
download: '/',
path: 'path',
status: 'installed',
savedObject,
},
{
title: 'Package Five',
description: 'Installed Description',
name: 'unknown',
release: 'beta',
id: 'id',
version: '1.0.0',
download: '/',
path: 'path',
status: 'installed',
savedObject,
},
{
title: 'Package Six',
description: 'Installed Description',
name: 'kibana',
release: 'experimental',
id: 'id',
version: '1.0.0',
download: '/',
path: 'path',
status: 'installed',
savedObject,
},
]}
onSearchChange={action('onSearchChange')}
setSelectedCategory={action('setSelectedCategory')}
{...props}
/>
);
EmptyList.args = args;
List.args = args;

View file

@ -28,7 +28,7 @@ import { useLocalSearch, searchIdField } from '../../../hooks';
import { PackageCard } from './package_card';
interface ListProps {
export interface ListProps {
isLoading?: boolean;
controls?: ReactNode;
title: string;

View file

@ -0,0 +1,39 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { Requirements as Component } from './requirements';
export default {
component: Component,
title: 'Sections/EPM/Requirements',
};
interface Args {
width: number;
}
const args: Args = {
width: 250,
};
export const Requirements = ({ width }: Args) => {
return (
<div style={{ width }}>
<Component
requirements={{
kibana: {
version: '1.2.3',
},
}}
/>
</div>
);
};
Requirements.args = args;

View file

@ -0,0 +1,79 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { of } from 'rxjs';
import type { DecoratorFn } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { createMemoryHistory } from 'history';
import { I18nProvider } from '@kbn/i18n/react';
import { ScopedHistory } from '../../../../src/core/public';
import { IntegrationsAppContext } from '../public/applications/integrations/app';
import type { FleetConfigType, FleetStartServices } from '../public/plugin';
// TODO: clintandrewhall - this is not ideal, or complete. The root context of Fleet applications
// requires full start contracts of its dependencies. As a result, we have to mock all of those contracts
// with Storybook equivalents. This is a temporary solution, and should be replaced with a more complete
// mock later, (or, ideally, Fleet starts to use a service abstraction).
//
// Expect this to grow as components that are given Stories need access to mocked services.
export const contextDecorator: DecoratorFn = (story: Function) => {
const basepath = '/';
const memoryHistory = createMemoryHistory({ initialEntries: [basepath] });
const history = new ScopedHistory(memoryHistory, basepath);
const startServices = {
application: {
currentAppId$: of('home'),
navigateToUrl: (url: string) => action(`Navigate to: ${url}`),
getUrlForApp: (url: string) => url,
},
http: {
basePath: {
prepend: () => basepath,
},
},
notifications: {},
history,
uiSettings: {
get$: (key: string) => {
switch (key) {
case 'theme:darkMode':
return of(false);
default:
return of();
}
},
},
i18n: {
Context: I18nProvider,
},
} as unknown as FleetStartServices;
const config = {
enabled: true,
agents: {
enabled: true,
elasticsearch: {},
},
} as unknown as FleetConfigType;
const extensions = {};
const kibanaVersion = '1.2.3';
return (
<IntegrationsAppContext
{...{ kibanaVersion, basepath, config, history, startServices, extensions }}
>
{story()}
</IntegrationsAppContext>
);
};

View file

@ -0,0 +1,20 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { Configuration } from 'webpack';
import { defaultConfig, WebpackConfig } from '@kbn/storybook';
module.exports = {
...defaultConfig,
addons: ['@storybook/addon-essentials'],
babel: () => ({
presets: [require.resolve('@kbn/babel-preset/webpack_preset')],
}),
webpackFinal: (config: Configuration) => {
return WebpackConfig({ config });
},
};

View file

@ -0,0 +1,20 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { addons } from '@storybook/addons';
import { create } from '@storybook/theming';
import { PANEL_ID } from '@storybook/addon-actions';
addons.setConfig({
theme: create({
base: 'light',
brandTitle: 'Kibana Fleet Storybook',
brandUrl: 'https://github.com/elastic/kibana/tree/master/x-pack/plugins/fleet',
}),
showPanel: true.valueOf,
selectedPanel: PANEL_ID,
});

View file

@ -0,0 +1,28 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { addDecorator } from '@storybook/react';
import { Title, Subtitle, Description, Primary, Stories } from '@storybook/addon-docs/blocks';
import { contextDecorator } from './decorator';
addDecorator(contextDecorator);
export const parameters = {
docs: {
page: () => (
<>
<Title />
<Subtitle />
<Description />
<Primary />
<Stories />
</>
),
},
};

View file

@ -14,6 +14,7 @@
"server/**/*.json",
"scripts/**/*",
"package.json",
"storybook/**/*",
"../../../typings/**/*"
],
"references": [