Skip to content
This repository has been archived by the owner on May 27, 2021. It is now read-only.

Commit

Permalink
Merge pull request #29 from andymikulski/merge-normandy
Browse files Browse the repository at this point in the history
#6: Merge Normandy
  • Loading branch information
rehandalal committed Mar 15, 2018
2 parents 859bc1a + 42caab1 commit cf7f3e0
Show file tree
Hide file tree
Showing 217 changed files with 13,466 additions and 217 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/normandy/**/*.*
18 changes: 10 additions & 8 deletions config-overrides.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,23 @@ module.exports = function(config, env) {
}
};

// If an --app=something parameter is present when running this script,
// change the entry point to start the given app.
let selectedApp = process.argv.find(arg => arg.startsWith('--app'));
if (selectedApp) {
selectedApp = selectedApp.split('=')[1];
config.entry = path.resolve(__dirname, `./src/${selectedApp}/index.js`);
}

// LESS support
config = rewireLess(config, env);
// Use Ant LESS imports
config = injectBabelPlugin(
["import", { libraryName: "antd", style: true }],
config
);

// If an --app=something parameter is present when running this script,
// change the entry point to start the given app.
let selectedApp = process.argv.find(val => val.startsWith('--app'));
if(selectedApp) {
selectedApp = selectedApp.split('=')[1];
config.entry = path.resolve(__dirname, `./src/${selectedApp}/index.js`);
}
// Decorator support (Normandy)
config = injectBabelPlugin('transform-decorators-legacy', config);

return config;
};
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"immutable": "^3.8.2",
"js-cookie": "^2.2.0",
"localforage": "^1.5.6",
"lodash.get": "^4.4.2",
"photon-ant": "^0.1.4",
"react": "^16.2.0",
"react-copy-to-clipboard": "^5.0.1",
Expand All @@ -17,13 +18,14 @@
"react-router-dom": "^4.2.2",
"react-scripts": "1.1.1",
"redux": "^3.7.2",
"redux-little-router": "^15.0.0",
"redux-little-router": "14.0.0-0",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.2.0",
"underscore": "^1.8.3"
},
"scripts": {
"start": "react-app-rewired start",
"start:normandy": "react-app-rewired start --app=normandy",
"build": "react-app-rewired build",
"test": "react-app-rewired test --env=jsdom",
"eject": "react-scripts eject",
Expand All @@ -35,6 +37,7 @@
},
"devDependencies": {
"babel-plugin-import": "^1.6.5",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"flow-bin": "^0.66.0",
"prettier": "^1.11.1",
"react-app-rewire-less": "^2.1.0",
Expand Down
5 changes: 2 additions & 3 deletions src/console/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import ReactDOM from 'react-dom';
import DevConsoleApp from './App';
import Root from './App';

ReactDOM.render(<DevConsoleApp />, document.getElementById('root'));
ReactDOM.render(<Root />, document.querySelector('#main'));
47 changes: 33 additions & 14 deletions src/normandy/App.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import React from 'react';
import { Provider } from 'react-redux';
import { applyMiddleware, compose, createStore } from 'redux';
import { initializeCurrentLocation } from 'redux-little-router';
import thunk from 'redux-thunk';

class App extends Component {
import './less/main.less';

import Router, {
enhancer as routerEnhancer,
middleware as routerMiddleware,
} from 'normandy/routes';
import reducers from 'normandy/state';

const middleware = [
routerMiddleware,
thunk,
];

const store = createStore(reducers, reducers(undefined, { type: 'initial' }), compose(
applyMiddleware(...middleware),
routerEnhancer,
));

const initialLocation = store.getState().router;
if (initialLocation) {
store.dispatch(initializeCurrentLocation(initialLocation));
}

export default class Root extends React.PureComponent {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to Normandy</h1>
</header>
{this.props.authToken && (
<div>authToken is "{this.props.authToken}"</div>
)}
<div id="normandy-app">
<Provider store={store}>
<Router />
</Provider>
</div>
);
}
}

export default App;
69 changes: 69 additions & 0 deletions src/normandy/components/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Layout, LocaleProvider } from 'antd';
import enUS from 'antd/lib/locale-provider/en_US';
import PropTypes from 'prop-types';
import React from 'react';
import { Link } from 'redux-little-router';

import CurrentUserDetails from 'normandy/components/common/CurrentUserDetails';
import NavigationCrumbs from 'normandy/components/common/NavigationCrumbs';
import NavigationMenu from 'normandy/components/common/NavigationMenu';
import EnvAlert from 'normandy/components/common/EnvAlert';
import QueryActions from 'normandy/components/data/QueryActions';
import QueryServiceInfo from 'normandy/components/data/QueryServiceInfo';

const { Content, Header, Sider } = Layout;


