eventstag-registration-station-builder v1.0.0
EventsTag Registration Station
Development setup
Run following command in root directory to install build and app dependencies
yarn install
Development process
yarn webpack:watch
yarn electron:watch
Release process
Bump version number according to semantic versioning with command:
yarn run version -- --version={major,minor,patch,1.2.5}
Add app changes to the changelog file.
- Push your changes, then release the app using Jenkins.
Writing Tests
Unit
You can write unit tests using Jest.
Integration
Integration tests are written using Spectron and Jest.
These "feature" tests are hosted in the features/
folder.
Try to keep the number of test files low, as they are executed one after another, and it takes some time for the app to booth.
The list of available methods for browser automation can be found here: http://webdriver.io/api.html
Customisation and Architecture
Customisation is done via composition, both when we're talking about components (like steps) or functions (like reducers).
How to Customise or Replace a Step
To customise a Step, you'd first need to add a custom app (in the AppBridge component), and inside of that, render the base App component, but with a function as its children.
This function receives the step's name that needs to be rendered. If you return a component in this function, then that's the one that gets rendered, while if yor function returns nothing, it falls back to rendering the default steps.
To customise a step, you can simply return either your custom version of a step, or even the same step component that would've been rendered anyway, just with different props.
Example:
import React from 'react';
import BaseApp from '../DefaultRegistrationStation/App';
import DataCaptureStep, { STEP__DATA_CAPTURE } from '../../components/DataCaptureStep/DataCaptureStep';
const App = props => (
<BaseApp {...props} renderStep={stepName => {
if (stepName === STEP__DATA_CAPTURE) {
return (
<DataCaptureStep
{
// Adding custom props here
}
/>
);
}
// I don't need to return anything for all other cases, the base app
// will render them as usual
}} />
);
export default App;
How to Change the User Journey
You might want to change the step order, or inject a new one into the current user journey.
The user journey is controlled by a reducer, which decides where to go next after a "step action" occurs on the current step. Since it's just a reducer, and we have a way to replace default reducers, customising the user journey becomes simple.
The default step actions are: BACK, CONTINUE, TO_START, TO_FINISH, ERROR.
If you need a more complex custom step, then of course you'd add custom step actions to which the reducer can react appropriately. For example, on a media review page, you'd have EMAIL and TEXT step actions along the usual CONTINUE.
To inject a new step in between the scanner and data capture step - assuming you already added the step itself in your custom app as explained above - you could create the following custom steps reducer:
import defaultStepsReducer, {
produceStepsState,
PROCEED,
CONTINUE,
} from '../../store/steps';
import { STEP__SCANNER } from '../../components/ScannerStep/ScannerStep';
import { STEP__DATA_CAPTURE } from '../../components/DataCaptureStep/DataCaptureStep';
import { STEP__CUSTOM } from '../../registration-stations/MyClient/CustomStep/CustomStep';
export default function customStepsReducer(steps, action) {
// PROCEED is the action that's used to dispatch all step actions.
// Ignore everything else.
if (action.type === PROCEED) {
const stepAction = action.payload.stepAction;
if (steps.current === STEP__SCANNER) {
// No need to implement a "BACK" step action, as that is handled automatically
if (stepAction === CONTINUE) {
// `produceStepsState` is important here, as it automatically
// handles step history, which is used when the BACK step action
// is handled
return produceStepsState(steps, STEP__CUSTOM);
}
}
if (steps.current === STEP__CUSTOM) {
if (stepAction === CONTINUE) {
return produceStepsState(steps, STEP__DATA_CAPTURE);
}
}
return steps;
}
// Fall back to default behaviour
return defaultStepsReducer(steps, action);
}
To register your reducer, you'd set it in the "Internal.js" config as such:
// ...require statement here ofc...
module.exports = {
/**
* @type {{}}
*/
customReducers: {
steps: customStepsReducer,
},
};
How to Customise Data Processing
There are a few things to keep in mind in regards to collecting, clearing and eventually processing data.
The first important rule is that each step has its own reducer to store data, instead of multiple ones potentially contributing to the same redux entry.
This way each step are completely agnostic as to what order they come in the user journey, all they know is that upon the user leaving the step "forward", they can just add their own piece of data to the store, while if the user is leaving the step "backwards", then they can clear the same data from the store without worrying that they may have affected another step.
Then once the user arrives on the "finish" step, all data can be processed in one go, in whatever way it's needed. (For example stitching together a single saved DataCapture from data coming from multiple steps before putting it in the queue.)
For all this to work, the reducers themselves have to take care of cleaning up, when either:
- The user is leaving the step "backwards" in the user journey.
- The user is being transferred to the start, and therefore restarting the user journey.
For example a reducer for a step that pre-selects a variant could look like this:
import { STEP__SELECT_VARIANT } from '../../registration-stations/MyClient/SelectVariantStep/SelectVariantStep';
export const SET_SELECTED_VARIANT = 'selectedVariant/SET';
export default function selectedVariantReducer(state = null, action) {
// Reset state if the above explained conditions are met
if (
isProceedingToStartStep(action) ||
isGoingBackFromStep(action, STEP__SELECT_VARIANT)
) {
return null;
}
if (action.payload === SET_SELECTED_VARIANT) {
return action.payload;
}
return state;
}
Then to do in App, you can have your own data processing logic, by rendering the FinishStep with a custom data processing handler:
import React from 'react';
import { connect } from 'react-redux';
import BaseApp from '../DefaultRegistrationStation/App';
import SelectVariantStep, {
STEP__SELECT_VARIANT,
} from '../../registration-stations/MyClient/SelectVariantStep/SelectVariantStep';
import FinishStep, {
STEP__FINISH,
} from '../../components/FinishStep/FinishStep';
import IPC from '../../IPC';
const handleDataProcessing = async ({
dataCapture,
selectedVariant,
scannedData,
}) => {
// Here we can assemble whatever we need for the final data processing
const resolvedDataCapture = {
...dataCapture,
selectedVariant,
identifier: scannedData.identifier,
registration: true,
};
await IPC.addDataCaptureJob(resolvedDataCapture);
};
const App = props => (
<BaseApp
{...props}
renderStep={stepName => {
if (stepName === STEP__SELECT_VARIANT) {
return <SelectVariantStep />;
} else if (stepName === STEP__FINISH) {
return (
<FinishStep
customDataProcessingHandler={() =>
handleDataProcessing(props.dataToProcess)
}
/>
);
}
}}
/>
);
App.propTypes = {
dataToProcess: PropTypes.object.isRequired,
};
const mapStateToProps = ({ dataCapture, selectedVariant, scannedData }) => ({
dataToProcess: {
dataCapture,
selectedVariant,
scannedData,
},
});
export default connect(mapStateToProps)(App);
6 years ago