Skip to content
This repository was archived by the owner on Mar 5, 2019. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
fadea4a
ui: include thunk middleware using redux-devtools-extension
aspiers Mar 22, 2017
fc582c5
ui: add stub EntityListContainer
aspiers Feb 10, 2017
633d881
ui: make EntityListContainer check entity type is valid
aspiers Mar 23, 2017
35bdd5f
ui: add redux-promise-middleware
aspiers Mar 21, 2017
12c0ca1
ui: move apiURL earlier
aspiers Mar 23, 2017
4cea67d
ui: use redux-promise-middleware to request lists of entities
aspiers Mar 23, 2017
cf4b2af
ui: don't try to display ChartTitle unless we have targetType
aspiers Mar 25, 2017
5dbc860
ui: fix name of Home component's JS and CSS classes
aspiers Mar 25, 2017
4ddb923
ui: add reducers for entity list API actions
aspiers Mar 23, 2017
0713580
ui: connect EntityListContainer to store
aspiers Mar 23, 2017
ef3d6b5
ui: display list of entities
aspiers Mar 25, 2017
1ba8142
ui: check entityType is valid for all actions
aspiers Mar 26, 2017
35d1547
ui: add comment explaining how ChartContainer gets sourceType
aspiers Mar 26, 2017
4f590d7
ui: only dispatch ADD_ENTITY if we don't already have it
aspiers Mar 26, 2017
20f8c31
ui: make entity list scrollable
aspiers Mar 26, 2017
69ab996
ui: catch occasional errors when store is empty
aspiers Mar 26, 2017
2f4728f
ui: extract BackToHome component
aspiers Mar 26, 2017
8f7ee82
ui: remove unused import of ReactDOM
aspiers Mar 26, 2017
9d0764b
ui: de-hyphenate targetType in ChartTitle
aspiers Mar 26, 2017
746473c
ui: fix rendering of About screen
aspiers Mar 26, 2017
653799e
ui: fix indentation in uiReducer
aspiers Mar 26, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/js/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"dependencies": {
"@n3dst4/browser-bundle": "^1.1.0",
"@n3dst4/build-stylesheets": "^1.0.1",
"axios": "^0.15.3",
"babel-polyfill": "6.9.1",
"browser-sync": "^2.12.3",
"d3": "^4.3.0",
Expand All @@ -38,7 +39,9 @@
"react-redux": "^4.4.5",
"react-router": "^2.4.1",
"redux": "^3.6.0",
"redux-devtools-extension": "^2.13.0",
"redux-logger": "^2.6.1",
"redux-promise-middleware": "^4.2.0",
"redux-storage": "^4.0.1",
"redux-storage-decorator-debounce": "^1.0.1",
"redux-storage-engine-localstorage": "^1.1.1",
Expand Down
6 changes: 6 additions & 0 deletions src/js/frontend/src/Redux/action-types.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
export const TOGGLE_ABOUT = "TOGGLE_ABOUT"

export const ADD_ENTITY = "ADD_ENTITY"

export const FETCH_ENTITIES = "FETCH_ENTITIES"
export const FETCH_ENTITIES_PENDING = "FETCH_ENTITIES_PENDING"
export const FETCH_ENTITIES_FULFILLED = "FETCH_ENTITIES_FULFILLED"
export const FETCH_ENTITIES_REJECTED = "FETCH_ENTITIES_REJECTED"
17 changes: 16 additions & 1 deletion src/js/frontend/src/Redux/actions.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as actionTypes from "./action-types"
import entityData from "../lib/entityData"

// These are action creators
// http://redux.js.org/docs/basics/Actions.html#action-creators
Expand All @@ -12,8 +13,22 @@ export const toggleAbout = () => {
export const addEntity = (entityType, id, data) => {
return {
type: actionTypes.ADD_ENTITY,
entityType,
meta: { entityType },
id,
data,
}
}

export const fetchEntities = (entityType) => {
const request = entityData.fetchEntitiesRequest(entityType);

// When dispatched, redux-promise-middleware will handle this and
// magically dispatch FETCH_ENTITIES_{PENDING,FULFILLED,REJECTED}
// actions according to the 3 possible stages of the conversation
// with the API endpoint.
return {
type: actionTypes.FETCH_ENTITIES,
meta: { entityType },
payload: request
}
}
88 changes: 67 additions & 21 deletions src/js/frontend/src/Redux/reducers/dataReducer.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,76 @@
import {fromJS} from "immutable";
import {ADD_ENTITY} from "../action-types";
import {fromJS, Map} from "immutable";

const initialState = fromJS({
entities: {
people: {},
organisations: {},
"government-offices": {},
import * as actionTypes from "../action-types";
import entityData from "../../lib/entityData"

const initialEntityState = {
fetching: false, // We can show a spinner if this is true
fetched: false, // We can show an entity list if this is true
byId: {},
};

const initialEntitiesState = entityData.validTypes.reduce(
(obj, type) => {
obj[type] = initialEntityState;
return obj;
},
{}
);

const initialState = fromJS({
entities: initialEntitiesState
});

const checkActionValidEntityType = (action, state) => {
let entityType = action.meta && action.meta.entityType;
if (!entityType) return true;
let valid = state.get("entities").has(entityType);
if (!valid) {
console.error(`Received ${action.type} action with invalid entity type ${entityType}`);
}
return valid;
}

export const dataReducer = (state = initialState, action) => {
let {type, entityType, id, data} = action;

switch (type) {
case ADD_ENTITY:
if (!state.get("entities").has(entityType)) {
console.error(`Received ADD_ENTITY action with invalid entity type ${entityType}`);
return state;
}
return state.mergeDeep({
entities: {
[entityType]: {
[id]: data
if (!checkActionValidEntityType(action, state)) {
return state;
}

switch (action.type) {
case actionTypes.ADD_ENTITY:
return state.setIn(
["entities", action.meta.entityType, "byId", action.id],
fromJS(action.data)
);

case actionTypes.FETCH_ENTITIES_PENDING:
return state.setIn(
["entities", action.meta.entityType, "fetching"],
true,
);

case actionTypes.FETCH_ENTITIES_FULFILLED:
let newState = state.setIn(
["entities", action.meta.entityType],
Map({fetching: false, fetched: true})
);

return newState.mergeIn(
['entities', action.meta.entityType, 'byId'],
action.payload.data.data.reduce(
(obj, entity) => {
obj[entity.id] = entity.attributes;
return obj
},
},
});
{}
)
);

case actionTypes.FETCH_ENTITIES_REJECTED:
return state.setIn(
["entities", action.meta.entityType, "fetching"],
false,
);

default:
return state;
Expand Down
2 changes: 1 addition & 1 deletion src/js/frontend/src/Redux/reducers/uiReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {fromJS} from "immutable";
import {TOGGLE_ABOUT} from "../action-types";

const initialState = fromJS({
showAboutScreen: false
showAboutScreen: false
});

export const uiReducer = (state = initialState, {type}) => {
Expand Down
22 changes: 14 additions & 8 deletions src/js/frontend/src/Redux/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import {rootReducer, storeStructure, initialStoreState} from "./reducer"
import * as storage from "redux-storage"
import createEngine from "redux-storage-engine-localstorage"
import storageDebounce from 'redux-storage-decorator-debounce'
import promiseMiddleware from 'redux-promise-middleware'
import immutablejs from 'redux-storage-decorator-immutablejs'
import merger from 'redux-storage-merger-immutablejs'

////////////////////////////////////////////////////////////////////////////////
// redux stuff
import { composeWithDevTools } from 'redux-devtools-extension'

// wrap our main reducer in a storage reducer - this intercepts LOAD actions and
// calls the merger function to merge in the new state
Expand All @@ -31,18 +33,22 @@ const loggerMiddleware = createLogger({
//predicate: (getState, action) => action.type !== CALCULATION_NEEDS_REFRESH
})

// now create our redux store, applying all our middleware
// const store = createStore(reducer, applyMiddleware(
// thunkMiddleware, // first, so function results get transformed
// loggerMiddleware, // now log everything at this state
// storageMiddleware // finally the storage middleware
// ))

const composeEnhancers = composeWithDevTools({
name: 'MUTI frontend',
// actionsBlacklist: ['REDUX_STORAGE_SAVE']
});

const store = createStore(
reducer,
initialStoreState,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
composeEnhancers(
applyMiddleware(
thunkMiddleware, // first, so function results get transformed
promiseMiddleware(),
// loggerMiddleware, // now log everything at this state
// storageMiddleware, // finally the storage middleware
)
)
);

window.store = store;
Expand Down
13 changes: 13 additions & 0 deletions src/js/frontend/src/components/BackToHome.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from "react"
import ReactDOM from "react-dom"
import Radium from "radium"
import { Link } from 'react-router'

const BackToHome = (props) =>
<Link style={{float: "right"}} to="/">Back to home</Link>;

BackToHome.propTypes = {
sourceName: React.PropTypes.string,
};

module.exports = Radium(BackToHome);
11 changes: 7 additions & 4 deletions src/js/frontend/src/components/ChartTitle.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import React from "react"
import ReactDOM from "react-dom"
import Radium from "radium"
import { Link } from 'react-router'

import BackToHome from './BackToHome'

function ChartTitle (props) {
let description = props.sourceName ?
let description = props.sourceName && props.targetType ?
<p style={{width: "50%", display: "inline"}}>
Showing all meetings of {props.targetType} with {props.sourceName}
Showing all meetings of {
props.targetType.replace('-', ' ')
} with {props.sourceName}
</p>
: "";

return (
<div>
{description}
<Link style={{float: "right"}} to="/">Back to home</Link>
<BackToHome />
</div>
)
}
Expand Down
66 changes: 66 additions & 0 deletions src/js/frontend/src/components/EntityList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from "react"
import Radium from "radium"
import { Link } from "react-router"

import * as sorters from "../lib/sorters"

// This is a truly horrible UI design, but good enough for testing.
const TEMPLATES = {
organisations: (id) =>
<span>
government {" "}
<Link to={`/organisation/${id}/government-offices`}>offices</Link>
{" "} and {" "}
<Link to={`/organisation/${id}/government-people`}>people</Link>
</span>,
people: (id) =>
<span>
<Link to={`/person/${id}/government-offices`}>government offices</Link>
{" "} and {" "}
<Link to={`/person/${id}/government-people`}>their people</Link>,
{" "} and {" "}
<Link to={`/person/${id}/organisations`}>organisations</Link>
{" "} and {" "}
<Link to={`/person/${id}/organisation-people`}>their people</Link>
</span>,
"government-offices": (id) =>
<span>
<Link to={`/government-office/${id}/organisations`}>organisations</Link>
{" "} and {" "}
<Link to={`/government-office/${id}/organisation-people`}>their people</Link>
</span>
};

function EntityList (props) {
if (props.fetching) {
return <p>Still fetching list of {props.entityType} ...</p>;
}

if (!props.fetched) {
return <p>Didn't fetch list of {props.entityType} yet</p>; // '
}

let sortedEntities = props.entitiesById.sortBy(data => data.get("name"));

let template = TEMPLATES[props.entityType];
let listItems = sortedEntities.map((data, id) =>
<li key={id}>
{data.get("name")} - meetings with {template(id)}
</li>
).toArray();

return (
<ul>
{listItems}
</ul>
)
}

EntityList.propTypes = {
entityType: React.PropTypes.string.isRequired,
fetching: React.PropTypes.bool.isRequired,
fetched: React.PropTypes.bool.isRequired,
entitiesById: React.PropTypes.object.isRequired,
};

module.exports = Radium(EntityList);
2 changes: 1 addition & 1 deletion src/js/frontend/src/components/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class Header extends React.Component {
<header
style={[
styles.base,
{ height: this.props.height }
]}>
<Logo />
<Navigation />
Expand All @@ -26,7 +27,6 @@ let shadow = 'inset #F2E9E9 0px -2px 0px 0px';
let styles = {
base: {
// backgroundColor: '#F5F5ED',
height: "12vh",
borderBottom: '5px solid white',
WebkitBoxShadow: shadow,
MozBoxShadow: shadow,
Expand Down
36 changes: 9 additions & 27 deletions src/js/frontend/src/components/Home.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@ import React from "react"
import Radium from "radium"
import { Link } from 'react-router'

class OrgList extends React.Component {
class Home extends React.Component {
render () {
return <div className="org-list">
<p>
Eventually this home page will allow you to choose from dynamic lists
of organisations, people, government offices etc.
</p>
return <div className="home">
<ul>
<li>
<Link to="/demo">
Expand All @@ -17,39 +13,25 @@ class OrgList extends React.Component {
</Link>
</li>
</ul>
<p>
Here are sample URLs you can use to visualize parts of the
data set relating to a given organisation. They assume that
the organisation BAE Systems has id 369 in the database, that
David Cameron has id 755, and that the FCO has id 437, so
currently you will have to manually adjust the URLs to point
to the right ids, but we'll hopefully fix that soon.
</p>
<ul>
<li>
<Link to="/organisation/369/government-people">
See which ministers met with BAE Systems the most
<Link to="/organisations">
See all organisations
</Link>
</li>
<li>
<Link to="/organisation/369/government-offices">
See which government offices met with BAE Systems the most
<Link to="/government-offices">
See all government offices
</Link>
</li>
<li>
<Link to="/government-person/755/organisations">
See which organisations met with David Cameron the most
</Link>
</li>
<li>
<Link to="/government-office/437/organisations">
See which organisations met with the Foreign &amp; Commonwealth
Office the most
<Link to="/people">
See all people
</Link>
</li>
</ul>
</div>
}
}

export default Radium(OrgList);
export default Radium(Home);
Loading