Skip to content

Conversation

@clintandrewhall
Copy link
Contributor

@clintandrewhall clintandrewhall commented Jan 21, 2019

Summary

This PR adds Storybook to our testing and development suite.

screen shot 2019-01-21 at 4 35 32 pm

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 environment;
  5. automatically generate snapshots based on any examples written;

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 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?

AdvancedFilter

  • 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.

FontPicker

  • 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:

screen shot 2019-01-21 at 4 25 58 pm

How to use

  • 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.

Future Work

  • Adding Jest coverage and output to the info panels, (this is sick functionality).
  • Adding automatic a11y testing, (currently blocked).
  • Adding generic knobs for stories
  • Adding more example info, (e.g. who edited last, descriptions, etc).

@clintandrewhall clintandrewhall added review Feature:Dev Tools Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas t// labels Jan 21, 2019
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-canvas

@elasticmachine
Copy link
Contributor

💔 Build Failed

@monfera
Copy link
Contributor

monfera commented Jan 22, 2019

retest

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@snide
Copy link
Contributor

snide commented Jan 22, 2019

@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.

@clintandrewhall
Copy link
Contributor Author

clintandrewhall commented Jan 23, 2019

@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.

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Copy link
Member

@markov00 markov00 left a 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

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Copy link
Contributor

@monfera monfera left a 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'

AdvancedFilter.propTypes = {
onChange: PropTypes.func,
onChange: PropTypes.func.isRequired,
value: PropTypes.string,
Copy link
Contributor

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.

Copy link

@stacey-gammon stacey-gammon left a 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');

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.

"apollo-link-state": "^0.4.1",
"apollo-server-errors": "^2.0.2",
"apollo-server-hapi": "^1.3.6",
"awesome-typescript-loader": "^5.2.1",

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.

Copy link
Contributor Author

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.

"sass-loader": "^7.1.0",
"simple-git": "1.37.0",
"sinon": "^5.0.7",
"storybook-addon-jsx": "^5.4.0",

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.

Copy link
Contributor Author

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,

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Contributor Author

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);

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?

Copy link
Contributor Author

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

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).

Copy link
Contributor Author

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>(

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).

Copy link
Contributor

@w33ble w33ble Jan 24, 2019

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 compose function 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

Copy link
Contributor

@w33ble w33ble Jan 24, 2019

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).

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@spalger
Copy link
Contributor

spalger commented Feb 15, 2019

[00:00:00]         └-: apis
[00:00:00]           └-> "before all" hook
[00:03:34]           └-: uptime
[00:03:34]             └-> "before all" hook
[00:03:35]             └-: graphql
[00:03:35]               └-> "before all" hook
[00:03:35]               └-> "before all" hook: load heartbeat data
[00:03:35]                 │ info [uptime/full_heartbeat] Loading "mappings.json"
[00:03:35]                 │ info [uptime/full_heartbeat] Loading "data.json.gz"
[00:03:35]                 │ info [o.e.c.m.MetaDataDeleteIndexService] [kibana-ci-immutable-debian-1550181393347087854] [heartbeat-8.0.0/i_1eskTPQDmAcL3XTMuZCQ] deleting index
[00:03:35]                 │ info [uptime/full_heartbeat] Deleted existing index "heartbeat-8.0.0"
[00:03:35]                 │ info [o.e.c.m.MetaDataCreateIndexService] [kibana-ci-immutable-debian-1550181393347087854] [heartbeat-8.0.0] creating index, cause [api], templates [], shards [1]/[0], mappings [_doc]
[00:03:35]                 │ info [uptime/full_heartbeat] Created index "heartbeat-8.0.0"
[00:03:35]                 │ debg [uptime/full_heartbeat] "heartbeat-8.0.0" settings {"index":{"analysis":{"analyzer":{"url":{"max_token_length":"1000","tokenizer":"uax_url_email","type":"standard"}}},"number_of_replicas":"0","number_of_shards":"1"}}
[00:04:06]                 └- ✖ fail: "apis uptime graphql "before all" hook: load heartbeat data"
[00:04:06]                 │      Error: Request Timeout after 30000ms
[00:04:06]                 │       at /var/lib/jenkins/workspace/elastic+kibana+pull-request/JOB/x-pack-ciGroup5/node/immutable/kibana/node_modules/elasticsearch/src/lib/transport.js:355:15
[00:04:06]                 │       at Timeout.<anonymous> (/var/lib/jenkins/workspace/elastic+kibana+pull-request/JOB/x-pack-ciGroup5/node/immutable/kibana/node_modules/elasticsearch/src/lib/transport.js:384:7)
[00:04:06]                 │ 

I've seen this failure a couple times now, investigating how to prevent it in the esarchiver, jenkins, test this please

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@clintandrewhall clintandrewhall merged commit 3727b53 into elastic:master Feb 15, 2019
clintandrewhall added a commit to clintandrewhall/kibana that referenced this pull request Apr 2, 2019
This PR adds [Storybook](https://storybook.js.org/) to our testing and development suite.

![screen shot 2019-01-21 at 4 35 32 pm](https://user-images.githubusercontent.com/297604/51502196-9f856780-1d9a-11e9-97bf-07c99c3f279b.png)

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:

![screen shot 2019-01-21 at 4 25 58 pm](https://user-images.githubusercontent.com/297604/51501908-5ed91e80-1d99-11e9-913a-ce1bb5f4e352.png)

- `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).
clintandrewhall added a commit that referenced this pull request Apr 2, 2019
* [Canvas] Storybook for testing and development (#29072)

This PR adds [Storybook](https://storybook.js.org/) to our testing and development suite.

![screen shot 2019-01-21 at 4 35 32 pm](https://user-images.githubusercontent.com/297604/51502196-9f856780-1d9a-11e9-97bf-07c99c3f279b.png)

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:

![screen shot 2019-01-21 at 4 25 58 pm](https://user-images.githubusercontent.com/297604/51501908-5ed91e80-1d99-11e9-913a-ce1bb5f4e352.png)

- `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
@clintandrewhall clintandrewhall deleted the examples-experiment branch June 6, 2019 04:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature:Dev Tools review Team:Presentation Presentation Team for Dashboard, Input Controls, and Canvas t//

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants