Skip to content

Commit

Permalink
Adding react
Browse files Browse the repository at this point in the history
  • Loading branch information
amirfefer committed May 7, 2020
1 parent 3376da2 commit 21a5036
Show file tree
Hide file tree
Showing 27 changed files with 321 additions and 1 deletion.
4 changes: 4 additions & 0 deletions .eslintrc
@@ -0,0 +1,4 @@
{
"plugins": ["@theforeman/foreman"],
"extends": ["plugin:@theforeman/foreman/core", "plugin:@theforeman/foreman/plugins"]
}
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -9,3 +9,6 @@ locale/*.mo
locale/*/*.edit.po
locale/*/*.po.time_stamp
locale/*/*.pox
node_modules
package-lock.json
Gemfile.lock
6 changes: 6 additions & 0 deletions .stylelintrc
@@ -0,0 +1,6 @@

{
"extends": [
"stylelint-config-standard",
],
}
17 changes: 17 additions & 0 deletions app/controllers/foreman_plugin_template/react_controller.rb
@@ -0,0 +1,17 @@
module ForemanPluginTemplate
class ReactController < ::ApplicationController
def index
render 'foreman_plugin_template/layouts/react', :layout => false
end

private

def controller_permission
:foreman_plugin_template
end

def action_permission
:view
end
end
end
16 changes: 16 additions & 0 deletions app/views/foreman_plugin_template/layouts/react.html.erb
@@ -0,0 +1,16 @@
<% content_for(:javascripts) do %>
<%= webpacked_plugins_js_for :'foreman_plugin_template' %>
<% end %>
<% content_for(:stylesheets) do %>
<%= webpacked_plugins_css_for :'foreman_plugin_template' %>
<% end %>
<% content_for(:content) do %>
<%= notifications %>
<div id="organization-id" data-id="<%= Organization.current.id if Organization.current %>" ></div>
<div id="user-id" data-id="<%= User.current.id if User.current %>" ></div>
<div id="foremanPluginTemplateRoot"></div>
<% end %>
<%= render file: "layouts/base" %>
<%= mount_react_component('PluginTemplate', '#foremanPluginTemplateRoot') %>
3 changes: 3 additions & 0 deletions babel.config.js
@@ -0,0 +1,3 @@
module.exports = {
presets: ['@theforeman/builder/babel'],
};
1 change: 1 addition & 0 deletions config/routes.rb
@@ -1,3 +1,4 @@
Rails.application.routes.draw do
get 'new_action', to: 'foreman_plugin_template/hosts#new_action'
get 'foreman_plugin_template', to: 'foreman_plugin_template/react#index'
end
6 changes: 5 additions & 1 deletion lib/foreman_plugin_template/engine.rb
Expand Up @@ -18,9 +18,13 @@ class Engine < ::Rails::Engine
Foreman::Plugin.register :foreman_plugin_template do
requires_foreman '>= 1.16'

# Add Global JS file for extending foreman-core components and routes
register_global_js_file 'fills'

# Add permissions
security_block :foreman_plugin_template do
permission :view_foreman_plugin_template, :'foreman_plugin_template/hosts' => [:new_action]
permission :view_foreman_plugin_template, { :'foreman_plugin_template/hosts' => [:new_action],
:'foreman_plugin_template/react' => [:index] }
end

# Add a new role called 'Discovery' if it doesn't exist
Expand Down
42 changes: 42 additions & 0 deletions package.json
@@ -0,0 +1,42 @@
{
"name": "plugin-name",
"version": "1.0.0",
"description": "DESCRIPTION",
"main": "index.js",
"scripts": {
"lint": "tfm-lint --plugin -d /webpack",
"test": "tfm-test --plugin",
"test:watch": "tfm-test --plugin --watchAll",
"test:current": "tfm-test --plugin --watch",
"publish-coverage": "tfm-publish-coverage",
"stories": "tfm-stories --plugin",
"stories:build": "tfm-build-stories --plugin",
"create-react-component": "yo react-domain"
},
"repository": {
"type": "git",
"url": "git+https://github.com/theforeman/foreman_plugin_template.git"
},
"bugs": {
"url": "http://projects.theforeman.org/projects/foreman_plugin_template/issues"
},
"peerDependencies": {
"@theforeman/vendor": ">= 4.0.7"
},
"dependencies": {
"react-intl": "^2.8.0"
},
"devDependencies": {
"@babel/core": "^7.7.0",
"@theforeman/builder": "^4.0.7",
"@theforeman/eslint-plugin-foreman": "4.0.7",
"@theforeman/stories": "^4.0.7",
"@theforeman/test": "^4.0.7",
"@theforeman/vendor-dev": "^4.0.7",
"babel-eslint": "^10.0.3",
"eslint": "^6.7.2",
"prettier": "^1.13.5",
"stylelint-config-standard": "^18.0.0",
"stylelint": "^9.3.0"
}
}
11 changes: 11 additions & 0 deletions webpack/__mocks__/foremanReact/readme.md
@@ -0,0 +1,11 @@
For testing components which have imported foreman-core components,
a mock file is required in this folder.