export default class App extends React.PureComponent {
static propTypes = {
children: PropTypes.node,
};

static defaultProps = {
children: null,
};

render() {
const { children } = this.props;

return (
<LocaleProvider locale={enUS}>
<Layout>
<EnvAlert />

{/*
Global query components; add any queries for data needed across the
entire app that we only need to fetch once.
*/}
<QueryActions />
<QueryServiceInfo />

<Header>
<CurrentUserDetails />
<div className="logo">
<Link href="/">SHIELD Control Panel</Link>
</div>
</Header>

<Layout>
<Sider
className="sidebar"
breakpoint="sm"
collapsedWidth="0"
>
<NavigationMenu />
</Sider>

<Layout className="content-wrapper">
<NavigationCrumbs />

<Content className="content">
{children}
</Content>
</Layout>
</Layout>
</Layout>
</LocaleProvider>
);
}
}
17 changes: 17 additions & 0 deletions src/normandy/components/common/BooleanIcon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Icon } from 'antd';
import PropTypes from 'prop-types';
import React from 'react';


export default class BooleanIcon extends React.PureComponent {
static propTypes = {
value: PropTypes.bool.isRequired,
};

render() {
const { value } = this.props;
const type = value ? 'check' : 'close';
const booleanClass = value ? 'is-true' : 'is-false';
return <Icon className={`boolean-icon ${booleanClass}`} type={type} />;
}
}
38 changes: 38 additions & 0 deletions src/normandy/components/common/CheckboxMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Button, Checkbox, Dropdown } from 'antd';
import PropTypes from 'prop-types';
import React from 'react';


export default class CheckboxMenu extends React.PureComponent {
static propTypes = {
checkboxes: PropTypes.array,
label: PropTypes.string,
onChange: PropTypes.func,
options: PropTypes.array,
};

static defaultProps = {
checkboxes: null,
label: null,
onChange: null,
options: null,
};

render() {
const { checkboxes, label, onChange, options } = this.props;

const menu = (
<Checkbox.Group
onChange={onChange}
options={options}
defaultValue={checkboxes}
/>
);

return (
<Dropdown overlay={menu}>
<Button icon="bars">{label}</Button>
</Dropdown>
);
}
}
40 changes: 40 additions & 0 deletions src/normandy/components/common/CurrentUserDetails.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Button } from 'antd';
import { Map } from 'immutable';
import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';

import {
getCurrentUser,
getLogoutUrl,
} from 'normandy/state/app/serviceInfo/selectors';


@connect(
state => ({
user: getCurrentUser(state, new Map()),
logoutUrl: getLogoutUrl(state, ''),
}),
)
export default class CurrentUserDetails extends React.PureComponent {
static propTypes = {
logoutUrl: PropTypes.string.isRequired,
user: PropTypes.instanceOf(Map).isRequired,
};

render() {
const { logoutUrl, user } = this.props;

return (
<div className="current-user">
<span className="email">{user.get('email')}</span>

<a href={logoutUrl}>
<Button type="primary" icon="logout" size="small" ghost>
Log out
</Button>
</a>
</div>
);
}
}
63 changes: 63 additions & 0 deletions src/normandy/components/common/EnrollmentStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Icon } from 'antd';
import cx from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import { Link } from 'redux-little-router';

// Ideally the prop.recipe would be an Immutable Map, but Ant's Table works with
// plain JS objects, which means this component can not be Pure.
export default class EnrollmentSatus extends React.Component {
static propTypes = {
recipe: PropTypes.object.isRequired,
};

getLabel() {
let label = 'Disabled';
if (this.isRecipeEnabled()) {
label = this.isRecipePaused() ? 'Paused' : 'Active';
}
return label;
}

getIcon() {
let iconType = 'minus';
if (this.isRecipeEnabled()) {
iconType = this.isRecipePaused() ? 'pause' : 'check';
}
return iconType;
}

getColor() {
let colorClass;
if (this.isRecipeEnabled()) {
colorClass = this.isRecipePaused() ? 'is-false' : 'is-true';
}
return colorClass;
}

isRecipePaused() {
const { recipe } = this.props;
return !recipe.enabled || !!recipe.arguments.isEnrollmentPaused;
}

isRecipeEnabled() {
const { recipe } = this.props;
return recipe.enabled;
}

render() {
const {
recipe,
} = this.props;

return (
<Link href={`/recipe/${recipe.id}/`} className={cx('status-link', !recipe.enabled && 'is-lowkey')}>
<Icon
className={cx('status-icon', this.getColor())}
type={this.getIcon()}
/>
<span className="enrollment-label">{this.getLabel()}</span>
</Link>
);
}
}

0 comments on commit cf7f3e0

Please sign in to comment.