diff --git a/.dockerignore b/.dockerignore index 9d73121d0ec..c275de2d4ec 100644 --- a/.dockerignore +++ b/.dockerignore @@ -49,7 +49,6 @@ vendor/ruby package-lock.json npm-debug.log .vscode -.yo-rc.json out* .byebug_history .gitignore diff --git a/.eslintignore b/.eslintignore index 276294dd234..b0d3fec0480 100644 --- a/.eslintignore +++ b/.eslintignore @@ -5,5 +5,4 @@ config/webpack.config.js webpack/simple_named_modules.js *.test.js *fixture* -*.stories.js *const* diff --git a/.github/workflows/storybook_deploy_main.yml b/.github/workflows/storybook_deploy_main.yml deleted file mode 100644 index c195ba00e36..00000000000 --- a/.github/workflows/storybook_deploy_main.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Deploy Main Storybook - -on: - push: - branches: - - develop - paths: - - 'webpack/**' - -permissions: - contents: read - -jobs: - deploy_main_storybook: - - runs-on: ubuntu-latest - if: github.repository == 'theforeman/foreman' - - steps: - - uses: actions/checkout@v3 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: 2.7 - - name: Run npm install - run: npm install - - name: Build Storybook - run: npm run build-storybook -- --output-dir=storybooks/main - - name: Deploy Storybook to surge.sh - env: - SURGE_TOKEN: ${{ secrets.SURGE_TOKEN }} - run: | - npm install --save-dev surge - npx surge --project storybooks/main --domain ${{ secrets.SURGE_DOMAIN }} - id: surge_deploy diff --git a/.gitignore b/.gitignore index 258b302f1cd..894cd62e741 100644 --- a/.gitignore +++ b/.gitignore @@ -50,7 +50,6 @@ public/webpack package-lock.json npm-debug.log .vscode -.yo-rc.json .vendor/ .solargraph.yml .nvmrc diff --git a/README.md b/README.md index 37cb7bef110..b5e3369f42e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ [![Code Climate](https://codeclimate.com/github/theforeman/foreman/badges/gpa.svg)](https://codeclimate.com/github/theforeman/foreman) [![Coverage Status](https://coveralls.io/repos/github/theforeman/foreman/badge.svg?branch=develop)](https://coveralls.io/github/theforeman/foreman?branch=develop) [![Support IRC channel](https://kiwiirc.com/buttons/irc.libera.chat/theforeman.png)](https://kiwiirc.com/client/irc.libera.chat/?#theforeman) -[![Storybook](https://raw.githubusercontent.com/storybooks/brand/master/badge/badge-storybook.svg)](https://foreman-storybook.surge.sh) [Foreman](https://theforeman.org) is a free open source project that gives you the power to easily **automate repetitive tasks**, quickly **deploy applications**, and proactively **manage your servers life cycle**, on-premises or in the cloud. diff --git a/webpack/stories/docs/adding-dependencies.stories.mdx b/developer_docs/adding-dependencies.asciidoc similarity index 65% rename from webpack/stories/docs/adding-dependencies.stories.mdx rename to developer_docs/adding-dependencies.asciidoc index 42244636585..9cbaeea321b 100644 --- a/webpack/stories/docs/adding-dependencies.stories.mdx +++ b/developer_docs/adding-dependencies.asciidoc @@ -1,22 +1,19 @@ -import { Meta } from '@theforeman/stories'; +[[adding-js-dependencies]] += NPM dependencies +:toc: right +:toclevels: 5 - - -# Using/Adding/updating NPM dependencies +## Using/Adding/updating NPM dependencies Foreman manage npm dependencies with a seperate project called `@theforeman/vendor` which responsible to deliver 3rd-party modules to foreman and its plugins. Foreman and its plugins consumes `@theforeman/vendor` project from `npm` in development and from `rpm` in production. -`@theforeman/vendor` lives inside a monorepo together with other foreman javascript tools in a project called [`foreman-js`](https://github.com/theforeman/foreman-js) +`@theforeman/vendor` lives inside a monorepo together with other foreman javascript tools in a project called https://github.com/theforeman/foreman-js[foreman-js] + -[Read more about `@theforeman/vendor`](https://github.com/theforeman/foreman-js/tree/master/packages/vendor) +https://github.com/theforeman/foreman-js/tree/master/packages/vendor[Read more about @theforeman/vendor] -### Consuming `foreman-js` projects from source (locally) +## Consuming `foreman-js` projects from source (locally) Clone, install, build and link the `foreman-js` project to foreman: diff --git a/developer_docs/adding-new-components.asciidoc b/developer_docs/adding-new-components.asciidoc new file mode 100644 index 00000000000..3470389e802 --- /dev/null +++ b/developer_docs/adding-new-components.asciidoc @@ -0,0 +1,116 @@ + +[[adding-new-react-components]] + +# Adding new React components + +## Component Storage + +Components are stored in the webpack/assets/javascripts/react_app/components/ directory. Each component should be placed in its own subfolder that follows the structure outlined below: + +``` +─ components// + ├─ .js ┈ react component + ├─ .scss ┈ styles if needed + ├─ .fixtures.js ┈ constants for testing, initial state, etc. + ├─ .test.js ┈ test file for the component + ├─ components/ ┈ folder for nested components if needed +``` + +React coponent files are limited to 100 lines of code. If you need to write more code, consider splitting the component into multiple components and/or wrapping the react component with an index file that will preform any general logic, api calls. + +If you are creating a component that uses legacy Redux actions and reducers, the structure should be as follows: + +``` +─ components// + ├─ .js ┈ pure react component + ├─ .scss ┈ styles if needed + ├─ Actions.js ┈ redux actions + ├─ Reducer.js ┈ redux reducer + ├─ Selectors.js ┈ reselect selectors + ├─ Constants.js ┈ constants such as action types + ├─ .fixtures.js ┈ constants for testing, initial state, etc. + ├─ components/ ┈ folder for nested components if needed + ├─ __tests__/ ┈ folder for tests + ╰─ index.js ┈ redux connected file +``` + +## Testing + +### Testing components + +Please use React-Testing-Library for tests. It's a library that helps you test your components in a way that resembles how they are used by the end user. It's a good idea to read the https://testing-library.com/docs/guiding-principles[guiding principles] of the library. + +### Running the tests + +All tests can be executed with `npm test`. + +If you want to run only a single test run `npm test \-- `. For example `npm test \-- BreadcrumbBar.test.js`. + +Linter (code style checking) can be executed with `npm run lint`. You can run it with parameter `--fix` to let it automatically fix the discovered issues. You need to pass the parameter to eslint, so run the command like this `npm run lint \-- --fix`. + +## Making it available from ERB + +If you want your component to be available for mounting into ERB templates, it must be added to [the component registry](https://github.com/theforeman/foreman/blob/develop/webpack/assets/javascripts/react_app/components/componentRegistry.js#L60-L71). + +Then, you can mount it with the `react_component` helper: + +```ruby +react_component(component_name, props) +``` + +**Example:** + +```erb +
+ <%= react_component('PowerStatus', id: host.id, url: power_host_path(host.id), errorText: 'N/A') %> +
+``` + +will render the following HTML: + +```html +
+ +
+``` + +(Note that the React component is rendered as a [web component](https://developer.mozilla.org/en-US/docs/Web/Web_Components).) + +### Changing the props from legacy JS + +We allow changing the root component props from the legacy JS. +Be aware, that this will re-render the component. +This feature should only be used for limited use cases: + +1. A legacy JS library/component needs to talk to a React component, AND +1. The component is simple enough that it wouldn't otherwise make sense to store its data in the Redux store. + +We will use a method `reactProps` that our web component exposes and get the current props. +Then we will change this props and use `reactProps=` setter that will trigger the rerender. + + +```js +var myCoolPowerElement = document.getElementById("my-cool-power-status").getElementsByTagName('foreman-react-component')[0]; +var newProps = myCoolPowerElement.reactProps; + +newProps.errorText = 'MyNewErrorText'; +myCoolPowerElement.reactProps = newProps; + +``` + + +*Note* that you can also directly set the data: `element.dataset.props = JSON.stringify({new: 'prop'});` +or even the attribute: `element.setAttribute('data-props', JSON.stringify({new: 'prop'}));` + +Both of these need a JSON string as a new value to work. + +## Before you start writing a new component + +It's worth checking [PatternFly](https://www.patternfly.org) to make sure such component doesn't exist yet. Also consider if your component is universal enough to be used in other projects. In such case it might make sense to add it to PatternFly instead. \ No newline at end of file diff --git a/webpack/stories/docs/api-middleware-usage.stories.mdx b/developer_docs/api-middleware-usage.asciidoc similarity index 66% rename from webpack/stories/docs/api-middleware-usage.stories.mdx rename to developer_docs/api-middleware-usage.asciidoc index 7b5a4f13de0..c84f38c1052 100644 --- a/webpack/stories/docs/api-middleware-usage.stories.mdx +++ b/developer_docs/api-middleware-usage.asciidoc @@ -1,13 +1,14 @@ -import { Meta } from '@theforeman/stories'; +[[api-middleware-intro]] - += API Middleware Usage +:toc: right +:toclevels: 5 -# How to use API in a Component using useAPI hook +# API Middleware + +Instead of each component handling API calls in the same way we have the API Middleware that will handle it. + +## How to use API in a Component using useAPI hook The API middleware is abstracted by the `useAPI` custom hook. @@ -19,7 +20,7 @@ import { successCallback, errorCallback } from './helper'; const MyComponent = () => { const options = { handleSuccess: successCallback, - handleError: errorCallback, + handleError: error => (error.response.status === 401 ? logoutUser() : null), successToast: response => 'This text will be shown as a toast after a success call', errorToast: response => 'This text will be shown as a toast when error occurs', }; @@ -27,31 +28,29 @@ const MyComponent = () => { response: { results }, status, // The current status of the API call key, // Generated key for storing in redux's store - } = useAPI('get', '/api/audits', options); + setAPIOptions, // Function to update the options and make a new api call + } = useAPI('get', '/api/hosts', options); return (
    - {audits.map(item => ( -
  • - {item.title} {item.action} -
  • + + + {results.map(item => ( +
  • {item.title}
  • ))}
); }; ``` -# Example: how to use the API middleware +## How to use the API middleware -The example API returns a JSON object like this: +The api middleware is a redux middleware that handles API calls in the application. +It is recommended to use the `useAPI` hook instead of using the middleware directly. -```json -{ - "items": [ - { "id": 319, "title": "setting", "action": "update" }, - { "id": 150, "title": "bookmark", "action": "create" } - ] -} -``` ```js /** MyComponent.js*/ @@ -113,6 +112,10 @@ ConnectedMyComponent.propTypes = { export default ConnectedMyComponent; ``` +### Access the API store + +We provided you the `selectAPIByKey` in '/APISelectors.js' which will return the key substate, +there are also `selectAPIStatus`, `selectAPIPayload`, `selectAPIResponse`, `selectAPIError` and `selectAPIErrorMessage`. ```js /** MyComponentSelectors.js*/ @@ -132,6 +135,15 @@ export const selectStatus = state => selectAPIStatus(state, MY_SPECIAL_KEY); export const selectError = state => selectAPIError(state, MY_SPECIAL_KEY); ``` +Then there will be called 2 actions: **MY_SPECIAL_KEY_REQUEST** and **MY_SPECIAL_KEY_SUCCESS/ MY_SPECIAL_KEY_FAILURE**: +**MY_SPECIAL_KEY_REQUEST** will have the payload only +**MY_SPECIAL_KEY_SUCCESS** will have the payload and the return data from the API call. +**MY_SPECIAL_KEY_FAILURE** will have the payload and the return error from the API call. + +In the **payload** field you should send any headers and params for the GET request, and any other data you want for the action. + +The actions types can be changed with the optional **actionTypes** parameter: + ```js /** MyComponentActions.js*/ @@ -145,10 +157,23 @@ export const getData = url => ({ page: 2, per_page: 10, }, + actionTypes: { + REQUEST: 'CUSTOM_REQUEST', + } }, }); ``` +The example API returns a JSON object like this: + +```json +{ + "items": [ + { "id": 319, "title": "setting", "action": "update" }, + { "id": 150, "title": "bookmark", "action": "create" } + ] +} +``` Once the action is triggered, the API middleware will manage the request and update the store with the request status: diff --git a/webpack/stories/docs/client-routing.stories.mdx b/developer_docs/client-routing.asciidoc similarity index 86% rename from webpack/stories/docs/client-routing.stories.mdx rename to developer_docs/client-routing.asciidoc index 54c35c76641..80ad58ad7d9 100644 --- a/webpack/stories/docs/client-routing.stories.mdx +++ b/developer_docs/client-routing.asciidoc @@ -1,40 +1,39 @@ -import { Meta } from '@theforeman/stories'; - - - +[[client-routing]] # Client Routing -Foreman uses `react-router` for rendering react pages without full page reload, +:toc: right +:toclevels: 5 +Foreman uses `react-router` for rendering react pages without full page reload. ## Core In order to add a new route in foreman core, please follow these steps: 1. Create a folder under `/react_app/routes` directory 2. Create an index file and import the wanted component: - -```js -import React from 'react'; -import Page1 from './page1'; -import { PAGE1_URL } from './constants'; - -export default { - path: PAGE1_URL, - render: props => , -}; -``` ++ +[source,js] +---- + import React from 'react'; + import Page1 from './page1'; + import { PAGE1_URL } from './constants'; + + export default { + path: PAGE1_URL, + render: props => , + }; +---- ++ 3. import that index in `routes.js` file: -```js ++ +[source,js] +---- // Other routes... import Page1 from './Page1'; import Page2 from './Page2' export const routes = [Page1, Page2]; -``` +---- ++ 4. Add a route that point to this page in `routes.rb` : ```ruby match 'page1' => 'react#index', :via => :get diff --git a/webpack/stories/docs/fetching-data-with-graphql.stories.mdx b/developer_docs/fetching-data-with-graphql.asciidoc similarity index 90% rename from webpack/stories/docs/fetching-data-with-graphql.stories.mdx rename to developer_docs/fetching-data-with-graphql.asciidoc index 98ca76c4c18..f81acfc8971 100644 --- a/webpack/stories/docs/fetching-data-with-graphql.stories.mdx +++ b/developer_docs/fetching-data-with-graphql.asciidoc @@ -1,12 +1,4 @@ -import { Meta } from '@theforeman/stories'; - - - +[[js-fetching-data-with-graphql]] # Fetching data with GraphQL Foreman uses Apollo client to talk to GraphQL endpoint. All the components within react routes have access to the Apollo client as routes are wrapped with ApolloProvider (see ReactApp component for details). Queries can be executed simply by using a hook: diff --git a/webpack/stories/docs/foreman-context.stories.mdx b/developer_docs/foreman-context.asciidoc similarity index 85% rename from webpack/stories/docs/foreman-context.stories.mdx rename to developer_docs/foreman-context.asciidoc index 56c8a195bae..3c3851282bc 100644 --- a/webpack/stories/docs/foreman-context.stories.mdx +++ b/developer_docs/foreman-context.asciidoc @@ -1,13 +1,7 @@ -import { Meta } from '@theforeman/stories'; - - - +[[foreman-context]] # ForemanContext +:toc: right +:toclevels: 5 ForemanContext is an implementation of the new `React Context` API. Global metadata (such as version, pagination, user, taxonomies, theme, foreman's settings, etc...) can be shared over all react nodes @@ -17,9 +11,8 @@ without any redux integration nor API request. `Foreman Context` comes with every react component by default, like redux's store. -### How to read a value +### Reading values from the context -#### With Hooks Like selectors, you can consume context values by custom hooks: ```js @@ -29,7 +22,8 @@ import { useForemanVersion, useForemanOrganization, useForemanLocation, - useForemanUser + useForemanUser, + useForemanDocUrl, } from '../../Root/Context/ForemanContext'; const { perPage } = useForemanSettings(); @@ -39,7 +33,7 @@ const { id, title } = useForemanOrganization(); const { id, login, firstname, lastname, admin } = useForemanUser(); ``` -### How to add a value to the context +## How to add a value to the context Keys and values are set in the `app_metadata` method of `/application_helper.rb`. The `app_metadata` object is passed down and becomes ForemanContext. diff --git a/webpack/stories/docs/getting-started.stories.mdx b/developer_docs/getting-started.asciidoc similarity index 71% rename from webpack/stories/docs/getting-started.stories.mdx rename to developer_docs/getting-started.asciidoc index 5d4a77820b4..843502df315 100644 --- a/webpack/stories/docs/getting-started.stories.mdx +++ b/developer_docs/getting-started.asciidoc @@ -1,13 +1,8 @@ -import { Meta } from '@theforeman/stories'; +[[js-getting-started]] - - -# Getting started +# Getting started with frontend development +:toc: right +:toclevels: 5 ## Development setup @@ -15,19 +10,20 @@ Following steps are required to setup a webpack development environment: 1. **Settings** There are 2 relevant settings in `config/settings.yml`. At least `webpack_dev_server` should be set to true: - - ```yaml - # Use the webpack development server? - # Should be set to true if you want to conveniently develop webpack-processed code. - # Make sure to run `rake webpack:compile` if disabled. - :webpack_dev_server: true - - # If you run Foreman in development behind some proxy or use HTTPS you need - # to enable HTTPS for webpack dev server too, otherwise you'd get mixed content - # errors in your browser - :webpack_dev_server_https: true - ``` - ++ +[source,yaml] +---- +# Use the webpack development server? +# Should be set to true if you want to conveniently develop webpack-processed code. +# Make sure to run `rake webpack:compile` if disabled. +:webpack_dev_server: true + +# If you run Foreman in development behind some proxy or use HTTPS you need +# to enable HTTPS for webpack dev server too, otherwise you'd get mixed content +# errors in your browser +:webpack_dev_server_https: true +---- ++ 2. **Dependencies** Make sure you have all npm dependencies up to date: `npm install` @@ -48,10 +44,10 @@ Following steps are required to setup a webpack development environment: 4. **Additional config** Both `foreman start` and `foreman-start-dev` support `WEBPACK_OPTS` environment variable for passing additional options. This is handy for example when you have development setup with Katello and want to use correct certificates. - An example of such setup: - - ```bash ++ +[source,bash] +---- ./node_modules/.bin/webpack-dev-server \ --config config/webpack.config.js \ --port 3808 \ @@ -61,18 +57,23 @@ Following steps are required to setup a webpack development environment: --cert /etc/pki/katello/certs/katello-apache.crt \ --cacert /etc/pki/katello/certs/katello-default-ca.crt \ --watch-poll 1000 # only use for NFS https://community.theforeman.org/t/webpack-watch-over-nfs/10922 - ``` - - Additionally you can set `NOTIFICATIONS_POLLING` variable to extend the notification polling interval that is 10s by default and can clutter the console. - - ```bash +---- ++ +Additionally you can set `NOTIFICATIONS_POLLING` variable to extend the notification polling interval that is 10s by default and can clutter the console. ++ +[source,bash] +---- NOTIFICATIONS_POLLING=${polling_interval_in_ms} - ``` - Webpack stats ([docs](https://webpack.js.org/configuration/stats/)) can be changed by `WEBPACK_STATS`. Default value is `minimal`. - ```bash +---- ++ +Webpack stats can be changed by `WEBPACK_STATS`. Default value is `minimal`. ++ +[source,bash] +---- WEBPACK_STATS=${verbose} - ``` + +---- ## Directory structure @@ -81,13 +82,12 @@ The webpack processed code is placed in the following folder structure: ``` ─ webpack/ ┈ all webpack processed code │ - ├─ stories/ ┈ storybook config (this pages) ╰─ assets/javascripts/ ┈ es6 code for erb pages, some still contain jQuery │ ╰─ react_app/ ┈ react components and related code ``` -More detailed description of a folder structure for components is in chapter [Adding new component](./?selectedKind=Introduction&selectedStory=Adding%20new%20component). +More detailed description of a folder structure for components is in chapter https://github.com/theforeman/foreman/blob/develop/developer_docs/adding-new-components.asciidoc[Adding new component]. There are still obsolete `redux` folders at some places. They used to be a place for files containing Redux actions and reducers before a standardized folder structure was introduced. We're migrating away from them. Please don't put additional code there. ## Useful tools diff --git a/webpack/stories/docs/hoc.stories.mdx b/developer_docs/hoc.asciidoc similarity index 60% rename from webpack/stories/docs/hoc.stories.mdx rename to developer_docs/hoc.asciidoc index d24ba364c06..267f9efdaa3 100644 --- a/webpack/stories/docs/hoc.stories.mdx +++ b/developer_docs/hoc.asciidoc @@ -1,13 +1,8 @@ -import { Meta } from '@theforeman/stories'; +[[react-hoc]] - +# Legacy Higher Order Components -# Higher Order Components +*Foremans `withRenderHandler` is not maintained anymore, and should not be used in new code.* A higher-order component (HOC) is an advanced technique in React for reusing component logic. We have created several HOCs which makes it easier to write, read and maintain new code. @@ -25,13 +20,14 @@ the following props are also required: Here we can see all the optional states and their chosen view. -| isLoading | hasData | hasError | VIEW | Comments | -| --------- | ------- | -------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| TRUE💚 | FALSE🔴 | FALSE🔴 | **Loading** | Initial Loading **OR** after Message | -| FALSE🔴 | TRUE💚 | FALSE🔴 | **Component** | Show Data | -| TRUE💚 | TRUE💚 | FALSE🔴 | **Component** | Outdated data exists, should not show loading to avoid flickering so we still show the component with the outdated data until the new data arrives | -| FALSE🔴 | FALSE🔴 | FALSE🔴 | **Empty** | query returned 0 results, this is not an error, and we display a message | -| FALSE🔴 | FALSE🔴 | TRUE💚 | **Error** | query failed, and we display a message | +|=== +| isLoading | hasData | hasError | VIEW | Comments +| 💚TRUE| FALSE🔴 | FALSE🔴 | **Loading** | Initial Loading **OR** after Message +| 🔴FALSE | TRUE💚 | FALSE🔴 | **Component** | Show Data +| 💚TRUE | TRUE💚 | FALSE🔴 | **Component** | Outdated data exists, should not show loading to avoid flickering so we still show the component with the outdated data until the new data arrives +| 🔴FALSE | FALSE🔴 | FALSE🔴 | **Empty** | query returned 0 results, this is not an error, and we display a message +| 🔴FALSE | FALSE🔴 | TRUE💚 | **Error** | query failed, and we display a message +|=== ### Example diff --git a/webpack/stories/docs/images/foreman-frontend-infra.png b/developer_docs/images/foreman-frontend-infra.png similarity index 100% rename from webpack/stories/docs/images/foreman-frontend-infra.png rename to developer_docs/images/foreman-frontend-infra.png diff --git a/webpack/stories/docs/interval-middleware.stories.mdx b/developer_docs/interval-middleware.asciidoc similarity index 95% rename from webpack/stories/docs/interval-middleware.stories.mdx rename to developer_docs/interval-middleware.asciidoc index da8c607b3eb..c96e27aebeb 100644 --- a/webpack/stories/docs/interval-middleware.stories.mdx +++ b/developer_docs/interval-middleware.asciidoc @@ -1,13 +1,6 @@ -import { Meta } from '@theforeman/stories'; - - - # Interval Middleware +:toc: right +:toclevels: 5 This middleware will run an action for every given interval miliseconds, and will manage all running intervals in the Redux store so we could clear the interval later. diff --git a/webpack/stories/docs/legacy-js.stories.mdx b/developer_docs/legacy-js.asciidoc similarity index 69% rename from webpack/stories/docs/legacy-js.stories.mdx rename to developer_docs/legacy-js.asciidoc index 576579e9c7a..71657ec2b2c 100644 --- a/webpack/stories/docs/legacy-js.stories.mdx +++ b/developer_docs/legacy-js.asciidoc @@ -1,30 +1,23 @@ -import { Meta } from '@theforeman/stories'; -import ForemanFrontendDiagram from './images/foreman-frontend-infra.png'; - - - +[[legacy-js]] # Legacy JS +:toc: right +:toclevels: 5 Foreman's legacy javascript is based on ruby on rails assets pipeline and located in `assets/javascripts` New features are most welcome on Webpack, but you can still get data directly from Redux's state and observe the state within the legacy JS. -> ⚠️ **Warning**: Do not update react content with jquery or other DOM manipulation -> ⚠️ **Warning**: Do not alter the state manually, only via actions -> ⚠️ **Warning**: Avoid changing the actual DOM with react +- ⚠️ **Warning**: Do not update react content with jquery or other DOM manipulation +- ⚠️ **Warning**: Do not alter the state manually, only via actions +- ⚠️ **Warning**: Avoid changing the actual DOM with react -Foreman Frontend Infrastructure +image::./images/foreman-frontend-infra.png["Foreman Frontend Infrastructure"] -### Access webpack's javascript +## Access webpack's javascript In order to access new js logic in old js files, we created a global object -`tfm`, which contains a set of functions and located in `/webpack/assets/javascripts/bundle.js` Please use this object instead of using the `window` object directly. -### How to invoke an action +## How to invoke an action Import the desired action in `/foreman_actions` file dispatch the action in a legacy js file: @@ -33,7 +26,7 @@ dispatch the action in a legacy js file: tfm.store.dispatch('actionName', arg1, arg2); ``` -### Observing the store +## Observing the store With `observeStore` you can listen for changes in the store: diff --git a/webpack/stories/docs/plugins.stories.mdx b/developer_docs/plugins.asciidoc similarity index 72% rename from webpack/stories/docs/plugins.stories.mdx rename to developer_docs/plugins.asciidoc index 82191b01563..9592168b43f 100644 --- a/webpack/stories/docs/plugins.stories.mdx +++ b/developer_docs/plugins.asciidoc @@ -1,18 +1,12 @@ -import { Meta } from '@theforeman/stories'; - - - +[[js-plugins]] # Plugins +:toc: right +:toclevels: 5 ## Using components from Foreman core There are three ways components provided by Foreman core can be re-used: - +[[mounting-components-into-erb]] ### 1. Mounting components into ERB No special setup is required and you can re-use React components that are available in `componentRegistry` even when your plugin doesn't use Webpack. @@ -42,7 +36,7 @@ will render the following HTML: (Note that the React component is rendered as a [web component](https://developer.mozilla.org/en-US/docs/Web/Web_Components).) -The list of available components is [here](https://github.com/theforeman/foreman/blob/develop/webpack/assets/javascripts/react_app/components/componentRegistry.js#L60). +The list of available components is [here](https://github.com/theforeman/foreman/blob/develop/webpack/assets/javascripts/react_app/components/componentRegistry.js). ### 2. Re-using core code in Webpack @@ -58,23 +52,7 @@ import { noop } from 'foremanReact/common/helpers'; import { MessageBox } from 'foremanReact/components/common/MessageBox'; ``` -### 3. Using components outside of Webpack - -The component registry is available in `Window.tfm.componentRegistry`. That gives you access to the components even from js code that isn't processed by Webpack. - -```js -const MyComponent = Window.tfm.componentRegistry.getComponent(componentName) - .type; -``` - -Most of the components must be wrapped with a [Higher-Order Component](https://reactjs.org/docs/higher-order-components.html) that provides some context like Redux store or Intl. `componentRegistry` publishes a wrapper factory that can create a wrapper function with HOCs according to your needs. - -```js -const i18nWrapper = componentRegistry.wrapperFactory().with('i18n').wrapper; -const MyComponentWithIntl = i18nWrapper(MyComponent); -``` - -# Using Webpack in plugins +## Using Webpack in plugins There are 3 conditions that a plugin has to fulfill to share the Webpack infrastructure from Foreman core: @@ -112,6 +90,6 @@ You can make sure Webpack knows about your plugin by executing script `plugin_we } } ``` -# How to extend core functionaly +## How to extend core functionaly -You can use [Slot&Fill](?selectedKind=Introduction&selectedStory=Slot%26Fill) to extend react components (See Slot&Fill section) +You can use https://github.com/theforeman/foreman/blob/develop/developer_docs/slot-and-fill.asciidoc[Slot&Fill] to extend react components from core. \ No newline at end of file diff --git a/webpack/stories/docs/slot-and-fill.stories.mdx b/developer_docs/slot-and-fill.asciidoc similarity index 81% rename from webpack/stories/docs/slot-and-fill.stories.mdx rename to developer_docs/slot-and-fill.asciidoc index cfda31007db..b15186d4ad4 100644 --- a/webpack/stories/docs/slot-and-fill.stories.mdx +++ b/developer_docs/slot-and-fill.asciidoc @@ -1,26 +1,14 @@ -import { Meta } from '@theforeman/stories'; - - - +[[slot-and-fill]] # Slot And Fill +:toc: right +:toclevels: 5 -Slot & Fill allows plugins to extend foreman core functionality in the UI +Slot & Fill allows plugins to extend foreman core functionality in the UI. ## Current Slots List -| Name | Id | Path | -| -------------------- |:---------------------:| -----:| -| **About-footer** | aboutFooterSlot | _views/about/index.html.erb_ -| **Hostgroup params** | HostgroupParams | _views/hostgroups/_form.html.erb_ -| **Host params** | HostParams | _views/hosts/_form.html.erb_ -| **Host Registration**| `registrationGeneral` | `` -| **Host Registration**| `registrationAdvanced`| `` - +You can find current slots by search which React component use the `` component. +You can find .erb pages that use `` component by searching for `slot` helper. ## Components @@ -52,7 +40,7 @@ _plugin A_ ```js
some text
-<\/Fill> +
``` _plugin B_ @@ -60,7 +48,7 @@ _plugin B_ ```js
some text
-<\/Fill> +
``` Plugin B has a fill with a higher weight, therefore it will be rendered in a dedicated slot. @@ -80,7 +68,7 @@ _plugin A_ ```js
some text
-<\/Fill> +
``` _plugin B_ @@ -88,7 +76,7 @@ _plugin B_ ```js
some text
-<\/Fill> +
``` Plugin B's fill will be render first @@ -105,7 +93,7 @@ const TextWrapper = ({ text }) =>
{text}
; -<\/Slot>; +; ``` _plugin A_ diff --git a/webpack/stories/docs/state-management.stories.mdx b/developer_docs/state-management.asciidoc similarity index 78% rename from webpack/stories/docs/state-management.stories.mdx rename to developer_docs/state-management.asciidoc index 9f9809319f0..faaa627a94c 100644 --- a/webpack/stories/docs/state-management.stories.mdx +++ b/developer_docs/state-management.asciidoc @@ -1,13 +1,7 @@ -import { Meta } from '@theforeman/stories'; - - - +[[state-management]] # State Management in Foreman & Plugins +:toc: right +:toclevels: 5 Where do I put data so that it's accessible by the React components that need it? @@ -30,7 +24,7 @@ The following sections will outline This includes anything that uses `useState` or `useReducer` hooks. Or, in class components, `this.state`. See the React docs for more info. -This is the best place for any data that is _used only locally by the component_: for example +This is the best place for any data that is _used only locally by the component and its children_: for example * input value for a controlled UI component * current form values for form elements * Other UI element states @@ -49,20 +43,25 @@ On the other hand, state management is not the only use for Redux. It can also ## ForemanContext -Data that doesn't often change but can impact many components across the application is stored in a special React Context object called ForemanContext. For more info on how to use it, see [Foreman Context](https://foreman-storybook.surge.sh/?path=/story/introduction-foreman-context--page). +Data that doesn't often change but can impact many components across the application is stored in a special React Context object called ForemanContext. For more info on how to use it, see https://github.com/theforeman/foreman/blob/develop/developer_docs/foreman-context.asciidoc[Foreman Context]. + +This data includes: -This data includes * Current taxonomy (organization, location) * Current user * Pagination settings * Foreman version +* UI Settings +* Documantation url + ## Global `window` object Because Foreman and plugins use both modern and legacy JS frameworks, Foreman has a unique way of storing some data on the global `window` object. (See `webpack/assets/javascripts/bundle.js`.) This `tfm` object includes -* A [`componentRegistry`](https://github.com/theforeman/foreman/blob/develop/webpack/assets/javascripts/react_app/components/componentRegistry.js) that makes React components accessible for mounting on Rails ERB pages. For more info, see [Mounting components into ERB](https://foreman-storybook.surge.sh/?path=/story/introduction-plugins--page#1-mounting-components-into-erb). -* A method for [legacy JS](https://foreman-storybook.surge.sh/?path=/story/introduction-legacy-js--page) frameworks to access Foreman's Redux store +* A https://github.com/theforeman/foreman/blob/develop/webpack/assets/javascripts/react_app/components/componentRegistry.js[`componentRegistry`] that makes React components accessible for mounting on Rails ERB pages. For more info, see https://github.com/theforeman/foreman/blob/develop/developer_docs/plugins.asciidoc#mounting-components-into-erb[Mounting components into ERB] + +* A method for https://github.com/theforeman/foreman/blob/develop/developer_docs/legacy-js.asciidoc[legacy JS] frameworks to access Foreman's Redux store * Several other utilities and helpers written in jQuery and vanilla JavaScript Should be used only if legacy code needs access to such data or methods. Avoid using it if possible. diff --git a/package-exclude.json b/package-exclude.json index 818dc95a830..0380ffe09c0 100644 --- a/package-exclude.json +++ b/package-exclude.json @@ -3,7 +3,6 @@ "@sheerun/mutationobserver-shim", "@theforeman/env", "@theforeman/eslint-plugin-foreman", - "@theforeman/stories", "@theforeman/test", "@theforeman/vendor-dev", "@theforeman/find-foreman", @@ -36,7 +35,6 @@ ], "EXCLUDE_NPM_PREFIXES": [ "@babel/eslint-", - "@storybook/", "@testing-library/", "enzyme", "eslint", diff --git a/package.json b/package.json index 8fbd8105d6a..84e8b4b21a9 100644 --- a/package.json +++ b/package.json @@ -16,12 +16,8 @@ "test:watch": "tfm-test --watchAll", "test:current": "tfm-test --watch", "publish-coverage": "tfm-publish-coverage", - "stories": "./script/npm-fix-foreman-stories.sh && tfm-stories", - "build-storybook": "./script/npm-fix-foreman-stories.sh && tfm-build-stories", - "deploy-storybook": "cross-env NODE_ENV=storybook NODE_OPTIONS=--max_old_space_size=8192 storybook-to-ghpages", "postinstall": "./script/npm_install_plugins.js", - "analyze": "./script/webpack-analyze", - "create-react-component": "yo react-domain" + "analyze": "./script/webpack-analyze" }, "dependencies": { "@theforeman/vendor": "^12.0.1", @@ -34,7 +30,6 @@ "@babel/core": "^7.7.0", "@theforeman/builder": "^12.0.1", "@theforeman/eslint-plugin-foreman": "^12.0.1", - "@theforeman/stories": "^12.0.1", "@theforeman/test": "^12.0.1", "@theforeman/vendor-dev": "^12.0.1", "@types/jest": "<27.0.0", diff --git a/script/npm-fix-foreman-stories.sh b/script/npm-fix-foreman-stories.sh deleted file mode 100755 index 4a6e064246f..00000000000 --- a/script/npm-fix-foreman-stories.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -# This script will allow you to run tfm-stories -# while foreman can still use webpack-3 for the normal build. -# -# It is here temporarily until the migration to webpack-4 will be done. - -if [ "$NODE_ENV" = "production" ]; then - exit 0 -fi - -if [ -d node_modules/@theforeman/stories/node_modules ]; then - exit 0 -fi - -cd ./node_modules/@theforeman/stories -npm install \ No newline at end of file diff --git a/script/npm_link_foreman_js.sh b/script/npm_link_foreman_js.sh deleted file mode 100755 index df0bbb73c6a..00000000000 --- a/script/npm_link_foreman_js.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -# This script replace the npm installation of `foreman-js` -# with your local version. Usefull when developing `foreman-js` -# Read more about foreman-js: https://github.com/theforeman/foreman-js -# -# This script designed to run using `npm run foreman-js:link` in foreman root - -echo "This script is deprecated, please use the 'npm run link' in 'foreman-js'" -echo "See the docs: https://foreman-storybook.surge.sh/?path=/docs/introduction-adding-dependencies--page" -exit 1 diff --git a/webpack/assets/javascripts/react_app/components/BreadcrumbBar/BreadcrumbBar.stories.js b/webpack/assets/javascripts/react_app/components/BreadcrumbBar/BreadcrumbBar.stories.js deleted file mode 100644 index 559e2e5ec99..00000000000 --- a/webpack/assets/javascripts/react_app/components/BreadcrumbBar/BreadcrumbBar.stories.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import { boolean, number } from '@storybook/addon-knobs'; -import { action } from '@storybook/addon-actions'; - -import BreadcrumbBar from './BreadcrumbBar'; -import Story from '../../../../../stories/components/Story'; - -export default { - title: 'Components/BreadcrumbBar', -}; - -export const withOpenSwitcher = () => ( - - - -); - -withOpenSwitcher.story = { - name: 'With open switcher', -}; diff --git a/webpack/assets/javascripts/react_app/components/ChartBox/ChartBox.fixtures.js b/webpack/assets/javascripts/react_app/components/ChartBox/ChartBox.fixtures.js deleted file mode 100644 index cb1199d121a..00000000000 --- a/webpack/assets/javascripts/react_app/components/ChartBox/ChartBox.fixtures.js +++ /dev/null @@ -1,84 +0,0 @@ -const mockStoryData = { - config: { - donut: { - width: 15, - label: { - show: false, - }, - }, - data: { - type: 'donut', - columns: [ - ['Fedora 21', 3], - ['Ubuntu 14.04', 4], - ['Centos 7', 2], - ['Debian 8', 1], - ], - names: { - 'Fedora 21': 'Fedora 21', - 'Ubuntu 14.04': 'Ubuntu 14.04', - 'Centos 7': 'Centos 7', - 'Debian 8': 'Debian 8', - }, - }, - tooltip: { - show: true, - }, - legend: { - show: false, - }, - padding: { - top: 0, - left: 0, - right: 0, - bottom: 0, - }, - bindto: '#operatingsystemChart', - }, - modalConfig: { - donut: { - width: 25, - label: { - show: false, - }, - }, - data: { - type: 'donut', - columns: [ - ['Fedora 21', 3], - ['Ubuntu 14.04', 4], - ['Centos 7', 2], - ['Debian 8', 1], - ], - names: { - 'Fedora 21': 'Fedora 21', - 'Ubuntu 14.04': 'Ubuntu 14.04', - 'Centos 7': 'Centos 7', - 'Debian 8': 'Debian 8', - }, - }, - tooltip: { - show: true, - }, - legend: { - show: false, - }, - padding: { - top: 0, - left: 0, - right: 0, - bottom: 0, - }, - size: { - height: 500, - }, - }, - noDataMsg: 'No data available', - tip: 'Expand the chart', - status: 'RESOLVED', - id: 'operatingsystem', - title: 'OS Distribution', - search: '/hosts?search=os_title=~VAL~', -}; - -export default mockStoryData; diff --git a/webpack/assets/javascripts/react_app/components/ChartBox/ChartBox.stories.js b/webpack/assets/javascripts/react_app/components/ChartBox/ChartBox.stories.js deleted file mode 100644 index 56951aee472..00000000000 --- a/webpack/assets/javascripts/react_app/components/ChartBox/ChartBox.stories.js +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react'; -import ChartBox from './ChartBox'; -import mockStoryData from './ChartBox.fixtures'; -import Story from '../../../../../stories/components/Story'; - -export default { - title: 'Components/Charts/ChartBox', - component: ChartBox, -}; - -export const Loading = () => ( - - - -); - -export const WithoutData = () => ( - - - -); - -export const WithError = () => ( - - - -); - -export const WithDataAndModal = () => ( - - - -); - -WithDataAndModal.story = { - name: 'With Data + Modal', -}; diff --git a/webpack/assets/javascripts/react_app/components/ConfigReports/DiffModal/DiffModal.stories.js b/webpack/assets/javascripts/react_app/components/ConfigReports/DiffModal/DiffModal.stories.js deleted file mode 100644 index 2498463ecf7..00000000000 --- a/webpack/assets/javascripts/react_app/components/ConfigReports/DiffModal/DiffModal.stories.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import { boolean, select } from '@storybook/addon-knobs'; -import { action } from '@storybook/addon-actions'; - -import DiffModal from './DiffModal'; -import Story from '../../../../../../stories/components/Story'; - -export default { - title: 'Components/DiffModal', -}; - -export const diffModal = () => ( - - - -); - -diffModal.story = { - name: 'DiffModal', -}; diff --git a/webpack/assets/javascripts/react_app/components/ConfirmModal/ConfirmModal.stories.js b/webpack/assets/javascripts/react_app/components/ConfirmModal/ConfirmModal.stories.js deleted file mode 100644 index fe0d8642289..00000000000 --- a/webpack/assets/javascripts/react_app/components/ConfirmModal/ConfirmModal.stories.js +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import { useDispatch } from 'react-redux'; -import { Button } from '@patternfly/react-core'; -import { action } from '@storybook/addon-actions'; -import { boolean, text, object } from '@storybook/addon-knobs'; -import storeDecorator from '../../../../../stories/storeDecorator'; -import Story from '../../../../../stories/components/Story'; -import ConfirmModal, { openConfirmModal } from '.'; - -export default { - title: 'Components/Confirm modal', - decorators: [storeDecorator], -}; - -export const ConfirmBasicUsage = () => - React.createElement(() => { - const dispatch = useDispatch(); - const isWarning = boolean('isWarning', false); - const title = text('title', 'Confirm'); - const message = text('message', 'Are you sure?'); - const confirmButtonText = text('confirmButtonText', null); - const handleConfirmClick = () => { - dispatch( - openConfirmModal({ - title, - message, - isWarning, - confirmButtonText, - onConfirm: action('Confirmed!'), - onCancel: action('Canceled!'), - }) - ) - }; - - return ( - - - {/* The confirm modal is already declared on the app's root */} - - - ); - }); - - ConfirmBasicUsage.story = { - name: 'Basic usage', -}; diff --git a/webpack/assets/javascripts/react_app/components/DiffView/DiffView.stories.js b/webpack/assets/javascripts/react_app/components/DiffView/DiffView.stories.js deleted file mode 100644 index ed732d42260..00000000000 --- a/webpack/assets/javascripts/react_app/components/DiffView/DiffView.stories.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { text } from '@storybook/addon-knobs'; - -import DiffContainer from './DiffContainer'; -import Story from '../../../../../stories/components/Story'; - -export default { - title: 'Components/DiffView', -}; - -export const diffView = () => ( - - - -); - -diffView.story = { - name: 'DiffView', -}; diff --git a/webpack/assets/javascripts/react_app/components/ForemanModal/ForemanModal.js b/webpack/assets/javascripts/react_app/components/ForemanModal/ForemanModal.js index ddf69541da2..fe1bec4f006 100644 --- a/webpack/assets/javascripts/react_app/components/ForemanModal/ForemanModal.js +++ b/webpack/assets/javascripts/react_app/components/ForemanModal/ForemanModal.js @@ -6,6 +6,30 @@ import ForemanModalHeader from './subcomponents/ForemanModalHeader'; import ForemanModalFooter from './subcomponents/ForemanModalFooter'; import { extractModalNodes } from './helpers'; +/** + * A modal component that provides a standardized layout and context for modals in Foreman. + * Should not be used in new components. use Patternfy 4 Modal instead. + * @param {string} id - The ID of the modal. + * @param {string} [title=''] - The title of the modal. will not be used if a custom header is provided. + * @param {boolean} [isOpen=false] - Whether the modal is open or not. + * @param {function} onClose - The function to call when the modal is closed. + * @param {boolean} [isSubmitting=false] - Whether the modal is currently submitting data or not. + * @param {Object} [submitProps={}] - Additional props to pass down to the submit button in the footer. + * @param {ReactNode} [children=null] - The child nodes of the modal. + * @returns {ReactNode} The rendered modal component. + */ +/* + Usage example for a custom header and footer: + + +

This is a custom header! :)

+
+ body content + + Custom footer + +
+*/ const ForemanModal = props => { const { id, diff --git a/webpack/assets/javascripts/react_app/components/ForemanModal/ForemanModal.stories.js b/webpack/assets/javascripts/react_app/components/ForemanModal/ForemanModal.stories.js deleted file mode 100644 index c1e5a25fe9c..00000000000 --- a/webpack/assets/javascripts/react_app/components/ForemanModal/ForemanModal.stories.js +++ /dev/null @@ -1,190 +0,0 @@ -import React from 'react'; -import { Button } from 'patternfly-react'; -import storeDecorator from '../../../../../stories/storeDecorator'; -import ForemanModal from '.'; -import { useForemanModal } from './ForemanModalHooks'; -import Story from '../../../../../stories/components/Story'; - -export default { - title: 'Components/ForemanModal/Props & Children', - decorators: [storeDecorator], -}; - -export const withDefaultHeaderFooter = () => - // using createElement here so that hooks work in stories - React.createElement(() => { - const { setModalOpen } = useForemanModal({ id: 'default' }); - return ( - - - - - If you supply a title prop, it will be used as the modal title. - - - - ); - }); - -withDefaultHeaderFooter.story = { - name: 'With default header & footer', -}; - -export const withNoChildren = () => - // using createElement here so that hooks work in stories - React.createElement(() => { - const { setModalOpen } = useForemanModal({ id: 'noChildren' }); - return ( - - - - - ); - }); - -withNoChildren.story = { - name: 'With no children', -}; - -export const withCustomHeaderFooter = () => - React.createElement(() => { - const { setModalOpen } = useForemanModal({ id: 'custom' }); - return ( - - - - -

This is a custom header! :)

-
- You can provide your own {``} - - Click the X in the upper right to close - -
-
- ); - }); - -withCustomHeaderFooter.story = { - name: 'With custom header & footer', -}; - -export const withUnorderedHeaderFooter = () => - React.createElement(() => { - const { setModalOpen } = useForemanModal({ id: 'unordered' }); - return ( - - - -
- Header and footer will be correctly ordered when rendering, even if - they are out of order in the markup -
- This is the footer - -

This is the header

-
-
-
- ); - }); - -withUnorderedHeaderFooter.story = { - name: 'With unordered header & footer', -}; - -export const withNoCloseButton = () => - React.createElement(() => { - const { setModalOpen } = useForemanModal({ id: 'noClose' }); - return ( - - - - -
- Props passed to ForemanModal.Header will be passed down to - Modal.Header - -
-
- ); - }); - -withNoCloseButton.story = { - name: 'With no close button', -}; - -export const withNoFooter = () => - React.createElement(() => { - const { setModalOpen } = useForemanModal({ id: 'noFooter' }); - return ( - - - - - This is the modal body. There is no footer. - - - ); - }); - -withNoFooter.story = { - name: 'With no footer', -}; - -export const withNoHeader = () => - React.createElement(() => { - const { setModalOpen } = useForemanModal({ id: 'noHeader' }); - return ( - - - - If neither a {``} nor a title prop are supplied,{' '} -
- the modal will have no header. - -
-
- ); - }); - -withNoHeader.story = { - name: 'With no header', -}; - -export const withPropsPassedDownViaSpreadSyntax = () => - React.createElement(() => { - const { setModalOpen } = useForemanModal({ id: 'propsPassed' }); - return ( - - - - - The inner {``} component will have any props you pass to - {``}. (Look in the React dev tools for - ‘myProp’) - - - - ); - }); - -withPropsPassedDownViaSpreadSyntax.story = { - name: 'With props passed down via spread syntax', -}; diff --git a/webpack/assets/javascripts/react_app/components/ForemanModal/ForemanModalUsage.stories.js b/webpack/assets/javascripts/react_app/components/ForemanModal/ForemanModalUsage.stories.js deleted file mode 100644 index 843ccb15c72..00000000000 --- a/webpack/assets/javascripts/react_app/components/ForemanModal/ForemanModalUsage.stories.js +++ /dev/null @@ -1,294 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Button } from 'patternfly-react'; -import { useDispatch, connect } from 'react-redux'; -import { - setModalOpen as setModalOpenAction, - setModalClosed as setModalClosedAction, -} from './ForemanModalActions'; -import { selectIsModalOpen } from './ForemanModalSelectors'; -import storeDecorator from '../../../../../stories/storeDecorator'; -import ForemanModal from '.'; -import { useForemanModal } from './ForemanModalHooks'; -import Story from '../../../../../stories/components/Story'; - -export default { - title: 'Components/ForemanModal/ForemanModal Usage', - decorators: [storeDecorator], -}; - -export const foremanModalBasics = () => - // using createElement here so that hooks work in stories - React.createElement(() => { - const { setModalOpen } = useForemanModal({ id: 'reduxModal' }); - return ( - - - - -

