If you're working on this codebase, some understanding of the following will help:
- jsx (inline templating)
- babel (es6 transpiler)
- flow typesystem
- mocha (unit testing)
- yadda (BDD testing)
Getting the code
git clone firstname.lastname@example.org:ask-izzy/ask-izzy.git cd ask-izzy npm install bower install
Running the dev server
You will need API keys for ISS and Google.
ISS_URL=... GOOGLE_API_KEY=... ./script/dev-server
Dealing with HTTP and browsers
Ask Izzy is a fully static site in production
conf/nginx.conf for the rewrite rules that make this
work - there is duplicated logic from
Pages which do not require ISS data are pre-generated and generally work
server/render-static.js (prod) and
Once the page JS loads:
- React attaches the react-router component to the document
- react-router looks at the page url to decide which components to render (see
- component 'mount' hooks fire and start fetching data from ISS
- components build a model of the DOM they expect (in
- the page is updated with any differences between the expected DOM and actual DOM (usually none)
- once any async data arrives from ISS, the page is updated again
Ask Izzy Routes
Here are the explict paths that AskIzzy supports:
/service/<service-id> /service/<service-id>/ /search/<search-term> /search/<search-term>/ /search/<search-term>/<personalise-path> /search/<search-term>/<suburb>-<state> /search/<search-term>/<suburb>-<state>/ /search/<search-term>/<suburb>-<state>/<personalise-path> /search/<search-term>/in/<suburb>-<state> /search/<search-term>/in/<suburb>-<state>/ /category/<category-name> /category/<category-name>/ /category/<category-name>/in/<suburb>-<state> /category/<category-name>/in/<suburb>-<state>/ /<category-name> /<category-name>/ /<category-name>/<suburb>-<state> /<category-name>/<suburb>-<state>/ /<category-name>/<suburb>-<state>/<personalise-path> /<category-name>/<personalise-path> /<static-pages> /<static-pages>/
Examples for routes terms are given below:
<service-id> e.g. 820532-infoxchange <personalise-path> e.g. personalise/, personalise/summary, personalise/page/location <category-name> e.g. housing, food, money-help <suburb> e.g. Melbourne, Richmond, Run-O-Waters <state> e.g. VIC, NSW, Victoria <static-pages> e.g. about, homeless-shelters
Note: Ask Izzy categories or static pages must NEVER be named "search", "static", "session", "service", "category" or "in".
Building HTML pages
Building JS and CSS
Ask Izzy uses Webpack to organize building JS / CSS.
Webpack configuration is very powerful but unfortunately quite complex.
then writes the result to
server/webpack-stats.json is also generated, so that our code can find the
path to the files.
By convention, we have one SCSS and one JS file per react component.
Each component renders an element with a
class equal to the name of the component.
This class is used in the SCSS file to avoid rules applying to the wrong component.
script/generate-component-index is required to be manually run each time you introduce a new react component.
The babel compiler and polyfill allows us to use advanced features which are not yet available in all browsers.
Promises, async and await
await extensively in Ask Izzy.
Under the hood these are syntactic sugar for promises
(which are well documented online).
Any function defined as
async function foo() will
have its return value converted to a promise (any
exception will result in a promise error and the
result is the promise result).
async function, you can use
[my variable] = await [a promise]
which will pause the function until the given promise
resolves to a value or an error. If it's a value it's
returned, if the promise has an error an exception is
Promises are part of the JS standard and are supported natively in every browser except IE (where they are polyfilled), but async and await are not yet standardized (see https://github.com/tc39/ecmascript-asyncawait for the in-progress standard)
Type system (flowtype)
Flow is configured using the
All JS files in the repo either start with
/* @flow */ if they are typechecked
/* flow:disable */ if they are not.
You can run flow on its own using
It's also run as part of the precommit hooks and in CI.
Libraries which aren't part of the code have type definitions
Search query generation
ResultsPage is added to the page, it builds a query
issParams which iterates through
personalisationComponents (inherited from
getSearch to build up a search object which is
For category pages,
to get the list of personalisation components.
If any personalisation component has not been answered, it
getSearch, and the
will redirect to the personalisation page to answer the question.
The whole personalisation space is a twisty mess of side effects and inheritance. Changes which fit into the existing model are super easy but I would be very careful testing any changes to the control code.
The question classes mostly derive from
All google analytics events are sent by calling the
push method in
We use the react-google-maps component to handle the maps integration.
removeOutliers method in
ResultsMap.js was implemented
because results sometimes include e.g. a hotline which is headquartered
hundreds of km away.
removeOutliers is reasonably well commented and
ResultsMapTest.js) but it is quite complex.
Directions / travel times
We use the google directions matrix API to fetch transit / walking times.
maps.js for the implementation, called from
We use the google places API to autocomplete place names.
locationSuggestions.js for implementation.
This approach means that we frequently have bugs where a location is known to google but not ISS. The obvious fix would be to use ISS as the source of location autocomplete data.
The search implementation attempts to build a free-text query. This is fundamentally not really a workable approach but for organisational and technical reasons it's very difficult to make changes to ISS3 which serves the queries.
All tests live in the
test toplevel directory.
Unit tests live in
test/unit and are invoked from
test/unit.js. They use
BDD features live in
test/features with step definitions in
test/steps invoked from
test/feature.js. They use
Maps features live in
test/maps with step definitions in
test/steps invoked from
test/maps.js. They use
Personalisation features live in
test/personalisation with step definitions in
test/steps invoked from
test/personlisation.js. They use
Search features live in
test/search with step definitions in
test/search-steps invoked from
test/search.js. They use
Forklift / docker
The dockerfile can run the tests but there's currently
no development server in
invoke.sh (should be easy to
fix but I don't use docker locally).
If you're using forklift
then you can add a config file
environment: ISS_URL: ... # ISS_URL: http://localhost:5000 GOOGLE_API_KEY: ...
forklift -- ./script/dev-server
There's also a mock ISS server available as
./script/mock-iss. This will
start a server on
Adding new icons
- Add your icon to https://github.com/ask-izzy/designs
- Clone the designs repo. e.g.
git clone https://github.com/ask-izzy/designs.git ~/git/designs
- The Iconify script requires babel, install by running
sudo npm install -g babel-cli
.script/iconify <path_to_cloned_designs_repo>. e.g.
The icons/index.js file will be updated, and a new js file for the icon will be generated in /icons.
Link up the git hooks:
ln -s ../.githooks .git/hooks
Add the git merge strategies to
[include] path = ../.gitmerge/strategies
Run the linters:
Running the tests:
./script/unit-test ./script/feature-test ./script/personalisation-test ./script/search-test
SELENIUM_BROWSER=firefox|phantomjs|chrome to choose a
browser to run the feature tests with (default is firefox)
You can pass
BROWSER_LOGS=yes to dump logs from the browser. Be aware not
all browsers support this.
If you have issues running tests on Ubuntu, follow the steps here: https://christopher.su/2015/selenium-chromedriver-ubuntu/
Then use Chrome as the selenium browser to run the tests.
If you're using a virtual machine open a terminal in the VM to run them.
Bits of the repo setup were based on https://github.com/gpbl/isomorphic500 @ 413c6533ae23 under the MIT licence