### Example: Mocking ForemanModal component
```js
// __mocks__/foremanReact/components/ForemanModal/index.js
const ForemanModal = () => jest.fn();
ForemanModal.Header = () => jest.fn();
ForemanModal.Footer = () => jest.fn();
export default ForemanModal;
```
15 changes: 15 additions & 0 deletions webpack/fills_index.js
@@ -0,0 +1,15 @@
// This example for extanding foreman-core's component via slot&fill

/*
import React from 'react';
import { addGlobalFill } from 'foremanReact/components/common/Fill/GlobalFill';
addGlobalFill('slotId', 'fillId', <SomeComponent key="some-key" />, 300);
addGlobalFill(
'slotId',
'fillId',
{ someProp: 'this is an override prop' },
300
);
*/
18 changes: 18 additions & 0 deletions webpack/index.js
@@ -0,0 +1,18 @@
/* eslint import/no-unresolved: [2, { ignore: [foremanReact/*] }] */
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable import/extensions */
import componentRegistry from 'foremanReact/components/componentRegistry';
import { registerReducer } from 'foremanReact/common/MountingService';
import reducers from './src/reducers';
import PluginTemplate from './src/PluginName';

// register reducers
Object.entries(reducers).forEach(([key, reducer]) =>
registerReducer(key, reducer)
);

// register components for erb mounting
componentRegistry.register({
name: 'PluginTemplate',
type: PluginTemplate,
});
2 changes: 2 additions & 0 deletions webpack/src/Components/EmptyState/Constants.js
@@ -0,0 +1,2 @@
export const ADD_CONTENT = '[PLUGIN_TEMPLATE] ADD_CONTENT';
export const FETCHING_KEY = '[PLUGIN_TEMPLATE] FETCHING_KEY';
13 changes: 13 additions & 0 deletions webpack/src/Components/EmptyState/EmptyPage.test.js
@@ -0,0 +1,13 @@
import { testComponentSnapshotsWithFixtures } from '@theforeman/test';

import EmptyState from './EmptyState';

const fixtures = {
render: {
header: 'an header',
description: 'a description',
},
};

describe('EmptyState', () =>
testComponentSnapshotsWithFixtures(EmptyState, fixtures));
2 changes: 2 additions & 0 deletions webpack/src/Components/EmptyState/EmptyPageSelectors.js
@@ -0,0 +1,2 @@
const selectEmptyState = state => state.pluginTemplate.emptyState;
export const selectEmptyStateHeader = state => selectEmptyState(state).header;
38 changes: 38 additions & 0 deletions webpack/src/Components/EmptyState/EmptyState.js
@@ -0,0 +1,38 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { Button, EmptyState as PfEmptyState } from 'patternfly-react';
import { useSelector, useDispatch } from 'react-redux';
import { selectEmptyStateHeader } from './EmptyPageSelectors';
import { AddEmptyStateContent } from './EmptyStateActions';

const EmptyState = ({ description }) => {
const dispatch = useDispatch();
const header = useSelector(selectEmptyStateHeader);

useEffect(() => {
dispatch(AddEmptyStateContent('Foreman Plugin Template - React Page'));
});

return (
<PfEmptyState>
<PfEmptyState.Icon name="add-circle-o" />
<PfEmptyState.Title>{header}</PfEmptyState.Title>
<PfEmptyState.Info>{description}</PfEmptyState.Info>
<PfEmptyState.Action>
<Button
href="https://theforeman.github.io/foreman"
bsStyle="primary"
bsSize="large"
>
Storybook
</Button>
</PfEmptyState.Action>
</PfEmptyState>
);
};

