Skip to content

Commit

Permalink
Frontend documentation and components gallery (Graylog2#4383)
Browse files Browse the repository at this point in the history
* Add react-styleguidist module

* Add basic react styleguidist configuration

- Include shared components in the gallery
- Add build scripts to build the gallery and serve it

* Add some more content to UI documentation

* Add documentation

* Add stylesheets to component gallery

* Add EntityList documentation

* Add examples to documented common components

* Add some more documentation

* Add styles for typeahead

* Add documentation for PageErrorOverview

* Remove missing id warning in example

* Add documentation for Page component

* Add PageHeader documentation

* Add PaginatedList documentation

* Correct header tags

* Add SearchForm documentation

* Use deprecated annotation

* Let consumers of `Select` customize `displayKey`

React Select uses `labelKey` instead of `displayKey` so pass the right
prop down.

* Add documentation for Select component

* Add SelectableList documentation

* Add SortableList and SortableListItem documentation

* Fix props definition in TableList component

* Fix display of select all Input

This prop was not updated when we migrated to our own Input component.

* Add documentation for TableList component

* Add documentation for Timestamp component

* Add documentation for TimeUnit component

* Add TimeUnitInput documentation

* Fix error while rendering component

We used the wrong import here, `moment` doesn't know about timezones.

* Be more explicit about the prop we support

As we specify the `onChange` prop in the TimezoneSelect prop types, we
should at least use it instead of passing it down as other props that
are not listed.

We should restrict the supported props at some point, protecting
our code from changes in underlying components.

* Add documentation for TimezoneSelect component

* Add documentation for TypeAheadDataFilter component

* Add TypeAheadFieldInput documentation

* Fix some TypeAheadDataFilter documentation

* Add TypeAheadInput documentation

* Add documentation for ReactGridContainer

* Add some basic docs

* Update yarn.lock
  • Loading branch information
edmundoa authored and bernd committed Dec 4, 2017
1 parent a58f56f commit 84843a8
Show file tree
Hide file tree
Showing 66 changed files with 2,627 additions and 72 deletions.
48 changes: 48 additions & 0 deletions graylog2-web-interface/docs/documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
Please help us to keep this documentation updated!

We all benefit from documenting our processes and components, as it help us to
use them without expending too much time reading at the source code, and also
to think twice about how we solved a certain problem.


## How to document your components

This guide was created using [React Styleguidist](https://react-styleguidist.js.org),
so that is a good place to get started if you want to know how to contribute to
this document.

Here is a summary of what to do if you just want to document a component:

1. Use [JSDoc](http://usejsdoc.org) to write documentation for the component
and its props, as those are the main API the component provides to its
consumers.
2. The component documentation **must** be placed just before the component
declaration and **must** contain a brief explanation of what the component
intents to do, followed by any warnings, notes, or deprecations if needed.
Please **do not** include code examples in here unless you cannot create an
example for some reason.
3. Each `propType` definition **must** be preceded by its documentation. Pay
special attention to:

- Document data structures the component expect in array and object props
- Document when a callback function will be called. This is specially
helpful if the prop name is not as clear as it should be
- Document arguments any callback functions will receive

4. In order to write a usage example of the component, you need to create a
[Markdown](https://en.wikipedia.org/wiki/Markdown) file in the same directory
as the component, with the same name as the component but the `md` extension.
E.g. `ReactGridContainer.md` will contain the examples for
`ReactGridContainer.jsx`.
5. Only write examples to show important use cases of the component, but you
do not need to write an example for each prop and value that the component
can accept.
6. You can write React components in examples, and you should do it if you
feel that showing how the state flows may help understanding how the
component works.
7. Examples do not support `import` statements, but you can `require` any
modules you need. Try to not require too many modules, though, as they will
make the example more obscure and harder to understand. You can also use
object destructuring when requiring more than one thing from the same module.
E.g. `const { Button, ButtonToolbar } = require('react-bootstrap');` can
save a line of the example without making it harder to read.
13 changes: 13 additions & 0 deletions graylog2-web-interface/docs/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
### Welcome to the Graylog UI documentation!

In this page we describe our processes to write frontend code. We also
offer a gallery of shared components that you can use when writing frontend
code for the Graylog web interface or one of our frontend plugins.

Please take your time to read through this document and make sure you follow
our guidelines. Otherwise your changes may not be merged into our repositories
until you do.

This document is still in a very early stage, but we will do our best to
make it our to-go reference when writing frontend code.

7 changes: 7 additions & 0 deletions graylog2-web-interface/docs/styleguide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
We use ESLint to detect some issues in our code. We mostly follow the
[Airbnb Javascript style guide](https://github.com/airbnb/javascript) to write
frontend code, with a few exceptions that we list here:


The list may not be up-to-date or complete, but you can take a look at the ESLint
rules we use in our [repository](https://raw.githubusercontent.com/Graylog2/graylog2-server/master/graylog2-web-interface/packages/eslint-config-graylog/index.js)
4 changes: 4 additions & 0 deletions graylog2-web-interface/docs/tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
We use [Jest](https://facebook.github.io/jest/) and [Enzime](http://airbnb.io/enzyme/)
to write frontend tests. We encourage you to write tests for any complex logic
that may be easily broken, but you may also write tests for components if you
really feel like it will help to make it more robust without much overhead.
7 changes: 7 additions & 0 deletions graylog2-web-interface/docs/util-objects.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## AppConfig

## ObjectUtils
__DEPRECATED__: Please use lodash when possible instead of this component.

Provide util functions to do some common tasks with objects easier.

3 changes: 3 additions & 0 deletions graylog2-web-interface/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
},
"readme": "../README.md",
"scripts": {
"docs:build": "styleguidist build",
"docs:server": "styleguidist server",
"start": "webpack-dev-server --config webpack.bundled.js --history-api-fallback --hot --inline",
"start-nohmr": "node devServer.js",
"watch": "webpack --watch --config webpack.bundled.js",
Expand Down Expand Up @@ -120,6 +122,7 @@
"phantomjs-prebuilt": ">=1.9",
"react-hot-loader": "^3.0.0-beta.6",
"react-proxy-loader": "^0.3.4",
"react-styleguidist": "^6.0.33",
"react-test-renderer": "^15.6.1",
"script-loader": "^0.7.0",
"style-loader": "^0.18.2",
Expand Down
18 changes: 15 additions & 3 deletions graylog2-web-interface/src/components/common/ClipboardButton.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
import PropTypes from 'prop-types';
import React from 'react';
import ReactDOM from 'react-dom';
import { Button, Tooltip, OverlayTrigger } from 'react-bootstrap';
import Clipboard from 'clipboard';

/**
* Component that renders a button to copy some text in the clipboard when pressed.
* The text to be copied can be given in the `text` prop, or in an external element through a CSS selector in the `target` prop.
*/
const ClipboardButton = React.createClass({
propTypes: {
/** Text or element used in the button. */
title: PropTypes.oneOfType([PropTypes.node, PropTypes.string]).isRequired,
/** Action to perform. */
action: PropTypes.oneOf(['copy', 'cut']),
text: PropTypes.string, // text to copy to clipboard
target: PropTypes.string, // css selector for the target element
/** Text to be copied in the clipboard. This overrides the `target` prop. */
text: PropTypes.string,
/** CSS selector to an element containing the text to be copied to the clipboard. This will only be used if `text` is not provided. */
target: PropTypes.string,
/** Function to call if text was successfully copied to clipboard. */
onSuccess: PropTypes.func,
/** Button's class name. */
className: PropTypes.string,
/** Button's style. */
style: PropTypes.object,
/** Button's bsStyle. */
bsStyle: PropTypes.string,
/** Button's bsSize. */
bsSize: PropTypes.string,
},
getDefaultProps() {
Expand Down
16 changes: 16 additions & 0 deletions graylog2-web-interface/src/components/common/ClipboardButton.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
```js
<ClipboardButton title="Copy me!"
text="Copy me!" />
```

`ClipboardButton` with `onSuccess` callback:
```js
<div>
<p id="clipboard-button-2">This text will be copied to your clipboard.</p>
<ClipboardButton title="Copy me too!"
target="#clipboard-button-2"
onSuccess={() => alert('Copied to clipboard!')}
bsStyle="info"
bsSize="small" />
</div>
```
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import PropTypes from 'prop-types';
import React from 'react';

/**
* Adds an icon to an entity that was created by a content pack.
*/
const ContentPackMarker = React.createClass({
propTypes: {
/** Content pack key of the entity's object. When set, the component will render the content pack marker. */
contentPack: PropTypes.string,
/** Margin-left the marker should use. */
marginLeft: PropTypes.number,
/** Margin-right the marker should use. */
marginRight: PropTypes.number,
},

Expand Down
26 changes: 26 additions & 0 deletions graylog2-web-interface/src/components/common/DataTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,48 @@ import React from 'react';
import DataTableElement from './DataTableElement';
import { TypeAheadDataFilter } from 'components/common';

/**
* Component that renders a data table, letting consumers of the component to
* decide exactly how the data should be rendered. It optionally adds a filter
* input to the data table by using the the `TypeAheadDataFilter` component.
*/
const DataTable = React.createClass({
propTypes: {
/** Adds a custom children element next to the data filter input. */
children: PropTypes.node,
/** Adds a custom class to the table element. */
className: PropTypes.string,
/** Adds a custom class to the row element. */
rowClassName: PropTypes.string,
/** Object key that should be used to display data in the data filter input. */
displayKey: PropTypes.string,
/**
* Function that renders a row in the table. It receives two arguments: the row, and its index.
* It usually returns a `<tr>` element with the formatted row.
*/
dataRowFormatter: PropTypes.func.isRequired,
/** Label to use next to the suggestions for the data filter input. */
filterBy: PropTypes.string,
/** Label to use next to the data filter input. */
filterLabel: PropTypes.string.isRequired,
/** List of object keys to use as filter in the data filter input. Use an empty array to disable data filter. */
filterKeys: PropTypes.array.isRequired,
/** Array to use as suggestions in the data filter input. */
filterSuggestions: PropTypes.array,
/**
* Function that renders a single header cell in the table. It receives two arguments: the header, and its index.
* It usually returns a `<th>` element with the header.
*/
headerCellFormatter: PropTypes.func.isRequired,
/** Array of values to be use as headers. The render is controlled by `headerCellFormatter`. */
headers: PropTypes.array.isRequired,
/** Element id to use in the table container */
id: PropTypes.string,
/** Text or element to show when there is no data. */
noDataText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
/** Array of objects to be rendered in the table. The render of those values is controlled by `dataRowFormatter`. */
rows: PropTypes.array.isRequired,
/** Object key to use to sort data table. */
sortByKey: PropTypes.string,
},
getDefaultProps() {
Expand Down
44 changes: 44 additions & 0 deletions graylog2-web-interface/src/components/common/DataTable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
```js
const entities = [
{
id: '1',
title: 'Dr. Emmett Brown',
nickname: 'Doc',
roles: ['Doctor', 'Human'],
description: '1.21 GIGAWATTS!!!',
},
{
id: '2',
title: 'Marty McFly',
nickname: 'Marty',
roles: ['Student', 'Human'],
description: 'Nobody calls me chicken',
},
];
const filterKeys = ['title', 'nickname'];
const headers = ['', 'Name', 'Nickname', 'Role', 'Quote'];

const rowFormatter = (entity, idx) => {
return (
<tr key={entity.id}>
<td>{idx + 1}</td>
<td>{entity.title}</td>
<td>{entity.nickname}</td>
<td>{entity.roles.join(', ')}</td>
<td>{entity.description}</td>
</tr>
);
};

<DataTable id="entity-list"
className="table-hover"
headers={headers}
headerCellFormatter={(header) => <th>{header}</th>}
sortByKey={'title'}
rows={entities}
filterBy="role"
filterSuggestions={['Doctor', 'Student', 'Human']}
dataRowFormatter={rowFormatter}
filterLabel="Filter entities"
filterKeys={filterKeys} />
```
11 changes: 11 additions & 0 deletions graylog2-web-interface/src/components/common/DataTableElement.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import PropTypes from 'prop-types';
import React from 'react';

/**
* Component used to encapsulate each header or row inside a `DataTable`. You probably
* should not use this component directly, but through `DataTable`. Look at the `DataTable`
* section for a usage example.
*/
const DataTableElement = React.createClass({
propTypes: {
/** Element to be formatted. */
element: PropTypes.any,
/**
* Formatter function. It expects to receive the `element`, and `index` as arguments and
* returns an element to be rendered.
*/
formatter: PropTypes.func.isRequired,
/** Element index. */
index: PropTypes.number,
},
render() {
Expand Down
15 changes: 13 additions & 2 deletions graylog2-web-interface/src/components/common/DatePicker.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,24 @@ import DayPicker from 'react-day-picker';

import 'react-day-picker/lib/style.css';

/**
* Component that renders a given children and wraps a date picker around it. The date picker will show when
* the children is clicked, and hidden when clicking somewhere else.
*/
const DatePicker = React.createClass({
propTypes: {
/** Element id to use in the date picker Popover. */
id: PropTypes.string.isRequired,
/** Title to use in the date picker Popover. */
title: PropTypes.string.isRequired,
/** Initial date to select in the date picker. */
date: PropTypes.string,
dateFormatString: PropTypes.string,
/**
* Callback that will be called when user picks a date. It will receive the new selected day,
* `react-day-picker`'s modifiers, and the original event as arguments.
*/
onChange: PropTypes.func.isRequired,
/** Element that will trigger the date picker Popover. */
children: PropTypes.node.isRequired,
},
render() {
Expand All @@ -36,7 +47,7 @@ const DatePicker = React.createClass({
};

const dayPickerFrom = (
<Popover id={this.props.id} placement="bottom" positionTop={25} title="">
<Popover id={this.props.id} placement="bottom" positionTop={25} title={this.props.title}>
<DayPicker initialMonth={selectedDate ? selectedDate.toDate() : undefined}
onDayClick={this.props.onChange}
modifiers={modifiers}
Expand Down
14 changes: 14 additions & 0 deletions graylog2-web-interface/src/components/common/DocumentTitle.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import PropTypes from 'prop-types';
import React from 'react';

/**
* React component that modifies the page `document.title` dynamically. When the component is unmounted, it
* resets the title to the default (`Graylog`).
*
* Example:
*
* ```js
* <DocumentTitle title="This site is great">
* {contents}
* </DocumentTitle>
* ```
*/
const DocumentTitle = React.createClass({
propTypes: {
/** Title to prepend to the page `document.title`. */
title: PropTypes.string.isRequired,
/** Children to be rendered. */
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.element),
PropTypes.element,
Expand Down
8 changes: 8 additions & 0 deletions graylog2-web-interface/src/components/common/EntityList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ import PropTypes from 'prop-types';
import React from 'react';
import { Alert } from 'react-bootstrap';

/**
* Component used to represent list of entities in Graylog, where each entity will have a title, description,
* action buttons, etc. You need to use this component alongside `EntityListItem` in order to get a similar
* look and feel among different entities.
*/
const EntityList = React.createClass({
propTypes: {
/** bsStyle to use when there are no items in the list. */
bsNoItemsStyle: PropTypes.oneOf(['info', 'success', 'warning']),
/** Text to show when there are no items in the list. */
noItemsText: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element,
]),
/** Array of `EntityListItem` that will be shown. */
items: PropTypes.array.isRequired,
},
getDefaultProps() {
Expand Down
Loading

0 comments on commit 84843a8

Please sign in to comment.