ForemanModal is controlled with Redux actions

-

- ForemanModals can be controlled from anywhere -
- in the application, with a single source of truth for modal state. -

-

How to use ForemanModal

-

ForemanModals require an ID prop.

-

It should be a descriptive unique string.

-
- - {``} - -
-
-

- Once you've assigned an ID, modal state can be controlled in - any of the following ways: -

    -
  • - In connected class components: By dispatching - Redux actions with mapDispatchToProps -
  • -
  • - In function components: With useForemanModal - hook, which handles Redux for you -
  • -
  • - In function components: With useSelector and - useDispatch hooks from react-redux -
  • -
-

-

See the stories below for examples.

- -
-
- ); - }); - -foremanModalBasics.story = { - name: 'ForemanModal Basics', -}; - -export const withUseForemanModalHook = () => - // using createElement here so that hooks work in stories - React.createElement(() => { - const { modalOpen, setModalOpen } = useForemanModal({ id: 'hooks' }); - return ( - - - - - The useForemanModal hook returns 3 objects:
-
    -
  • modalOpen: boolean
  • -
  • setModalOpen: function to open that specific modal
  • -
  • setModalClosed: function to close that specific modal
  • -
-
- These functions take care of the Redux state and actions for you, -
- so you don't have to connect your parent component directly. -

