How to build the project locally
Configure the project to run in production, test and local environments
Deploy the application
Hook functions for processing orders and other backend tasks
Visualise IOT data from a google cloud database
Link the LIMS runs with the stock orders
Install dependencies
npm install
Update your .env
file with values for each environment variable
API_KEY=AIzaSyBkkFF0XhNZeWuDmOfEhsgdfX1VBG7WTas
etc ...
Install the Vercel CLI
npm install -g vercel
Link codebase to a Vercel project and run development server
vercel dev
When the above command completes you’ll be able to view your website at http://localhost:3000
.
Note: You can run just the front-end with npm run start
, but vercel dev
also handles running your API endpoints (located in the /api
directory).
This project uses the following libraries and services:
import { Link, useRouter } from "./../util/router.js";
function MyComponent() {
// Get the router object
const router = useRouter();
// Get value from query string (?postId=123) or route param (/:postId)
console.log(router.query.postId);
// Get current pathname
console.log(router.pathname);
// Navigate with the <Link> component or with router.push()
return (
<div>
<Link to="/about">About</Link>
<button onClick={(e) => router.push("/about")}>About</button>
</div>
);
}
import { useAuth } from "./../util/auth.js";
function MyComponent() {
// Get the auth object in any component
const auth = useAuth();
// Depending on auth state show signin or signout button
// auth.user will either be an object, null when loading, or false if signed out
return (
<div>
{auth.user ? (
<button onClick={(e) => auth.signout()}>Signout</button>
) : (
<button onClick={(e) => auth.signin("hello@divjoy.com", "yolo")}>Signin</button>
)}
</div>
);
}
import { useAuth } from './../util/auth.js';
import { useItemsByOwner } from './../util/db.js';
import ItemsList from './ItemsList.js';
function ItemsPage(){
const auth = useAuth();
// Fetch items by owner
// Returned status value will be "idle" if we're waiting on
// the uid value or "loading" if the query is executing.
const uid = auth.user ? auth.user.uid : undefined;
const { data: items, status } = useItemsByOwner(uid);
// Once we have items data render ItemsList component
return (
<div>
{(status === "idle" || status === "loading") ? (
<span>One moment please</span>
) : (
<ItemsList data={items}>
)}
</div>
);
}
npm install -g vercel
Add each variable from your .env
file to your Vercel project, including the ones prefixed with “REACT_APP_”. You’ll be prompted to enter its value and choose one or more environments (development, preview, or production). See Vercel Environment Variables to learn more about how this works, how to update values through the Vercel UI, and how to use secrets for extra security.
vercel env add plain VARIABLE_NAME
Run this command to deploy to a unique preview URL. Your “preview” environment variables will be used.
vercel
Run this command to deploy to your production domain. Your “production” environment variables will be used.
vercel --prod
See Vercel Deployments for more details.
The Opencell BioHotel Lims uses a number of different data sources.
To start the project locally, first install the vercel cli as detailed above.
Data is stored in cloud firestore, create a project and generate a set of credentials which are needed for the following environment variables.
This will allow firebase to connect to your app.
The firestore database requires records in some of the following formats:
Users
admin
to enable admin view)created
)Cells (collection of cells)
input
, output
, default
determines cell join logic)gid://shopify/Product/6679469129795
)booking
or closure
allows a continuous period of booking or recurring periods of bookings)booking
closure
01111111
000001111111000000101110
Labs
Sensors
onewiresensor2
same as registered in IOT coretempprobe
The project uses CubeJS
to render sensor data. You can either create an account or host your own instances. the .env file needs an CUBEJS_API_SECRET=
for graphing and authentication with the service to work
CubeJs needs to be given access to the BigQuery database in which the sensor data is written.
Data for the LIMS is stored in the Firestore instance, sensor data is piped to a BigQuery instance using a standard google cloud dataflow template. (Job name Pub/Sub Topic to BigQuery)
Data for the sensors is passed from an ESP8266 sensor to the google cloud IOT gateway.
In addition to this, the data can be analyzed in stream by creating a dataflow pipeline with code from this repo. This model analyzes temperature data and if it breaches a certain threshold will write this error to a message queue.
Messages from this queue can be consumed by a cloud function and written to an alerts collection in the firestore database.
n.b use own service account credentials or load from the environment
// * Background Cloud Function to be triggered by Pub/Sub.
// * This function is exported by index.js, and executed when
// * the trigger topic receives a message.
// *
// * @param {object} message The Pub/Sub message.
// * @param {object} context The event metadata.
// */
const { initializeApp, applicationDefault, cert } = require('firebase-admin/app');
const { getFirestore } = require('firebase-admin/firestore');
const serviceAccount = {
"type": "service_account",
"project_id": MY_PROJECT_ID,
"private_key_id": MY_PRIVATE_KEY_ID,
"private_key": PRIVATE_KEY,
"client_id": CLIENT_ID,
"auth_uri": AUTH_URI,
"token_uri": TOKEN_URI,
"auth_provider_x509_cert_url": CERT_URL,
"client_x509_cert_url": X509_CERT_URL
}
;
initializeApp({
credential: cert(serviceAccount)
});
exports.helloPubSub = async (message, context) => {
console.log(message, 'message')
const db = getFirestore();
// get the RUN id and the user ID
const data = message.data
let buff = Buffer.from(data, 'base64');
let text = buff.toString('utf-8');
console.log(text)
const body = JSON.parse(text)
console.log(body)
const docRef = db.collection('alerts').doc()
const resp = await docRef.set(body)
console.log(resp)
The following BigQuery schema is a mapping of the data passed from the sensors into the system.
Typically sensor1 and sensor2 are enabled and used for temperature and humidity data, sensor3 is used for battery voltage.
Data is timestamped and linked to a particular sensor, this can be linked to the device / cell / lab as per the firestore schema above.
The LIMS system has a notion of supplies needed to complete runs.
We achieve this by linking the runs and workflow builder to a separate instance of shopify and using it’s graphQL api to create baskets of products.
Shopify requires a GraphQL app to be created, a current store and a storefront access token to get data from the store to the client application.
The consumables are to be registed in the companion shopify instance and are to be used as part of the workflow planning stage.
As part of the ordering process, the LIMS application needs to know when an order has been placed through shopify. There is a google cloud function provided to update the run with the order ID from shopify using the shopify hooks API.
// * Background Cloud Function to be triggered by Pub/Sub.
// * This function is exported by index.js, and executed when
// * the trigger topic receives a message.
// *
// * @param {object} message The Pub/Sub message.
// * @param {object} context The event metadata.
// */
const { initializeApp, applicationDefault, cert } = require('firebase-admin/app');
const { getFirestore } = require('firebase-admin/firestore');
const serviceAccount = {
"type": "service_account",
"project_id": MY_PROJECT_ID,
"private_key_id": MY_PRIVATE_KEY_ID,
"private_key": PRIVATE_KEY,
"client_id": CLIENT_ID,
"auth_uri": AUTH_URI,
"token_uri": TOKEN_URI,
"auth_provider_x509_cert_url": CERT_URL,
"client_x509_cert_url": X509_CERT_URL
}
;
initializeApp({
credential: cert(serviceAccount)
});
exports.updateOrder = async (message, context) => {
console.log(message, 'message')
const db = getFirestore();
// get the RUN id and the user ID
const data = message.data
let buff = Buffer.from(data, 'base64');
let text = buff.toString('utf-8');
console.log(text)
const body = JSON.parse(text)
console.log(body)
const attributes = body.note_attributes
console.log(attributes)
const run = attributes.find(attr => attr.name === "runID")
if (!run) {
console.log('not a run order')
return
}
const runID = run.value
const user = attributes.find(attr => attr.name === "userID")
if (!user) {
console.log('invalid as no user id')
throw new Error("user ID not set")
}
const userID = user.value
const updateData = { runID, userID, orderUrl: body.order_status_url }
console.log(updateData)
const docRef = db.collection('users').doc(userID).collection('runs').doc(runID);
const resp = await docRef.update({ order: updateData, status: 'ordered' })
console.log(resp)
};