EmptyState.propTypes = {
description: PropTypes.string.isRequired,
};

export default EmptyState;
20 changes: 20 additions & 0 deletions webpack/src/Components/EmptyState/EmptyStateActions.js
@@ -0,0 +1,20 @@
import { API_OPERATIONS } from 'foremanReact/redux/API/APIConstants';
import { ADD_CONTENT, FETCHING_KEY } from './Constants';

export const AddEmptyStateContent = header => ({
type: ADD_CONTENT,
payload: header,
});

/*
This action fetches data from the server
For accessing the response's data, select it via this path: state.api.<FETCHING_KEY>.response`
For further information please visit APIMiddleware page in foreman's storybook
*/

export const fetchData = url => ({
type: API_OPERATIONS.GET,
key: FETCHING_KEY, // you will need to re-use this key in order to access the right API reducer later.
url,
payload: {},
});
16 changes: 16 additions & 0 deletions webpack/src/Components/EmptyState/EmptyStateReducer.js
@@ -0,0 +1,16 @@
import Immutable from 'seamless-immutable';
import { ADD_CONTENT } from './Constants';

const initialState = Immutable({});

export default (state = initialState, action) => {
const { payload } = action;

switch (action.type) {
case ADD_CONTENT:
return state.set('header', payload);

default:
return state;
}
};
11 changes: 11 additions & 0 deletions webpack/src/PluginName.js
@@ -0,0 +1,11 @@
import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import PluginTemplateRoute from './Route';

const PluginName = () => (
<BrowserRouter>
<PluginTemplateRoute />
</BrowserRouter>
);

export default PluginName;
11 changes: 11 additions & 0 deletions webpack/src/Route/WelcomePage/Welcome.js
@@ -0,0 +1,11 @@
import React from 'react';
import EmptyState from '../../Components/EmptyState/EmptyState';

const WelcomePage = () => (
<EmptyState
description="This is an example for a full react page!
For further documentation please visit foreman's Storybook page or run it locally via 'npm run stories'"
/>
);

export default WelcomePage;
14 changes: 14 additions & 0 deletions webpack/src/Route/WelcomePage/__test__/Welcome.test.js
@@ -0,0 +1,14 @@
import { testComponentSnapshotsWithFixtures } from '@theforeman/test';

import WelcomePage from '../Welcome';

const fixtures = {
render: {
history: {
push: jest.fn(),
},
},
};

describe('WelcomePage', () =>
testComponentSnapshotsWithFixtures(WelcomePage, fixtures));
1 change: 1 addition & 0 deletions webpack/src/Route/WelcomePage/index.js
@@ -0,0 +1 @@
export { default } from './Welcome';
14 changes: 14 additions & 0 deletions webpack/src/Route/index.js
@@ -0,0 +1,14 @@
import React from 'react';
import { Switch, Route } from 'react-router-dom';

import routes from './routes';

const Router = () => (
<Switch>
{Object.entries(routes).map(([key, props]) => (
<Route key={key} {...props} />
))}
</Switch>
);

export default Router;
12 changes: 12 additions & 0 deletions webpack/src/Route/routes.js
@@ -0,0 +1,12 @@
import React from 'react';
import WelcomPage from './WelcomePage';

const routes = {
welcome: {
path: '/foreman_plugin_template',
exact: true,
render: () => <WelcomPage />,
},
};

export default routes;
15 changes: 15 additions & 0 deletions webpack/src/Route/routes.test.js
@@ -0,0 +1,15 @@
import React from 'react';
import { shallow } from '@theforeman/test';

import Routes from './routes';

describe('PluginTemplateRoutes', () => {
it('should create routes', () => {
Object.entries(Routes).forEach(([key, Route]) => {
const component = shallow(<Route.render history={{}} some="props" />);
Route.renderResult = component;
});

expect(Routes).toMatchSnapshot();
});
});
1 change: 1 addition & 0 deletions webpack/src/index.js
@@ -0,0 +1 @@
export { default } from './PluginName';
10 changes: 10 additions & 0 deletions webpack/src/reducers.js
@@ -0,0 +1,10 @@
import { combineReducers } from 'redux';
import EmptyStateReducer from './Components/EmptyState/EmptyStateReducer';

const reducers = {
pluginTemplate: combineReducers({
emptyState: EmptyStateReducer,
}),
};

export default reducers;

0 comments on commit 21a5036

Please sign in to comment.