Click the STORY tab below to see the code.

- -
-
- ); - }); - -withUseForemanModalHook.story = { - name: 'With useForemanModal Hook', -}; - -export const withReactReduxHooks = () => - // using createElement here so that hooks work in stories - React.createElement(() => { - const dispatch = useDispatch(); - return ( - - - - -

If you prefer, you can use the hooks provided by react-redux.

-

As before, make sure to assign your modal an ID prop:

-
- - {``} - -
-

Modal state is controlled with Redux actions

-

- Control the modal state with the setModalOpen and setModalClosed - actions. Make sure the ID passed to the action matches your ID prop. -

-
- - {`const dispatch = useDispatch(); - ... - dispatch(setModalOpen({ id: 'reduxActionsModal' })); - `} - -
- - {`const dispatch = useDispatch(); - ... - dispatch(setModalClosed({ id: 'reduxActionsModal' })); - `} - -

- In this way, ForemanModals can be controlled from anywhere -
- in the application, with a single source of truth for modal state. -

-

You can also read the modal state by ID by using the selectors:

-
- {`// returns { open: true }`} -
- - {`const modalOpenState = useSelector(state => selectModalStateById(state, 'reduxActionsModal'));`} - -
-
- {/* returns true */} -
- - {`const isModalOpen = useSelector(state => selectIsModalOpen(state, 'reduxActionsModal'));`} - - -
-
- ); - }); - -withReactReduxHooks.story = { - name: 'With react-redux hooks', -}; - -export const withConnectedComponent = () => { - const FmContainer = ({ setModalOpen, setModalClosed, modalOpen }) => ( - - - - - If the parent of {``} is a class component, or you just - don't want to use Hooks, you can use Redux like normal, with - connect(), mapStateToProps, and mapDispatchToProps. Click the STORY tab - to see the code. - - - - ); - - FmContainer.propTypes = { - setModalOpen: PropTypes.func.isRequired, - setModalClosed: PropTypes.func.isRequired, - modalOpen: PropTypes.bool.isRequired, - }; - - const mapStateToProps = state => ({ - modalOpen: selectIsModalOpen(state, 'connectedContainer'), - }); - const mapDispatchToProps = { - setModalOpen: () => setModalOpenAction({ id: 'connectedContainer' }), - setModalClosed: () => setModalClosedAction({ id: 'connectedContainer' }), - }; - const ConnectedContainer = connect( - mapStateToProps, - mapDispatchToProps - )(FmContainer); - // normally you would export default ConnectedContainer here - - return ( - - - - ); -}; - -withConnectedComponent.story = { - name: 'With connected component', -}; - -export const asConfirmationDialog = () => - React.createElement(() => { - const dispatch = useDispatch(); - return ( - - - {}, - }} - > - You have requested to delete the Internet. Are you sure? - - - - ); - }); - -asConfirmationDialog.story = { - name: 'As a confirmation dialog', -}; - -export const asConfirmationDialogWithCustomizedButtons = () => - React.createElement(() => { - const dispatch = useDispatch(); - return ( - - - {}, - submitBtnProps: { - btnText: 'Agree', - bsStyle: 'default', - }, - cancelBtnProps: { - btnText: 'Disagree', - bsStyle: 'danger', - }, - }} - > - One does not simply walk into Mordor. - - - ); - }); diff --git a/webpack/assets/javascripts/react_app/components/Loading/Loading.stories.js b/webpack/assets/javascripts/react_app/components/Loading/Loading.stories.js deleted file mode 100644 index 9f11b024546..00000000000 --- a/webpack/assets/javascripts/react_app/components/Loading/Loading.stories.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import Story from '../../../../../stories/components/Story'; -import Loading from './Loading'; - -export default { - title: 'Components/Loading', -}; - -export const defaultStory = () => ( - - - -); - -export const smallText = () => ( - - - -); - -export const noText = () => ( - - - -); - -export const smallerIconAndText = () => ( - - - -); - -defaultStory.story = { - name: 'Default', -}; diff --git a/webpack/assets/javascripts/react_app/components/PF4/Bookmarks/Bookmarks.stories.js b/webpack/assets/javascripts/react_app/components/PF4/Bookmarks/Bookmarks.stories.js deleted file mode 100644 index a450207a8d6..00000000000 --- a/webpack/assets/javascripts/react_app/components/PF4/Bookmarks/Bookmarks.stories.js +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; -import { Provider } from 'react-redux'; -import Bookmarks from './Bookmarks'; -import Story from '../../../../../../stories/components/Story'; -import store from '../../../redux'; - -export default { - title: 'Components|PF4 Bookmarks', - decorators: [], -}; - -export const defaultStory = () => ( - - - null} - url="" - canCreate - bookmarks={[ - { - name: 'recent hosts', - query: '', - }, - { - name: 'managed hosts', - query: '', - }, - { - name: 'disabled hosts', - query: '', - }, - ]} - status="RESOLVED" - setModalOpen={() => null} - setModalClosed={() => null} - searchQuery="" - /> - - -); - -defaultStory.story = { - name: 'Default', -}; - -export const pendingStory = () => ( - - - null} - url="" - canCreate - bookmarks={[]} - status="PENDING" - setModalOpen={() => null} - setModalClosed={() => null} - searchQuery="" - /> - - -); - -pendingStory.story = { - name: 'Pending', -}; - -export const errorStory = () => ( - - - null} - url="" - canCreate - bookmarks={[]} - status="ERROR" - errors="Some error!" - setModalOpen={() => null} - setModalClosed={() => null} - searchQuery="" - /> - - -); - -errorStory.story = { - name: 'Error', -}; diff --git a/webpack/assets/javascripts/react_app/components/PF4/DocumentationLink/DocumentationLink.stories.js b/webpack/assets/javascripts/react_app/components/PF4/DocumentationLink/DocumentationLink.stories.js deleted file mode 100644 index 0baf7b4a0e7..00000000000 --- a/webpack/assets/javascripts/react_app/components/PF4/DocumentationLink/DocumentationLink.stories.js +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import { action } from '@theforeman/stories'; - -import Story from '../../../../../../stories/components/Story'; -import DocumentationLink from './index'; - -export default { - title: 'Components|PF4 DocumentationLink', -}; - -export const defaultStory = () => ( - -
    - -
-
-); - -defaultStory.story = { - name: 'Default', -}; diff --git a/webpack/assets/javascripts/react_app/components/PF4/InlineEdit/InlineEdit.stories.js b/webpack/assets/javascripts/react_app/components/PF4/InlineEdit/InlineEdit.stories.js deleted file mode 100644 index 9d5dfdecfec..00000000000 --- a/webpack/assets/javascripts/react_app/components/PF4/InlineEdit/InlineEdit.stories.js +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useState, createElement } from 'react'; -import { text, boolean } from '@storybook/addon-knobs'; -import Story from '../../../../../../stories/components/Story'; -import InlineEdit from './InlineEdit'; - -export default { - title: 'Components/PF4/InlineEdit', -}; - -// Wrapping in createElement so the hooks work properly -// see https://github.com/storybookjs/storybook/issues/5721#issuecomment-472769646 -export const defaultStory = () => createElement(() => { - const [value, setValue] = useState('hello'); - const [value2, setValue2] = useState('hello again'); - - const onSave = async (v, a) => { - // delay to emulate an asynchronous backend call - await (async () => new Promise(resolve => setTimeout(resolve, 1000)))(); - setValue(v); - alert(`Update of ${a} successful`); - }; - - const onSave2 = async (v, a) => { - // delay to emulate an asynchronous backend call - await (async () => new Promise(resolve => setTimeout(resolve, 1000)))(); - setValue2(v); - alert(`Update of ${a} successful`); - }; - - return ( - - - - - ); -}); - -export const withTextArea = () => createElement(() => { - const [value, setValue] = useState('Text area: Pressing enter will not submit'); - - return ( - - setValue(v)} - /> - - ); -}); - -export const nothingProvided = () => createElement(() => { - const [value, setValue] = useState(null); - - return ( - - setValue(v)} - /> - - ); -}); - -defaultStory.story = { - name: 'With backend call', -}; diff --git a/webpack/assets/javascripts/react_app/components/PermissionDenied/PermissionDenied.js b/webpack/assets/javascripts/react_app/components/PermissionDenied/PermissionDenied.js index 6ee459f6d3d..97fb7b8997a 100644 --- a/webpack/assets/javascripts/react_app/components/PermissionDenied/PermissionDenied.js +++ b/webpack/assets/javascripts/react_app/components/PermissionDenied/PermissionDenied.js @@ -3,6 +3,18 @@ import { Button, Icon } from 'patternfly-react'; import PropTypes from 'prop-types'; import { translate as __ } from '../../common/I18n'; +/** + * A component that explains to the user about the permissions needed to perform an operation. + * @param {Object} props - The props object. + * @param {string[]} [props.missingPermissions=['unknown']] - An array of strings representing the missing permissions. + * @param {Object} [props.texts={}] - An object containing text strings for the component. + * @param {string} [props.texts.notAuthorizedMsg='You are not authorized to perform this action.'] - The message to display when the user is not authorized to perform the action. + * @param {string} [props.texts.permissionDeniedMsg='Permission denied'] - The message to display when the user does not have the required permissions. + * @param {string} [props.texts.pleaseRequestMsg='Please request one of the required permissions listed below from a Foreman administrator:'] - The message to display instructing the user to request the required permissions. + * @param {string} [props.backHref='/'] - The URL to navigate to when the user clicks the back button. + * @returns {ReactNode} The rendered `PermissionDenied` component. + */ + const PermissionDenied = ({ missingPermissions, texts, backHref }) => { const { notAuthorizedMsg, permissionDeniedMsg, pleaseRequestMsg } = texts; return ( diff --git a/webpack/assets/javascripts/react_app/components/PermissionDenied/PermissionDenied.stories.mdx b/webpack/assets/javascripts/react_app/components/PermissionDenied/PermissionDenied.stories.mdx deleted file mode 100644 index d2b20db3858..00000000000 --- a/webpack/assets/javascripts/react_app/components/PermissionDenied/PermissionDenied.stories.mdx +++ /dev/null @@ -1,55 +0,0 @@ -import { Meta, Story, Canvas, ArgsTable } from '@theforeman/stories'; -import PermissionDenied from '.'; - - - -# PermissionDenied - -Use `PermissionDenied` to explain to the user about the permissions needed to perform an operation. - - - - - - - -## Props - - - -The `missingPermissions` prop accepts an array of strings, representing the missing permissions. - - - - - - - -The text can be overridden by using the `texts` prop. - - - - - - - -The back button will take the user to `/` by default. Use the `backHref` prop to change the back button url. - - - - - - diff --git a/webpack/assets/javascripts/react_app/components/ToastsList/stories.js b/webpack/assets/javascripts/react_app/components/ToastsList/stories.js deleted file mode 100644 index 79a7a16b40a..00000000000 --- a/webpack/assets/javascripts/react_app/components/ToastsList/stories.js +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; -import { Provider } from 'react-redux'; -import { - Form, - FormGroup, - FormControl, - ControlLabel, - Checkbox, - Grid, - Col, - Row, - Button, - Alert, -} from 'patternfly-react'; -import store from '../../redux'; -import ToastsList, { addToast } from './index'; -import Story from '../../../../../stories/components/Story'; - -export default { - title: 'Components/Toast Notifications', -}; - -export const toaster = () => { - const inputRefs = {}; - - const dispatchAddToast = () => { - const toast = { - message: inputRefs.message.value, - type: inputRefs.type.value, - sticky: inputRefs.sticky.checked, - timerdelay: Number(inputRefs.timerdelay.value), - }; - - if (inputRefs.showLink.checked) { - toast.link = { - href: inputRefs.linkUrl.value, - children: inputRefs.linkText.value, - }; - } - - store.dispatch(addToast(toast)); - }; - - const setRef = key => ref => { - inputRefs[key] = ref; - }; - - // eslint-disable-next-line react/prop-types - const FormField = ({ id, label, children }) => ( - - - {label} - - {React.cloneElement(children, { inputRef: setRef(id) })} - - ); - - const toastCreatorForm = ( -
- - - - - - {Alert.ALERT_TYPES.map(type => ( - - ))} - - - - - - - Sticky - - - Show Link - - - - - - - - - - - - -
- ); - - return ( - - - - {toastCreatorForm} - - - - - ); -}; diff --git a/webpack/assets/javascripts/react_app/components/common/ActionButtons/ActionButtons.stories.js b/webpack/assets/javascripts/react_app/components/common/ActionButtons/ActionButtons.stories.js deleted file mode 100644 index aeaf419728b..00000000000 --- a/webpack/assets/javascripts/react_app/components/common/ActionButtons/ActionButtons.stories.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import { ActionButtons } from './ActionButtons'; -import Story from '../../../../../../stories/components/Story'; -import Text from '../../../../../../stories/components/Text'; - -import { buttons } from './ActionButtons.fixtures'; - -export default { - title: 'Components/Common/ActionButtons', -}; - -export const ButtonsStory = () => ( - - - Input: an array of button props each containing: a title string and - an action object.
- action can be href with data-method or - onClick -
- Output: a button or drop down of buttons -
-
-
- No buttons - -
- One button - -
- Three buttons - -
-); - -ButtonsStory.story = { - name: 'ActionButtons', -}; diff --git a/webpack/assets/javascripts/react_app/components/common/DateTimePicker/DatePicker.stories.js b/webpack/assets/javascripts/react_app/components/common/DateTimePicker/DatePicker.stories.js deleted file mode 100644 index 77c96e15dd1..00000000000 --- a/webpack/assets/javascripts/react_app/components/common/DateTimePicker/DatePicker.stories.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { Grid, Row, Col } from 'patternfly-react'; -import DatePicker from './DatePicker'; - -export default { - title: 'Components/DatePicker', - component: DatePicker, - parameters: { - centered: { disable: true }, - }, -}; - -export const useDatePicker = () => ( - - - - - - - - -); diff --git a/webpack/assets/javascripts/react_app/components/common/DateTimePicker/DateTimePicker.stories.js b/webpack/assets/javascripts/react_app/components/common/DateTimePicker/DateTimePicker.stories.js deleted file mode 100644 index cdcc0a15ed4..00000000000 --- a/webpack/assets/javascripts/react_app/components/common/DateTimePicker/DateTimePicker.stories.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { Grid, Row, Col } from 'patternfly-react'; -import DateTimePicker from './DateTimePicker'; - -export default { - title: 'Components/DateTimePicker', - component: DateTimePicker, - parameters: { - centered: { disable: true }, - }, -}; - -export const useDateTimePicker = () => ( - - - - - - - - -); diff --git a/webpack/assets/javascripts/react_app/components/common/DocumentationLink/DocumentationLink.stories.js b/webpack/assets/javascripts/react_app/components/common/DocumentationLink/DocumentationLink.stories.js deleted file mode 100644 index e81f3afef06..00000000000 --- a/webpack/assets/javascripts/react_app/components/common/DocumentationLink/DocumentationLink.stories.js +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import { action } from '@storybook/addon-actions'; - -import Story from '../../../../../../stories/components/Story'; -import DocumentationLink from './index'; - -export default { - title: 'Components/DocumentationLink', -}; - -export const defaultStory = () => ( - -
    - -
-
-); - -defaultStory.story = { - name: 'Default', -}; diff --git a/webpack/assets/javascripts/react_app/components/common/EmptyState/EmptyState.stories.js b/webpack/assets/javascripts/react_app/components/common/EmptyState/EmptyState.stories.js deleted file mode 100644 index 818724f39a2..00000000000 --- a/webpack/assets/javascripts/react_app/components/common/EmptyState/EmptyState.stories.js +++ /dev/null @@ -1,134 +0,0 @@ -import React from 'react'; -import { Provider } from 'react-redux'; -import store from '../../../redux'; -import { text, select, boolean } from '@storybook/addon-knobs'; -import { action } from '@storybook/addon-actions'; -import { Button } from '@patternfly/react-core'; -import DefaultEmptyState, { EmptyStatePattern } from './index'; -import Story from '../../../../../../stories/components/Story'; - -export default { - title: 'Components/Empty State Pattern', -}; - -export const defaultStory = () => ( - - - -); - -defaultStory.story = { - name: 'Default', -}; - -export const withPrimaryAction = () => ( - - Do Something now! - } - /> - -); - -withPrimaryAction.story = { - name: 'with Primary Action', -}; - -export const withPrimaryAndSecondaryActions = () => ( - - - Create - - } - secondaryActions={ - - - - - - } - /> - -); - -withPrimaryAndSecondaryActions.story = { - name: 'with Primary and Secondary Actions', -}; - -export const withCustomizedDocumentation = () => ( - - - To read more about this click on the link below -
- Documentation - - } - /> -
-); - -withCustomizedDocumentation.story = { - name: 'with customized Documentation', -}; - -export const foremanEmptyState = () => { - const customizeDocLabel = boolean('customize doc label', false); - const customizeDocButtonLabel = boolean('customize button label', false); - const docObject = { url: '#' }; - if (customizeDocLabel) { - docObject.label = text('documentation label', 'Read documents ->'); - } - if (customizeDocButtonLabel) { - docObject.buttonLabel = text('documentation button label', 'Click here'); - } - return ( - - - - - - ); -}; diff --git a/webpack/assets/javascripts/react_app/components/common/SkeletonLoader/SkeletonLoader.stories.js b/webpack/assets/javascripts/react_app/components/common/SkeletonLoader/SkeletonLoader.stories.js deleted file mode 100644 index ebb31a357fc..00000000000 --- a/webpack/assets/javascripts/react_app/components/common/SkeletonLoader/SkeletonLoader.stories.js +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import { ExclamationCircleIcon } from '@patternfly/react-icons'; -import { STATUS } from '../../../constants'; -import Story from '../../../../../../stories/components/Story'; -import SkeletonLoader from '.'; - -export default { - title: 'Components/Common/SkeletonLoader', -}; - -export const defaultStory = () => ( - -
    - Loading: -
    - Content -
-
    - Loading with multiple lines: -
    - - Content - -
-
    - Resolved: -
    - Some Content -
-
    - Error: -
    - - Error - - } - /> -
-
    - Empty Value -
    - -
-
-); - -defaultStory.story = { - name: 'Skeleton Loader', -}; diff --git a/webpack/assets/javascripts/react_app/components/common/TypeAheadSelect/TypeAheadSelect.stories.js b/webpack/assets/javascripts/react_app/components/common/TypeAheadSelect/TypeAheadSelect.stories.js deleted file mode 100644 index 426fe61e9ae..00000000000 --- a/webpack/assets/javascripts/react_app/components/common/TypeAheadSelect/TypeAheadSelect.stories.js +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import TypeAheadSelect from '.'; -import Story from '../../../../../..//stories/components/Story'; -import storeDecorator from '../../../../../..//stories/storeDecorator'; - -export default { - title: 'Components/TypeAheadSelect', - decorators: [storeDecorator], - component: TypeAheadSelect, -}; - -export const defaultTypeAhead = () => ( - - - -); - -export const withPlaceholderText = () => ( - - - -); - -export const multipleSelections = () => ( - - - -); - -export const freehandSelections = () => ( - -
New options can be freely added that are not present in options
- -
-); - -export const withClearButton = () => ( - - - -); diff --git a/webpack/assets/javascripts/react_app/components/common/charts/AreaChart/AreaChart.stories.js b/webpack/assets/javascripts/react_app/components/common/charts/AreaChart/AreaChart.stories.js deleted file mode 100644 index 84287cb1323..00000000000 --- a/webpack/assets/javascripts/react_app/components/common/charts/AreaChart/AreaChart.stories.js +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import AreaChart from './'; -import { areaChartData } from './AreaChart.fixtures' - -export default { - title: 'Components/Charts/AreaChart', - component: AreaChart, -}; - -export const areaChart = () => ( - -); diff --git a/webpack/assets/javascripts/react_app/components/common/charts/BarChart/BarChart.stories.js b/webpack/assets/javascripts/react_app/components/common/charts/BarChart/BarChart.stories.js deleted file mode 100644 index 3c2eff7dff6..00000000000 --- a/webpack/assets/javascripts/react_app/components/common/charts/BarChart/BarChart.stories.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import BarChart from './'; - -export default { - title: 'Components/Charts/BarChart', - component: BarChart, -}; - -export const barChart = () => ( - -); diff --git a/webpack/assets/javascripts/react_app/components/common/charts/DonutChart/DonutChart.fixtures.js b/webpack/assets/javascripts/react_app/components/common/charts/DonutChart/DonutChart.fixtures.js index 831044c4dc0..ed7e6796307 100644 --- a/webpack/assets/javascripts/react_app/components/common/charts/DonutChart/DonutChart.fixtures.js +++ b/webpack/assets/javascripts/react_app/components/common/charts/DonutChart/DonutChart.fixtures.js @@ -3,7 +3,7 @@ export default [ ['column2', 50], ]; -export const mockStoryData = { +export const mockData = { donut: { width: 15, label: { diff --git a/webpack/assets/javascripts/react_app/components/common/charts/DonutChart/DonutChart.stories.js b/webpack/assets/javascripts/react_app/components/common/charts/DonutChart/DonutChart.stories.js deleted file mode 100644 index 7b74d5a6113..00000000000 --- a/webpack/assets/javascripts/react_app/components/common/charts/DonutChart/DonutChart.stories.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import DonutChart from './'; - -export default { - title: 'Components/Charts/DonutChart', - component: DonutChart, -}; - -export const donutChart = () => ( - -); diff --git a/webpack/assets/javascripts/react_app/components/common/charts/DonutChart/DonutChart.test.js b/webpack/assets/javascripts/react_app/components/common/charts/DonutChart/DonutChart.test.js index f8a82ffe8aa..f82921cb624 100644 --- a/webpack/assets/javascripts/react_app/components/common/charts/DonutChart/DonutChart.test.js +++ b/webpack/assets/javascripts/react_app/components/common/charts/DonutChart/DonutChart.test.js @@ -1,14 +1,14 @@ import { shallow } from '@theforeman/test'; import React from 'react'; -import { mockStoryData, emptyData } from './DonutChart.fixtures'; +import { mockData, emptyData } from './DonutChart.fixtures'; import DonutChart from './'; import * as chartService from '../../../../../services/charts/DonutChartService'; jest.unmock('./'); describe('renders DonutChart', () => { it('render donut chart', () => { - chartService.getDonutChartConfig = jest.fn(() => mockStoryData); - const wrapper = shallow(); + chartService.getDonutChartConfig = jest.fn(() => mockData); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); diff --git a/webpack/assets/javascripts/react_app/components/common/charts/LineChart/LineChart.stories.js b/webpack/assets/javascripts/react_app/components/common/charts/LineChart/LineChart.stories.js deleted file mode 100644 index adad7b65753..00000000000 --- a/webpack/assets/javascripts/react_app/components/common/charts/LineChart/LineChart.stories.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; - -import LineChart from './index'; - -export default { - title: 'Components/Charts/LineChart', - component: LineChart, -}; - -export const lineChart = () => ( - -); - -export const lineChartTimeseries = () => ( - -); diff --git a/webpack/assets/javascripts/react_app/components/common/charts/LineChart/index.js b/webpack/assets/javascripts/react_app/components/common/charts/LineChart/index.js index 0363599554d..5b168b59d10 100644 --- a/webpack/assets/javascripts/react_app/components/common/charts/LineChart/index.js +++ b/webpack/assets/javascripts/react_app/components/common/charts/LineChart/index.js @@ -7,6 +7,13 @@ import { getLineChartConfig } from '../../../../../services/charts/LineChartServ import MessageBox from '../../MessageBox'; +/* Data format example: + data={[ + ['red', [5, 7, 9], '#AA4643'], + ['green', [2, 4, 6], '#89A54E'], + ['x', [1557014400000, 1559779200000, 1562457600000], null], + ]} +*/ const LineChart = ({ data, title, diff --git a/webpack/assets/javascripts/react_app/components/common/dates/dates.stories.js b/webpack/assets/javascripts/react_app/components/common/dates/dates.stories.js deleted file mode 100644 index 85dd7deab2e..00000000000 --- a/webpack/assets/javascripts/react_app/components/common/dates/dates.stories.js +++ /dev/null @@ -1,104 +0,0 @@ -import React from 'react'; -import { date, boolean, select } from '@storybook/addon-knobs'; - -import IsoDate from './IsoDate'; -import LongDateTime from './LongDateTime'; -import RelativeDateTime from './RelativeDateTime'; -import ShortDateTime from './ShortDateTime'; -import { i18nProviderWrapperFactory } from '../../../common/i18nProviderWrapperFactory'; -import Code from '../../../../../../stories/components/Code'; -import Story from '../../../../../../stories/components/Story'; -import Text from '../../../../../../stories/components/Text'; - -export default { - title: 'Components/Common', -}; - -export const dates = () => { - const now = new Date(); - const defaultValue = new Date('2018-11-12T00:54:55-1100'); - - const dateToShow = new Date( - date('Date and time in your time zone', defaultValue) - ); - const showSeconds = boolean('Show seconds'); - const showRelativeTimeTooltip = boolean('Show relative time tooltip'); - - const timezoneOptions = [ - 'America/Phoenix', - 'America/Chicago', - 'America/New_York', - 'UTC', - 'Europe/Prague', - 'Europe/Kiev', - 'Asia/Jerusalem', - ]; - const timezone = select( - "User's time zone", - timezoneOptions, - timezoneOptions[1] - ); - - const DatesStorybook = i18nProviderWrapperFactory( - now, - timezone - )(() => ( - - -

Dates

- There are 4 date/time formats that should be used across the Foreman and - plugins. Each of the formats is represented by one React component. -
-
- Examples display {dateToShow.toString()}.

IsoDate

- Renders only date in iso format: -
-          
-        
-

LongDateTime

- Renders full date with time. Relative time tooltip and seconds can be - displayed optionally : -
-          
-        
- There's an erb helper alternative for rendering the same format - with relative time tooltip true as a default: - - date_time_absolute(time, :short, seconds = false, - show_relative_time_tooltip = true) - -

ShortDateTime

- Renders shortened date with time. Relative time tooltip and seconds can - be displayed optionally : -
-          
-        
- There's an erb helper alternative for rendering the same format - with relative time tooltip true as a default: - - date_time_absolute(time, :long, seconds = false, - show_relative_time_tooltip = true) - -

RelativeDateTime

- Renders relative date with long date in a tooltip: -
-          
-        
- There's an erb helper alternative for rendering a relative time: - date_time_relative(time) -
-
- )); - - return ; -}; diff --git a/webpack/assets/javascripts/react_app/components/common/forms/ForemanForm/ForemanForm.stories.js b/webpack/assets/javascripts/react_app/components/common/forms/ForemanForm/ForemanForm.stories.js deleted file mode 100644 index a51398aeb85..00000000000 --- a/webpack/assets/javascripts/react_app/components/common/forms/ForemanForm/ForemanForm.stories.js +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react'; -import * as Yup from 'yup'; -import PropTypes from 'prop-types'; - -import ForemanForm from '.'; -import TextField from '../TextField'; -import RadioButtonGroup from '../RadioButtonGroup'; -import Story from '../../../../../../../stories/components/Story'; - -const DisplayFormikState = props => ( -
-
-      props = {JSON.stringify(props, null, 2)}
-    
-
-); - -const fixtures = { - submitForm: () => {}, - initialValues: { - nickname: 'El Duderino', - weapon: 'hammer', - vegetarian: false, - }, - validationSchema: Yup.object().shape({ - nickname: Yup.string().required('is required'), - }), - onCancel: () => {}, -}; - -const radios = [ - { label: 'Hammer', value: 'hammer' }, - { label: 'Nail Gun', value: 'nailgun' }, - { label: 'Wrecking Ball', value: 'wreckingball' }, -]; - -const FormComponent = ({ - submitForm, - initialValues, - validationSchema, - onCancel, -}) => ( - submitForm(values)} - initialValues={initialValues} - validationSchema={validationSchema} - onCancel={onCancel} - > - - - - - -); - -FormComponent.propTypes = { - submitForm: PropTypes.func.isRequired, - initialValues: PropTypes.object.isRequired, - validationSchema: PropTypes.object.isRequired, - onCancel: PropTypes.func.isRequired, -}; - -export default { - title: 'Components/Foreman Form', -}; - -export const basicForemanForm = () => ( - - - -); diff --git a/webpack/assets/javascripts/react_app/components/common/forms/Form.stories.js b/webpack/assets/javascripts/react_app/components/common/forms/Form.stories.js deleted file mode 100644 index 8f8ad28fc31..00000000000 --- a/webpack/assets/javascripts/react_app/components/common/forms/Form.stories.js +++ /dev/null @@ -1,119 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Grid, Row } from 'patternfly-react'; -import { number, text } from '@storybook/addon-knobs'; -import { action } from '@storybook/addon-actions'; -import { Formik } from 'formik'; -import RadioButtonGroup from './RadioButtonGroup'; -import FormField from './FormField'; -import { registerInputComponent } from './InputFactory'; -import Form from './Form'; -import OrderableSelect from './OrderableSelect'; -import storeDecorator from '../../../../../../stories/storeDecorator'; -import Story from '../../../../../../stories/components/Story'; - -import { yesNoOpts } from './__fixtures__/Form.fixtures'; -import { - textFieldWithHelpProps, - selectProps, - dateTimeWithErrorProps, - ownComponentFieldProps, - counterProps, - memoryProps, -} from './FormField.fixtures'; - -const StoryForm = () => ( - {}} - initialValues={{ hamburger: 'yes' }} - > - {formikProps => ( -
- - - )} -
-); - -function CustomSelect(props) { - return ( - - ); -} -CustomSelect.propTypes = { - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, -}; -registerInputComponent('ownInput', CustomSelect); - -export default { - title: 'Components/Form', - decorators: [storeDecorator], -}; - -export const radioButtonGroup = () => ( - - - -); - -export const formField = () => ( - - - - - - - - - - - - - - - - - - - - - - -); - -formField.story = { - name: 'FormField', -}; - -export const orderableSelect = () => ( - - - -); - -orderableSelect.story = { - name: 'OrderableSelect', -}; diff --git a/webpack/assets/javascripts/react_app/components/common/forms/OrderableSelect/Orderable.stories.js b/webpack/assets/javascripts/react_app/components/common/forms/OrderableSelect/Orderable.stories.js deleted file mode 100644 index 05b117f443c..00000000000 --- a/webpack/assets/javascripts/react_app/components/common/forms/OrderableSelect/Orderable.stories.js +++ /dev/null @@ -1,75 +0,0 @@ -import React, { useState } from 'react'; -import { DndProvider } from 'react-dnd'; -import HTML5Backend from 'react-dnd-html5-backend'; -import PropTypes from 'prop-types'; - -import Story from '../../../../../../../stories/components/Story'; -import { orderable, orderDragged } from './helpers'; -import { yesNoOpts } from '../__fixtures__/Form.fixtures'; - -const style = { - border: '1px dashed gray', - padding: '0.5rem 1rem', - marginBottom: '.5rem', - backgroundColor: 'white', - cursor: 'move', -}; -const StoryTag = ({ text, isDragging, value }) => { - const opacity = isDragging ? 0.6 : 1; - return ( - - {text} - - ); -}; -StoryTag.propTypes = { - text: PropTypes.string.isRequired, - isDragging: PropTypes.bool.isRequired, - value: PropTypes.string.isRequired, -}; - -const OrderableStoryTag = orderable(StoryTag, { - type: 'storyTag', - getItem: props => ({ value: props.value }), -}); - -const OrderAppSandbox = props => { - const [options, setOptions] = useState(props.options); - - const moveValue = (dragIndex, hoverIndex) => { - setOptions(orderDragged(options, dragIndex, hoverIndex)); - }; - - return ( -
- {options.map((opt, i) => ( - - ))} -
- ); -}; -OrderAppSandbox.propTypes = { - options: PropTypes.object.isRequired, -}; - -export default { - title: 'Components/Common/Orderable', -}; - -export const orderableStory = () => ( - - - - - -); - -orderableStory.story = { - name: 'Orderable', -}; diff --git a/webpack/assets/javascripts/react_app/components/common/table/TableSelection.stories.mdx b/webpack/assets/javascripts/react_app/components/common/table/TableSelection.stories.mdx deleted file mode 100644 index d301a41e117..00000000000 --- a/webpack/assets/javascripts/react_app/components/common/table/TableSelection.stories.mdx +++ /dev/null @@ -1,85 +0,0 @@ -import { Meta } from '@theforeman/stories'; -import { Icon } from 'patternfly-react'; - - - -# Table With Select boxes - -All needed functions and components are located at: `/react_app/components/common/table/index.js` -To add select boxes to your foreman table please follow this steps: - -## Connect The reducer -Create a unique reducer for your table by calling the `selectionReducer(tableID)` from the table folder to your component. -**tableID** - a string that will represents the table in the store and will be used in the actions - - -{' '}Tip: You can connect a few reducers to one component using Redux's - combineReducers - -## Connect The Actions - -Create a `selectionController` using the `getSelectionController` helper - -```js -getSelectionController({ - tableID, - allRowsSelected, - rows, - selectedRows, - dispatch, -}); -``` - -You will need to provide the function the following argumets: -**tableID**: a string that will represents the table in the store -**allRowsSelected**: a boolean that describes if all the rows available are selected. -This boolean is provided by the selection reducer and should be in the components store -**rows**: an array of row object that are available in the current page of the table. Each object should have an id. -**selectedRows**: an array of the selected ids (if all rows are selected this can be empty) This array is provided by the selection reducer and should be in the components store. -**dispatch**: dispatch function from the Redux store. -Can be created using the useDispatch() hook. -This is used for the selction actions. - -## Add To The Table TableScheme - -In the table schema array add the following item: - -```js - column( - '', // property - 'Select all rows', // label - [label => selectionHeaderCellFormatter(selectionController, label)], // headFormat - [ - (value, additionalData) => - selectionCellFormatter(selectionController, additionalData), - ] // cellFormat - ), -``` - -The `property` is not used in the formatter so it should be empty. -The `label` will be user for the tooltip information (title property) and aria-label. - -### Data provided by the reducer - -**allRowsSelected**: a boolean that describes if all the rows available are selected -**selectedRows**: an array of the selected ids -**showSelectAll**: a boolean that indicates should the select all option be available? -This is true after a user is selection the whole page. Use this to show a select all button or message box. - -### Additional Actions - -If you want to use the selection action for uses outside the table you can use import selection actions from the table folder. -Available actions: - -```js -selectPage(tableID, rows); -selectAllRows(tableID); -unselectAllRows(tableID); -selectRow(tableID, id); -unselectRow(tableID, id, rows); -``` diff --git a/webpack/assets/javascripts/react_app/components/common/table/schemaHelpers/selection.js b/webpack/assets/javascripts/react_app/components/common/table/schemaHelpers/selection.js index 033e0f9a25a..36b1d54ffb8 100644 --- a/webpack/assets/javascripts/react_app/components/common/table/schemaHelpers/selection.js +++ b/webpack/assets/javascripts/react_app/components/common/table/schemaHelpers/selection.js @@ -5,6 +5,19 @@ import { unselectRow, } from '../actions/selectionActions'; +/** + * @property {string} tableID - A string that represents the table in the store. + * @property {boolean} allRowsSelected - A boolean that describes if all the rows available are selected. + * This boolean is provided by the selection reducer and should be in the component's store. + * @property {Object[]} rows - An array of row objects that are available in the current page of the table. + * Each object should have an id. + * @property {string[]} selectedRows - An array of the selected ids (if all rows are selected this can be empty). + * This array is provided by the selection reducer and should be in the component's store. + * @property {function} dispatch - Dispatch function from the Redux store. + * Can be created using the `useDispatch()` hook. + * This is used for the selection actions. + */ + export const getSelectionController = ({ tableID, allRowsSelected, diff --git a/webpack/assets/javascripts/react_app/components/hosts/powerStatus/PowerStatus.stories.js b/webpack/assets/javascripts/react_app/components/hosts/powerStatus/PowerStatus.stories.js deleted file mode 100644 index 6f6948bc57e..00000000000 --- a/webpack/assets/javascripts/react_app/components/hosts/powerStatus/PowerStatus.stories.js +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react'; -import PowerStatus from './index'; -import { - Story, - Code, - Text, - StoryWithCustomState, -} from '../../../../../../stories/components'; -import { - pendingStore, - resolvedStore, - resolvedStoreWithOff, - errorStore, - serverProps, -} from './PowerStatus.fixtures'; - -export default { - title: 'Components/Power Status', -}; - -export const loading = () => ( - - - -); - -export const ON = () => ( - - - -); - -export const OFF = () => ( - - - -); - -export const errorStory = () => ( - - - -); - -errorStory.story = { - name: 'Error', -}; - -export const connectedMD = () => ( - - -

