Open KashGit opened 8 years ago
import React, { PropTypes } from 'react'; import { Provider } from 'react-redux'; import App from './App';
const Root = ({ store }) => (
);
Root.propTypes = { store: PropTypes.object };
export default Root;
import React, { Component, PropTypes } from 'react'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import * as SummaryActions from '../actions/SummaryActions'; import Actions from './Actions'; import Contact from './Contact'; import Networth from './Networth'; import Messages from './Messages'; import Spending from './Spending'; import AnnuityAndIncomePlan from './AnnuityAndIncomePlan'; import Investments from './Investments'; import GroupPlans from './GroupPlans'; import AccessFunds from './AccessFunds'; import TermsAndConditions from './TermsAndConditions'; import IsaCard from './Billing'; import Exception from './Exception'; import Title from './Title'; import NetworthOvertime from './NetworthOvertime';
class App extends Component {
componentDidMount() {
this.props.getSessionStorageData('dismissedSysMessages');
}
render() {
return (<main>
<div className="grid-container">
<div className="row" id="summary-title">
<Title />
</div>
<div className="row" id="card-content">
<div className="large-24 col">
<Actions />
<GroupPlans />
</div>
</div>
</div>
</main>);
}
}
function mapDispatchToProps(dispatch) { return bindActionCreators(SummaryActions, dispatch); }
App.propTypes = { getSessionStorageData: PropTypes.func };
export default connect(null, mapDispatchToProps)(App);
--functional-- import React from 'react';
const Title = () => (
);
export default Title;
import { createStore, applyMiddleware, compose } from 'redux'; import rootReducer from '../reducers'; import IS_BROWSER from '../utils/isBrowser'; import thunk from 'redux-thunk'; import adobedtm from '../middleware/adobedtm'; import optimizely from '../middleware/optimizely'; import { analyticsReporter, crashReporter, errorReporter } from '../middleware/newrelic';
const newrelicMiddleware = (IS_BROWSER && window.newrelic) ? [crashReporter, analyticsReporter, errorReporter] : []; const middleware = [thunk, ...newrelicMiddleware, adobedtm, optimizely];
const enhancer = compose( applyMiddleware(...middleware), IS_BROWSER && window.devToolsExtension ? window.devToolsExtension() : (f) => f );
export default function configureStore(initialState = {}) { const store = createStore(rootReducer, initialState, enhancer); if (module.hot) { module.hot.accept('../reducers', () => { const nextRootReducer = require('../reducers').default; // eslint-disable-line store.replaceReducer(nextRootReducer); }); } return store; }
import React, { Component, PropTypes } from 'react'; import classNames from 'classnames'; import { deleteAction, addAction } from '../../actions/MainActions';
export default class Accounts extends Component { renderLinkedAccounts(data) {}
renderAccountsWithErrors(data) {}
render() {
const { data } = this.props;
const classes = classNames('active', 'center', 'xlarge');
const addData = this.renderAdd(data);
const deleteData = this.renderDelete(data);
return (
<div className={classes}>
{addData}
{deleteData}
</div>
);
}
}
Accounts.propTypes = { data: PropTypes.object };
Tech Stack Used: JavaScript is written in ES6 and transpiled with Babel.
postcss.config.js module.exports = { plugins: [ require('autoprefixer')({ / ...options / }) ] }
package.json { "name": "bx-group-detail", "version": "0.0.0", "description": "A fantastic micro app for BX group to see your financials", "scripts": { "build": "npm run build:client", "build:client": "BABEL_ENV=production webpack --progress --config ./webpack/webpack.client.babel.js", "start": "BABEL_ENV=production babel-node ./src/server", "start:mock": "NODE_ENV=development BABEL_ENV=development babel-node ./src/server/mock", "dev": "NODE_ENV=development BABEL_ENV=development babel-node ./webpack/webpack-dev-server.js & NODE_ENV=development BABEL_ENV=development babel-node ./src/server & npm run start:mock", "dev:int": "NODE_ENV=development NODE_APP_INSTANCE=int BABEL_ENV=development babel-node ./webpack/webpack-dev-server.js & NODE_ENV=development NODE_APP_INSTANCE=int BABEL_ENV=development babel-watch --exclude src/client ./src/server & npm run start:mock", "clean": "rm -r ./build ./node_modules", "lint": "eslint src -f junit > ./test_results/eslint.xml", "lint:watch": "eslint src", "lint:fix": "eslint src --fix", "test": "NODE_ENV=development BABEL_ENV=development mocha --opts test/mocha.opts --bail", "test:watch": "npm test -- --watch --reporter list" }, "private": true, "devDependencies": { "autoprefixer": "6.7.7", "babel-core": "6.24.1", "babel-eslint": "7.2.1", "babel-loader": "6.4.1", "babel-plugin-transform-object-assign": "6.22.0", "babel-plugin-transform-object-rest-spread": "6.23.0", "babel-plugin-transform-react-constant-elements": "6.23.0", "babel-plugin-transform-react-inline-elements": "6.22.0", "babel-plugin-transform-react-remove-prop-types": "0.3.3", "babel-plugin-transform-runtime": "6.23.0", "babel-polyfill": "6.23.0", "babel-preset-es2015": "6.24.1", "babel-preset-react": "6.24.1", "babel-preset-react-hmre": "1.1.1", "babel-register": "6.24.1", "babel-watch": "2.0.6", "case-sensitive-paths-webpack-plugin": "2.0.0", "chai": "3.5.0", "clean-webpack-plugin": "0.1.16", "cookie-parser": "1.4.3", "css-loader": "0.28.0", "enzyme": "2.8.0", "es6-promise": "4.1.0", "eslint": "3.19.0", "eslint-config-airbnb": "14.1.0", "eslint-plugin-babel": "4.1.1", "eslint-plugin-import": "2.2.0", "eslint-plugin-jsx-a11y": "4.0.0", "eslint-plugin-react": "6.10.3", "extract-text-webpack-plugin": "2.1.0", "fetch-mock": "5.9.4", "file-loader": "0.11.1", "html-tagged-literals": "1.0.2", "image-webpack-loader": "3.3.0", "imports-loader": "0.7.1", "jsdom": "9.12.0", "json-loader": "0.5.4", "mocha": "3.2.0", "mocha-junit-reporter": "1.13.0", "node-sass": "3.8.0", "numeral": "2.0.6", "postcss-loader": "1.3.3", "proxyquire": "1.7.11", "react-addons-css-transition-group": "15.5.2", "react-addons-test-utils": "15.5.1", "react-css-transition-replace": "2.2.0", "reselect": "3.0.0", "sass-loader": "4.0.2", "sinon": "2.1.0", "style-loader": "0.16.1", "supertest": "3.0.0", "ui-toolkit": "1.10.0", "url-loader": "0.5.8", "webpack": "2.3.3", "webpack-assets-manifest": "0.6.2", "webpack-dev-server": "2.4.2" }, "dependencies": { "babel-cli": "6.24.1", "classnames": "2.2.5", "compression": "1.6.2", "config": "1.25.1", "cookie-parser": "1.4.3", "d3": "4.7.4", "express": "4.15.2", "isomorphic-fetch": "2.2.1", "json-server": "0.9.6", "keymirror": "0.1.1", "lodash": "4.17.4", "moment": "2.18.1", "moment-timezone": "0.5.13", "newrelic": "1.38.2", "nuka-carousel": "2.0.4", "numeral": "2.0.6", "react": "15.5.4", "react-addons-css-transition-group": "15.5.2", "react-dom": "15.5.4", "react-redux": "5.0.4", "react-responsive": "1.2.7", "redux": "3.6.0", "redux-mock-store": "1.2.3", "redux-persist": "4.6.0", "redux-reporter": "0.1.1", "redux-thunk": "2.2.0", "run-middleware": "0.6.6", "useragent": "2.1.13", "winston": "2.3.1" } }
.npmrc registry:path save-exact=true
.eslintrc { "ecmaFeatures": { "jsx": true, "modules": true }, "env": { "browser": true, "node": true }, "extends": "airbnb", "parser": "babel-eslint", "rules": { "no-use-before-define": ["error", { "functions": false, "classes": false }], "class-methods-use-this": [0], "comma-dangle": [2, "never"], "indent": [2, 4, { "SwitchCase": 1 }], "jsx-a11y/no-static-element-interactions": [0], "jsx-a11y/tabindex-no-positive": [0], "max-len": [1, 150], "no-console": [0], "arrow-body-style": ["error", "as-needed"], "no-param-reassign": [0], "no-underscore-dangle": [0], "import/no-named-as-default": [0], "new-cap": [0], "no-shadow": [0], "react/jsx-no-target-blank": [0], "react/jsx-closing-bracket-location": [2, "after-props"], "react/forbid-prop-types": [0], "react/no-danger": [0], "react/jsx-indent": [0], "react/jsx-indent-props": [0], "react/jsx-no-bind": [2, { "ignoreRefs": true, "allowArrowFunctions": true, "allowBind": true }], "react/jsx-first-prop-new-line": [0], "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }] }, "plugins": [ "babel", "react" ] }
.editorconfig root = true
[*] charset = utf-8 indent_size = 4 indent_style = space insert_final_newline = true trim_trailing_whitespace = true
[*.md] trim_trailing_whitespace = false
[{.eslintrc,package.json}]
package.json
file cannot be changedindent_size = 2 indent_style = space max_line_length = 150
.babelrc { "presets": [ ["es2015", { "es2015": { "loose": true, "modules": false } }], "react" ], "plugins": [ "transform-object-assign", "transform-object-rest-spread", "transform-runtime" ], "env": { "production": { "plugins": [ "transform-react-remove-prop-types", "transform-react-constant-elements" ] } } }
webpack Folder
status-webpack-plugin.js const webpack = require('webpack');
module.exports = function(name, doneMessage){
return new webpack.ProgressPlugin(function(percentage, message) {
var MOVE_LEFT = new Buffer('1b5b3130303044', 'hex').toString();
var CLEAR_LINE = new Buffer('1b5b304b', 'hex').toString();
process.stdout.write(CLEAR_LINE + 'webpack compiling ['+name+']: ' + Math.round(percentage * 100) + '%: ' + message + MOVE_LEFT);
if(percentage == 1){
setTimeout(function(){
process.stdout.write(doneMessage || "webpack say good, fire up...\n");
},0);
}
});
}
uglifyjs.json { "compress": { "warnings": false, "screw_ie8": true, "sequences": true, "dead_code": true, "drop_debugger": true, "comparisons": true, "conditionals": true, "evaluate": true, "booleans": true, "loops": true, "unused": true, "hoist_funs": true, "if_return": true, "join_vars": true, "cascade": true, "drop_console": true }, "output": { "comments": false } }
web-pack-dev-server.js import webpack from 'webpack'; import DevServer from 'webpack-dev-server'; import config from './webpack.client.babel';
const { port, host } = config.devServer;
const devServer = new DevServer(webpack(config), config.devServer)
.listen(port, () => {
console.log(HMR server on ${host}:${port} [${process.env.NODE_ENV}]
);
});
webpack-client.babel.js
const IS_DEV = require('../src/server/utils/isDev'); const path = require('path'); const webpack = require('webpack'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const WebpackAssetsManifest = require('webpack-assets-manifest'); const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); const StatusWebpackPlugin = require('./status-webpack-plugin'); const config = require('config'); const fs = require('fs');
const basePath = config.basePath;
const hash = IS_DEV
? '[name]'
: '[name]-[chunkhash]';
const root = process.cwd();
const styleLoaders = ['css-loader?minimize&sourceMap&importLoaders=5', 'postcss-loader', 'sass-loader?sourceMap'];
const prefetches = ['react', 'redux'];
const port = 8081;
const host = 'http://localhost';
const contentBase = ${host}:${port}
;
const publicPath = IS_DEV
? ${contentBase}${basePath}/assets/
: ${basePath}/assets/
;
let babelrc = JSON.parse(fs.readFileSync(path.join(root, '.babelrc'), 'utf8'));
var plugins = [
new webpack.IgnorePlugin(/\/server\//),
new WebpackAssetsManifest({
output: path.join(root, '/build/assets', 'asset-manifest.json'),
writeToDisk: true
}),
new CaseSensitivePathsPlugin(),
new CleanWebpackPlugin(['build/assets'], {
'root': path.join(__dirname, '../'),
'verbose': false
}),
new webpack.DefinePlugin({
'IS_BROWSER': true,
'IS_DEV': IS_DEV,
'process.env.NODE_ENV': JSON.stringify(IS_DEV
? 'development'
: 'production'),
'process.env.NODE_APP_INSTANCE': JSON.stringify(process.env.NODE_APP_INSTANCE)
}),
...prefetches.map((i) => new webpack.PrefetchPlugin(i)),
...(IS_DEV
? [
new webpack.HotModuleReplacementPlugin(),
new webpack.SourceMapDevToolPlugin({filename: "[file].map", append: \n//# sourceMappingURL=${basePath}/assets/[url]
, exclude: ["vendors.js"]}),
new StatusWebpackPlugin('client')
]
: [
new webpack.ContextReplacementPlugin(/.$/, /NEVER_MATCH^/), // ignore dynamic reqires
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.AggressiveMergingPlugin(),
new ExtractTextPlugin({filename: public/${hash}.css
, disable: false, allChunks: true}),
new webpack.optimize.UglifyJsPlugin(require('./uglifyjs.json'))
]),
// new OfflinePlugin({publicPath:${basePath}/assets/
, relativePaths: false, caches: {main: ["GuardianSans-Thin-Web.woff"]}, safeToUseOptionalCaches: true, AppCache: false })
];
module.exports = {
devServer: {
host,
port,
proxy: {
"*": ${host}:${port}
},
quiet: true,
noInfo: true,
hot: true,
inline: true,
lazy: false,
publicPath,
headers: {
'Access-Control-Allow-Origin': ''
},
stats: {
colors: true
}
},
context: path.join(root, 'src'),
entry: {
main: IS_DEV
? ["webpack/hot/dev-server", webpack-dev-server/client?${contentBase}
, './client/js/index.js', './client/scss/index.scss']
: [
'./client/js/index.js', './client/scss/index.scss'
],
criticalpath: ['./client/scss/blur.scss', './client/scss/loaders.scss', './client/scss/fonts.scss']
},
output: {
path: path.join(root, '/build/assets'),
publicPath, // needed so fonts can be found
filename: hash + '.js',
chunkFilename: hash + '.js'
},
externals: {
'window.newrelic': 'newrelic'
},
plugins: plugins,
module: {
rules: [
{
test: /.js$/,
loader: 'babel-loader',
include: path.join(root, 'src'),
options: {
cacheDirectory: false,
babelrc: false,
...(IS_DEV)
? Object.assign({}, babelrc, {
presets: [].concat(babelrc.presets, ['react-hmre'])
})
: babelrc
}
}, {
test: /.json$/,
loader: 'json-loader'
}, {
test: /.(jpe?g|png|gif)([\?]?.)$/,
include: [path.join(root, 'src/client')],
use: ['url-loader?limit=100', 'image-webpack-loader?bypassOnDebug&optimizationLevel=7&interlaced=false'],
}, {
test: /.(woff|woff2|eot|ttf|svg)([?]?.)$/,
use: 'file-loader?name=[name].[ext]', //name fonts to enable de-dup shared header/footer fonts
}, {
test: /.s?css$/,
include: [
path.join(root, 'src/client'),
path.resolve(root, 'node_modules')
],
loader: IS_DEV
? 'style-loader!' + styleLoaders.join('!')
: ExtractTextPlugin.extract({fallback: 'style-loader', use: styleLoaders})
}
]
}
};
Test Folder
env_setup.js
import { jsdom } from 'jsdom';
let exposedProperties = ['window', 'navigator', 'document'];
global.document = jsdom(''); global.window = document.defaultView; Object.keys(document.defaultView).forEach((property) => { if (typeof global[property] === 'undefined') { exposedProperties.push(property); global[property] = document.defaultView[property]; } });
global.navigator = { userAgent: 'node.js' };
mocha.opts test/*/.test.js --require test/env_setup.js --recursive --timeout 30000 --reporter spec --compilers js:babel-register
Client Folder
components->groupDettail Folder
group.test.js
import React from 'react'; import { expect } from 'chai'; import { shallow, mount, render } from 'enzyme'; import { GroupDetail } from './../../../../src/client/js/components/GroupDetail';
import { mockState } from './../mock/mock-state';
describe('
server Folder app.test.js
import request from 'supertest'; import { expect } from 'chai'; import { Router } from 'express'; import proxyquire from 'proxyquire';
let fake = new Router(); fake.use(function (req, res) { res.json({}); });
let app = proxyquire .noCallThru() .load('../../src/server/app', { './routes/health': fake, './routes/info': fake, './routes/root': fake, './routes/api/frsummary': fake, }).default;
describe('test the main application', function() {
// it('should be registered for a health route', function (done) {
// request(app)
// .get('/summary/health')
// .expect('Content-Type', /json/)
// .expect(200)
// .end(function (err, res) {
// if (err) {
// return done(err);
// }
// expect(res.body).to.deep.equal({});
// done();
// });
// });
//
// it('should be registered for a info route', function (done) {
// request(app)
// .get('/summary/info')
// .expect('Content-Type', /json/)
// .expect(200)
// .end(function (err, res) {
// if (err) {
// return done(err);
// }
// expect(res.body).to.deep.equal({});
// done();
// });
// });
//
// it('should be registered for a spending route', function (done) {
// request(app)
// .get('/summary/api/spending')
// .expect('Content-Type', /json/)
// .expect(200)
// .end(function (err, res) {
// if (err) {
// return done(err);
// }
// expect(res.body).to.deep.equal({});
// done();
// });
// });
//
// it('should be registered for a networth route', function (done) {
// request(app)
// .get('/summary/api/networth')
// .expect('Content-Type', /json/)
// .expect(200)
// .end(function (err, res) {
// if (err) {
// return done(err);
// }
// expect(res.body).to.deep.equal({});
// done();
// });
// });
//
// it('should be registered for a frsummary route', function (done) {
// request(app)
// .get('/summary/api/frsummary')
// .expect('Content-Type', /json/)
// .expect(200)
// .end(function (err, res) {
// if (err) {
// return done(err);
// }
// expect(res.body).to.deep.equal({});
// done();
// });
// });
//
// it('should be registered for a accounts route', function (done) {
// request(app)
// .get('/summary/api/accounts')
// .expect('Content-Type', /json/)
// .expect(200)
// .end(function (err, res) {
// if (err) {
// return done(err);
// }
// expect(res.body).to.deep.equal({});
// done();
// });
// });
// it('should be registered to handle 404', function (done) {
// request(app)
// .get('/summary/api/junk')
// .expect('Content-Type', /json/)
// .expect(404)
// .end(function (err, res) {
// if (err) {
// return done(err);
// }
// done();
// });
// });
});
test file: import React from 'react'; import { expect } from 'chai'; import { shallow, mount, render } from 'enzyme'; import ULife from './../../../../src/client/js/components/groupDetail/ULife'; import SortHeader from './../../../../src/client/js/components/SortHeader'; import { mockState } from './../mock/mock-state';
const mockGroupData = mockState.accounts;
const ulife = shallow(
describe('
it('Should not render when it doesnt have data', () => {
const ulife = shallow(<ULife ulifeData={[]} />);
expect(ulife.find('table').length).to.be.equal(0);
});
it('Should have 8 policy numbers in group details', () => {
expect(ulife.find('ChildRow').length).to.be.equal(8);
});
it('Should display the Ulife group details', () => {
const tableRows = ulife.find('.solo-row');
tableRows.forEach((row, index) => {
const rowData = row.find('td');
const groupPolicy = mockGroupData[index];
const expectedgroupPolicyData = [
`${groupPolicy.clientName}`,
`${groupPolicy.accountNumberMasked}`,
`${groupPolicy.policyDate}`,
`${groupPolicy.netDeathBenefit}`,
`${groupPolicy.contractFundValue}`,
`${groupPolicy.netCashSurrenderValue}`,
`${groupPolicy.netCostBasisValue}`];
rowData.forEach((column, index) => {
expect(column.text().trim()).to.be.equal(expectedgroupPolicyData[index]);
});
});
});
it('Should display the Ulife group details header', () => {
const tableRows = ulife.find('.group-table');
tableRows.forEach((row) => {
const rowData = row.find('th');
const expectedgroupDetailHeader = [
'Policy Number',
'Policy Date',
'Net Death Benefit',
'Contract Fund Value',
'Net Cash Surrender Value',
'Net Cost Basis'
];
rowData.forEach((column, index) => {
expect(column.text().trim()).to.be.equal(expectedgroupDetailHeader[index]);
});
});
});
it('Should contain SortHeader component', () => {
expect(ulife.find(SortHeader).length).to.be.equal(1);
});
});
config folder default.json { "basePath": "/groupdetail", "apikey": "", "assets": "/assets", "domains": { "nmc": "", "nmlvhub": "http://nmapi", "plan": "" }, "nmlvhub": { "groupDetail": "/groupDetails" } }
Docker folder Dockerfile
FROM ubuntu:16.04
ENV DEBIAN_FRONTEND=noninteractive \ LANG=en_US.UTF-8 \ TERM=xterm RUN echo "export > /etc/envvars" >> /root/.bashrc && \ echo "export PS1='\e[1;31m]\u@\h:\w\$[\e[0m] '" | tee -a /root/.bashrc /etc/bash.bashrc && \ echo "alias tcurrent='tail /var/log/*/current -f'" | tee -a /root/.bashrc /etc/bash.bashrc
RUN apt-get update RUN apt-get install -y locales && locale-gen en_US en_US.UTF-8
RUN apt-get install -y --no-install-recommends runit CMD export > /etc/envvars && /usr/sbin/runsvdir-start
RUN apt-get install -y --no-install-recommends vim less net-tools inetutils-ping wget curl git telnet nmap socat dnsutils netcat tree htop unzip sudo software-properties-common jq psmisc iproute python ssh rsync
RUN wget -O - http://nodejs.org/dist/v5.8.0/node-v5.8.0-linux-x64.tar.gz | tar xz RUN mv node* node && \ ln -s /node/bin/node /usr/local/bin/node && \ ln -s /node/bin/npm /usr/local/bin/npm ENV NODE_PATH /usr/local/lib/node_modules RUN npm config set loglevel info
RUN apt-get install -y libfontconfig1
ADD .npmrc /app/.npmrc
ADD package.json /app/package.json RUN cd /app && \ npm install
ADD . /app RUN cd /app && \ npm run build
RUN cd /app && \ npm run test
ADD docker/sv /etc/service
Server folder
server.js import app from './app';
const PORT = 8080;
const httpServer = app.listen(PORT, () => {
console.log(Micro-App on http://localhost:${PORT} [${app.settings.env}]
);
});
process.on('SIGTERM', () => { httpServer.close(() => { process.exit(0); }); });
index.js import './server';
app.js import 'newrelic'; import compression from 'compression'; import { basePath } from 'config'; import cookieParser from 'cookie-parser'; import express from 'express'; import path from 'path'; import attachRunMiddleware from 'run-middleware'; import { cacheControl, handleErrors, notFound, newrelic } from './middleware'; import groups from './routes/api'; import health from './routes/health'; import root from './routes/root'; import IS_DEV from './utils/isDev';
const app = express();
attachRunMiddleware(app);
app.use(cookieParser());
app.disable('x-powered-by');
app.use(compression());
app.use(newrelic());
app.use(${basePath}/assets
, express.static(path.join(dirname, '/../../build/assets'), {
maxAge: '365d'
}));
app.use(${basePath}/whatsnew
, express.static(path.join(dirname, '/routes/whatsnew'), {
maxAge: '365d'
}));
app.use(cacheControl());
app.use(${basePath}/health
, health);
app.use(${basePath}/api/groupDetails
, groups);
app.use(${basePath}
, root);
if (IS_DEV) { let times = 0;
app.use('/checksession', (req, res) => {
// simulate checksession scenario in dev
times === 1 ? //eslint-disable-line
res.status(200).send()
:
times;
times++;// eslint-disable-line
res.status(403).send();
});
app.use('/aspping.aspx', (req, res) => {
res.status(200).send();
});
app.use('/clientinit', (req, res) => {
res.redirect(302, '/summary');
});
app.use('/whatsnew/frame.js', (req, res) => {
res.status(200).send();
});
}
// use mock route in non prod env for testing purposes
app.use(notFound()); app.use(handleErrors());
export default app;
routes folder
root.js import { Router } from 'express'; import fs from 'fs'; import React from 'react'; import newrelic from 'newrelic'; import { renderToString } from 'react-dom/server';
import Root from '../../client/js/components/Root'; import createStore from '../../client/js/store/createStore';
import AppShell from '../components/AppShell'; import clientConfig from './../utils/clientConfig'; import { getBrowser, getHeaders, getLoginId, getUniqueId, getIdHash } from './../utils/headersUtils'; import getGroupDetails from '../utils/groups'; import IS_DEV from './../utils/isDev';
const router = Router();
let assets;
function readSyncAssetManifest() {
if (!assets) {
const assetManifest = JSON.parse(fs.readFileSync('./build/assets/asset-manifest.json', 'utf8'));
assets = {
mainJS: assetManifest['main.js'],
mainCSS: (IS_DEV) ? null : assetManifest['main.css'],
inlineCSS: (IS_DEV) ? null : fs.readFileSync(./build/assets/${assetManifest['criticalpath.css']}
, 'utf8')
};
}
return assets;
}
function getMeta(idHash, ua, uniqueId, loginId, groupId) { return { title: 'GroupDetail', idHash, loginId, uniqueId, groupId, ua }; }
router.get('/:groupId', (req, res, next) => {
const nreumJS = newrelic.getBrowserTimingHeader();
const idHash = getIdHash(req);
const loginId = getLoginId(req);
const ua = getBrowser(req);
const uniqueId = getUniqueId(req);
const assetManifest = readSyncAssetManifest();
const store = createStore({
meta: getMeta(idHash, ua, uniqueId, loginId, req.params.groupId)
});
const options = {
headers: getHeaders(req)
};
Promise.resolve(getGroupDetails(options, req.params.groupId)).then((data) => {
Promise.resolve(store.dispatch({
type: 'GROUPS_ACCOUNTS_SUCCESS',
payload: data,
meta: { receivedAt: Date.now() }
})).then(() => {
const currentState = store.getState();
const rootHtml = renderToString(Error fetching group details\n ${err.stack}
)));
});
export default router;
health.js import { Router } from 'express';
const router = Router();
router.get('/', (req, res) => { res.json({ status: 'UP' }); });
export default router;
api folder
groups.js import { Router } from 'express'; import { fetchGroupDetail } from '../../models/groupDetail';
const router = Router();
router.get('/:id', (req, res, next) => { fetchGroupDetail(req) .then(data => res.json(data)) .catch(err => next(err)); });
export default router;
index.js import groups from './groups';
export default groups;
model folder groupDetail.js
import { domains } from 'config'; import fetch from 'isomorphic-fetch'; import formatAsDollars from '../utils/stringUtils'; import { getHeaders } from './../utils/headersUtils'; import { parseResponse, checkStatus, normalizeJSON } from './../utils/apiUtils';
export function createUrl(req) {
return ${domains.nmlvhub}/business/groups/${req.params.id}/accounts
;
}
export function parseGroupsJSON(json) { return { groupType: json.groupType, groupNumberMasked: json.groupNumberMasked, groupName: json.groupName, accounts: json.accounts.map(account => ({ accountNumber: account.accountNumber, accountNumberMasked: account.accountNumberMasked, clientName: account.clientName, policyDate: account.policyDate, netDeathBenefit:formatAsDollars(account.netDeathBenefit), contractFundValue:formatAsDollars(account.contractFundValue), netCashSurrenderValue:formatAsDollars(account.netCashSurrenderValue), netCostBasisValue:formatAsDollars(account.netCostBasisValue) })) }; }
export function fetchGroupDetail(req) { const headers = getHeaders(req); const url = createUrl(req);
return fetch(url, { headers })
.then(parseResponse)
.then(checkStatus)
.then(normalizeJSON)
.then(parseGroupsJSON);
}
mock folder index.js import express from 'express'; import jsonServer from 'json-server'; import groupAccounts from './group-accounts.json';
const PORT = process.env.PORT || 8090; const app = express(); const db = { groupAccounts };
// don't actaully want to save anything here and json server doesn't handle responses of post operations super great // build our routes for this by hand
app.use(jsonServer.defaults());
app.use(jsonServer.rewriter({ '/mock/api/business/groups/:id/accounts': '/mock/api/groupAccounts' }));
app.use('/mock/api', jsonServer.router(db));
const httpServer = app.listen(PORT, () => {
console.log(Mock API on http://localhost:${PORT} [${app.settings.env}]
);
});
process.on('SIGTERM', () => { httpServer.close(() => { process.exit(0); }); });
group-accounts.json Json object containing data
middleware folder index.js
export { default as cacheControl } from './cacheControl'; export { default as handleErrors } from './handleErrors'; export { default as newrelic } from './newrelic'; export { default as notFound } from './notFound';
cacheControl.js export default () => (req, res, next) => { res.header('Cache-Control', 'no-cache, no-store, must-revalidate'); next(); };
handleErrors.js import newrelic from 'newrelic';
import IS_DEV from './../utils/isDev';
export default () => (err, req, res, next) => { // eslint-disable-line no-unused-vars newrelic.noticeError(err); console.log(err.stack); res.status(500).send((IS_DEV) ? err.stack : 'Internal application error'); };
notfound.js
export default () => (req, res) => {
const err = Not found: ${req.originalUrl}
;
console.log(err);
res.status(404).send(err);
};
Utils clientconfig.js import config from 'config';
const createClientConfig = { /
basePath: config.basePath,
nmcDomain: config.domains.nmc,
planDomain: config.domains.plan
};
export default createClientConfig;
isProd.js module.exports = (process.env.NODE_ENV && process.env.NODE_ENV === 'production');
isDev.js module.exports = (process.env.BABEL_ENV && process.env.BABEL_ENV === 'development');
group.js import { basePath, nmlvhub } from 'config'; import makeFetch from '../../client/js/utils/makeFetch';
export default function getGroupDetails(options, groupId) {
const url = ${basePath}/api${nmlvhub.groupDetail}/${groupId}
;
return makeFetch(url, { options })
.catch(error => Promise.reject(new Error(Failed to fetch group details ${error.stack}
)));
}
apiUtil.js export function parseResponse(response) { return response.json().then(json => ({ json, response }) ); // provide parsed response and original response }
export function checkStatus({ json, response }) {
if (!response.ok) { // status in the range 200-299 or not
// reject & let catch decide how to handle/throw
return Promise.reject(new Error(${response.statusText} ${response.url}
|| 'Status not OK'));
}
return { json, response };
}
export function normalizeJSON({ json }) { return json; }
components - Folder AppShell.js
/eslint-disable/ import { minify } from 'html-tagged-literals'; import { assetPath } from 'config';
import { name, version } from '../../../package'
const AppShell = ({ mainJS, mainCSS, inlineCSS}, ua, clientConfig, rootHtml, initialState, nreumJS) => { return minify` <!DOCTYPE html>
src-clinet-js Index.js import React from 'react'; import ReactDom from 'react-dom'; import Root from './components/Root'; import checkSession from './utils/checkSession';
checkSession(() => { const createStore = require('./store/createStore').default; // eslint-disable-line const ensurePolyfills = require('./utils/ensurePolyfills').default; // eslint-disable-line const store = createStore(window.STATE); // eslint-disable-line
if (window.newrelic) {
window.newrelic.setCustomAttribute('nmUniqueId', window.__STATE__.meta.uniqueId);
}
ReactDom.render(
<Root store={store} />,
document.getElementById('root')
);
});
Hi this is not done.