Skip to content

Commit

Permalink
Adding react
Browse files Browse the repository at this point in the history
  • Loading branch information
amirfefer committed May 4, 2020
1 parent 3376da2 commit f1d5f16
Show file tree
Hide file tree
Showing 28 changed files with 336 additions and 0 deletions.
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",
],
}
37 changes: 37 additions & 0 deletions Gemfile.lock
@@ -0,0 +1,37 @@
PATH
remote: .
specs:
foreman_plugin_template (0.0.1)

GEM
remote: https://rubygems.org/
specs:
ast (2.4.0)
jaro_winkler (1.5.4)
parallel (1.19.1)
parser (2.7.1.2)
ast (~> 2.4.0)
rainbow (3.0.0)
rdoc (6.1.0)
rexml (3.2.4)
rubocop (0.80.1)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.7.0.1)
rainbow (>= 2.2.2, < 4.0)
rexml
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 1.7)
ruby-progressbar (1.10.1)
unicode-display_width (1.6.1)

PLATFORMS
ruby

DEPENDENCIES
foreman_plugin_template!
rdoc
rubocop

BUNDLED WITH
2.1.3
7 changes: 7 additions & 0 deletions app/controllers/foreman_plugin_template/react_controller.rb
@@ -0,0 +1,7 @@
module ForemanPluginTemplate
class ReactController < ::ApplicationController
def index
render 'foreman_plugin_template/layouts/react', :layout => false
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-tasks' %>
<% end %>
<% content_for(:stylesheets) do %>
<%= webpacked_plugins_css_for :'foreman-tasks' %>
<% 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'],
};
2 changes: 2 additions & 0 deletions config/routes.rb
@@ -1,3 +1,5 @@
Rails.application.routes.draw do
get 'new_action', to: 'foreman_plugin_template/hosts#new_action'
get 'foreman_plugin_template', :controller => 'react', :action => 'index'
# match '/foreman_plugin_template' => 'react#index', :via => [:get]
end
3 changes: 3 additions & 0 deletions lib/foreman_plugin_template/engine.rb
Expand Up @@ -18,6 +18,9 @@ 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]
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.2"
},
"dependencies": {
"react-intl": "^2.8.0"
},
"devDependencies": {
"@babel/core": "^7.7.0",
"@theforeman/builder": "^4.0.2",
"@theforeman/eslint-plugin-foreman": "4.0.5",
"@theforeman/stories": "^4.0.2",
"@theforeman/test": "^4.0.2",
"@theforeman/vendor-dev": "^4.0.2",
"babel-eslint": "^10.0.3",
"eslint": "^6.7.2",
"prettier": "^1.13.5",
"stylelint": "^9.3.0",
"stylelint-config-standard": "^18.0.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 @@
import React from 'react';
import { addGlobalFill } from 'foremanReact/components/common/Fill/GlobalFill';

// This example for extanding foreman-core's component via slot&fill

/*
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));
3 changes: 3 additions & 0 deletions webpack/src/Components/EmptyState/EmptyPageSelectors.js
@@ -0,0 +1,3 @@

const selectEmptyState = state => state.pluginTemplate.emptyState;
export const selectEmptyStateHeader = state => selectEmptyState(state).header;
21 changes: 21 additions & 0 deletions webpack/src/Components/EmptyState/EmptyState.js
@@ -0,0 +1,21 @@
import React from 'react';
import PropTypes from 'prop-types';
import DefaultEmptyState from 'patternfly-react';
import { useSelector } from 'react-redux';
import { selectEmptyStateHeader } from './EmptyPageSelectors';

const EmptyPage = ({ description }) => {
const header = useSelector(selectEmptyStateHeader)
return <DefaultEmptyState
icon='add-circle-o'
header={header}
description={description}
/>
};

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

export default EmptyPage;
22 changes: 22 additions & 0 deletions webpack/src/Components/EmptyState/EmptyStateActions.js
@@ -0,0 +1,22 @@
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, 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;
13 changes: 13 additions & 0 deletions webpack/src/Route/WelcomePage/Welcome.js
@@ -0,0 +1,13 @@
import React from 'react';
import EmptyState from '../../Components/EmptyState/EmptyState';

const WelcomePage = () => (
<EmptyState
icon='add-circle-o'
header='Hello World!'
description='This is an exampe for a full react page!
For further documantion please run `npm run stories` under foreman-core directory'
/>
);

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: '/plugin_template/welcome',
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';
11 changes: 11 additions & 0 deletions webpack/src/reducers.js
@@ -0,0 +1,11 @@

import { combineReducers } from 'redux';
import EmptyStateReducer from './Components/EmptyState/EmptyStateReducer';

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

export default reducers;

0 comments on commit f1d5f16

Please sign in to comment.