Alerting: Storybook integration (#107024)
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
# @grafana/alerting
|
||||
|
||||
This is the alerting package for Grafana. It provides components and utilities for creating and managing alerts within Grafana.
|
||||
|
||||
It is designed to be used in conjunction with Grafana's alerting system, allowing developers to create custom alerting solutions.
|
||||
|
||||
## Installation
|
||||
|
||||
To install the package:
|
||||
|
||||
```bash
|
||||
npm install @grafana/alerting
|
||||
```
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { ArgTypes } from '@storybook/blocks';
|
||||
import { ContactPointSelector } from './ContactPointSelector';
|
||||
|
||||
# ContactPointSelector
|
||||
|
||||
A component for selecting contact points in Grafana. Only works with the built-in Grafana Alertmanager.
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { defaultDecorators } from '../../../../../tests/story-utils';
|
||||
|
||||
import { ContactPointSelector } from './ContactPointSelector';
|
||||
import mdx from './ContactPointSelector.mdx';
|
||||
import { simpleContactPointsListScenario, withErrorScenario } from './ContactPointSelector.test.scenario';
|
||||
|
||||
const meta: Meta<typeof ContactPointSelector> = {
|
||||
component: ContactPointSelector,
|
||||
title: 'ContactPointSelector',
|
||||
decorators: defaultDecorators,
|
||||
parameters: {
|
||||
docs: {
|
||||
page: mdx,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ContactPointSelector>;
|
||||
|
||||
export const Basic: Story = {
|
||||
parameters: {
|
||||
msw: {
|
||||
handlers: simpleContactPointsListScenario,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const WithError: Story = {
|
||||
parameters: {
|
||||
msw: {
|
||||
handlers: withErrorScenario,
|
||||
},
|
||||
},
|
||||
};
|
||||
+3
-2
@@ -1,3 +1,5 @@
|
||||
import { HttpResponse } from 'msw';
|
||||
|
||||
import {
|
||||
ContactPointFactory,
|
||||
EmailIntegrationFactory,
|
||||
@@ -30,5 +32,4 @@ export const simpleContactPointsList = ListReceiverApiResponseFactory.build({
|
||||
// export the simple contact points list as a separate list of handlers (scenario) so we can load it in the front-end
|
||||
export const simpleContactPointsListScenario = [listReceiverHandler(simpleContactPointsList)];
|
||||
|
||||
// the default export will allow us to load this scenario on the front-end using the MSW web worker
|
||||
export default simpleContactPointsListScenario;
|
||||
export const withErrorScenario = [listReceiverHandler(() => new HttpResponse(null, { status: 500 }))];
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import { useEffect } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { alertingAPIv0alpha1 } from '../src/unstable';
|
||||
|
||||
// create an empty store
|
||||
export const store = configureStore({
|
||||
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(alertingAPIv0alpha1.middleware),
|
||||
reducer: {
|
||||
[alertingAPIv0alpha1.reducerPath]: alertingAPIv0alpha1.reducer,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a wrapper component that implements all of the providers that components
|
||||
* within the app will need
|
||||
*/
|
||||
export const getDefaultWrapper = () => {
|
||||
/**
|
||||
* Returns a wrapper that should (eventually?) match the main `AppWrapper`, so any tests are rendering
|
||||
* in mostly the same providers as a "real" hierarchy
|
||||
*/
|
||||
return function Wrapper({ children }: React.PropsWithChildren) {
|
||||
useResetQueryCacheAfterUnmount();
|
||||
return <Provider store={store}>{children}</Provider>;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Whenever the test wrapper unmounts, we also want to clear the RTKQ cache entirely.
|
||||
* if we don't then we won't be able to test components / stories with different responses for the same endpoint since
|
||||
* the responses will be cached between renders / components / stories.
|
||||
*/
|
||||
function useResetQueryCacheAfterUnmount() {
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
store.dispatch(alertingAPIv0alpha1.util.resetApiState());
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { Meta } from '@storybook/react';
|
||||
|
||||
import { getDefaultWrapper } from './provider';
|
||||
|
||||
const Wrapper = getDefaultWrapper();
|
||||
|
||||
export const defaultDecorators: Meta['decorators'] = [
|
||||
(Story) => (
|
||||
<Wrapper>
|
||||
<Story />
|
||||
</Wrapper>
|
||||
),
|
||||
];
|
||||
@@ -1,37 +1,13 @@
|
||||
/**
|
||||
* ⚠️ @TODO this will eventually be replaced with "@grafana/test-utils", consider helping out instead of adding things here!
|
||||
*/
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import { type RenderOptions, render } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { getDefaultWrapper, store } from './provider';
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import { alertingAPIv0alpha1 } from '../src/unstable';
|
||||
|
||||
// create an empty store
|
||||
const store = configureStore({
|
||||
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(alertingAPIv0alpha1.middleware),
|
||||
reducer: {
|
||||
[alertingAPIv0alpha1.reducerPath]: alertingAPIv0alpha1.reducer,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Get a wrapper component that implements all of the providers that components
|
||||
* within the app will need
|
||||
*/
|
||||
const getDefaultWrapper = ({}: RenderOptions) => {
|
||||
/**
|
||||
* Returns a wrapper that should (eventually?) match the main `AppWrapper`, so any tests are rendering
|
||||
* in mostly the same providers as a "real" hierarchy
|
||||
*/
|
||||
return function Wrapper({ children }: React.PropsWithChildren) {
|
||||
return <Provider store={store}>{children}</Provider>;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Extended [@testing-library/react render](https://testing-library.com/docs/react-testing-library/api/#render)
|
||||
* method which wraps the passed element in all of the necessary Providers,
|
||||
@@ -39,7 +15,7 @@ const getDefaultWrapper = ({}: RenderOptions) => {
|
||||
*/
|
||||
const customRender = (ui: React.ReactNode, renderOptions: RenderOptions = {}) => {
|
||||
const user = userEvent.setup();
|
||||
const Providers = renderOptions.wrapper || getDefaultWrapper(renderOptions);
|
||||
const Providers = renderOptions.wrapper || getDefaultWrapper();
|
||||
|
||||
return {
|
||||
renderResult: render(ui, { wrapper: Providers, ...renderOptions }),
|
||||
|
||||
@@ -45,6 +45,11 @@ export function copyAssetsSync() {
|
||||
to: './static/public/lib',
|
||||
},
|
||||
...iconPaths,
|
||||
// copy over the MSW mock service worker so we can mock requests in Storybook
|
||||
{
|
||||
from: '../../../public/mockServiceWorker.js',
|
||||
to: './static/mockServiceWorker.js',
|
||||
},
|
||||
];
|
||||
|
||||
const staticDir = resolve(__dirname, 'static', 'public');
|
||||
|
||||
@@ -3,12 +3,27 @@ import type { StorybookConfig } from '@storybook/react-webpack5';
|
||||
import { copyAssetsSync } from './copyAssets';
|
||||
|
||||
// Internal stories should only be visible during development
|
||||
const storyGlob =
|
||||
const coreComponentsGlobs: StorybookConfig['stories'] = [
|
||||
'../src/Intro.mdx',
|
||||
process.env.NODE_ENV === 'production'
|
||||
? '../src/components/**/!(*.internal).story.tsx'
|
||||
: '../src/components/**/*.story.tsx';
|
||||
: '../src/components/**/*.story.tsx',
|
||||
];
|
||||
|
||||
const stories = ['../src/Intro.mdx', storyGlob];
|
||||
const alertingComponentsGlobs: StorybookConfig['stories'] = [
|
||||
{
|
||||
titlePrefix: 'Alerting',
|
||||
directory: '../../grafana-alerting/src',
|
||||
files: 'Intro.mdx',
|
||||
},
|
||||
{
|
||||
titlePrefix: 'Alerting',
|
||||
directory: '../../grafana-alerting/src',
|
||||
files: process.env.NODE_ENV === 'production' ? '**/!(*.internal).story.tsx' : '**/*.story.tsx',
|
||||
},
|
||||
];
|
||||
|
||||
const stories = [...coreComponentsGlobs, ...alertingComponentsGlobs];
|
||||
|
||||
// Copy the assets required by storybook before starting the storybook server.
|
||||
copyAssetsSync();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Preview } from '@storybook/react';
|
||||
import { initialize, mswLoader } from 'msw-storybook-addon';
|
||||
|
||||
import 'jquery';
|
||||
import { getBuiltInThemes, getTimeZone, getTimeZones, GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
@@ -42,6 +44,13 @@ if (process.env.NODE_ENV === 'development') {
|
||||
allowedExtraThemes.push('tron');
|
||||
}
|
||||
|
||||
/*
|
||||
* Initializes MSW
|
||||
* See https://github.com/mswjs/msw-storybook-addon#configuring-msw
|
||||
* to learn how to customize it
|
||||
*/
|
||||
initialize();
|
||||
|
||||
const preview: Preview = {
|
||||
decorators: [withTheme(handleThemeChange), withTimeZone()],
|
||||
parameters: {
|
||||
@@ -102,6 +111,7 @@ const preview: Preview = {
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
loaders: [mswLoader],
|
||||
};
|
||||
|
||||
export default preview;
|
||||
|
||||
@@ -186,6 +186,8 @@
|
||||
"expose-loader": "5.0.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"mock-raf": "1.0.1",
|
||||
"msw": "^2.10.2",
|
||||
"msw-storybook-addon": "^2.0.5",
|
||||
"process": "^0.11.10",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
|
||||
@@ -3717,6 +3717,8 @@ __metadata:
|
||||
mock-raf: "npm:1.0.1"
|
||||
moment: "npm:2.30.1"
|
||||
monaco-editor: "npm:0.34.1"
|
||||
msw: "npm:^2.10.2"
|
||||
msw-storybook-addon: "npm:^2.0.5"
|
||||
ol: "npm:7.4.0"
|
||||
prismjs: "npm:1.30.0"
|
||||
process: "npm:^0.11.10"
|
||||
@@ -19979,7 +19981,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-node-process@npm:^1.2.0":
|
||||
"is-node-process@npm:^1.0.1, is-node-process@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "is-node-process@npm:1.2.0"
|
||||
checksum: 10/930765cdc6d81ab8f1bbecbea4a8d35c7c6d88a3ff61f3630e0fc7f22d624d7661c1df05c58547d0eb6a639dfa9304682c8e342c4113a6ed51472b704cee2928
|
||||
@@ -23164,7 +23166,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"msw@npm:2.10.2":
|
||||
"msw-storybook-addon@npm:^2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "msw-storybook-addon@npm:2.0.5"
|
||||
dependencies:
|
||||
is-node-process: "npm:^1.0.1"
|
||||
peerDependencies:
|
||||
msw: ^2.0.0
|
||||
checksum: 10/c7c2c77fbe64775f6d01a2724c1e43d67c2590dc5961d4cd14c654d954b84b7938eba0404888d056752e360a41d4a8e4535ebc27a80d4108ce78552ac904be32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"msw@npm:2.10.2, msw@npm:^2.10.2":
|
||||
version: 2.10.2
|
||||
resolution: "msw@npm:2.10.2"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user