Using the connected component

-
-

- Currently the redux-connected component receives the following props: -

- {'{ id, url }'} -

- On the connected component mount we are making an API call with the - given url, -

-

- the state will update by it and the PowerStatus component will receive - the following props from the selectors: -

- {'{ state, title }'} -
-
-); - -connectedMD.story = { - name: 'Using the redux-connected component', -}; diff --git a/webpack/assets/javascripts/react_app/components/hosts/storage/vmware/__tests__/StorageContainer.fixtures.js b/webpack/assets/javascripts/react_app/components/hosts/storage/vmware/__tests__/StorageContainer.fixtures.js index 1f307971441..35d89a224cd 100644 --- a/webpack/assets/javascripts/react_app/components/hosts/storage/vmware/__tests__/StorageContainer.fixtures.js +++ b/webpack/assets/javascripts/react_app/components/hosts/storage/vmware/__tests__/StorageContainer.fixtures.js @@ -66,144 +66,4 @@ export const hiddenFieldValue = { thin: true, }, ], -}; - -/* storybook data */ -export const state1 = { - config: { - datastoresUrl: '/api/v2/compute_resources/1/available_storage_domains', - storagePodsUrl: '/api/v2/compute_resources/1/available_storage_pods', - controllerTypes, - diskModeTypes: defaultDiskModeTypes, - storagePods: storagePodsStubData, - datastores: datastoresStubData('org'), - paramsScope: 'abc', - }, - volumes: [ - { - thin: true, - name: 'Hard disk', - mode: 'persistent', - controllerKey: 1000, - sizeGb: 10, - }, - ], - controllers: [{ type: 'VirtualLsiLogicController', key: 1000 }], - cluster: 'Foreman_Cluster', -}; - -export const state2 = { - config: { - datastoresUrl: '/api/v2/compute_resources/1/available_storage_domains', - storagePodsUrl: '/api/v2/compute_resources/1/available_storage_pods', - controllerTypes, - diskModeTypes: defaultDiskModeTypes, - paramsScope: 'abc', - }, - controllers: [ - { - type: 'VirtualLsiLogicController', - sharedBus: 'noSharing', - unitNumber: 7, - key: 1000, - }, - ], - volumes: [ - { - thin: true, - name: 'Hard disk 1', - mode: 'persistent', - controllerKey: 1000, - serverId: '502e324d-a2af-108b-1e10-b6d9eddfc53a', - datastore: 'MyDatastore', - id: '6000C297-9a11-998a-fc7c-8125ce9042a3', - filename: - '[Local-Ironforge] wanda-marcial.www.somedomain.com/wanda-marcial.www.somedomain.com.vmdk', - sizeGb: 10, - key: 2000, - unitNumber: 0, - }, - ], - cluster: 'Foreman_Cluster', -}; - -export const clone = { - config: { - datastoresUrl: '/api/v2/compute_resources/1/available_storage_domains', - storagePodsUrl: '/api/v2/compute_resources/1/available_storage_pods', - vmExists: false, - controllerTypes, - diskModeTypes: { - persistent: '»Persistent«', - independent_persistent: '»Independent - Persistent«', - independent_nonpersistent: '»Independent - Nonpersistent«', - }, - }, - volumes: [ - { - thin: true, - name: 'Hard disk 1', - mode: 'persistent', - controllerKey: 1000, - serverId: '500478d2-0c9b-3652-a378-b7703858a3c8', - datastore: 'MyDatastore', - id: '6000C293-d882-595d-670c-836daa2a2aa4', - filename: '[MyDatastore] alton-buttner.example.com/alton-buttner.example.com.vmdk', - size: 13631488, - key: 2000, - unitNumber: 0, - sizeGb: 13, - }, - { - thin: false, - name: 'Hard disk 2', - mode: 'persistent', - controllerKey: 1001, - serverId: '500478d2-0c9b-3652-a378-b7703858a3c8', - datastore: 'MyDatastore', - id: '6000C292-ca02-a2e7-c868-fe8f86d66ae8', - filename: '[MyDatastore] alton-buttner.example.com/alton-buttner.example.com_1.vmdk', - size: 11534336, - key: 2016, - unitNumber: 0, - sizeGb: 11, - }, - { - thin: false, - name: 'Hard disk 3', - mode: 'persistent', - controllerKey: 1001, - serverId: '500478d2-0c9b-3652-a378-b7703858a3c8', - datastore: 'MyDatastore', - id: '6000C294-4706-a370-4f30-8022353519ba', - filename: '[MyDatastore] alton-buttner.example.com/alton-buttner.example.com_2.vmdk', - size: 1048576, - key: 2017, - unitNumber: 1, - sizeGb: 1, - }, - ], - controllers: [ - { - type: 'VirtualLsiLogicController', - key: 1000, - }, - { - type: 'VirtualLsiLogicController', - key: 1001, - }, - ], - cluster: 'Foreman_Cluster', -}; - -export const emptyState = { - config: { - datastoresUrl: '/api/v2/compute_resources/1/available_storage_domains', - storagePodsUrl: '/api/v2/compute_resources/1/available_storage_pods', - controllerTypes, - diskModeTypes: defaultDiskModeTypes, - }, - volumes: [], - controllers: [], - cluster: null, -}; +}; \ No newline at end of file diff --git a/webpack/assets/javascripts/react_app/components/hosts/storage/vmware/__tests__/StorageContainer.stories.js b/webpack/assets/javascripts/react_app/components/hosts/storage/vmware/__tests__/StorageContainer.stories.js deleted file mode 100644 index 63284215046..00000000000 --- a/webpack/assets/javascripts/react_app/components/hosts/storage/vmware/__tests__/StorageContainer.stories.js +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import storeDecorator from '../../../../../../../../stories/storeDecorator'; -import StorageContainer from '../index'; -import * as VMWareData from './StorageContainer.fixtures'; -import Story from '../../../../../../../../stories/components/Story'; - -export default { - title: 'Page chunks/Host VMWare Storage', - decorators: [storeDecorator], -}; - -export const defaultStateForNewHost = () => { - return ( - - - - ); -}; - -defaultStateForNewHost.story = { - name: 'default state for new host', - decorators: [storeDecorator], -}; - -export const multipleControllers = () => { - return ( - - - - ); -}; - -multipleControllers.story = { - name: 'multiple controllers', - decorators: [storeDecorator], -}; - -export const onClone = () => { - return ( - - - - ); -}; - -onClone.story = { - name: 'on clone', - decorators: [storeDecorator], -}; - -export const withoutAnyControllers = () => { - return ( - - - - ); -}; - -withoutAnyControllers.story = { - name: 'without any controllers', - decorators: [storeDecorator], -}; diff --git a/webpack/assets/javascripts/react_app/routes/common/PageLayout/components/ExportButton/ExportButton.stories.js b/webpack/assets/javascripts/react_app/routes/common/PageLayout/components/ExportButton/ExportButton.stories.js deleted file mode 100644 index 951f9d439b2..00000000000 --- a/webpack/assets/javascripts/react_app/routes/common/PageLayout/components/ExportButton/ExportButton.stories.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import Story from '../../../../../../../../stories/components/Story'; -import ExportButton from './ExportButton'; - -export default { - title: 'Components/ExportButton', -}; - -export const defaultStory = () => ( - -
    - -
-
-); - -defaultStory.story = { - name: 'Default', -}; diff --git a/webpack/stories/components/Code/Code.js b/webpack/stories/components/Code/Code.js deleted file mode 100644 index c5d5bf3c208..00000000000 --- a/webpack/stories/components/Code/Code.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import hljs from 'highlight.js'; -import './Code.scss'; - -const Code = ({ children, lang }) => { - if (lang !== undefined) { - const highlightedCode = hljs.highlight(lang, children).value; - return
;
-  }
-
-  return 
{children}
; -}; - -Code.propTypes = { - lang: PropTypes.string.isRequired, - children: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.node), - PropTypes.node, - ]).isRequired, -}; - -export default Code; diff --git a/webpack/stories/components/Code/Code.scss b/webpack/stories/components/Code/Code.scss deleted file mode 100644 index c44bc448b4f..00000000000 --- a/webpack/stories/components/Code/Code.scss +++ /dev/null @@ -1 +0,0 @@ -@import '~highlight.js/styles/github'; diff --git a/webpack/stories/components/Code/index.js b/webpack/stories/components/Code/index.js deleted file mode 100644 index affce8530be..00000000000 --- a/webpack/stories/components/Code/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import Code from './Code'; - -export default Code; diff --git a/webpack/stories/components/Markdown/Markdown.js b/webpack/stories/components/Markdown/Markdown.js deleted file mode 100644 index 8da0a3d5dc1..00000000000 --- a/webpack/stories/components/Markdown/Markdown.js +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import hljs from 'highlight.js'; -import Remarkable from 'react-remarkable'; -import Text from '../Text'; -import './Markdown.scss'; - -const Markdown = ({ source }) => { - const options = { - html: true, - breaks: true, - linkTarget: '_parent', - highlight(code, lang) { - if (lang !== undefined) { - return hljs.highlight(lang, code).value; - } - return ''; - }, - }; - - return ( - - - - ); -}; - -Markdown.propTypes = { - source: PropTypes.string, -}; - -Markdown.defaultProps = { - source: '', -}; - -export default Markdown; diff --git a/webpack/stories/components/Markdown/Markdown.scss b/webpack/stories/components/Markdown/Markdown.scss deleted file mode 100644 index c44bc448b4f..00000000000 --- a/webpack/stories/components/Markdown/Markdown.scss +++ /dev/null @@ -1 +0,0 @@ -@import '~highlight.js/styles/github'; diff --git a/webpack/stories/components/Markdown/index.js b/webpack/stories/components/Markdown/index.js deleted file mode 100644 index c81e62b3c14..00000000000 --- a/webpack/stories/components/Markdown/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import Markdown from './Markdown'; - -export default Markdown; diff --git a/webpack/stories/components/Story/Story.js b/webpack/stories/components/Story/Story.js deleted file mode 100644 index ea5f1b1104b..00000000000 --- a/webpack/stories/components/Story/Story.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Story.scss'; - -const Story = ({ narrow, children }) => { - const classes = `story ${narrow ? 'narrow' : ''}`; - - return
{children}
; -}; - -Story.propTypes = { - narrow: PropTypes.bool, - children: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.node), - PropTypes.node, - ]), -}; - -Story.defaultProps = { - narrow: false, - children: null, -}; - -export default Story; diff --git a/webpack/stories/components/Story/Story.scss b/webpack/stories/components/Story/Story.scss deleted file mode 100644 index 646d5f18d7e..00000000000 --- a/webpack/stories/components/Story/Story.scss +++ /dev/null @@ -1,7 +0,0 @@ -.story { - padding: 30px 50px; - - &.narrow { - max-width: 700px; - } -} diff --git a/webpack/stories/components/Story/index.js b/webpack/stories/components/Story/index.js deleted file mode 100644 index 708d55e33a1..00000000000 --- a/webpack/stories/components/Story/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import Story from './Story'; - -export default Story; diff --git a/webpack/stories/components/StoryWithCustomState/index.js b/webpack/stories/components/StoryWithCustomState/index.js deleted file mode 100644 index 85b4e6cd46c..00000000000 --- a/webpack/stories/components/StoryWithCustomState/index.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Provider } from 'react-redux'; -import { action } from '@storybook/addon-actions'; // eslint-disable-line import/no-extraneous-dependencies -import Story from '../Story'; - -// A super-simple mock of a redux store with a custom state. -const StoryWithCustomState = ({ state, children }) => { - const subscribe = () => 0; - const dispatch = () => action('dispatch'); - - return ( - state, - subscribe, - dispatch, - }} - > - {children} - - ); -}; - -StoryWithCustomState.propTypes = { - state: PropTypes.object.isRequired, - children: PropTypes.node.isRequired, -}; - -export default StoryWithCustomState; diff --git a/webpack/stories/components/Text/Text.js b/webpack/stories/components/Text/Text.js deleted file mode 100644 index 5589fdbe7ba..00000000000 --- a/webpack/stories/components/Text/Text.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import './Text.scss'; - -const Text = ({ children }) =>
{children}
; - -Text.propTypes = { - children: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.node), - PropTypes.node, - ]), -}; - -Text.defaultProps = { - children: null, -}; - -export default Text; diff --git a/webpack/stories/components/Text/Text.scss b/webpack/stories/components/Text/Text.scss deleted file mode 100644 index 2054afd4a90..00000000000 --- a/webpack/stories/components/Text/Text.scss +++ /dev/null @@ -1,16 +0,0 @@ -.story { - .text { - h1 { - margin-top: 40px; - font-size: 28px; - } - - h2 { - margin-top: 40px; - } - - h3 { - margin-top: 40px; - } - } -} diff --git a/webpack/stories/components/Text/index.js b/webpack/stories/components/Text/index.js deleted file mode 100644 index ddd972643d3..00000000000 --- a/webpack/stories/components/Text/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import Text from './Text'; - -export default Text; diff --git a/webpack/stories/components/index.js b/webpack/stories/components/index.js deleted file mode 100644 index bd10d2315b2..00000000000 --- a/webpack/stories/components/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export { default as Code } from './Code'; -export { default as Markdown } from './Markdown'; -export { default as Story } from './Story'; -export { default as Text } from './Text'; -export { default as StoryWithCustomState } from './StoryWithCustomState'; diff --git a/webpack/stories/data/charts/donutChartMockData.js b/webpack/stories/data/charts/donutChartMockData.js deleted file mode 100644 index b63ffdec22a..00000000000 --- a/webpack/stories/data/charts/donutChartMockData.js +++ /dev/null @@ -1,84 +0,0 @@ -const WithDataProps = { - config: { - donut: { - width: 15, - label: { - show: false, - }, - }, - data: { - type: 'donut', - columns: [ - ['Fedora 21', 3], - ['Ubuntu 14.04', 4], - ['Centos 7', 2], - ['Debian 8', 1], - ], - names: { - 'Fedora 21': 'Fedora 21', - 'Ubuntu 14.04': 'Ubuntu 14.04', - 'Centos 7': 'Centos 7', - 'Debian 8': 'Debian 8', - }, - }, - tooltip: { - show: true, - }, - legend: { - show: false, - }, - padding: { - top: 0, - left: 0, - right: 0, - bottom: 0, - }, - bindto: '#operatingsystemChart', - }, - modalConfig: { - donut: { - width: 25, - label: { - show: false, - }, - }, - data: { - type: 'donut', - columns: [ - ['Fedora 21', 3], - ['Ubuntu 14.04', 4], - ['Centos 7', 2], - ['Debian 8', 1], - ], - names: { - 'Fedora 21': 'Fedora 21', - 'Ubuntu 14.04': 'Ubuntu 14.04', - 'Centos 7': 'Centos 7', - 'Debian 8': 'Debian 8', - }, - }, - tooltip: { - show: true, - }, - legend: { - show: false, - }, - padding: { - top: 0, - left: 0, - right: 0, - bottom: 0, - }, - size: { - height: 500, - }, - }, - noDataMsg: 'No data available', - tip: 'Expand the chart', - status: 'RESOLVED', - id: 'operatingsystem', - title: 'OS Distribution', - search: '/hosts?search=os_title=~VAL~', -}; - -export default WithDataProps; diff --git a/webpack/stories/data/storage/vmware.js b/webpack/stories/data/storage/vmware.js deleted file mode 100644 index f60655aec35..00000000000 --- a/webpack/stories/data/storage/vmware.js +++ /dev/null @@ -1,356 +0,0 @@ -/* eslint-disable */ -/* eslint-disable */ -export const state1 = { - config: { - controllerTypes: { - VirtualBusLogicController: 'Bus Logic Parallel', - VirtualLsiLogicController: 'LSI Logic Parallel', - VirtualLsiLogicSASController: 'LSI Logic SAS', - ParaVirtualSCSIController: 'VMware Paravirtual' - }, - diskModeTypes: { - persistent: 'Persistent', - independent_persistent: 'Independent - Persistent', - independent_nonpersistent: 'Independent - Nonpersistent' - }, - storagePods: { - StorageCluster: 'StorageCluster (free: 1.01 TB, prov: 7.49 TB, total: 8.5 TB)' - }, - datastores: { - 'org-esx-55-01-local': 'org-esx-55-01-local (free: 524 GB, prov: 465 GB, total: 924 GB)', - 'org-esx-55-03-local': 'org-esx-55-03-local (free: 898 GB, prov: 165 GB, total: 924 GB)', - 'org-esx-55-04-local': 'org-esx-55-04-local (free: 250 GB, prov: 681 GB, total: 924 GB)', - 'org-esx-55-na01a': 'org-esx-55-na01a (free: 448 GB, prov: 8.56 TB, total: 4 TB)', - 'org-esx-55-na01b': 'org-esx-55-na01b (free: 587 GB, prov: 7.25 TB, total: 4.5 TB)', - 'org-esx-admin-lun-na01b': - 'org-esx-admin-lun-na01b (free: 553 GB, prov: 519 GB, total: 1020 GB)', - 'org-esx-glob-na01a-s': 'org-esx-glob-na01a-s (free: 1.45 TB, prov: 3.49 TB, total: 1.9 TB)', - 'org-esx-glob-na01b-s': 'org-esx-glob-na01b-s (free: 1.37 TB, prov: 2.22 TB, total: 1.9 TB)', - 'org-iso-glob-na01a-s': 'org-iso-glob-na01a-s (free: 341 GB, prov: 134 GB, total: 475 GB)', - 'do-not-use-datastore': 'do-not-use-datastore (free: 462 GB, prov: 12.6 GB, total: 475 GB)', - 'do-not-use-host-prov': 'do-not-use-host-prov (free: 0 Bytes, prov: 973 MB, total: 973 MB)', - master_iso: 'master_iso (free: 689 GB, prov: 289 GB, total: 973 GB)', - temp_store: 'temp_store (free: 475 GB, prov: 19.5 MB, total: 475 GB)', - vsanDatastore: 'vsanDatastore (free: 207 GB, prov: 26.1 GB, total: 233 GB)' - }, - paramsScope: 'abc' - }, - volumes: [ - { - thin: true, - name: 'Hard disk', - mode: 'persistent', - controllerKey: 1000, - sizeGb: 10 - } - ], - controllers: [{ type: 'VirtualLsiLogicController', key: 1000 }] -}; - -export const state2 = { - config: { - controllerTypes: { - VirtualBusLogicController: 'Bus Logic Parallel', - VirtualLsiLogicController: 'LSI Logic Parallel', - VirtualLsiLogicSASController: 'LSI Logic SAS', - ParaVirtualSCSIController: 'VMware Paravirtual' - }, - diskModeTypes: { - persistent: 'Persistent', - independent_persistent: 'Independent - Persistent', - independent_nonpersistent: 'Independent - Nonpersistent' - }, - storagePods: {}, - datastores: { - 'Local-Bulgaria': 'Local-Bulgaria (free: 4.62 TB, prov: 2.34 TB, total: 5.91 TB)', - 'Local-Ironforge': 'Local-Ironforge (free: 1.49 TB, prov: 1.3 TB, total: 2.72 TB)', - 'Local-Jericho': 'Local-Jericho (free: 1.99 TB, prov: 3.02 TB, total: 4.09 TB)', - 'Local-Nightwing': 'Local-Nightwing (free: 591 GB, prov: 182 GB, total: 756 GB)', - 'Local-Supermicro': 'Local-Supermicro (free: 599 GB, prov: 317 GB, total: 917 GB)', - 'NFS-Engineering': 'NFS-Engineering (free: 2.3 TB, prov: 1.74 TB, total: 2.64 TB)' - }, - paramsScope: 'abc' - }, - controllers: [ - { - type: 'VirtualLsiLogicController', - sharedBus: 'noSharing', - unitNumber: 7, - key: 1000 - } - ], - volumes: [ - { - thin: true, - name: 'Hard disk 1', - mode: 'persistent', - controllerKey: 1000, - serverId: '502e324d-a2af-108b-1e10-b6d9eddfc53a', - datastore: 'Local-Ironforge', - id: '6000C297-9a11-998a-fc7c-8125ce9042a3', - filename: - '[Local-Ironforge] wanda-marcial.www.somedomain.com/wanda-marcial.www.somedomain.com.vmdk', - sizeGb: 10, - key: 2000, - unitNumber: 0 - } - ] -}; - -export const clone = { - config: { - vmExists: false, - controllerTypes: { - VirtualBusLogicController: 'Bus Logic Parallel', - VirtualLsiLogicController: 'LSI Logic Parallel', - VirtualLsiLogicSASController: 'LSI Logic SAS', - ParaVirtualSCSIController: 'VMware Paravirtual' - }, - diskModeTypes: { - persistent: '»Persistent«', - independent_persistent: '»Independent - Persistent«', - independent_nonpersistent: '»Independent - Nonpersistent«' - }, - storagePods: { - 'LX-ESX-SOA-DC1-NoMirror': - 'LX-ESX-SOA-DC1-NoMirror (»free«: 3.82 TB, »prov«: 5.18 TB, »total«: 9 TB)', - 'LX-ESX-SOA-DC2-NoMirror': - 'LX-ESX-SOA-DC2-NoMirror (»free«: 3.79 TB, »prov«: 5.21 TB, »total«: 9 TB)', - 'LX-LAN-MIRROR-DC1': 'LX-LAN-MIRROR-DC1 (»free«: 4.62 TB, »prov«: 14.9 TB, »total«: 19.5 TB)', - 'LX-LAN-MIRROR-DC2': 'LX-LAN-MIRROR-DC2 (»free«: 3.93 TB, »prov«: 8.38 TB, »total«: 12.3 TB)', - 'LX-LAN-NOMIRROR-DC1': - 'LX-LAN-NOMIRROR-DC1 (»free«: 4.94 TB, »prov«: 13.1 TB, »total«: 18 TB)', - 'LX-LAN-NOMIRROR-DC2': - 'LX-LAN-NOMIRROR-DC2 (»free«: 5.96 TB, »prov«: 12 TB, »total«: 17.9 TB)', - 'LX-LAN-NOMIRROR-RZ3': - 'LX-LAN-NOMIRROR-RZ3 (»free«: 597 GB, »prov«: 403 GB, »total«: 1000 GB)', - 'LX-LAN-NOMIRROR-SSD-DC1': - 'LX-LAN-NOMIRROR-SSD-DC1 (»free«: 407 GB, »prov«: 105 GB, »total«: 512 GB)', - 'LX-LAN-NOMIRROR-SSD-DC2': - 'LX-LAN-NOMIRROR-SSD-DC2 (»free«: 386 GB, »prov«: 126 GB, »total«: 512 GB)', - 'LX-Root-LAN-DC1': 'LX-Root-LAN-DC1 (»free«: 2.28 TB, »prov«: 3.22 TB, »total«: 5.5 TB)', - 'LX-Root-LAN-DC2': 'LX-Root-LAN-DC2 (»free«: 2.04 TB, »prov«: 1.96 TB, »total«: 4 TB)', - LX_LAN_BDA_DC1: 'LX_LAN_BDA_DC1 (»free«: 3.89 TB, »prov«: 1.11 TB, »total«: 5 TB)', - LX_LAN_DC1_BDABI_NoBackup: - 'LX_LAN_DC1_BDABI_NoBackup (»free«: 8.45 TB, »prov«: 1.55 TB, »total«: 10 TB)', - LX_LAN_DC1_BDASAS_NoBackup: - 'LX_LAN_DC1_BDASAS_NoBackup (»free«: 14.6 TB, »prov«: 15.9 TB, »total«: 30.5 TB)' - }, - datastores: { - ESX_RZ3_1: 'ESX_RZ3_1 (»free«: 346 GB, »prov«: 709 GB, »total«: 500 GB)', - ESX_RZ3_2: 'ESX_RZ3_2 (»free«: 251 GB, »prov«: 877 GB, »total«: 500 GB)', - FC0001_LX_LAN_ROOT_PROD_01: - 'FC0001_LX_LAN_ROOT_PROD_01 (»free«: 218 GB, »prov«: 1.13 TB, »total«: 1020 GB)', - FC0001_LX_LAN_ROOT_PROD_02: - 'FC0001_LX_LAN_ROOT_PROD_02 (»free«: 238 GB, »prov«: 1.74 TB, »total«: 1020 GB)', - FC0001_LX_LAN_ROOT_PROD_03: - 'FC0001_LX_LAN_ROOT_PROD_03 (»free«: 211 GB, »prov«: 4.39 TB, »total«: 1020 GB)', - FC0001_LX_LAN_ROOT_PROD_04: - 'FC0001_LX_LAN_ROOT_PROD_04 (»free«: 1.63 TB, »prov«: 2.99 TB, »total«: 2.5 TB)', - FC0001_LX_LAN_ROOT_PROD_05: - 'FC0001_LX_LAN_ROOT_PROD_05 (»free«: 1.72 TB, »prov«: 6.33 TB, »total«: 2.5 TB)', - FC0001_LX_LAN_ROOT_PROD_06: - 'FC0001_LX_LAN_ROOT_PROD_06 (»free«: 2.16 TB, »prov«: 2.4 TB, »total«: 2.5 TB)', - FC0002_LX_LAN_ROOT_PROD_01: - 'FC0002_LX_LAN_ROOT_PROD_01 (»free«: 632 GB, »prov«: 1.37 TB, »total«: 1020 GB)', - FC0002_LX_LAN_ROOT_PROD_02: - 'FC0002_LX_LAN_ROOT_PROD_02 (»free«: 283 GB, »prov«: 1010 GB, »total«: 1020 GB)', - FC0002_LX_LAN_ROOT_PROD_03: - 'FC0002_LX_LAN_ROOT_PROD_03 (»free«: 382 GB, »prov«: 2.57 TB, »total«: 1020 GB)', - FC0002_LX_LAN_ROOT_PROD_04: - 'FC0002_LX_LAN_ROOT_PROD_04 (»free«: 787 GB, »prov«: 1.96 TB, »total«: 1020 GB)', - LX_ESX_DC1_01_NoMirror: - 'LX_ESX_DC1_01_NoMirror (»free«: 628 GB, »prov«: 2.03 TB, »total«: 2.25 TB)', - LX_ESX_DC1_NoMirror_08: - 'LX_ESX_DC1_NoMirror_08 (»free«: 623 GB, »prov«: 3.28 TB, »total«: 2.25 TB)', - LX_ESX_SOA_DC1_01_NoMirror: - 'LX_ESX_SOA_DC1_01_NoMirror (»free«: 1010 GB, »prov«: 2.02 TB, »total«: 3 TB)', - LX_ESX_SOA_DC1_02_NoMirror: - 'LX_ESX_SOA_DC1_02_NoMirror (»free«: 1.88 TB, »prov«: 1.28 TB, »total«: 3 TB)', - LX_ESX_SOA_DC1_03_NoMirror: - 'LX_ESX_SOA_DC1_03_NoMirror (»free«: 983 GB, »prov«: 2.04 TB, »total«: 3 TB)', - LX_ESX_SOA_DC2_01_NoMirror: - 'LX_ESX_SOA_DC2_01_NoMirror (»free«: 1.85 TB, »prov«: 1.2 TB, »total«: 3 TB)', - LX_ESX_SOA_DC2_02_NoMirror: - 'LX_ESX_SOA_DC2_02_NoMirror (»free«: 983 GB, »prov«: 2.04 TB, »total«: 3 TB)', - LX_ESX_SOA_DC2_03_NoMirror: - 'LX_ESX_SOA_DC2_03_NoMirror (»free«: 1010 GB, »prov«: 2.02 TB, »total«: 3 TB)', - LX_ESX_SVC_DC1_21: 'LX_ESX_SVC_DC1_21 (»free«: 254 GB, »prov«: 1.71 TB, »total«: 768 GB)', - LX_ESX_SVC_DC1_22: 'LX_ESX_SVC_DC1_22 (»free«: 168 GB, »prov«: 1.35 TB, »total«: 768 GB)', - LX_ESX_SVC_DC1_23: 'LX_ESX_SVC_DC1_23 (»free«: 262 GB, »prov«: 1.86 TB, »total«: 780 GB)', - LX_ESX_SVC_DC1_24: 'LX_ESX_SVC_DC1_24 (»free«: 250 GB, »prov«: 1.65 TB, »total«: 768 GB)', - LX_ESX_SVC_DC1_25: 'LX_ESX_SVC_DC1_25 (»free«: 212 GB, »prov«: 1.63 TB, »total«: 780 GB)', - LX_ESX_SVC_DC1_26: 'LX_ESX_SVC_DC1_26 (»free«: 182 GB, »prov«: 1.53 TB, »total«: 768 GB)', - LX_ESX_SVC_DC1_BDABI_01: - 'LX_ESX_SVC_DC1_BDABI_01 (»free«: 2.13 TB, »prov«: 3.35 TB, »total«: 2.5 TB)', - LX_ESX_SVC_DC1_BDABI_02: - 'LX_ESX_SVC_DC1_BDABI_02 (»free«: 2.11 TB, »prov«: 3.13 TB, »total«: 2.5 TB)', - LX_ESX_SVC_DC1_BDABI_03: - 'LX_ESX_SVC_DC1_BDABI_03 (»free«: 2.07 TB, »prov«: 5.34 TB, »total«: 2.5 TB)', - LX_ESX_SVC_DC1_BDABI_04: - 'LX_ESX_SVC_DC1_BDABI_04 (»free«: 2.14 TB, »prov«: 1.61 TB, »total«: 2.5 TB)', - LX_ESX_SVC_DC1_BDASAS_01: - 'LX_ESX_SVC_DC1_BDASAS_01 (»free«: 2 TB, »prov«: 2.72 TB, »total«: 3 TB)', - LX_ESX_SVC_DC1_BDASAS_02: - 'LX_ESX_SVC_DC1_BDASAS_02 (»free«: 1.5 TB, »prov«: 2.97 TB, »total«: 3 TB)', - LX_ESX_SVC_DC1_BDASAS_03: - 'LX_ESX_SVC_DC1_BDASAS_03 (»free«: 1.29 TB, »prov«: 10.6 TB, »total«: 3 TB)', - LX_ESX_SVC_DC1_BDASAS_04: - 'LX_ESX_SVC_DC1_BDASAS_04 (»free«: 2.27 TB, »prov«: 10.4 TB, »total«: 12.5 TB)', - LX_ESX_SVC_DC1_BDASAS_05: - 'LX_ESX_SVC_DC1_BDASAS_05 (»free«: 2.37 TB, »prov«: 3.44 TB, »total«: 3 TB)', - LX_ESX_SVC_DC1_BDASAS_06: - 'LX_ESX_SVC_DC1_BDASAS_06 (»free«: 2.5 TB, »prov«: 2.13 TB, »total«: 3 TB)', - LX_ESX_SVC_DC1_BDASAS_07: - 'LX_ESX_SVC_DC1_BDASAS_07 (»free«: 2.62 TB, »prov«: 565 GB, »total«: 3 TB)', - LX_ESX_SVC_DC2_17: 'LX_ESX_SVC_DC2_17 (»free«: 233 GB, »prov«: 534 GB, »total«: 768 GB)', - LX_ESX_SVC_DC2_18: 'LX_ESX_SVC_DC2_18 (»free«: 179 GB, »prov«: 401 GB, »total«: 500 GB)', - LX_ESX_SVC_DC2_19: 'LX_ESX_SVC_DC2_19 (»free«: 126 GB, »prov«: 811 GB, »total«: 500 GB)', - LX_ESX_SVC_DC2_20: 'LX_ESX_SVC_DC2_20 (»free«: 167 GB, »prov«: 938 GB, »total«: 500 GB)', - LX_ESX_SVC_DC2_21: 'LX_ESX_SVC_DC2_21 (»free«: 142 GB, »prov«: 864 GB, »total«: 500 GB)', - LX_ESX_SVC_DC2_22: 'LX_ESX_SVC_DC2_22 (»free«: 170 GB, »prov«: 1.56 TB, »total«: 500 GB)', - LX_ESX_SVC_SSD_DC1_1_NoMirror: - 'LX_ESX_SVC_SSD_DC1_1_NoMirror (»free«: 407 GB, »prov«: 760 GB, »total«: 512 GB)', - LX_ESX_SVC_SSD_DC2_1_NoMirror: - 'LX_ESX_SVC_SSD_DC2_1_NoMirror (»free«: 386 GB, »prov«: 662 GB, »total«: 512 GB)', - LX_ESX_STD_DC1_01: 'LX_ESX_STD_DC1_01 (»free«: 215 GB, »prov«: 874 GB, »total«: 1020 GB)', - LX_ESX_STD_DC1_02: 'LX_ESX_STD_DC1_02 (»free«: 158 GB, »prov«: 1.72 TB, »total«: 768 GB)', - LX_ESX_STD_DC1_02_NoMirror: - 'LX_ESX_STD_DC1_02_NoMirror (»free«: 661 GB, »prov«: 1.78 TB, »total«: 2.25 TB)', - LX_ESX_STD_DC1_03: 'LX_ESX_STD_DC1_03 (»free«: 157 GB, »prov«: 1.07 TB, »total«: 768 GB)', - LX_ESX_STD_DC1_03_NoMirror: - 'LX_ESX_STD_DC1_03_NoMirror (»free«: 766 GB, »prov«: 1.72 TB, »total«: 2.25 TB)', - LX_ESX_STD_DC1_04: 'LX_ESX_STD_DC1_04 (»free«: 165 GB, »prov«: 1.97 TB, »total«: 768 GB)', - LX_ESX_STD_DC1_04_NoMirror: - 'LX_ESX_STD_DC1_04_NoMirror (»free«: 731 GB, »prov«: 3.7 TB, »total«: 2.25 TB)', - LX_ESX_STD_DC1_05: 'LX_ESX_STD_DC1_05 (»free«: 220 GB, »prov«: 1.32 TB, »total«: 768 GB)', - LX_ESX_STD_DC1_05_NoMirror: - 'LX_ESX_STD_DC1_05_NoMirror (»free«: 608 GB, »prov«: 1.97 TB, »total«: 2.25 TB)', - LX_ESX_STD_DC1_06: 'LX_ESX_STD_DC1_06 (»free«: 158 GB, »prov«: 1.8 TB, »total«: 768 GB)', - LX_ESX_STD_DC1_06_NoMirror: - 'LX_ESX_STD_DC1_06_NoMirror (»free«: 485 GB, »prov«: 3.13 TB, »total«: 2.25 TB)', - LX_ESX_STD_DC1_07: 'LX_ESX_STD_DC1_07 (»free«: 175 GB, »prov«: 1.25 TB, »total«: 768 GB)', - LX_ESX_STD_DC1_07_NoMirror: - 'LX_ESX_STD_DC1_07_NoMirror (»free«: 557 GB, »prov«: 3.04 TB, »total«: 2.25 TB)', - LX_ESX_STD_DC1_08: 'LX_ESX_STD_DC1_08 (»free«: 115 GB, »prov«: 703 GB, »total«: 500 GB)', - LX_ESX_STD_DC1_09: 'LX_ESX_STD_DC1_09 (»free«: 177 GB, »prov«: 1.31 TB, »total«: 768 GB)', - LX_ESX_STD_DC1_10: 'LX_ESX_STD_DC1_10 (»free«: 160 GB, »prov«: 1.07 TB, »total«: 768 GB)', - LX_ESX_STD_DC1_11: 'LX_ESX_STD_DC1_11 (»free«: 160 GB, »prov«: 1.05 TB, »total«: 768 GB)', - LX_ESX_STD_DC1_12: 'LX_ESX_STD_DC1_12 (»free«: 157 GB, »prov«: 1.41 TB, »total«: 768 GB)', - LX_ESX_STD_DC1_13: 'LX_ESX_STD_DC1_13 (»free«: 177 GB, »prov«: 1.19 TB, »total«: 768 GB)', - LX_ESX_STD_DC1_14: 'LX_ESX_STD_DC1_14 (»free«: 157 GB, »prov«: 1.53 TB, »total«: 768 GB)', - LX_ESX_STD_DC1_15: 'LX_ESX_STD_DC1_15 (»free«: 161 GB, »prov«: 1010 GB, »total«: 768 GB)', - LX_ESX_STD_DC1_16: 'LX_ESX_STD_DC1_16 (»free«: 252 GB, »prov«: 940 GB, »total«: 1020 GB)', - LX_ESX_STD_DC1_17: 'LX_ESX_STD_DC1_17 (»free«: 154 GB, »prov«: 1.88 TB, »total«: 768 GB)', - LX_ESX_STD_DC1_18: 'LX_ESX_STD_DC1_18 (»free«: 186 GB, »prov«: 1.01 TB, »total«: 768 GB)', - LX_ESX_STD_DC1_19: 'LX_ESX_STD_DC1_19 (»free«: 183 GB, »prov«: 1.36 TB, »total«: 768 GB)', - LX_ESX_STD_DC1_20: 'LX_ESX_STD_DC1_20 (»free«: 117 GB, »prov«: 495 GB, »total«: 500 GB)', - LX_ESX_STD_DC2_01: 'LX_ESX_STD_DC2_01 (»free«: 100 GB, »prov«: 1.12 TB, »total«: 500 GB)', - LX_ESX_STD_DC2_01_NoMirror: - 'LX_ESX_STD_DC2_01_NoMirror (»free«: 1.07 TB, »prov«: 2.84 TB, »total«: 2.25 TB)', - LX_ESX_STD_DC2_02: 'LX_ESX_STD_DC2_02 (»free«: 286 GB, »prov«: 913 GB, »total«: 768 GB)', - LX_ESX_STD_DC2_02_NoMirror: - 'LX_ESX_STD_DC2_02_NoMirror (»free«: 471 GB, »prov«: 3.63 TB, »total«: 2.25 TB)', - LX_ESX_STD_DC2_03: 'LX_ESX_STD_DC2_03 (»free«: 240 GB, »prov«: 801 GB, »total«: 768 GB)', - LX_ESX_STD_DC2_03_NoMirror: - 'LX_ESX_STD_DC2_03_NoMirror (»free«: 848 GB, »prov«: 1.42 TB, »total«: 2.25 TB)', - LX_ESX_STD_DC2_04: 'LX_ESX_STD_DC2_04 (»free«: 175 GB, »prov«: 605 GB, »total«: 500 GB)', - LX_ESX_STD_DC2_04_NoMirror: - 'LX_ESX_STD_DC2_04_NoMirror (»free«: 532 GB, »prov«: 2.62 TB, »total«: 2.25 TB)', - LX_ESX_STD_DC2_05: 'LX_ESX_STD_DC2_05 (»free«: 149 GB, »prov«: 1.57 TB, »total«: 500 GB)', - LX_ESX_STD_DC2_05_NoMirror: - 'LX_ESX_STD_DC2_05_NoMirror (»free«: 852 GB, »prov«: 3.92 TB, »total«: 2.25 TB)', - LX_ESX_STD_DC2_06: 'LX_ESX_STD_DC2_06 (»free«: 169 GB, »prov«: 769 GB, »total«: 500 GB)', - LX_ESX_STD_DC2_06_NoMirror: - 'LX_ESX_STD_DC2_06_NoMirror (»free«: 817 GB, »prov«: 1.72 TB, »total«: 2.25 TB)', - LX_ESX_STD_DC2_07: 'LX_ESX_STD_DC2_07 (»free«: 160 GB, »prov«: 524 GB, »total«: 500 GB)', - LX_ESX_STD_DC2_07_NoMirror: - 'LX_ESX_STD_DC2_07_NoMirror (»free«: 694 GB, »prov«: 4.62 TB, »total«: 2.25 TB)', - LX_ESX_STD_DC2_08: 'LX_ESX_STD_DC2_08 (»free«: 116 GB, »prov«: 414 GB, »total«: 500 GB)', - LX_ESX_STD_DC2_08_NoMirror: - 'LX_ESX_STD_DC2_08_NoMirror (»free«: 791 GB, »prov«: 1.42 TB, »total«: 2.2 TB)', - LX_ESX_STD_DC2_09: 'LX_ESX_STD_DC2_09 (»free«: 285 GB, »prov«: 1.03 TB, »total«: 768 GB)', - LX_ESX_STD_DC2_10: 'LX_ESX_STD_DC2_10 (»free«: 117 GB, »prov«: 938 GB, »total«: 500 GB)', - LX_ESX_STD_DC2_11: 'LX_ESX_STD_DC2_11 (»free«: 283 GB, »prov«: 1.22 TB, »total«: 768 GB)', - LX_ESX_STD_DC2_12: 'LX_ESX_STD_DC2_12 (»free«: 273 GB, »prov«: 949 GB, »total«: 768 GB)', - LX_ESX_STD_DC2_13: 'LX_ESX_STD_DC2_13 (»free«: 173 GB, »prov«: 1.26 TB, »total«: 500 GB)', - LX_ESX_STD_DC2_14: 'LX_ESX_STD_DC2_14 (»free«: 172 GB, »prov«: 593 GB, »total«: 500 GB)', - LX_ESX_STD_DC2_15: 'LX_ESX_STD_DC2_15 (»free«: 132 GB, »prov«: 1.1 TB, »total«: 500 GB)', - LX_ESX_STD_DC2_16: 'LX_ESX_STD_DC2_16 (»free«: 173 GB, »prov«: 511 GB, »total«: 500 GB)' - } - }, - volumes: [ - { - thin: true, - name: 'Hard disk 1', - mode: 'persistent', - controllerKey: 1000, - serverId: '500478d2-0c9b-3652-a378-b7703858a3c8', - datastore: 'LX_ESX_SVC_DC1_21', - id: '6000C293-d882-595d-670c-836daa2a2aa4', - filename: '[LX_ESX_SVC_DC1_21] alton-buttner.example.com/alton-buttner.example.com.vmdk', - size: 13631488, - key: 2000, - unitNumber: 0, - sizeGb: 13 - }, - { - thin: false, - name: 'Hard disk 2', - mode: 'persistent', - controllerKey: 1001, - serverId: '500478d2-0c9b-3652-a378-b7703858a3c8', - datastore: 'LX_ESX_SVC_DC1_21', - id: '6000C292-ca02-a2e7-c868-fe8f86d66ae8', - filename: '[LX_ESX_SVC_DC1_21] alton-buttner.example.com/alton-buttner.example.com_1.vmdk', - size: 11534336, - key: 2016, - unitNumber: 0, - sizeGb: 11 - }, - { - thin: false, - name: 'Hard disk 3', - mode: 'persistent', - controllerKey: 1001, - serverId: '500478d2-0c9b-3652-a378-b7703858a3c8', - datastore: 'LX_ESX_SVC_DC1_21', - id: '6000C294-4706-a370-4f30-8022353519ba', - filename: '[LX_ESX_SVC_DC1_21] alton-buttner.example.com/alton-buttner.example.com_2.vmdk', - size: 1048576, - key: 2017, - unitNumber: 1, - sizeGb: 1 - } - ], - controllers: [ - { - type: 'VirtualLsiLogicController', - key: 1000 - }, - { - type: 'VirtualLsiLogicController', - key: 1001 - } - ] -}; - -export const emptyState = { - config: { - controllerTypes: { - VirtualBusLogicController: 'Bus Logic Parallel', - VirtualLsiLogicController: 'LSI Logic Parallel', - VirtualLsiLogicSASController: 'LSI Logic SAS', - ParaVirtualSCSIController: 'VMware Paravirtual' - }, - diskModeTypes: { - persistent: 'Persistent', - independent_persistent: 'Independent - Persistent', - independent_nonpersistent: 'Independent - Nonpersistent' - }, - storagePods: {}, - datastores: {} - }, - volumes: [], - controllers: [] -}; \ No newline at end of file diff --git a/webpack/stories/docs/adding-new-components.stories.mdx b/webpack/stories/docs/adding-new-components.stories.mdx deleted file mode 100644 index e6ced6d3920..00000000000 --- a/webpack/stories/docs/adding-new-components.stories.mdx +++ /dev/null @@ -1,171 +0,0 @@ -import { Meta } from '@theforeman/stories'; - - - -# Adding new components - -## Component Generator - -We are using `generator-react-domain` to help you generate your component easily. - -```sh -npm i -g yo generator-react-domain # install the generator globally - -npm run create-react-component # create a component. -npm run create-react-component -- --redux # create a connected component -``` - -This generator will create all folders and files for you with prewritten templates. - -## Where to put them - -Components are stored in `webpack/assets/javascripts/react_app/components/`. Each component should be placed in its own subfolder that respects the following structure: - -``` -─ components// - ├─ .js ┈ pure react component - ├─ .scss ┈ styles if needed - ├─ Actions.js ┈ redux actions - ├─ Reducer.js ┈ redux reducer - ├─ Selectors.js ┈ reselect selectors - ├─ Constants.js ┈ constants such as action types - ├─ .fixtures.js ┈ constants for testing, initial state, etc. - ├─ .stories.js ┈ storybook pages for the component - ├─ components/ ┈ folder for nested components if needed - ├─ __tests__/ ┈ folder for tests - ╰─ index.js ┈ redux connected file -``` - -## Storybook - -[Storybook](https://storybook.js.org/) is an isolated environment for developing and demonstrating components. It serves as a nice documentation of a component usage. - -Each component that is supposed to be used at multiple places in the application should add its stories into the "Components" menu. The stories should be placed in a file `.stories.js` in the component's main folder. The storybook in Foreman loads all `*.story.js` files from `webpack/` automatically. - -An example of such stories is [BreadcrumbBar.stories.js](https://github.com/theforeman/foreman/blob/develop/webpack/assets/javascripts/react_app/components/BreadcrumbBar/BreadcrumbBar.stories.js). - -On top of the basic storybook Foreman uses [knobs addon](https://github.com/storybooks/storybook/tree/master/addons/knobs#available-knobs) that enables switching values of component's properties and [actions addon](https://github.com/storybooks/storybook/tree/master/addons/actions#getting-started) for logging of callbacks. It's worth checking their docs as the usage is simple and they can provide nice user experience. - -### Running storybook - -Starting the storybook server is as easy as `npm run storybook`. By default the server is started on `localhost:6006`. - -If some extra configuration is needed, you can start the storybook's executable directly. For example: - -```bash -# Starting storybook on https with Katello certs -./node_modules/.bin/start-storybook \ - --port 6006 \ - --host $(hostname) \ - --https \ - --ssl-key /etc/pki/katello/private/katello-apache.key \ - --ssl-cert /etc/pki/katello/certs/katello-apache.crt \ - --ssl-ca /etc/pki/katello/certs/katello-default-ca.crt -``` - -## Testing - -Tests must be placed in `__tests__` subfolder of the main component's folder. Tests for the component, reducer and actions must have their own files named `.test.js`: - -``` -╰─ __tests__ - ├─ Actions.test.js - ├─ Reducer.test.js - ├─ .test.js - ├─ integration.test.js - ╰─ __snapshots__ - ├── # All snapshot files (created automaically, updated with `npm test -- -u`) -``` - -### Testing components - -In most cases (when the component doesn't provide any user interaction callbacks) it's enough to test how the component is rendered with various supported properties. Foreman uses [enzyme](https://github.com/airbnb/enzyme) for that. - -There are 3 ways how a component can be rendered in enzyme: - -- **shallow** - render subcomponents as names, **preferred alternative**, more details in [docs](https://github.com/airbnb/enzyme/blob/master/docs/api/shallow.md) -- **mount** - full rendering API, useful when you need to interact with dom, more details in [docs](https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md) -- **render** - static rendering, more details in [docs](https://github.com/airbnb/enzyme/blob/master/docs/api/render.md) - -### Running the tests - -All tests can be executed with `npm test`. - -If you want to run only a single test use `jest` directly: `./node_modules/.bin/jest ` -This is useful especially for debugging because it doesn't hide console output. - -Linter (code style checking) can be executed with `npm run lint`. You can run it with parameter `--fix` to let it automatically fix the discovered issues. You need to pass the parameter to eslint, so run the command like this `npm run lint -- --fix`. - -## Making it available from ERB - -If you want your component to be available for mounting into ERB templates, it must be added to [the component registry](https://github.com/theforeman/foreman/blob/develop/webpack/assets/javascripts/react_app/components/componentRegistry.js#L60-L71). - -Then, you can mount it with the `react_component` helper: - -```ruby -react_component(component_name, props) -``` - -**Example:** - -```erb -
- <%= react_component('PowerStatus', id: host.id, url: power_host_path(host.id), errorText: 'N/A') %> -
-``` - -will render the following HTML: - -```html -
- -
-``` - -(Note that the React component is rendered as a [web component](https://developer.mozilla.org/en-US/docs/Web/Web_Components).) - -### Changing the props from legacy JS - -We allow changing the root component props from the legacy JS. -Be aware, that this will re-render the component. -This feature should only be used for limited use cases: - -1. A legacy JS library/component needs to talk to a React component, AND -1. The component is simple enough that it wouldn't otherwise make sense to store its data in the Redux store. - -We will use a method `reactProps` that our web component exposes and get the current props. -Then we will change this props and use `reactProps=` setter that will trigger the rerender. - - -```js -var myCoolPowerElement = document.getElementById("my-cool-power-status").getElementsByTagName('foreman-react-component')[0]; -var newProps = myCoolPowerElement.reactProps; - -newProps.errorText = 'MyNewErrorText'; -myCoolPowerElement.reactProps = newProps; - -``` - - -*Note* that you can also directly set the data: `element.dataset.props = JSON.stringify({new: 'prop'});` -or even the attribute: `element.setAttribute('data-props', JSON.stringify({new: 'prop'}));` - -Both of these need a JSON string as a new value to work. - -## Before you start writing a new component - -It's worth checking patternfly-react [Github repository](https://github.com/patternfly/patternfly-react) and [storybook](https://rawgit.com/patternfly/patternfly-react/gh-pages/index.html) to make sure such component doesn't exist yet. Also consider if your component is universal enough to be used in other projects. In such case it might make sense to add it to patternfly-react instead. - -Another place to look in is [move_to_foreman](https://github.com/Katello/katello/tree/master/webpack/move_to_foreman) folder in Katello. It already contains some components and code that is waiting to be moved to the core. diff --git a/webpack/stories/docs/api-middleware.stories.mdx b/webpack/stories/docs/api-middleware.stories.mdx deleted file mode 100644 index f12ee8e5c5f..00000000000 --- a/webpack/stories/docs/api-middleware.stories.mdx +++ /dev/null @@ -1,199 +0,0 @@ -import { Meta } from '@theforeman/stories'; - - - -# API Middleware - -Instead of each component handling API calls in the same way we have the API Middleware that will handle it. - -`APIActions` can be imported from 'webpack/assets/javascripts/react_app/redux/API/index.js' -or 'foremanReact/redux/API/index.js' for plugins - -`APIActions` contains the following methods: `get`, `post`, `put`, `delete`, `patch` which can be also imported independently, e.g: - -```js -import { get, post, put, patch } from '../../redux/API'; -// can't import 'delete' as it's a reserved word in JS. -``` - -## How to use it in your action - -```js -import { get } from '../../redux/API'; - -const someAction = url => - get({ - key: MY_SPECIAL_KEY, // this will be used later to identify your API call, so keep it unique. - url, - params: {}, // some params you will want to pass to the API request. - headers: {}, // some headers you will want to pass to the API request. - }); -``` - -or - -```js -dispatch( - get({ - key: MY_SPECIAL_KEY, // this will be used later to identify your API call, so keep it unique. - url, - params: {}, // some params you will want to pass to the API request. - headers: {}, // some headers you will want to pass to the API request. - }) -); -``` - -Then there will be called 2 actions: **MY_SPECIAL_KEY_REQUEST** and **MY_SPECIAL_KEY_SUCCESS/ MY_SPECIAL_KEY_FAILURE**: -**MY_SPECIAL_KEY_REQUEST** will have the payload only -**MY_SPECIAL_KEY_SUCCESS** will have the payload and the return data from the API call. -**MY_SPECIAL_KEY_FAILURE** will have the payload and the return error from the API call. - -In the **payload** field you should send any headers and params for the GET request, and any other data you want for the action. - -The actions types can be changed with the optional **actionTypes** parameter: - -```js -dispatch( - get({ - key: MY_SPECIAL_KEY, // this will be used later to identify your API call, so keep it unique. - url, - params: {} // some params you will want to pass to the API request. - headers: {}, // some headers you will want to pass to the API request. - actionTypes: { - REQUEST: 'CUSTOM_REQUEST', - SUCCESS: 'CUSTOM_SUCCESS', - FAILURE: 'CUSTOM_FAILURE', - }, - }) -); -``` - -## Main API Reducer - -The API will manage the request lifecycle in the redux store > API > the provided key, e.g: - -```js -{ - API: { - MY_COMPONENT_KEY: { // this is the key you provided in the API action. - status: 'RESOLVED', - payload, // the payload you passed to the API action - response, // the API response. it will store data on success or error on failure. - } - } -} -``` - -it will manage the request statuses, errors and responses. - -### Access the API store - -We provided you the `selectAPIByKey` in '/APISelectors.js' which will return the key substate, -there are also `selectAPIStatus`, `selectAPIPayload`, `selectAPIResponse`, `selectAPIError` and `selectAPIErrorMessage`. - -## Error handling - -In case you want to invoke some method or action immediately when an error occurs, -we support the `handleError` param where you could pass an error handling function, e.g: - -```js -dispatch( - get({ - key: MY_SPECIAL_KEY, - url, - handleError: error => error.response.status === 401 ? logoutUser() : null - ... - }) -); -``` - -The error handling function will receive the error object from the API request. - -## Success handling - -In case you want to invoke some method or action immediately after the request succeed, -we support the `handleSuccess` param where you could pass a success handling function, e.g: - -```js -dispatch( - get({ - key: MY_SPECIAL_KEY, - url, - handleSuccess: response => { dispatch(addToast({ type: 'success' })) }, - ... - }) -); -``` - -The success handling function will receive the response object from the API request. - -In case you are also using the interval middleware with the same `KEY`, -the `stopInterval` for your key will be passed as the second param in your `handleSuccess`/ `handleError` callbacks. - -## Toast handling - -Instead of managing and dispatching the toast action by yourself, you can use the API middleware shortcuts, -by passing `toastSuccess` or `toastError` as keys in the API action, e.g: - -```js -dispatch( - get({ - key: MY_SPECIAL_KEY, - url, - toastError: error => `Oh no! Something went wrong while submitting the form, the server returned the following error: ${error}` - toastSuccess: response => `Yay! task: ${response.taskID} was successfully created.` - }) -); -``` - -## Update resources object - -After an API request SUCCESS action, it is often reconfirmed with a second GET request to view the updated data, and keep the Redux store up to date. -However, if we receive a SUCCESS it is acceptable to assume that the data was actually updated as requested, and not completely necessary to make the additional request. -To facilitate this, the API middleware can accept an `updateData` callback which returns the updated resource object corresponding to its key. -Thus, Redux's copy of the backend data is changed based on this trust, and an additional API request to show the updated data is avoided. - -`updateData` callback accepts two arguments, prevState and currentResponse: - -#### prevState -The previous response data which stored on redux store and corresponding with the given key. - -#### currentResponse -The current API call response. - - -Examples: - -```js -// This action creates a post API call and only if it ends succesfully, -// the response object will update with the returned data from 'updateData' callback -dispatch( - post({ - key: MY_SPECIAL_KEY, - url, - updateData: (prevResponseState, currentResponse) => ({ - results: prevStateData.filter( - item => item.id !== currentResponse.id - ), - }), - }) -); -``` - -```js -post({ - key: MY_SPECIAL_KEY, - url, - params: { id: selectedID } - updateData: prevResponseState => ({ - results: prevResponseState.filter( - item => item.id !== selectedID - ), - }), -}) -``` diff --git a/webpack/stories/docs/connected-react-router.stories.mdx b/webpack/stories/docs/connected-react-router.stories.mdx deleted file mode 100644 index 63c251d6b71..00000000000 --- a/webpack/stories/docs/connected-react-router.stories.mdx +++ /dev/null @@ -1,42 +0,0 @@ -import { Meta } from '@theforeman/stories'; - - - -# Connected React Router -Foreman uses react-router for client routing in react pages, however foreman contains lots of erb content which includes react components. -in order to control react router better and to have one source of truth, -foreman uses `connected-react-router` which enable controling react router via redux. - -### Push url -This pushed a new url (including querystring) to react router and creates a transition - -```js -import { pushUrl } from '../foreman_navigation'; -import { foremanUrl } from '../foreman_tools'; - - -``` - -### React Router Selectors -You can use react router selectors for retriving data - -selectRouterLocation - the current location. -electRouterPath - the current path -selectRouterSearch - the current search -selectRouterHash - the current hash -selectRouter - the entire router object which includes every entry above - -```js -import { useSelector } from 'react-redux'; -import { selectRouterLocation } from '../routes/RouterSelector'; - - -const location = useSelector(selectRouterLocation); -``` diff --git a/webpack/stories/docs/creating-a-form.stories.mdx b/webpack/stories/docs/creating-a-form.stories.mdx deleted file mode 100644 index 52e7bbccae5..00000000000 --- a/webpack/stories/docs/creating-a-form.stories.mdx +++ /dev/null @@ -1,76 +0,0 @@ -import { Meta } from '@theforeman/stories'; - - - -# Creating a Form - -Foreman uses [Formik](https://jaredpalmer.com/formik/) which takes care of repetitive code when creating forms. -You can create a form with just Formik, however Foreman provides additional layer of obstraction over the Formik forms to make things more convenient and uniform. This is what a simple form might look like: - -```js -// import things we need -import React from 'react'; -import * as Yup from 'yup'; -// import ForemanForm and TextField specifying the relative path - -// define a validation schema using Yup -const validationSchema = Yup.object().shape({ - name: Yup.string().required('is required'), -}); - -const FlavorForm = ({ - url, - submitForm, - controller, - onCancel, - initialValues, -}) => ( - - submitForm({ - url, - values, - message: 'New ice cream flavor successfully created!', - }) - } - initialValues={initialValues} - validationSchema={validationSchema} - onCancel={onCancel} - > - - <\/ForemanForm> -); -``` - -`ForemanForm` component requires the following props: - -- `onSubmit` - function called on form submission -- `onCancel` - function called on form cancel -- `initialValues` - object with initial values for the fields. For the example above, it could be: `{ name: 'Strawberry Super Sweetness' }` - -You can also pass a [Yup](https://github.com/jquense/yup) object as a `validationSchema`, which will take care of validations. - -### Toast Notifications - -Toast Notifications are enabled by default after a success or a failure. -- `errorToast` - creates a toast notification based on `error.full_messages` object -- `successToast` - creates a toast notification with the given `meesage` or the given `item` props - -You can override the default `successToast` or `errorToast` functions if needed: - -```js - - submitForm({ - ...otherProps, - successToast: response => sprintf(__('a custom success message with %s'), response.data.item), - errorToast: error => sprintf(__('a custom error message with %s'), error.response.data.error.item), - }) - } - > -``` diff --git a/webpack/stories/docs/internationalization.stories.mdx b/webpack/stories/docs/internationalization.stories.mdx deleted file mode 100644 index aedb5ef5686..00000000000 --- a/webpack/stories/docs/internationalization.stories.mdx +++ /dev/null @@ -1,69 +0,0 @@ -import { Meta } from '@theforeman/stories'; - - - -# Internationalization - -All strings need to be translated. - -### Foreman core - -`import I18n.js` which located in `/webpack/assets/javascripts/react_app/common` - -```js -import { - sprintf, - translate as __, - ngettext as n_, -} from '../react_app/common/I18n'; - -// - Sets the key to translate -const str = __('a string'); - -// - Plural version of translate -const num_toasters = 2; -const plural_str = n_( - 'I have one toaster.', - 'I have %1$d toasters.', - num_toasters -); -``` - -Use the `__` and `n_` aliasing, it is necessary for the string extration process - -### Plugins - -Same as foreman core, just use `foremanReact` alias: - -```js -import { - sprintf, - translate as __, - ngettext as n_, -} from 'foremanReact/common/I18n'; -``` - -## Interpolation - -Make sure that the strings inside the underscore function don't contain any interpolated values. -Using interpolation would break the actual translation of the string in runtime. Use `sprintf` from `I18n` instead. - -**Example:** - -```js -// Wrong: -let msg = __(`%{taskCount} tasks complete`); -``` - -```js -// Correct: -import { sprintf } from '../react_app/common/I18n'; -let msg = sprintf(__('%(taskCount)s tasks complete'), { taskCount }); -``` - -**Please note that the global underscore, sprintf and ngettext are deprecated**. diff --git a/webpack/stories/docs/withReactRoutes.stories.mdx b/webpack/stories/docs/withReactRoutes.stories.mdx deleted file mode 100644 index 9b5fb591322..00000000000 --- a/webpack/stories/docs/withReactRoutes.stories.mdx +++ /dev/null @@ -1,26 +0,0 @@ -import { Meta } from '@theforeman/stories'; - - - -# withReactRoutes - -`withReactRoutes` wrapps your component with `Router` which gives you access to the react-router. This is useful for components in erb pages that need access to client routing - a good example is a button that redirects to a page in React: - -```js -import { Link } from 'react-router-dom'; - -import withReactRoutes from './react_app/common/withReactRoutes'; - -const LinkButton = props => ( - - - -); - -export default withReactRoutes(LinkButton); -``` diff --git a/webpack/stories/store.js b/webpack/stories/store.js deleted file mode 100644 index 0f2d4f399a2..00000000000 --- a/webpack/stories/store.js +++ /dev/null @@ -1,5 +0,0 @@ -import createLogger from 'redux-logger'; -import configureMockStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; - -export default configureMockStore([thunk, createLogger()]); diff --git a/webpack/stories/storeDecorator.js b/webpack/stories/storeDecorator.js deleted file mode 100644 index ddcaf1fa9bc..00000000000 --- a/webpack/stories/storeDecorator.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { Provider } from 'react-redux'; -import store from '../assets/javascripts/react_app/redux'; - -const storeDecorator = getStory => ( - {getStory()} -); - -export default storeDecorator;