Blip is a web app for Type-1 Diabetes (T1D) built on top of the Tidepool platform. It allows patients and their "care team" (family, doctors) to visualize their device data and message each other.
Tech stack:
Table of contents:
Requirements:
Clone this repo then install dependencies:
$ npm install
If you're running the entire Tidepool platform locally as per starting services, you can start blip using your local platform with:
$ source config/local.sh
$ npm start
Open your web browser and navigate to http://localhost:3000/
.
When running locally, there is a workaround so you don't have to verify the e-mail address of a new user: if you create a new user and add the localhost secret +skip to the e-mail address - e.g. [email protected]
- this will then allow you to login straightaway, skipping the e-mail verification step.
NB: The UI is not guaranteed to display correctly for +skip-created users on all pages. For now, you must create a normal account (without +skip) if you want to work on the sign-up flow, although we have plans to fix the way the +skip workaround operates on the platform to address this.
Configuration values are set with environment variables (see config/local.sh
).
You can set environment variables manually, or use a bash script. For example:
source config/local.sh
Ask the project owners to provide you with config scripts for different environments, or you can create one of your own. It is recommended to put them in the config/
directory, where they will be ignored by Git.
The following snippets of documentation should help you find your way around and contribute to the app's code.
- Bootstrap (
app/bootstrap.js
): Where our application is "bootstrapped" into the HTML served. We initialize the API and then render the React application here. - Redux (
app/redux
): Where our redux implementation lives. This code is responsible for state management of the application. - Root (
app/redux/containers/Root.js
): The Root component for our React application. - Routes (
app/routes.js
): Our route definitions for the application. - Core (
app/core
): Scripts and styles shared by all app components. This is where the API and various utilities lives. - Components (
app/components
): Reusable React components, the building-blocks of the application - Pages (
app/pages
): Higher-level React components that combine reusable components together; switch from page to page on route change - Services (
app/core/<service>.js
): Singletons used to interface with external services or to provide some common utility; they are attached to the globalapp
object (for example,app.api
which handles communicating with the platform).
When writing React components, try to follow the following guidelines:
- Keep components small. If a component gets too big, it might be worth splitting it out into smaller pieces.
- Keep state to a minimum. A component without anything in
state
and onlyprops
would be best. When state is needed, make sure nothing is redundant and can be derived from other state values. Move state upstream (to parent components) as much as it makes sense. - Use the
propTypes
attribute to document what props the component requires.
See "Writing good React components".
More on state:
- Each page (
app/pages
is a connected "smart" component (in redux's terminology) that is connected to our redux store, which holds and manages all global app state. - Each page (
app/pages
) can hold some state specific to that page. - Reusable components (
app/components
) typically hold no state (with rare exceptions, like forms).
The config.app.js
file will have some magic constants that look like __FOO__
statements replaced by the value of the corresponding environment variable when the build or development server is run. If you need to add new environment variables, you should also update webpack.config.js
with definitions for them, as well as .eslintrc
.
The app uses the bows library to log debugging messages to the browser's console. It is disabled by default (which makes it production-friendly). To see the messages type localStorage.debug = true
in the browser console and refresh the page. Create a logger for a particular app module by giving it a name, such as:
app.foo = {
log: bows('Foo'),
bar: function() {
this.log('Walked into bar');
}
};
Prefix all CSS classes with the component name. For example, if I'm working on the PatientList
component, I'll prefix CSS classes with patient-list-
.
Keep styles in the same folder as the component, and import them in the main app/style.less
stylesheet. If working on a "core" style, don't forget to import the files in app/core/core.less
.
In organizing the core styles in different .less
files, as well as naming core style classes, we more or less take inspiration from Twitter Bootstrap (see https://github.com/twbs/bootstrap/tree/master/less).
Some styles we'd rather not use on touch screens (for example hover effects which can be annoying while scrolling on touch screens). For that purpose, a small snippet (app/core/notouch.js
) will add the .no-touch
class to the root document element, so you can use:
.no-touch .list-item:hover {
// This will not be used on touch screens
background-color: #ccc;
}
We use an icon font for app icons (in app/core/fonts/
). To use an icon, simply add the correct class to an element (convention is to use the <i>
element), for example:
<i class="icon-logout"></i>
Take a look at the app/core/less/icons.less
file for available icons.
In a separate terminal, you can lint JS files with:
$ npm run lint
Fetching data from the server and rendering the UI to display that data is a classic pattern. The approach we try to follow (see The Need for Speed) is to "render as soon as possible" and "save optimistically".
In short, say a component <Items />
needs to display a data
object passed through the props by the parent, we will also give the component a fetchingData
prop, so it can render accordingly. There are 4 possible situations (the component may choose to render more than one situation in the same way):
data
is falsy andfetchingData
is truthy: first data load, or reset, we can render for example an empty "skeleton" while we wait for datadata
andfetchingData
are both falsy: data load returned an empty set, we can display a message for exampledata
is truthy andfetchingData
is falsy: display the data "normally"data
andfetchingData
are both truthy: a data refresh, either don't do anything and wait for data to come back, or display some kind of loading indicator
For forms, we try as much as possible to "save optimistically", meaning when the user "saves" the form, we immediately update the app state (and thus the UI), and then send the new data to the server to be saved. If the server returns an error, we should be able to rollback the app state and display some kind of error message.
We use Mocha with Chai for the test framework, Sinon.JS and Sinon-Chai for spy, stubs. Karma is our test runner, running currently just on PhantomJS (headless WebKit browser).
To run the unit tests, use:
$ npm test
To run the unit tests in Chrome, use:
$ npm run browser-tests
To run the unit tests in watch, use:
$ npm run test-watch
$ docker-compose up -d
- run Nightwatch with:
$ npm run nightwatch
by default this will run all the tests in the integration
directory
- Exit test containers with:
$ docker-compose down
The app is built as a static site in the dist/
directory.
We use Shio to deploy, so we separate the build in two.
Shio's build.sh
script will take care of building the app itself with:
$ npm run build-app
Shio's start.sh
script then builds the config from environment variables as a separate file with:
$ source config/env.sh
$ npm run build-config
After that, the app is ready to be served using the static web included in this repo:
$ npm run server
You can also build everything at once locally by simply running:
$ source config/local.sh
$ npm run build
$ npm run server