sh-skyweather-fe v0.0.1
SkyWeather FE
PWA app for SkyWeather project.
Branch | CircleCI |
---|---|
dev |
.env file
API_PREFIX=https://7jxr6biy93.execute-api.ap-southeast-1.amazonaws.com/latest
API_KEY=IBM_API_KEY
SDK_API_KEY=SDK_API_KEY
GOOGLE_API_KEY=GOOGLE_API_KEY
ONE_SIGNAL_APP_ID=OBTAIN_THIS_FROM_ONE_SIGNAL
GTM_ENV=wt_MF-wCo9Sac4ftdGQgEw
GTM_PVW=env-65
ARCGIS_SUGGEST=https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/suggest
ARCGIS_CANDIDATE=https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates
AUTH0_CLIENT_ID=qPg6TqfLd7wqy0oMwp7X6nwRhYWAZDMh
AUTH0_DOMAIN=local-farmer-india.eu.auth0.com
AUTH0_AUDIENCE=http://localhost:3001
AUTH0_APP_ID=AUTH0_APP_ID
AUTH0_ENVIRONMENT=LOCAL
AUTH0_ENABLED_COUNTRIES=IN,KE
Commands
- Setup
yarn
- Test + lint
yarn test
- Add new component/container
yarn generate
- (temp) Build && deploy
yarn build; yarn deploy:s3; yarn deploy:cloudfront;
- Reduce SVGs in folders
yarn shrink-svg
- Get Cloudfront invalidation ID
aws cloudfront get-invalidation --distribution-id <CF distribution> --id <ID>
- Running Ionic locally
- Android
yarn ionic:android
- iOS
ionic build
- Android
Routing
Look at app/containers/App/index.js
Utils - Stage 0
weather
Function name | Params | Returned Type |
---|---|---|
getRainLevel | qpf: number | string |
geolocation
Function name | Params | Returned Type |
---|---|---|
getGeoPermission | None | Promise |
getCurrentPosition | options: object | Promise |
getGeocode | { locationSelectorIdx: number, isCurrentLocation: boolean, start: function, finish: function, history: object} | Promise |
string
Function name | Params | Returned Type |
---|---|---|
getCamelCase | originalStr: string | string |
authentication
Function name | Params | Returned Type |
---|---|---|
getToken | None | string |
setToken | None | string |
removeToken | None | string |
dotenv files for different environments
- Decided to go with deploying the same branch
dev
to all environments for the time being. - Use feature toggles and specific env variables in separate dotenv files in each environment so we could differentiate from within the environment:
if (process.env.DEV_ONLY_STUFF)
...
Bundle optimisation
- Webpack has 2 aliases that swap existing dependencies to light-weight ones:
Preact (3Kb) to React (30Kb)- preact-compat layer that handles transformation of React to Preact. Read about limitations here. Not using for now, wait until closer to release date and revisit.- unfetch (0.5Kb) to whatwg-fetch (2.7Kb) - different
fetch
polyfills that do the same thing.
- Assets like images and webfonts could be optimised later.
Styling
- Stylesheet for now and Sass at some point when we need it.
- Adopting BEM for CSS class naming convention to avoid polluting global scope.
- Block - parent element
- Element - child elements
- Modifier - attributes specific to parent or children, meaning parents and children could have their own states. Take this markup below for different versions of parent component with different states of child elements.
<div class="component">
<div class="component__child-element"></div>
</div>
<div class="component component--reversed">
<div class="component--reversed__child-element--active"></div>
<div class="component__child-element"></div>
</div>
We want to style parent with 'normal' and 'reversed' states, and style children of 'normal' and 'reversed' parents as well. Children may have an 'active' state that could look differently depending on their parents.
- CSS
.component {
}
.component .component__child-element--reversed {
}
.component .component__child-element {
}
.component.component--reversed .component__child-element {
}
.component.component--reversed .component--reversed__child-element--active {
}
- SASS
.component {
$self: &;
&--reversed {
// here, $self gets evaluated to .component
#{ $self }__child-element {
&--active {
}
}
}
&__child-element {
// matches .component--child-element but only if it is under .component--reversed by referencing parent selectors
#{ $self }--reversed & {
}
}
}
Service worker in localhost
- To allow Chrome to whitelist
https://localhost:3000
, run this in Mac
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --user-data-dir=/tmp/foo --ignore-certificate-errors --unsafely-treat-insecure-origin-as-secure=https://localhost:3000
- Add
OfflinePlugin
with extra entries for additional functionality likepush.js
Google Tag Manager
Google Tag Manager (GTM) is loaded in app/index.html
.
Environments:
- Localhost: Localhost environment
- Stage: Stage environment
- Preprod: Preprod environment
- Production: Live
Currently the deployments need to be done manually from the GTM console. Script versioning and CI/CD workflow pipeline to be fleshed out soon.
Tracking interactive elements:
For interactive elements such as divs or buttons with onClick
props, GTM with GA is already configured to track click events as long as it has the following attributes:
Attribute | What is it? | Examples |
---|---|---|
data-analytics-capture | Trigger that GTM will listen to, so this will not work without this attribute | |
data-event-category | Event category which is either a generic category type (e.g. navigation ) or a container/page (e.g. locationBenefits ) when the button event is tracking a specific data-event-action (e.g. save ) in that container/page. Data attribute GTM will forward to GA to track | navigation , errorRetry , share , cropSelection , locationBenefits , locationPage |
data-event-action | Event action which is either a generic action type (e.g. save ) or if the data-event-category is a generic category type (e.g. share ), it will be the specific attribute it's being performed on (e.g. description,${index} ) i.e. share action on description on daily page at ${index} day. Data attribute GTM will forward to GA to track | click , save , click-new-location-modal-confirm , click-new-location-modal-edit , click-new-location-modal-open , click-banner , description,${index} |
data-event-label | Event label is the specific attribute it's being performed on (e.g. /crop-selection ) when data-event-action does not cover it. Otherwise default as Date.now() . Data attribute GTM will forward to GA to track | url , selectedCropIds , Date.now() |
Example:
<ActionButton
data-analytics-capture
data-event-category="locationBenefits"
data-event-action="click"
data-event-label="add-new-location"
onClick={() => history.push('/new-location')}
/>
Validating that it works:
- Make sure you have
GTM_ENV
andGTM_PVW
set in your.env
file. You can find the values in 1password. - Use GTM preview mode to ensure it's being tracked.
- Check
Realtime > Events
onFarmWeather - localhost
on GA - this is because theBehaviour
section on GA has processing delays that can be somewhere around 1 day delay (https://support.google.com/analytics/thread/11472446?hl=en)
Getting GTM to work with Ionic Capacitor:
Why GTM doesn't work out of the box with Ionic Capacitor:
1. When running the app on an android or iOS build, the protocol of the URI begins with capacitor i.e. capacitor://...
. GTM by default only allows http
or https
protocols.
2. When running the app on an android or iOS build, you can not set or get cookies which GA uses by default to store the client id for GA.
Solution:
1. Create a new GTM variable called Empty function
as a Custom Javascript
type with the following contents:
function() {
return function() {}
}
- Create a new GTM variable called
JS - GetClientId
as aCustom Javascript
type with the following contents:
function() {
if (window.Storage) {
return window.localStorage.getItem('_clientId') || undefined;
}
return;
}
- Create a new GTM variable called
JS - SetClientId
as aCustom Javascript
type with the following contents:
function() {
return function() {
if (window.Storage) {
window.localStorage.setItem('_clientId', ga.getAll()[0].get('clientId'));
}
}
}
- Open your GTM variable where you set the
Google Analytics Settings
type. Go toMore Settings > Fields to Set
.- Set
Field Name
tocheckProtocolTask
withValue
as{{Empty function}}
- Set
Field Name
tostorage
withValue
asnone
- Set
Field Name
tohitCallback
withValue
as{{JS - SetClientId}}
- Set
Field Name
toclientId
withValue
as{{JS - GetClientId}}
- Set
- Publish, and you're done! (tip, check
GA tracking ID
variable for different environments)
React PWA Browser compatibility:
We have a script injection through GTM which spits out a custom html page depending on the user's browser type and browser version. To update this script snippet, login to GTM and make changes to Enforce Chrome Tag Injection
tag. Then publish to the appropriate environment on GTM.
Please check this confluence page to get the list of browser features supported in what version:
https://yaradigitalfarming.atlassian.net/wiki/spaces/SDS/pages/895811595/Browser+Compatibility+Findings
Unit testing
React testing library
React testing library provides HOCs such as wait(), waitForElement() to handle asynchronous calls gracefully, Enzyme on the other hand, still hasn't had a decent solution to support React hooks. React testing library also uses actual DOM element for testing, which is more precise and easier to integrate with Cucumber.
Mocking
In order to have clear speration of behaviors and integrations for Cucumber implementation, we need to mock utility and API functions extensively.
When mocking for user modules, we should always create a directory __mocks__ under the module, for example if I have a folder structure as below:
and I want to mock getGeoCode()
, I should create a directory __mocks__ under utils and a new file geolocation.js, which looks like this:
Inside the geolocation.js, I should mock the implementation:
const mock = jest.fn().mockImplemention(()=>({
getGeoCode: Promise.resolve(something),
...
}));
export default mock;
Ionic Framework
Icons and Splash Screen
- Cordova-res is used for our project to crop,resize and generate icon and splash screen assets.
- Our icons and splash screen assets are stored in
resources
under; namesicons.png
andsplash.png
respectively. - To generate these assets for android and ios, run
cordova res ios --skip-config --copy
andcordova-res android --skip-config--copy
Other docs
4 years ago