-
Notifications
You must be signed in to change notification settings - Fork 8.5k
[Canvas] Storybook for testing and development #29072
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
[Canvas] Storybook for testing and development #29072
Conversation
|
Pinging @elastic/kibana-canvas |
💔 Build Failed |
|
retest |
💚 Build Succeeded |
|
@clintandrewhall Abstracting a bit out here. Peeping your code I can see you set this up for canvas, but is there any reason this couldn't be set up for general Kibana development? Just thinking ahead and wondering where this will eventually lead. Do you see each team with their own storybook setup? If so would there be a way to navigate between them or would you spin each one up separately? I know this is a PoC so not pushing hard on those questions, just curious what you think. Getting react dev out of Kibana's build system is obviously a win, I just have a hangup with reusability and always try to push people to share tooling between teams :) Any documentation and dev tooling is always time for applause in my book. |
|
@snide I was only keeping it sandboxed. There's no reason we couldn't migrate the dependencies and scripts up to Kibana. Several devs have indicated an interest in this kind of setup/testing environment, but I figured keeping it contained and then contributing upward would be the right order of operations. I think if the Canvas team leverages this the way I intend, this will certainly be a model moving forward... particularly since it has the potential to automate our a11y/Jest/snapshot tests. Considering the YMMV nature, I just kept it in Canvas. |
💚 Build Succeeded |
markov00
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey Clint, I've just added few suggestions on dependencies. I didn't check the code or tested it.
I'm also using storybook with TS on my charts library and I've more or less the same setup
💚 Build Succeeded |
💚 Build Succeeded |
monfera
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fantastic PR, storybook and the component improvements are great!
Testing HMR: when changing the button label "Apply" to "asdkhjhkj" it took 14 seconds from code to page, which is a welcome 2x improvement compared to a full Kibana server restart + page reload with the Dev Console on, and fully automatic. Plus storybook brings in a ton of other benefits. I'm wondering if the latency can somehow be tweaked, or if it's some issue with my environment. Maybe because ours is an exceptionally large codebase? storybook now covers Canvas only, not sure if it is going to be slower when it's for the entire Kibana as @snide suggested. Canvas is a small slice of all components, three of which are covered in this PR. Maybe there's speed headroom in a follow-up PR.
Mentioning it on the off-chance it relates to replacement latency, I get a bunch of unrelated warnings in the terminal, eg.
WARNING in /Users/robert/repos/elastic/kibana/node_modules/@elastic/eui/es/components/icon/index.js 1:0-129
"export 'IconColor' was not found in './icon'
x-pack/plugins/canvas/canvas_plugin_src/renderers/advanced_filter/component/advanced_filter.tsx
Show resolved
Hide resolved
x-pack/plugins/canvas/canvas_plugin_src/renderers/advanced_filter/component/advanced_filter.tsx
Show resolved
Hide resolved
| AdvancedFilter.propTypes = { | ||
| onChange: PropTypes.func, | ||
| onChange: PropTypes.func.isRequired, | ||
| value: PropTypes.string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Completely unrelated to this PR, just trying to learn, and saw you've added component/type improvements: in this context, wouldn't it be useful to expect a value, even if it's the empty string? No specific reason, other than just simplifying types, value is likely coming from somewhere or could (should?) be initialized.
stacey-gammon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool stuff! Few questions but no issues stand out to me, lookin good.
| addDecorator(storyWrapper); | ||
|
|
||
| function loadStories() { | ||
| //require('./welcome'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like this can be deleted.
x-pack/package.json
Outdated
| "apollo-link-state": "^0.4.1", | ||
| "apollo-server-errors": "^2.0.2", | ||
| "apollo-server-hapi": "^1.3.6", | ||
| "awesome-typescript-loader": "^5.2.1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked up this package and looks like it's similar to ts-loader? Could we get away with only one, or does this come with benefits? Just thinking in the mindset of trying to minimize our dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me test it with just ts-loader... I seem to recall documentation having a problem, but I may have resolved that.
x-pack/package.json
Outdated
| "sass-loader": "^7.1.0", | ||
| "simple-git": "1.37.0", | ||
| "sinon": "^5.0.7", | ||
| "storybook-addon-jsx": "^5.4.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have pretty much nil experience with storybook. What's the JSX part supposed to do? In the UI looks like it's always blank.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can remove this, sure.
|
|
||
| AdvancedFilter.propTypes = { | ||
| onChange: PropTypes.func, | ||
| onChange: PropTypes.func.isRequired, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are your thoughts on having both propTypes and typescript prop interfaces? When I was typescripting react files I was replacing the former with the latter. I suppose you lose the runtime warnings, but if you have the compile time warnings, that seems better, and then no risk of them getting out of sync - single source of truth. But maybe there are good reason to keep both I'm unaware of.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious about that too. I had planned to remove propTypes completely when converting to tsx, but I'm super new to all this type stuff and maybe there's some benefit to using both that I don't know about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have to remember that TypeScript types are for build time warnings, which are transpiled away in final source, and prop types are evaluated at run time.
So prop types are incredibly important when some code is not typed with TypeScript... it's your last line of defense.
Once Canvas is fully converted, prop types become redundant. But for now, they're the bridge between TS code and React.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, so we're leaving them there so that non-ts components consuming ts components can still provide runtime warnings, but once all the components are converted, we'll be removing all the propType stuff?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@w33ble yes indeed. We'll have to add rules to not add .js files, as well... at least that would be my preference.
|
|
||
| // Wrap each story with a div -- we may make this smarter later. | ||
| // const storyWrapper = story => <div style={{ margin: 24 }}>{story()}</div>; | ||
| // addDecorator(storyWrapper); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be deleted too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep!
| ], | ||
| }); | ||
|
|
||
| config.plugins.push(new TSDocgenPlugin()); // optional |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TSDocgenPlugin is optional? This comment doesn't help me much as a reader of the code, can you elaborate (or maybe just remove).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copy/paste from the "getting started" docs. :-/
| commit: (value: string) => void; | ||
| } | ||
|
|
||
| export const AdvancedFilter = compose<ComponentProps, Props>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't used compose before, but I was using connect over in dashboard code. If you have experience in both I'd be interested to hear the differences. Also wondering about your thoughts on naming. Over in dashboard I was doing something like dashboard_panel_container.ts is the wrapper and dashboard_panel.ts is the inner component. It's not the best set up, and there was some back and forth on it (don't like leaking implementation details to users - what do they care if it's a wrapper or the actual thing, the "container" suffix means nothing to them). Actually at some point we did something similar to this, so externally the component name looks the same as the internal name:
export DashboardPanelContainer as DashboardPanel; in the index file.
But, we migrated back to the first version when it became clear this was caused confusion when devs not familiar with the code were searching for the component being used - it's not obvious which is which.
Not something that needs to be solved in this PR but I'd be interested to hear your opinions on the matter and maybe we can come up with a "best practice" across the board for this. Not too many users of redux at this point so probably wouldn't be too tough to align. (assuming this is even a redux thing and similar to the connect stuff being done in dashboard code).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't used compose before, but I was using connect over in dashboard code. If you have experience in both I'd be interested to hear the differences.
compose and connect are not the same thing. connect is an HoC used with redux to inject state values as props. compose is a functional concept where each function is executed and the output is passed to the next one. It's an easy way to tack on multiple HoC's.
// instead of writing this (warning, i might have the function order backwards here... i mix that up a lot)
connect(...)(withState(...)(withRouter(...)(Component)))
// you can write this (functions are called from left to right, no need to think about the order)
compose(
connect(...),
withState(...),
withRouter(...)
)(Component)In this specific instance, there's only one HoC, so compose isn't needed.
For reference:
- nearly every math-centric library has a compose function, like R.compose and _.flow
- redux also comes with a compose function which is commonly used to stack multiple middlewares
- we use recompose's
composefunction since it's all part of the same dependency - it's not too complicated, just a weird concept. the implementation in just-compose is pretty easy to follow, and the one in recompose is even easier with all the es6 goodness
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As for the file naming/organization question, we got that idea from the Cloud team, and it's worked quite well for us. The pattern is that you always import the component from the directory, which resolves to the index file by default, and that's our container. That's now the "public API" for the component, the state is just wrapped up transparently.
If you need to test the component in isolation, you can do so by importing the file instead of the directory (ie components/my_component/my_component).
Early on we decided that all components should be functional, and all state, lifecycles, etc. should live in the container. We used recompose extensively to do that. We've soured on the idea a bit, opting for class components when we need local state and lifecycle stuff, but there's still a bunch of old components kicking around. With hooks coming to React, I suspect we'll remove recompose entirely (the API is very similar in most cases).
💔 Build Failed |
💔 Build Failed |
💔 Build Failed |
💔 Build Failed |
💔 Build Failed |
I've seen this failure a couple times now, investigating how to prevent it in the esarchiver, jenkins, test this please |
💚 Build Succeeded |
This PR adds [Storybook](https://storybook.js.org/) to our testing and development suite.  This will allow us to: 1. create a site outlining all components within Canvas, including their TS type information; 2. demonstrate usage of all components by example; 3. allow for individual component testing, both manually and by Jest; 4. iterate and fix bugs on individual components *without* having to start up Kibana, in a [HMR](https://webpack.js.org/concepts/hot-module-replacement/) environment; 5. automatically generate [snapshots](https://jestjs.io/docs/en/snapshot-testing) based on any examples written; This PR also converts a few components to Typescript and adds examples. I was inspired to add this when I was fixing elastic#25342. In order to fix my changes, I had to run elasticsearch and kibana, as well as refresh my page whenever I needed to test a change. Had I had a Storybook instance, I would have been done much faster. In this PR, you'll see I converted `AdvancedFilter` from `renderers` and `FontPicker` and `ImageUpload` from `public/components`. Would you believe I discovered and fixed bugs just by converting to Typescript and writing examples? - `onChange` and `commit` are not marked as required in `propTypes`, but the component will error out if they're not supplied. - `commit` was actually being called twice when 'Apply' was clicked. This was shown in the 'Actions' panel when I was testing it. - The `fonts` collection was not strongly-typed, therefore any string could be passed to the `value` parameter without error. - While the code allows for any font string to be given to the component, there is no way to currently select that value, nor type it in within the control. This is likely a bug in design. - The `aria-labeledby` attribute in the drop down includes `undefined`. This is likely a bug in EUI:  - `cd x-pack/plugins/canvas/` - Run `node scripts/storybook` to start up a local development version, with HMR. - Run `node scripts/storybook_build` to build a complete static version of the book. - Run `node scripts/jest` which will run the Storyshots test; run `node scripts/jest --updateSnapshot` if source code has changed as expected. - Adding Jest coverage and output to the info panels, ([this](https://www.npmjs.com/package/@storybook/addon-jest) is *sick* functionality). - Adding automatic [a11y testing](https://www.npmjs.com/package/@storybook/addon-a11y), (currently [blocked](storybookjs/storybook#4889)). - Adding generic knobs for stories - Adding more example info, (e.g. who edited last, descriptions, etc).
* [Canvas] Storybook for testing and development (#29072) This PR adds [Storybook](https://storybook.js.org/) to our testing and development suite.  This will allow us to: 1. create a site outlining all components within Canvas, including their TS type information; 2. demonstrate usage of all components by example; 3. allow for individual component testing, both manually and by Jest; 4. iterate and fix bugs on individual components *without* having to start up Kibana, in a [HMR](https://webpack.js.org/concepts/hot-module-replacement/) environment; 5. automatically generate [snapshots](https://jestjs.io/docs/en/snapshot-testing) based on any examples written; This PR also converts a few components to Typescript and adds examples. I was inspired to add this when I was fixing #25342. In order to fix my changes, I had to run elasticsearch and kibana, as well as refresh my page whenever I needed to test a change. Had I had a Storybook instance, I would have been done much faster. In this PR, you'll see I converted `AdvancedFilter` from `renderers` and `FontPicker` and `ImageUpload` from `public/components`. Would you believe I discovered and fixed bugs just by converting to Typescript and writing examples? - `onChange` and `commit` are not marked as required in `propTypes`, but the component will error out if they're not supplied. - `commit` was actually being called twice when 'Apply' was clicked. This was shown in the 'Actions' panel when I was testing it. - The `fonts` collection was not strongly-typed, therefore any string could be passed to the `value` parameter without error. - While the code allows for any font string to be given to the component, there is no way to currently select that value, nor type it in within the control. This is likely a bug in design. - The `aria-labeledby` attribute in the drop down includes `undefined`. This is likely a bug in EUI:  - `cd x-pack/plugins/canvas/` - Run `node scripts/storybook` to start up a local development version, with HMR. - Run `node scripts/storybook_build` to build a complete static version of the book. - Run `node scripts/jest` which will run the Storyshots test; run `node scripts/jest --updateSnapshot` if source code has changed as expected. - Adding Jest coverage and output to the info panels, ([this](https://www.npmjs.com/package/@storybook/addon-jest) is *sick* functionality). - Adding automatic [a11y testing](https://www.npmjs.com/package/@storybook/addon-a11y), (currently [blocked](storybookjs/storybook#4889)). - Adding generic knobs for stories - Adding more example info, (e.g. who edited last, descriptions, etc). * Update based on errors in CI * Fix mismerge of dependencies * Fix yarn.lock as well
Summary
This PR adds Storybook to our testing and development suite.
This will allow us to:
This PR also converts a few components to Typescript and adds examples.
How this can help us, (with examples)
I was inspired to add this when I was fixing #25342. In order to fix my changes, I had to run elasticsearch and kibana, as well as refresh my page whenever I needed to test a change. Had I had a Storybook instance, I would have been done much faster.
In this PR, you'll see I converted
AdvancedFilterfromrenderersandFontPickerandImageUploadfrompublic/components. Would you believe I discovered and fixed bugs just by converting to Typescript and writing examples?AdvancedFilteronChangeandcommitare not marked as required inpropTypes, but the component will error out if they're not supplied.commitwas actually being called twice when 'Apply' was clicked. This was shown in the 'Actions' panel when I was testing it.FontPickerfontscollection was not strongly-typed, therefore any string could be passed to thevalueparameter without error.aria-labeledbyattribute in the drop down includesundefined. This is likely a bug in EUI:How to use
cd x-pack/plugins/canvas/node scripts/storybookto start up a local development version, with HMR.node scripts/storybook_buildto build a complete static version of the book.node scripts/jestwhich will run the Storyshots test; runnode scripts/jest --updateSnapshotif source code has changed as expected.Future Work