Jest and Storybook fixes (#116132)

* Fix all broken stories
* In stories that were broken, add an associated Jest test so they if they break in the future we'll know
* Fix all console.error messages that were being printed during Jest test runs
* Add test setup which makes it so `console.error` throws an error so tests will fail if error console messages are printed
This commit is contained in:
Nathan L Smith 2021-10-26 07:25:01 -05:00 committed by GitHub
parent 3c8fa527a7
commit 57c2555f27
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 496 additions and 1600 deletions

View file

@ -11,7 +11,10 @@ module.exports = {
preset: '@kbn/test',
rootDir: path.resolve(__dirname, '../../..'),
roots: ['<rootDir>/x-pack/plugins/apm'],
setupFiles: ['<rootDir>/x-pack/plugins/apm/.storybook/jest_setup.js'],
setupFiles: [
'<rootDir>/x-pack/plugins/apm/jest_setup.js',
'<rootDir>/x-pack/plugins/apm/.storybook/jest_setup.js',
],
coverageDirectory: '<rootDir>/target/kibana-coverage/jest/x-pack/plugins/apm',
coverageReporters: ['text', 'html'],
collectCoverageFrom: [

View file

@ -0,0 +1,14 @@
/*
* 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.
*/
/* global jest */
// When a `console.error` is encountered, throw the error to make the test fail.
// This effectively treats logged errors during the test run as failures.
jest.spyOn(console, 'error').mockImplementation((message) => {
throw new Error(message);
});

View file

@ -11,11 +11,17 @@ import { AlertParams, ErrorCountAlertTrigger } from '.';
import { CoreStart } from '../../../../../../../src/core/public';
import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public';
import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values';
import { createCallApmApi } from '../../../services/rest/createCallApmApi';
import { AlertMetadata } from '../helper';
const KibanaReactContext = createKibanaReactContext({
const coreMock = {
http: { get: async () => ({}) },
notifications: { toasts: { add: () => {} } },
} as unknown as Partial<CoreStart>);
uiSettings: { get: () => {} },
} as unknown as CoreStart;
const KibanaReactContext = createKibanaReactContext(coreMock);
interface Args {
alertParams: AlertParams;
@ -27,6 +33,8 @@ const stories: Meta<{}> = {
component: ErrorCountAlertTrigger,
decorators: [
(StoryComponent) => {
createCallApmApi(coreMock);
return (
<KibanaReactContext.Provider>
<div style={{ width: 400 }}>

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import React from 'react';
import * as stories from './error_count_alert_trigger.stories';
import { composeStories } from '@storybook/testing-react';
@ -13,7 +13,9 @@ import { composeStories } from '@storybook/testing-react';
const { CreatingInApmFromService } = composeStories(stories);
describe('ErrorCountAlertTrigger', () => {
it('renders', () => {
expect(() => render(<CreatingInApmFromService />)).not.toThrowError();
it('renders', async () => {
render(<CreatingInApmFromService />);
expect(await screen.findByText('Service')).toBeInTheDocument();
});
});

View file

@ -1,55 +0,0 @@
/*
* 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 { mount } from 'enzyme';
import React from 'react';
import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context';
import { MockUrlParamsContextProvider } from '../../../../context/url_params_context/mock_url_params_context_provider';
import { mockMoment, toJson } from '../../../../utils/testHelpers';
import { ErrorGroupList } from './index';
import props from './__fixtures__/props.json';
import { MemoryRouter } from 'react-router-dom';
import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common';
describe('ErrorGroupOverview -> List', () => {
beforeAll(() => {
mockMoment();
});
it('should render empty state', () => {
const storeState = {};
const wrapper = mount(
<MemoryRouter>
<MockUrlParamsContextProvider>
<ErrorGroupList items={[]} serviceName="opbeans-python" />
</MockUrlParamsContextProvider>
</MemoryRouter>,
storeState
);
expect(toJson(wrapper)).toMatchSnapshot();
});
it('should render with data', () => {
const wrapper = mount(
<EuiThemeProvider>
<MemoryRouter>
<MockApmPluginContextWrapper>
<MockUrlParamsContextProvider>
<ErrorGroupList
items={props.items}
serviceName="opbeans-python"
/>
</MockUrlParamsContextProvider>
</MockApmPluginContextWrapper>
</MemoryRouter>
</EuiThemeProvider>
);
expect(toJson(wrapper)).toMatchSnapshot();
});
});

View file

@ -0,0 +1,100 @@
/*
* 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 { Meta, Story } from '@storybook/react';
import React, { ComponentProps } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context';
import { MockUrlParamsContextProvider } from '../../../../context/url_params_context/mock_url_params_context_provider';
import { ErrorGroupList } from '.';
type Args = ComponentProps<typeof ErrorGroupList>;
const stories: Meta<Args> = {
title: 'app/ErrorGroupOverview/ErrorGroupList',
component: ErrorGroupList,
decorators: [
(StoryComponent) => {
return (
<MemoryRouter>
<MockApmPluginContextWrapper>
<MockUrlParamsContextProvider>
<StoryComponent />
</MockUrlParamsContextProvider>
</MockApmPluginContextWrapper>
</MemoryRouter>
);
},
],
};
export default stories;
export const Example: Story<Args> = (args) => {
return <ErrorGroupList {...args} />;
};
Example.args = {
items: [
{
message: 'net/http: abort Handler',
occurrenceCount: 14,
culprit: 'Main.func2',
groupId: '83a653297ec29afed264d7b60d5cda7b',
latestOccurrenceAt: '2021-10-21T16:18:41.434Z',
handled: false,
type: 'errorString',
},
{
message: 'POST /api/orders (500)',
occurrenceCount: 5,
culprit: 'logrusMiddleware',
groupId: '7a640436a9be648fd708703d1ac84650',
latestOccurrenceAt: '2021-10-21T16:18:40.162Z',
handled: false,
type: 'OpError',
},
{
message:
'write tcp 10.36.2.24:3000->10.36.1.14:34232: write: connection reset by peer',
occurrenceCount: 4,
culprit: 'apiHandlers.getProductCustomers',
groupId: '95ca0e312c109aa11e298bcf07f1445b',
latestOccurrenceAt: '2021-10-21T16:18:42.650Z',
handled: false,
type: 'OpError',
},
{
message:
'write tcp 10.36.0.21:3000->10.36.1.252:57070: write: connection reset by peer',
occurrenceCount: 3,
culprit: 'apiHandlers.getCustomers',
groupId: '4053d7e33d2b716c819bd96d9d6121a2',
latestOccurrenceAt: '2021-10-21T16:07:44.078Z',
handled: false,
type: 'OpError',
},
{
message:
'write tcp 10.36.0.21:3000->10.36.0.88:33926: write: broken pipe',
occurrenceCount: 2,
culprit: 'apiHandlers.getOrders',
groupId: '94f4ca8ec8c02e5318cf03f46ae4c1f3',
latestOccurrenceAt: '2021-10-21T16:13:45.742Z',
handled: false,
type: 'OpError',
},
],
serviceName: 'test service',
};
export const EmptyState: Story<Args> = (args) => {
return <ErrorGroupList {...args} />;
};
EmptyState.args = {
items: [],
serviceName: 'test service',
};

View file

@ -0,0 +1,19 @@
/*
* 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 { composeStories } from '@storybook/testing-react';
import { render } from '@testing-library/react';
import React from 'react';
import * as stories from './error_group_list.stories';
const { Example } = composeStories(stories);
describe('ErrorGroupList', () => {
it('renders', () => {
expect(() => render(<Example />)).not.toThrowError();
});
});

View file

@ -23,7 +23,7 @@ import { useFetcher } from '../../../hooks/use_fetcher';
import { useTimeRange } from '../../../hooks/use_time_range';
import { FailedTransactionRateChart } from '../../shared/charts/failed_transaction_rate_chart';
import { ErrorDistribution } from '../error_group_details/Distribution';
import { ErrorGroupList } from './List';
import { ErrorGroupList } from './error_group_list';
export function ErrorGroupOverview() {
const { serviceName } = useApmServiceContext();

View file

@ -0,0 +1,81 @@
/*
* 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 { Meta, Story } from '@storybook/react';
import React, { ComponentProps } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { CoreStart } from '../../../../../../../../src/core/public';
import { createKibanaReactContext } from '../../../../../../../../src/plugins/kibana_react/public';
import { ServiceHealthStatus } from '../../../../../common/service_health_status';
import type { ApmPluginContextValue } from '../../../../context/apm_plugin/apm_plugin_context';
import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context';
import { ServiceList } from './';
import { items } from './__fixtures__/service_api_mock_data';
type Args = ComponentProps<typeof ServiceList>;
const coreMock = {
http: {
get: async () => {
return { fallBackToTransactions: false };
},
},
notifications: { toasts: { add: () => {} } },
uiSettings: { get: () => ({}) },
} as unknown as CoreStart;
const KibanaReactContext = createKibanaReactContext(coreMock);
const stories: Meta<Args> = {
title: 'app/ServiceInventory/ServiceList',
component: ServiceList,
decorators: [
(StoryComponent) => {
return (
<KibanaReactContext.Provider>
<MemoryRouter
initialEntries={['/services?rangeFrom=now-15m&rangeTo=now']}
>
<MockApmPluginContextWrapper
value={{ core: coreMock } as unknown as ApmPluginContextValue}
>
<StoryComponent />
</MockApmPluginContextWrapper>
</MemoryRouter>
</KibanaReactContext.Provider>
);
},
],
};
export default stories;
export const Example: Story<Args> = (args) => {
return <ServiceList {...args} />;
};
Example.args = {
isLoading: false,
items,
};
export const EmptyState: Story<Args> = (args) => {
return <ServiceList {...args} />;
};
EmptyState.args = {
isLoading: false,
items: [],
};
export const WithHealthWarnings: Story<Args> = (args) => {
return <ServiceList {...args} />;
};
WithHealthWarnings.args = {
isLoading: false,
items: items.map((item) => ({
...item,
healthStatus: ServiceHealthStatus.warning,
})),
};

View file

@ -5,58 +5,27 @@
* 2.0.
*/
import React, { ReactNode } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { Breakpoints } from '../../../../hooks/use_breakpoints';
import { ServiceHealthStatus } from '../../../../../common/service_health_status';
import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context';
import { mockMoment, renderWithTheme } from '../../../../utils/testHelpers';
import { getServiceColumns, ServiceList } from './';
import { items } from './__fixtures__/service_api_mock_data';
import { composeStories } from '@storybook/testing-react';
import { render, screen } from '@testing-library/react';
import React from 'react';
import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values';
import {
getCallApmApiSpy,
getCreateCallApmApiSpy,
} from '../../../../services/rest/callApmApiSpy';
import { Breakpoints } from '../../../../hooks/use_breakpoints';
import { getServiceColumns } from './';
import * as stories from './service_list.stories';
function Wrapper({ children }: { children?: ReactNode }) {
return (
<MemoryRouter initialEntries={['/services?rangeFrom=now-15m&rangeTo=now']}>
<MockApmPluginContextWrapper>{children}</MockApmPluginContextWrapper>
</MemoryRouter>
);
}
const { Example, EmptyState, WithHealthWarnings } = composeStories(stories);
describe('ServiceList', () => {
beforeAll(() => {
mockMoment();
it('renders empty state', async () => {
render(<EmptyState />);
const callApmApiSpy = getCallApmApiSpy().mockImplementation(
({ endpoint }) => {
if (endpoint === 'GET /internal/apm/fallback_to_transactions') {
return Promise.resolve({ fallbackToTransactions: false });
}
return Promise.reject(`Response for ${endpoint} is not defined`);
}
);
getCreateCallApmApiSpy().mockImplementation(() => callApmApiSpy as any);
expect(await screen.findByRole('table')).toBeInTheDocument();
});
it('renders empty state', () => {
expect(() =>
renderWithTheme(<ServiceList isLoading={false} items={[]} />, {
wrapper: Wrapper,
})
).not.toThrowError();
});
it('renders with data', async () => {
render(<Example />);
it('renders with data', () => {
expect(() =>
renderWithTheme(<ServiceList isLoading={false} items={items} />, {
wrapper: Wrapper,
})
).not.toThrowError();
expect(await screen.findByRole('table')).toBeInTheDocument();
});
describe('responsive columns', () => {
@ -212,44 +181,20 @@ describe('ServiceList', () => {
});
describe('without ML data', () => {
it('does not render the health column', () => {
const { queryByText } = renderWithTheme(
<ServiceList isLoading={false} items={items} />,
{
wrapper: Wrapper,
}
);
const healthHeading = queryByText('Health');
expect(healthHeading).toBeNull();
});
it('sorts by throughput', async () => {
const { findByTitle } = renderWithTheme(
<ServiceList isLoading={false} items={items} />,
{
wrapper: Wrapper,
}
);
render(<Example />);
expect(await findByTitle('Throughput')).toBeInTheDocument();
expect(await screen.findByTitle('Throughput')).toBeInTheDocument();
});
});
describe('with ML data', () => {
it('renders the health column', async () => {
const { findByTitle } = renderWithTheme(
<ServiceList
isLoading={false}
items={items.map((item) => ({
...item,
healthStatus: ServiceHealthStatus.warning,
}))}
/>,
{ wrapper: Wrapper }
);
render(<WithHealthWarnings />);
expect(await findByTitle('Health')).toBeInTheDocument();
expect(
await screen.findByRole('button', { name: /Health/ })
).toBeInTheDocument();
});
});
});

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { __IntlProvider as IntlProvider } from '@kbn/i18n/react';
import { render, screen, waitFor } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import React, { ReactNode } from 'react';
@ -49,25 +50,27 @@ function Wrapper({ children }: { children?: ReactNode }) {
}) as unknown as ApmPluginContextValue;
return (
<EuiThemeProvider darkMode={false}>
<KibanaReactContext.Provider>
<MockApmPluginContextWrapper
history={history}
value={mockPluginContext}
>
<MockUrlParamsContextProvider
params={{
rangeFrom: 'now-15m',
rangeTo: 'now',
start: 'mystart',
end: 'myend',
}}
<IntlProvider locale="en">
<EuiThemeProvider darkMode={false}>
<KibanaReactContext.Provider>
<MockApmPluginContextWrapper
history={history}
value={mockPluginContext}
>
{children}
</MockUrlParamsContextProvider>
</MockApmPluginContextWrapper>
</KibanaReactContext.Provider>
</EuiThemeProvider>
<MockUrlParamsContextProvider
params={{
rangeFrom: 'now-15m',
rangeTo: 'now',
start: 'mystart',
end: 'myend',
}}
>
{children}
</MockUrlParamsContextProvider>
</MockApmPluginContextWrapper>
</KibanaReactContext.Provider>
</EuiThemeProvider>
</IntlProvider>
);
}
@ -93,13 +96,13 @@ describe('transaction_details/distribution', () => {
}));
render(
<Wrapper>
<TransactionDistribution
onChartSelection={jest.fn()}
onClearSelection={jest.fn()}
traceSamples={[]}
/>
</Wrapper>
<TransactionDistribution
onChartSelection={jest.fn()}
onClearSelection={jest.fn()}
traceSamples={[]}
/>,
{ wrapper: Wrapper }
);
await waitFor(() => {

View file

@ -0,0 +1,75 @@
/*
* 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 { Meta, Story } from '@storybook/react';
import type { CoreStart, DocLinksStart } from 'kibana/public';
import React, { ComponentProps } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public';
import type { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context';
import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context';
import { SettingsTemplate } from './settings_template';
type Args = ComponentProps<typeof SettingsTemplate>;
const coreMock = {
notifications: { toasts: { add: () => {} } },
usageCollection: { reportUiCounter: () => {} },
observability: {
navigation: {
PageTemplate: () => {
return <>hello world</>;
},
},
},
http: {
basePath: {
prepend: (path: string) => `/basepath${path}`,
get: () => `/basepath`,
},
get: async () => ({}),
},
docLinks: {
DOC_LINK_VERSION: '0',
ELASTIC_WEBSITE_URL: 'https://www.elastic.co/',
links: {
apm: {},
observability: { guide: '' },
},
} as unknown as DocLinksStart,
} as unknown as Partial<CoreStart>;
const KibanaReactContext = createKibanaReactContext(coreMock);
const stories: Meta<Args> = {
title: 'routing/templates/SettingsTemplate',
component: SettingsTemplate,
decorators: [
(StoryComponent) => {
return (
<MemoryRouter>
<KibanaReactContext.Provider>
<MockApmPluginContextWrapper
value={{ core: coreMock } as unknown as ApmPluginContextValue}
>
<StoryComponent />
</MockApmPluginContextWrapper>
</KibanaReactContext.Provider>
</MemoryRouter>
);
},
],
};
export default stories;
export const Example: Story<Args> = (args) => {
return <SettingsTemplate {...args} />;
};
Example.args = {
children: <>test</>,
selectedTab: 'agent-configurations',
};

View file

@ -5,69 +5,17 @@
* 2.0.
*/
import { render } from '@testing-library/react';
import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context';
import React, { ReactNode } from 'react';
import { SettingsTemplate } from './settings_template';
import { createMemoryHistory } from 'history';
import { MemoryRouter, RouteComponentProps } from 'react-router-dom';
import { CoreStart, DocLinksStart, HttpStart } from 'kibana/public';
import { createKibanaReactContext } from 'src/plugins/kibana_react/public';
import { createCallApmApi } from '../../../services/rest/createCallApmApi';
import { composeStories } from '@storybook/testing-react';
import { render, screen } from '@testing-library/react';
import React from 'react';
import * as stories from './settings_template.stories';
const { location } = createMemoryHistory();
const KibanaReactContext = createKibanaReactContext({
notifications: { toasts: { add: () => {} } },
usageCollection: { reportUiCounter: () => {} },
observability: {
navigation: {
PageTemplate: () => {
return <>hello world</>;
},
},
},
http: {
basePath: {
prepend: (path: string) => `/basepath${path}`,
get: () => `/basepath`,
},
} as HttpStart,
docLinks: {
DOC_LINK_VERSION: '0',
ELASTIC_WEBSITE_URL: 'https://www.elastic.co/',
links: {
apm: {},
observability: { guide: '' },
},
} as unknown as DocLinksStart,
} as unknown as Partial<CoreStart>);
function Wrapper({ children }: { children?: ReactNode }) {
return (
<MemoryRouter>
<KibanaReactContext.Provider>
<MockApmPluginContextWrapper>{children}</MockApmPluginContextWrapper>
</KibanaReactContext.Provider>
</MemoryRouter>
);
}
const { Example } = composeStories(stories);
describe('Settings', () => {
beforeEach(() => {
createCallApmApi({} as CoreStart);
});
it('renders', async () => {
const routerProps = {
location,
} as unknown as RouteComponentProps<{}>;
expect(() =>
render(
<SettingsTemplate selectedTab="agent-configurations" {...routerProps}>
<div>hello world</div>
</SettingsTemplate>,
{ wrapper: Wrapper }
)
).not.toThrowError();
render(<Example />);
expect(await screen.findByText('hello world')).toBeInTheDocument();
});
});

View file

@ -31,6 +31,12 @@ export const Example: Story<Args> = (args) => {
};
Example.args = {
backendName: 'postgres',
query: {
environment: 'ENVIRONMENT_ALL',
kuery: '',
rangeFrom: 'now-15m',
rangeTo: 'now',
},
type: 'db',
subtype: 'postgresql',
};

View file

@ -0,0 +1,19 @@
/*
* 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 { composeStories } from '@storybook/testing-react';
import { render } from '@testing-library/react';
import React from 'react';
import * as stories from './backend_link.stories';
const { Example } = composeStories(stories);
describe('BackendLink', () => {
it('renders', () => {
expect(() => render(<Example />)).not.toThrowError();
});
});

View file

@ -20,18 +20,18 @@ import {
ALERT_RULE_UUID,
ALERT_RULE_NAME,
ALERT_RULE_CATEGORY,
ALERT_RULE_CONSUMER,
ALERT_RULE_PRODUCER,
SPACE_IDS,
} from '@kbn/rule-data-utils';
import { StoryContext } from '@storybook/react';
import React, { ComponentType } from 'react';
import { MemoryRouter, Route } from 'react-router-dom';
import { Meta, Story } from '@storybook/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public';
import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values';
import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types';
import {
ApmPluginContext,
ApmPluginContextValue,
} from '../../../../context/apm_plugin/apm_plugin_context';
import type { ApmPluginContextValue } from '../../../../context/apm_plugin/apm_plugin_context';
import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context';
import { APMServiceContext } from '../../../../context/apm_service/apm_service_context';
import { ChartPointerEventContextProvider } from '../../../../context/chart_pointer_event/chart_pointer_event_context';
import { MockUrlParamsContextProvider } from '../../../../context/url_params_context/mock_url_params_context_provider';
@ -46,7 +46,7 @@ interface Args {
latencyChartResponse: APIReturnType<'GET /internal/apm/services/{serviceName}/transactions/charts/latency'>;
}
export default {
const stories: Meta<Args> = {
title: 'shared/charts/LatencyChart',
component: LatencyChart,
argTypes: {
@ -57,7 +57,7 @@ export default {
},
},
decorators: [
(Story: ComponentType, { args }: StoryContext) => {
(StoryComponent, { args }) => {
const { alertsResponse, latencyChartResponse } = args as Args;
const serviceName = 'testService';
@ -88,44 +88,46 @@ export default {
const transactionType = `${Math.random()}`; // So we don't memoize
return (
<ApmPluginContext.Provider value={apmPluginContextMock}>
<MemoryRouter initialEntries={[`/services/${serviceName}`]}>
<Route path="/services/{serviceName}">
<KibanaContextProvider
services={{ ...apmPluginContextMock.core }}
<MemoryRouter
initialEntries={[
`/services/${serviceName}/overview?environment=ENVIRONMENT_ALL&kuery=&rangeFrom=now-15m&rangeTo=now&transactionType=request&comparisonEnabled=true&comparisonType=day`,
]}
>
<MockApmPluginContextWrapper value={apmPluginContextMock}>
<KibanaContextProvider services={{ ...apmPluginContextMock.core }}>
<MockUrlParamsContextProvider
params={{
latencyAggregationType: LatencyAggregationType.avg,
}}
>
<MockUrlParamsContextProvider
params={{
latencyAggregationType: LatencyAggregationType.avg,
<APMServiceContext.Provider
value={{
alerts: alertsResponse.alerts,
serviceName,
transactionType,
transactionTypes: [],
}}
>
<APMServiceContext.Provider
value={{
alerts: alertsResponse.alerts,
serviceName,
transactionType,
transactionTypes: [],
}}
>
<ChartPointerEventContextProvider>
<Story />
</ChartPointerEventContextProvider>
</APMServiceContext.Provider>
</MockUrlParamsContextProvider>
</KibanaContextProvider>
</Route>
</MemoryRouter>
</ApmPluginContext.Provider>
<ChartPointerEventContextProvider>
<StoryComponent />
</ChartPointerEventContextProvider>
</APMServiceContext.Provider>
</MockUrlParamsContextProvider>
</KibanaContextProvider>
</MockApmPluginContextWrapper>
</MemoryRouter>
);
},
],
};
export function Example(_args: Args) {
export default stories;
export const Example: Story<Args> = () => {
return (
<LatencyChart height={300} environment={ENVIRONMENT_ALL.value} kuery="" />
);
}
};
Example.args = {
alertsResponse: {
alerts: [
@ -139,6 +141,7 @@ Example.args = {
tags: ['apm', 'service.name:frontend-rum'],
'transaction.type': ['page-load'],
[ALERT_RULE_PRODUCER]: ['apm'],
[ALERT_RULE_CONSUMER]: ['apm'],
[ALERT_UUID]: ['af2ae371-df79-4fca-b0eb-a2dbd9478180'],
[ALERT_RULE_UUID]: ['82e0ee40-c2f4-11eb-9a42-a9da66a1722f'],
'event.action': ['active'],
@ -149,9 +152,11 @@ Example.args = {
[ALERT_START]: ['2021-06-02T04:00:00.000Z'],
'event.kind': ['state'],
[ALERT_RULE_CATEGORY]: ['Latency threshold'],
[SPACE_IDS]: [],
},
{
[ALERT_RULE_TYPE_ID]: ['apm.transaction_duration'],
[ALERT_EVALUATION_VALUE]: [2001708.19],
'service.name': ['frontend-rum'],
[ALERT_RULE_NAME]: ['Latency threshold | frontend-rum'],
@ -160,6 +165,7 @@ Example.args = {
tags: ['apm', 'service.name:frontend-rum'],
'transaction.type': ['page-load'],
[ALERT_RULE_PRODUCER]: ['apm'],
[ALERT_RULE_CONSUMER]: ['apm'],
[ALERT_SEVERITY]: ['warning'],
[ALERT_UUID]: ['af2ae371-df79-4fca-b0eb-a2dbd9478181'],
[ALERT_RULE_UUID]: ['82e0ee40-c2f4-11eb-9a42-a9da66a1722f'],
@ -171,9 +177,11 @@ Example.args = {
[ALERT_START]: ['2021-06-02T10:45:00.000Z'],
'event.kind': ['state'],
[ALERT_RULE_CATEGORY]: ['Latency threshold'],
[SPACE_IDS]: [],
},
{
[ALERT_RULE_TYPE_ID]: ['apm.transaction_duration'],
[ALERT_EVALUATION_VALUE]: [2001708.19],
'service.name': ['frontend-rum'],
[ALERT_RULE_NAME]: ['Latency threshold | frontend-rum'],
@ -182,6 +190,7 @@ Example.args = {
tags: ['apm', 'service.name:frontend-rum'],
'transaction.type': ['page-load'],
[ALERT_RULE_PRODUCER]: ['apm'],
[ALERT_RULE_CONSUMER]: ['apm'],
[ALERT_SEVERITY]: ['critical'],
[ALERT_UUID]: ['af2ae371-df79-4fca-b0eb-a2dbd9478182'],
[ALERT_RULE_UUID]: ['82e0ee40-c2f4-11eb-9a42-a9da66a1722f'],
@ -193,6 +202,7 @@ Example.args = {
[ALERT_START]: ['2021-06-02T16:50:00.000Z'],
'event.kind': ['state'],
[ALERT_RULE_CATEGORY]: ['Latency threshold'],
[SPACE_IDS]: [],
},
],
},
@ -801,19 +811,24 @@ Example.args = {
},
],
},
previousPeriod: { latencyTimeseries: [] },
previousPeriod: { latencyTimeseries: [], overallAvgDuration: null },
},
};
export function NoData(_args: Args) {
export const NoData: Story<Args> = () => {
return (
<LatencyChart height={300} environment={ENVIRONMENT_ALL.value} kuery="" />
);
}
};
NoData.args = {
alertsResponse: { alerts: [] },
latencyChartResponse: {
currentPeriod: { latencyTimeseries: [] },
previousPeriod: { latencyTimeseries: [] },
anomalyTimeseries: {
jobId: 'apm-production-00aa-high_mean_transaction_duration',
anomalyScore: [],
anomalyBoundaries: [],
},
currentPeriod: { latencyTimeseries: [], overallAvgDuration: null },
previousPeriod: { latencyTimeseries: [], overallAvgDuration: null },
},
};

View file

@ -0,0 +1,21 @@
/*
* 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 { composeStories } from '@storybook/testing-react';
import { render, waitFor } from '@testing-library/react';
import React from 'react';
import * as stories from './latency_chart.stories';
const { Example } = composeStories(stories);
describe('LatencyChart', () => {
it('renders', async () => {
await waitFor(() => {
expect(() => render(<Example />)).not.toThrowError();
});
});
});

View file

@ -31,5 +31,11 @@ export const Example: Story<Args> = (args) => {
};
Example.args = {
agentName: 'java',
query: {
environment: 'ENVIRONMENT_ALL',
kuery: '',
rangeFrom: 'now-15m',
rangeTo: 'now',
},
serviceName: 'opbeans-java',
};

View file

@ -0,0 +1,19 @@
/*
* 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 { composeStories } from '@storybook/testing-react';
import { render } from '@testing-library/react';
import React from 'react';
import * as stories from './service_link.stories';
const { Example } = composeStories(stories);
describe('ServiceLink', () => {
it('renders', () => {
expect(() => render(<Example />)).not.toThrowError();
});
});

View file

@ -6,14 +6,16 @@
*/
import { composeStories } from '@storybook/testing-react';
import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import React from 'react';
import * as stories from './suggestions_select.stories';
const { Example } = composeStories(stories);
describe('SuggestionsSelect', () => {
it('renders', () => {
expect(() => render(<Example />)).not.toThrowError();
it('renders', async () => {
render(<Example />);
expect(await screen.findByRole('combobox')).toBeInTheDocument();
});
});