Connect ara-web with ara-server
* install react-router and redux * create redux store and start fetching some playbooks from ara-server * create playbooks and config states in redux Change-Id: I455f217797fc69d722bedd573eaed2cea70ede6b
This commit is contained in:
parent
fc81f257d6
commit
722ea92916
9065
package-lock.json
generated
9065
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@ -3,15 +3,25 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"patternfly": "^3.42.0",
|
||||
"react": "^16.2.0",
|
||||
"react-dom": "^16.2.0",
|
||||
"react-scripts": "1.1.1"
|
||||
"axios": "^0.18.0",
|
||||
"patternfly": "^3.54.2",
|
||||
"patternfly-react": "^2.17.3",
|
||||
"react": "^16.5.1",
|
||||
"react-dom": "^16.5.1",
|
||||
"react-redux": "^5.0.7",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-scripts": "^1.1.5",
|
||||
"redux": "^4.0.0",
|
||||
"redux-thunk": "^2.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios-mock-adapter": "^1.15.0",
|
||||
"redux-mock-store": "^1.5.3"
|
||||
}
|
||||
}
|
||||
|
35
scripts/faker.js
Normal file
35
scripts/faker.js
Normal file
@ -0,0 +1,35 @@
|
||||
const results = [];
|
||||
for (let i = 0; i < 200; i++) {
|
||||
const random = Math.random();
|
||||
if (random < 0.001) {
|
||||
results.push({
|
||||
id: `${i}`,
|
||||
status: "changed",
|
||||
duration: 55
|
||||
});
|
||||
} else if (random < 0.01) {
|
||||
results.push({
|
||||
id: `${i}`,
|
||||
status: "skipped",
|
||||
duration: 20
|
||||
});
|
||||
} else if (random < 0.02) {
|
||||
results.push({
|
||||
id: `${i}`,
|
||||
status: "changed",
|
||||
duration: 20
|
||||
});
|
||||
} else {
|
||||
results.push({
|
||||
id: `${i}`,
|
||||
status: "ok",
|
||||
duration: Math.random() * 20 + 20
|
||||
});
|
||||
}
|
||||
}
|
||||
results.push({
|
||||
id: "200",
|
||||
status: "failed",
|
||||
duration: Math.random() * 20 + 20
|
||||
});
|
||||
console.log(results);
|
@ -1,3 +1,3 @@
|
||||
.App-intro {
|
||||
text-align: center;
|
||||
body {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
49
src/App.js
49
src/App.js
@ -1,13 +1,50 @@
|
||||
import React from "react";
|
||||
import "./App.css";
|
||||
import Navbar from "./components/Header/Navbar";
|
||||
import React, { Component } from "react";
|
||||
import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
|
||||
import { Provider } from "react-redux";
|
||||
|
||||
import "./App.css";
|
||||
import store from "./store";
|
||||
import { setConfig } from "./config/configActions";
|
||||
import * as Containers from "./containers";
|
||||
|
||||
class App extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
store.dispatch(setConfig({ apiURL: "http://localhost:8000" }));
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="App">
|
||||
<Navbar />
|
||||
<p className="App-intro">...</p>
|
||||
{this.state.loading ? (
|
||||
<Containers.LoadingContainer />
|
||||
) : (
|
||||
<Provider store={store}>
|
||||
<BrowserRouter>
|
||||
<Switch>
|
||||
<Redirect from="/" exact to="/playbooks" />
|
||||
<Route
|
||||
path="/playbooks"
|
||||
exact
|
||||
component={Containers.PlaybooksContainer}
|
||||
/>
|
||||
<Route
|
||||
path="/about"
|
||||
exact
|
||||
component={Containers.AboutContainer}
|
||||
/>
|
||||
<Route component={Containers.Container404} />
|
||||
</Switch>
|
||||
</BrowserRouter>
|
||||
</Provider>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
12
src/about/AboutContainer.js
Normal file
12
src/about/AboutContainer.js
Normal file
@ -0,0 +1,12 @@
|
||||
import React, { Component } from "react";
|
||||
import { MainContainer } from "../containers";
|
||||
|
||||
export default class AboutContainer extends Component {
|
||||
render() {
|
||||
return (
|
||||
<MainContainer>
|
||||
<p>AboutContainer</p>
|
||||
</MainContainer>
|
||||
);
|
||||
}
|
||||
}
|
8
src/config/configActions.js
Normal file
8
src/config/configActions.js
Normal file
@ -0,0 +1,8 @@
|
||||
import * as types from "./configActionsTypes";
|
||||
|
||||
export function setConfig(config) {
|
||||
return {
|
||||
type: types.SET_CONFIG,
|
||||
config
|
||||
};
|
||||
}
|
13
src/config/configActions.test.js
Normal file
13
src/config/configActions.test.js
Normal file
@ -0,0 +1,13 @@
|
||||
import * as actions from "./configActions";
|
||||
import * as types from "./playbooksActionsTypes";
|
||||
|
||||
it("setConfig", () => {
|
||||
const config = { apiURL: "http://example.org" };
|
||||
const expectedActions = [
|
||||
{
|
||||
type: types.SET_CONFIG,
|
||||
config
|
||||
}
|
||||
];
|
||||
expect(actions.setConfig(config)).toEqual(expectedActions);
|
||||
});
|
1
src/config/configActionsTypes.js
Normal file
1
src/config/configActionsTypes.js
Normal file
@ -0,0 +1 @@
|
||||
export const SET_CONFIG = "SET_CONFIG";
|
14
src/config/configReducer.js
Normal file
14
src/config/configReducer.js
Normal file
@ -0,0 +1,14 @@
|
||||
import * as types from "./configActionsTypes";
|
||||
|
||||
const initialState = {};
|
||||
|
||||
export default function(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case types.SET_CONFIG:
|
||||
return {
|
||||
...action.config
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
19
src/config/configReducer.test.js
Normal file
19
src/config/configReducer.test.js
Normal file
@ -0,0 +1,19 @@
|
||||
import reducer from "./configReducer";
|
||||
import * as types from "./configActionsTypes";
|
||||
|
||||
it("returns the initial state", () => {
|
||||
expect(reducer(undefined, {})).toEqual({});
|
||||
});
|
||||
|
||||
it("SET_CONFIG", () => {
|
||||
const state = reducer(
|
||||
{},
|
||||
{
|
||||
type: types.SET_CONFIG,
|
||||
config: {
|
||||
apiURL: "http://example.org"
|
||||
}
|
||||
}
|
||||
);
|
||||
expect(state.apiURL).toBe("http://example.org");
|
||||
});
|
5
src/containers.js
Normal file
5
src/containers.js
Normal file
@ -0,0 +1,5 @@
|
||||
export { default as Container404 } from "./layout/Container404";
|
||||
export { default as LoadingContainer } from "./layout/LoadingContainer";
|
||||
export { default as MainContainer } from "./layout/MainContainer";
|
||||
export { default as PlaybooksContainer } from "./playbooks/PlaybooksContainer";
|
||||
export { default as AboutContainer } from "./about/AboutContainer";
|
14
src/index.js
14
src/index.js
@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import 'patternfly/dist/css/patternfly.min.css';
|
||||
import 'patternfly/dist/css/patternfly-additions.min.css';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import "patternfly/dist/css/patternfly.min.css";
|
||||
import "patternfly/dist/css/patternfly-additions.min.css";
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
ReactDOM.render(<App />, document.getElementById("root"));
|
||||
|
12
src/layout/Container404.js
Normal file
12
src/layout/Container404.js
Normal file
@ -0,0 +1,12 @@
|
||||
import React, { Component } from "react";
|
||||
import MainContainer from "./MainContainer";
|
||||
|
||||
export default class Container404 extends Component {
|
||||
render() {
|
||||
return (
|
||||
<MainContainer>
|
||||
<p>404</p>
|
||||
</MainContainer>
|
||||
);
|
||||
}
|
||||
}
|
7
src/layout/LoadingContainer.js
Normal file
7
src/layout/LoadingContainer.js
Normal file
@ -0,0 +1,7 @@
|
||||
import React, { Component } from "react";
|
||||
|
||||
export default class LoadingContainer extends Component {
|
||||
render() {
|
||||
return <div>loading...</div>;
|
||||
}
|
||||
}
|
19
src/layout/MainContainer.js
Normal file
19
src/layout/MainContainer.js
Normal file
@ -0,0 +1,19 @@
|
||||
import React, { Component } from "react";
|
||||
import Navbar from "./navigation/Navbar";
|
||||
import { Grid, Row, Col } from "patternfly-react";
|
||||
|
||||
export default class MainContainer extends Component {
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
return (
|
||||
<div className="MainContent">
|
||||
<Navbar />
|
||||
<Grid fluid>
|
||||
<Row>
|
||||
<Col xs={12}>{children}</Col>
|
||||
</Row>
|
||||
</Grid>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
15
src/layout/navigation/NavLink.js
Normal file
15
src/layout/navigation/NavLink.js
Normal file
@ -0,0 +1,15 @@
|
||||
import React, { Component } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export default class NavLink extends Component {
|
||||
render() {
|
||||
const { id, to, location, children, ...rest } = this.props;
|
||||
return (
|
||||
<li className={location.pathname === to ? "active" : ""}>
|
||||
<Link to={to} id={`navbar-navbar-primary__${id}-link`} {...rest}>
|
||||
{children}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,8 +1,13 @@
|
||||
import React from "react";
|
||||
import logo from "./logo.svg";
|
||||
import React, { Component } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { withRouter } from "react-router";
|
||||
|
||||
export default class Navbar extends React.Component {
|
||||
import logo from "./logo.svg";
|
||||
import NavLink from "./NavLink";
|
||||
|
||||
export class Navbar extends Component {
|
||||
render() {
|
||||
const { location } = this.props;
|
||||
return (
|
||||
<nav className="navbar navbar-default navbar-pf">
|
||||
<div className="navbar-header">
|
||||
@ -17,14 +22,18 @@ export default class Navbar extends React.Component {
|
||||
<span className="icon-bar" />
|
||||
<span className="icon-bar" />
|
||||
</button>
|
||||
<a className="navbar-brand" href="/reports/">
|
||||
<Link
|
||||
to="/playbooks"
|
||||
id="navbar-navbar-header__playbooks-link"
|
||||
className="navbar-brand"
|
||||
>
|
||||
<img
|
||||
src={logo}
|
||||
alt="ARA: Ansible Run Analysis"
|
||||
width="81"
|
||||
height="32"
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="collapse navbar-collapse navbar-collapse-1">
|
||||
<ul className="nav navbar-nav navbar-utility">
|
||||
@ -66,15 +75,17 @@ export default class Navbar extends React.Component {
|
||||
</li>
|
||||
</ul>
|
||||
<ul className="nav navbar-nav navbar-primary">
|
||||
<li className="active">
|
||||
<a href="/reports/">Playbook reports</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/about/">About</a>
|
||||
</li>
|
||||
<NavLink id="playbooks" to="/playbooks" location={location}>
|
||||
Playbooks reports
|
||||
</NavLink>
|
||||
<NavLink id="about" to="/about" location={location}>
|
||||
About
|
||||
</NavLink>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(Navbar);
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
40
src/playbooks/PlaybooksContainer.js
Normal file
40
src/playbooks/PlaybooksContainer.js
Normal file
@ -0,0 +1,40 @@
|
||||
import React, { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { MainContainer } from "../containers";
|
||||
import { getPlaybooks } from "./playbooksActions";
|
||||
|
||||
export class PlaybooksContainer extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getPlaybooks();
|
||||
}
|
||||
render() {
|
||||
const { playbooks } = this.props;
|
||||
return (
|
||||
<MainContainer>
|
||||
<p>playbooks</p>
|
||||
<ul>
|
||||
{playbooks.map(playbook => (
|
||||
<li key={playbook.id}>{playbook.id}</li>
|
||||
))}
|
||||
</ul>
|
||||
</MainContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
playbooks: Object.values(state.playbooks)
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
getPlaybooks: () => dispatch(getPlaybooks())
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(PlaybooksContainer);
|
15
src/playbooks/playbooksActions.js
Normal file
15
src/playbooks/playbooksActions.js
Normal file
@ -0,0 +1,15 @@
|
||||
import axios from "axios";
|
||||
import * as types from "./playbooksActionsTypes";
|
||||
|
||||
export function getPlaybooks() {
|
||||
return (dispatch, getState) => {
|
||||
const { apiURL } = getState().config;
|
||||
return axios.get(`${apiURL}/api/v1/playbooks`).then(response => {
|
||||
dispatch({
|
||||
type: types.FETCH_PLAYBOOKS,
|
||||
playbooks: response.data.results
|
||||
});
|
||||
return response;
|
||||
});
|
||||
};
|
||||
}
|
31
src/playbooks/playbooksActions.test.js
Normal file
31
src/playbooks/playbooksActions.test.js
Normal file
@ -0,0 +1,31 @@
|
||||
import axios from "axios";
|
||||
import configureMockStore from "redux-mock-store";
|
||||
import thunk from "redux-thunk";
|
||||
import axiosMockAdapter from "axios-mock-adapter";
|
||||
|
||||
import { getPlaybooks } from "./playbooksActions";
|
||||
import * as types from "./playbooksActionsTypes";
|
||||
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureMockStore(middlewares);
|
||||
|
||||
const axiosMock = new axiosMockAdapter(axios);
|
||||
|
||||
it("getPlaybooks", () => {
|
||||
axiosMock.onGet("https://api.example.org/api/v1/playbooks").reply(200, {
|
||||
count: 1,
|
||||
next: null,
|
||||
previous: null,
|
||||
results: [{ id: "p1" }]
|
||||
});
|
||||
const expectedActions = [
|
||||
{
|
||||
type: types.FETCH_PLAYBOOKS,
|
||||
playbooks: [{ id: "p1" }]
|
||||
}
|
||||
];
|
||||
const store = mockStore({ config: { apiURL: "https://api.example.org" } });
|
||||
return store.dispatch(getPlaybooks()).then(() => {
|
||||
expect(store.getActions()).toEqual(expectedActions);
|
||||
});
|
||||
});
|
1
src/playbooks/playbooksActionsTypes.js
Normal file
1
src/playbooks/playbooksActionsTypes.js
Normal file
@ -0,0 +1 @@
|
||||
export const FETCH_PLAYBOOKS = "FETCH_PLAYBOOKS";
|
15
src/playbooks/playbooksReducer.js
Normal file
15
src/playbooks/playbooksReducer.js
Normal file
@ -0,0 +1,15 @@
|
||||
import * as types from "./playbooksActionsTypes";
|
||||
|
||||
const initialState = {};
|
||||
|
||||
export default function(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case types.FETCH_PLAYBOOKS:
|
||||
return action.playbooks.reduce((accumulator, playbook) => {
|
||||
accumulator[playbook.id] = playbook;
|
||||
return accumulator;
|
||||
}, {});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
12
src/playbooks/playbooksReducer.test.js
Normal file
12
src/playbooks/playbooksReducer.test.js
Normal file
@ -0,0 +1,12 @@
|
||||
import reducer from "./playbooksReducer";
|
||||
import * as types from "./playbooksActionsTypes";
|
||||
|
||||
it("FETCH_PLAYBOOKS", () => {
|
||||
const newState = reducer(undefined, {
|
||||
type: types.FETCH_PLAYBOOKS,
|
||||
playbooks: [{ id: "p1" }]
|
||||
});
|
||||
expect(newState).toEqual({
|
||||
p1: { id: "p1" }
|
||||
});
|
||||
});
|
14
src/store.js
Normal file
14
src/store.js
Normal file
@ -0,0 +1,14 @@
|
||||
import { createStore, applyMiddleware, combineReducers } from "redux";
|
||||
import thunk from "redux-thunk";
|
||||
import configReducer from "./config/configReducer";
|
||||
import playbooksReducer from "./playbooks/playbooksReducer";
|
||||
|
||||
const store = createStore(
|
||||
combineReducers({
|
||||
config: configReducer,
|
||||
playbooks: playbooksReducer
|
||||
}),
|
||||
applyMiddleware(thunk)
|
||||
);
|
||||
|
||||
export default store;
|
Loading…
x
Reference in New Issue
Block a user