From 7be90742ed71b4327789ad36753f0e69195dd295 Mon Sep 17 00:00:00 2001 From: Jorge Samuel Mendes de Jesus Date: Mon, 19 Apr 2021 16:37:42 +0200 Subject: [PATCH 1/8] Dockerfile/K8s implementation, prod/dev run (#24) --- .dockerignore | 16 +++++++++++++++ .env.default | 4 ++-- .gitignore | 5 +++++ Dockerfile | 29 ++++++++++++++++++++++++++ README.md | 37 +++++++++++++++++++++++++++------- components/footer/component.js | 2 +- components/map/constants.js | 2 +- index.js | 1 + run.sh | 32 +++++++++++++++++++++++++++++ 9 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100755 run.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..329282c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,16 @@ +# ignore folder on copy (more for local build) +*.md +.git +.cache +.history +node_modules +npm-debug.log +.prettierrc +.gitignore +.github +.git +.eslintrc +.env.default +.editorconfig +.dockerignore +.env diff --git a/.env.default b/.env.default index 7c1e2ce..eafb378 100644 --- a/.env.default +++ b/.env.default @@ -1,11 +1,11 @@ -PORT=3000 +PORT=3001 MAPBOX_API_KEY= API_URL= ANALYSIS_API_URL= -DEPLOYMENT_KEY= GOOGLE_ANALYTICS_KEY= AWS_REGION= AWS_BUCKET_NAME= AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_MAX_Z_TILE_STORAGE= +DEPLOYMENT_KEY= diff --git a/.gitignore b/.gitignore index 756b8f6..d89545e 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,8 @@ yarn-error.log* # keys gee.key.json + +#VScode +.history/ + + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ede5796 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +FROM docker.io/debian:stable-slim@sha256:a939c03c4d3e3f53e3ef4ef6e75cb681a3ad56537842f95bf89755da86559b13 + +# Source: https://www.kabisa.nl/tech/nvm-in-docker/ +# docker build --force-rm --no-cache -t soils-revealed:latest . +# docker run -p3001:3001 --env-file .env soils-revealed:latest + +SHELL ["/bin/bash","-l","-c"] +ENV DEBIAN_FRONTEND noninteractive + +RUN apt-get update \ + && apt-get install -y tini curl \ + && rm -rf /var/lib/apt/lists/* + +RUN mkdir /soils-revealed +WORKDIR /soils-revealed +COPY . . + +RUN curl --silent -o- https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash +RUN nvm install && nvm use +RUN echo -e "Implementing node: $(node --version)\nFrom: $(which node)" + +RUN npm install -g yarn +RUN npm install -g pm2 +RUN yarn install --frozen-lockfile + +RUN DEPLOYMENT_KEY=$(date +%s) && echo "DEPLOYMENT_KEY=$DEPLOYMENT_KEY" > .env + +ENTRYPOINT ["/usr/bin/tini","-g","--"] +CMD ["/soils-revealed/run.sh","production"] diff --git a/README.md b/README.md index a21af21..97ab754 100644 --- a/README.md +++ b/README.md @@ -17,14 +17,15 @@ In order to start modifying the app, please make sure to correctly configure you 5. Use the correct Node.js version for this app by running `nvm use`; if you didn't install NVM (step 2), then manually install the Node.js version described in `.nvmrc` 6. Install the dependencies: `yarn` 7. Create a `.env` file at the root of the project by copying `.env.default` and giving a value for each of the variables (see next section for details) -8. Create a `gee.key.json` file at the root of the project with the Google Earth Engine's private key inside +8. Create a gee.key.json file at the root of the project with the Google Earth Engine's private key inside. + 9. Run the server: `yarn dev` You can access a hot-reloaded version of the app on [http://localhost:3000](http://localhost:3000). The application is built using [React](https://reactjs.org/) and the framework [Next.js](https://nextjs.org/). The styles use [Sass](https://sass-lang.com/) and the [Bootstrap](https://getbootstrap.com/) framework. -A continuous deployment system is in place. Each time you push to the `master` branch, the application is deployed to production through a GitHub Action defined in `.github/workflows/production.yml`. Each time you push to `develop`, the application is deployed to staging through an action defined in `.github/workflows/staging.yml`. You can see the status of the build in the “Actions” tab of the repository on GitHub. +A continuous deployment system is in place. Each time you push to the `master` branch, the application is deployed to production. Github will trigger an event on google cloud run and make a deployment. The same for the `develop` branch. ## Environment variables @@ -47,15 +48,37 @@ Below is a description of each of the keys. ## Deployment -As explained before, the application is automatically deployed to staging when pushing new changes to the `develop` branch, and deployed to production when pushing changes to `master`. This is achieved through GitHub Actions defined in `.github/workflows`. +### Local computer + +It is possible to run a local test deployment using the docker image. Docker implement an agnostic build and then during run it will pickup the container's env variables, and properly set the system + +`run.sh` accepts 2 arguments: production or develop. Production argument will run `yarn start` and the code will production ready, while `develop` runs nodejs in development mode, necessary if you are testing content and changes. + +```bash +docker build --force-rm --no-cache -t soils-revealed:latest . +docker run -p3001:3001 --env-file .env soils-revealed:latest /soils-revealed/run.sh production +``` + +**Note:** We have created a `.env` file on the project root with all variables, and this will be used to run nodejs in production mode. This is a deployment to test nodejs in production mode. + +**Note:** Dockerfile has `CMD` implementing production. + + +### Google GKE -When an action is executed, it connects via SSH to the server hosting the application. The server's credentials are stored in GitHub's “secrets” vault. A script is then executed: the running instance of the application is stopped, the code is pulled, the correct version of node is selected, the dependencies are installed, a local `.env` file is generated, a local `gee.key.json` file is generated, and the application is restarted. +Public deployment is based on Google Cloud build and Google GKE (Kubernetes). Up on push to `master` or `develop`, the following steps will happen: -The `.env` file is programmatically generated on the server because it differs for each environment. Some of the keys are hard coded in the `.github/workflows/XXX.yml` file and others are pulled from GitHub's “secrets” vault. +1. Github will trigger a Google Cloud run trigger +2. Google cloud will pull the branch content. +3. Docker build will be initicated. +4. After completed Docker image is stored on a private repository. +5. Image will then be deplyed into the soils-revealed cluster. +6. GKE contains a specific `ConfigMap` with all .env necessary for deployment. +7. `gee.key.json` is added to the pods using a `ConfigMap` mount -The `gee.key.json` file contains the credentials for the Google Earth Engine library. It is also programmatically generated as its value is stored in GitHub's “secrets” vault. +GKE will implement the available Dockerfile. -Overall, deploying to either environment takes between 1 to 2 minutes to complete. +Overall, deploying to either environment takes between 5 to 10 minutes to complete. ## Architecture diff --git a/components/footer/component.js b/components/footer/component.js index 0151bbd..4708091 100644 --- a/components/footer/component.js +++ b/components/footer/component.js @@ -42,7 +42,7 @@ const Footer = () => {
  • - + Contact
  • diff --git a/components/map/constants.js b/components/map/constants.js index 5dbdeb9..2457ebe 100644 --- a/components/map/constants.js +++ b/components/map/constants.js @@ -1082,7 +1082,7 @@ exports.LAYERS = { 'soc-experimental': { label: 'Soil organic carbon experimental approach', description: - 'These maps display soil organic carbon in Argentina in any year between 1982 and 2017. You can toggle between soil organic carbon concentration and soil organic carbon stock, as well as between soil organic carbon maps for any given year and change maps between years. Selecting “Change” will display where Argentina has experienced a gain in soil organic carbon and which parts a loss for a chosen time period. To produce these maps, we used a novel machine learning algorithm based on field samples to map soil organic carbon over space and time.\n\nA global implementation of this approach is in process. If you would like to join this effort, please [contact us](mailto:soilsrevealed@tnc.org), and join our growing partnership.', + 'These maps display soil organic carbon in Argentina in any year between 1982 and 2017. You can toggle between soil organic carbon concentration and soil organic carbon stock, as well as between soil organic carbon maps for any given year and change maps between years. Selecting “Change” will display where Argentina has experienced a gain in soil organic carbon and which parts a loss for a chosen time period. To produce these maps, we used a novel machine learning algorithm based on field samples to map soil organic carbon over space and time.\n\nA global implementation of this approach is in process. If you would like to join this effort, please [contact us](mailto:info@soilsrevealed.org), and join our growing partnership.', group: 'soc', attributions: [], bbox: [ diff --git a/index.js b/index.js index 66b4c51..a2f920b 100644 --- a/index.js +++ b/index.js @@ -36,6 +36,7 @@ const handle = app.getRequestHandler(); // Initialize GEE let geePrivateKey; try { + geePrivateKey = require('./gee.key.json'); ee.data.authenticateViaPrivateKey( geePrivateKey, diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..f7af374 --- /dev/null +++ b/run.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +yarn_build () { + echo -e "Using: $(cat .env)" + source /root/.nvm/nvm.sh + echo -e "Preparing .env file" + echo -e "PORT=$PORT\nMAPBOX_API_KEY=$MAPBOX_API_KEY\nAPI_URL=$API_URL\nANALYSIS_API_URL=$ANALYSIS_API_URL\nGOOGLE_ANALYTICS_KEY=$GOOGLE_ANALYTICS_KEY\nAWS_REGION=$AWS_REGION\nAWS_BUCKET_NAME=$AWS_BUCKET_NAME\nAWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID\nAWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY\nAWS_MAX_Z_TILE_STORAGE=$AWS_MAX_Z_TILE_STORAGE" >> .env + echo -e "Initiating yarn build" + + yarn build + +} + + +case "$1" in + production) + yarn_build + exec pm2 start --no-daemon yarn -- start + ;; + develop) + yarn_build + exec node index.js + ;; + *) + echo >&2 "Invalid option: $@ \n either production or develop"; exit 1 + ;; +esac + +#node index.js +#yarn start +#exec pm2 start --no-daemon yarn -- start + From 1ae308776dcc93173d1b9dbf0908c00cb69e332b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Prod=27homme?= Date: Fri, 30 Apr 2021 08:32:09 +0200 Subject: [PATCH 2/8] Delete the GitHub Actions The app will now be built with ISRIC's Google Cloud account --- .github/workflows/production.yml | 61 -------------------------------- .github/workflows/staging.yml | 61 -------------------------------- 2 files changed, 122 deletions(-) delete mode 100644 .github/workflows/production.yml delete mode 100644 .github/workflows/staging.yml diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml deleted file mode 100644 index 81c7fa3..0000000 --- a/.github/workflows/production.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Production build - -on: - push: - branches: - - master - -jobs: - deploy: - runs-on: ubuntu-latest - - steps: - - name: Run SSH commands - # Documentation: https://github.com/marketplace/actions/ssh-remote-commands - uses: appleboy/ssh-action@master - env: - NODE_PORT: 3000 - MAPBOX_API_KEY: pk.eyJ1IjoidG5jc29pbHNjaWVuY2UiLCJhIjoiY2s5Y3dweXg0MDBlZjNkbXE5ZGk4Y294biJ9.nGYVoUKe7Z8MJoi5GwCGjg - API_URL: http://35.233.41.65/user/skydipper/api/v2 - ANALYSIS_API_URL: https://soilsrevealed.org/api/v1/analysis - GEE_KEY: ${{ secrets.GEE_KEY }} - GOOGLE_ANALYTICS_KEY: UA-179817360-1 - AWS_REGION: ${{ secrets.AWS_REGION }} - AWS_BUCKET_NAME: ${{ secrets.AWS_BUCKET_NAME }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_MAX_Z_TILE_STORAGE: 5 - with: - host: ${{ secrets.SSH_HOST }} - key: ${{ secrets.SSH_KEY }} - port: ${{ secrets.SSH_PORT }} - username: ${{ secrets.SSH_USER }} - envs: NODE_PORT,MAPBOX_API_KEY,API_URL,ANALYSIS_API_URL,GEE_KEY,GOOGLE_ANALYTICS_KEY,AWS_REGION,AWS_BUCKET_NAME,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_MAX_Z_TILE_STORAGE - script: | - echo '> Source nvm' - export NVM_DIR=~/.nvm - source ~/.nvm/nvm.sh - echo '> Export the path' - export PATH=$PATH:/home/ubuntu/.nvm/versions/node/v12.16.1/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin - echo '> Kill previous node server' - pm2 stop production - echo '> Open production folder' - cd ~/soils-revealed - echo '> Pull from git' - git fetch origin master - git reset --hard origin/master - echo '> Use correct node version' - nvm install - nvm use - echo '> Install dependencies' - yarn install --frozen-lockfile - echo '> Generate a deployment key' - DEPLOYMENT_KEY=`date +%s` - echo '> Create .env file' - echo -e "PORT=$NODE_PORT\nMAPBOX_API_KEY=$MAPBOX_API_KEY\nAPI_URL=$API_URL\nANALYSIS_API_URL=$ANALYSIS_API_URL\nDEPLOYMENT_KEY=$DEPLOYMENT_KEY\nGOOGLE_ANALYTICS_KEY=$GOOGLE_ANALYTICS_KEY\nAWS_REGION=$AWS_REGION\nAWS_BUCKET_NAME=$AWS_BUCKET_NAME\nAWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID\nAWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY\nAWS_MAX_Z_TILE_STORAGE=$AWS_MAX_Z_TILE_STORAGE" > .env - echo '> Create gee.key.json file' - echo $GEE_KEY > gee.key.json - echo '> Build the app' - yarn build - echo '> Run the server' - pm2 start production diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml deleted file mode 100644 index b102368..0000000 --- a/.github/workflows/staging.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Staging build - -on: - push: - branches: - - develop - -jobs: - deploy: - runs-on: ubuntu-latest - - steps: - - name: Run SSH commands - # Documentation: https://github.com/marketplace/actions/ssh-remote-commands - uses: appleboy/ssh-action@master - env: - NODE_PORT: 3001 - MAPBOX_API_KEY: pk.eyJ1IjoidG5jc29pbHNjaWVuY2UiLCJhIjoiY2s5Y3dweXg0MDBlZjNkbXE5ZGk4Y294biJ9.nGYVoUKe7Z8MJoi5GwCGjg - API_URL: http://35.233.41.65/user/skydipper/api/v2 - ANALYSIS_API_URL: https://soilsrevealed.org/api/v1/analysis - GEE_KEY: ${{ secrets.GEE_KEY }} - GOOGLE_ANALYTICS_KEY: - AWS_REGION: ${{ secrets.AWS_REGION }} - AWS_BUCKET_NAME: ${{ secrets.AWS_BUCKET_NAME }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_MAX_Z_TILE_STORAGE: 5 - with: - host: ${{ secrets.SSH_HOST }} - key: ${{ secrets.SSH_KEY }} - port: ${{ secrets.SSH_PORT }} - username: ${{ secrets.SSH_USER }} - envs: NODE_PORT,MAPBOX_API_KEY,API_URL,ANALYSIS_API_URL,GEE_KEY,GOOGLE_ANALYTICS_KEY,AWS_REGION,AWS_BUCKET_NAME,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_MAX_Z_TILE_STORAGE - script: | - echo '> Source nvm' - export NVM_DIR=~/.nvm - source ~/.nvm/nvm.sh - echo '> Export the path' - export PATH=$PATH:/home/ubuntu/.nvm/versions/node/v12.16.1/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin - echo '> Kill previous node server' - pm2 stop staging - echo '> Open staging folder' - cd ~/soils-revealed-staging - echo '> Pull from git' - git fetch origin develop - git reset --hard origin/develop - echo '> Use correct node version' - nvm install - nvm use - echo '> Install dependencies' - yarn install --frozen-lockfile - echo '> Generate a deployment key' - DEPLOYMENT_KEY=`date +%s` - echo '> Create .env file' - echo -e "PORT=$NODE_PORT\nMAPBOX_API_KEY=$MAPBOX_API_KEY\nAPI_URL=$API_URL\nANALYSIS_API_URL=$ANALYSIS_API_URL\nDEPLOYMENT_KEY=$DEPLOYMENT_KEY\nGOOGLE_ANALYTICS_KEY=$GOOGLE_ANALYTICS_KEY\nAWS_REGION=$AWS_REGION\nAWS_BUCKET_NAME=$AWS_BUCKET_NAME\nAWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID\nAWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY\nAWS_MAX_Z_TILE_STORAGE=$AWS_MAX_Z_TILE_STORAGE" > .env - echo '> Create gee.key.json file' - echo $GEE_KEY > gee.key.json - echo '> Build the app' - yarn build - echo '> Run the server' - pm2 start staging From d58b3c461d1cfee64ac86e30550ef3789d5c628d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Luena=20Rodr=C3=ADguez?= Date: Fri, 28 May 2021 11:27:01 +0200 Subject: [PATCH 3/8] google search verification file added (#31) --- google5dcefa970456a1f5.html | 1 + 1 file changed, 1 insertion(+) create mode 100644 google5dcefa970456a1f5.html diff --git a/google5dcefa970456a1f5.html b/google5dcefa970456a1f5.html new file mode 100644 index 0000000..f01a71b --- /dev/null +++ b/google5dcefa970456a1f5.html @@ -0,0 +1 @@ +google-site-verification: google5dcefa970456a1f5.html From 73e0c7e30aa14b13f5ef42b5a72381377bb31ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Luena=20Rodr=C3=ADguez?= Date: Sun, 6 Jun 2021 11:50:41 +0200 Subject: [PATCH 4/8] Modal to recruit user (airtable) (#30) * Modal to recruit user (airtable) * airtable: prevent giving a description if 'other' field is not selected * mobile fixes * updates in user recruitment modal * Updated env variable structure * cloudbuild configuration - automatic deployment Co-authored-by: Jorge S. Mendes de Jesus --- .cloudbuild.yaml | 29 +++++ .dockerignore | 2 + .env.default | 2 + README.md | 16 +-- components/explore/component.js | 11 ++ components/forms/radio/index.js | 5 +- components/user-modal/component.js | 95 ++++++++++++++ components/user-modal/content/constants.js | 22 ++++ components/user-modal/content/step1.js | 140 +++++++++++++++++++++ components/user-modal/content/step2.js | 78 ++++++++++++ components/user-modal/index.js | 1 + components/user-modal/style.scss | 65 ++++++++++ package.json | 3 +- run.sh | 17 ++- utils/airtable.js | 35 ++++++ yarn.lock | 28 ++++- 16 files changed, 536 insertions(+), 13 deletions(-) create mode 100644 .cloudbuild.yaml create mode 100644 components/user-modal/component.js create mode 100644 components/user-modal/content/constants.js create mode 100644 components/user-modal/content/step1.js create mode 100644 components/user-modal/content/step2.js create mode 100644 components/user-modal/index.js create mode 100644 components/user-modal/style.scss create mode 100644 utils/airtable.js diff --git a/.cloudbuild.yaml b/.cloudbuild.yaml new file mode 100644 index 0000000..7837d15 --- /dev/null +++ b/.cloudbuild.yaml @@ -0,0 +1,29 @@ +steps: + - id: 'build-image' + name: 'gcr.io/cloud-builders/docker' + args: + - 'build' + - '-t' + - 'eu.gcr.io/$PROJECT_ID/$REPO_NAME/$BRANCH_NAME/$REPO_NAME:$SHORT_SHA' + - '-t' + - 'eu.gcr.io/$PROJECT_ID/$REPO_NAME/$BRANCH_NAME/$REPO_NAME:latest' + - '.' + - '-f' + - './Dockerfile' + timeout: 1200s + - id: 'push-to-registry' + name: 'gcr.io/cloud-builders/docker' + args: + - 'push' + - 'eu.gcr.io/$PROJECT_ID/$REPO_NAME/$BRANCH_NAME/$REPO_NAME' + - id: 'deploy-to-gke' + name: 'gcr.io/cloud-builders/gcloud' + env: + - 'KUBECONFIG=/.kube/config' + entrypoint: 'bash' + args: + - '-c' + - | + gcloud container clusters get-credentials soils-revealed-cluster --project=$PROJECT_ID --zone=europe-west4-a + kubectl set image deployment/$REPO_NAME --namespace=$BRANCH_NAME soils-revealed=eu.gcr.io/$PROJECT_ID/$REPO_NAME/$BRANCH_NAME/$REPO_NAME:$SHORT_SHA + kubectl rollout restart deployment $REPO_NAME --namespace=$BRANCH_NAME diff --git a/.dockerignore b/.dockerignore index 329282c..9eadd8e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,3 +14,5 @@ npm-debug.log .editorconfig .dockerignore .env +.Dockerfile +.cloudbuild.yaml diff --git a/.env.default b/.env.default index eafb378..cbda645 100644 --- a/.env.default +++ b/.env.default @@ -9,3 +9,5 @@ AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_MAX_Z_TILE_STORAGE= DEPLOYMENT_KEY= +AIRTABLE_API_KEY= +AIRTABLE_USER_ID= diff --git a/README.md b/README.md index 97ab754..033cd36 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,9 @@ Below is a description of each of the keys. | AWS_ACCESS_KEY_ID | Access key ID of the AWS server storing the tiles of the soils layers | | AWS_SECRET_ACCESS_KEY | Secret access key of the AWS server storing the tiles of the soils layers | | AWS_MAX_Z_TILE_STORAGE | Maximum zoom at which tiles generated on-the-fly will be saved in the AWS S3 bucket | +| AIRTABLE_API_KEY | Secret access key for [Airtable](https://airtable.com/) | +| AIRTABLE_USER_ID | Airtable User ID | + ## Deployment @@ -66,19 +69,18 @@ docker run -p3001:3001 --env-file .env soils-revealed:latest /soils-revealed/run ### Google GKE -Public deployment is based on Google Cloud build and Google GKE (Kubernetes). Up on push to `master` or `develop`, the following steps will happen: +Public deployment is based on Google Cloud build and file `.cloudbuild.yaml`. Up on push to `master` or `develop`, the following steps will happen: 1. Github will trigger a Google Cloud run trigger 2. Google cloud will pull the branch content. -3. Docker build will be initicated. -4. After completed Docker image is stored on a private repository. -5. Image will then be deplyed into the soils-revealed cluster. +3. Docker build will be iniciated, using `Dockerfile` and `.cloudbuild.yaml` +4. After completed Docker image is stored on a private repository, using tags `latest` and `$SHORT_SHA` +5. Google Cloud build will update the image on GKE and make a `kubectl rollout restart` 6. GKE contains a specific `ConfigMap` with all .env necessary for deployment. 7. `gee.key.json` is added to the pods using a `ConfigMap` mount + -GKE will implement the available Dockerfile. - -Overall, deploying to either environment takes between 5 to 10 minutes to complete. +Overall, deploying to either environment takes between 5 to 10 minutes to complete. If deployment is not successful GKE will continue implementing the previous deployment. ## Architecture diff --git a/components/explore/component.js b/components/explore/component.js index fe1ba2b..e0a9d70 100644 --- a/components/explore/component.js +++ b/components/explore/component.js @@ -6,6 +6,7 @@ import throttle from 'lodash/debounce'; import { Router } from 'lib/routes'; import { logEvent } from 'utils/analytics'; +import { isFirstVisit } from 'utils/explore'; import { useHasMounted, useDesktop } from 'utils/hooks'; import { toggleBasemap, toggleLabels, toggleRoads } from 'utils/map'; import { @@ -28,6 +29,7 @@ import InfoModal from './info-modal'; import InteractiveFeaturePopup from './interactive-feature-popup'; import DrawBoard from './draw-board'; import MapContainer from './map-container'; +import UserModal from 'components/user-modal'; import './style.scss'; @@ -76,6 +78,14 @@ const Explore = ({ const [interactiveFeatures, setInteractiveFeatures] = useState(null); const [showTour, setShowTour] = useState(false); + // User recruitment modal. This modal should appear just the first time the user + // visits the map section + const [userModalOpen, setUserModalOpen] = useState(isFirstVisit()); + + const handleModalClose = () => { + setUserModalOpen(false); + }; + // When the user clicks the popup's button that triggers its close, the map also receives the // event and it opens a new popup right after // This is a bug of react-map-gl's library @@ -222,6 +232,7 @@ const Explore = ({ className="c-explore" style={isDesktop ? { backgroundColor: BASEMAPS[basemap].backgroundColor } : undefined} > + {isDesktop && ( <> {showTour && } diff --git a/components/forms/radio/index.js b/components/forms/radio/index.js index b8db140..d5302eb 100644 --- a/components/forms/radio/index.js +++ b/components/forms/radio/index.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import './style.scss'; -const Radio = ({ id, name, disabled, checked, onChange, children, className }) => ( +const Radio = ({ id, name, disabled, checked, onChange, children, className, required }) => (
    ); + Radio.propTypes = { id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, diff --git a/components/user-modal/style.scss b/components/user-modal/style.scss index eae02b9..1e9797a 100644 --- a/components/user-modal/style.scss +++ b/components/user-modal/style.scss @@ -63,3 +63,4 @@ } } + diff --git a/utils/explore.js b/utils/explore.js index cba04b6..48c2e24 100644 --- a/utils/explore.js +++ b/utils/explore.js @@ -22,3 +22,26 @@ export const isFirstVisit = () => { return isFirstVisit; }; + +/* This function tracks the first time the app shows the recruitment modal to users */ + +export const isModalShown = () => { + let modalShown = true; + + try { + const storedValue = localStorage.getItem('showUserRecruitmentModal'); + modalShown = storedValue !== 'false'; + } catch (e) { + console.error('Unable to access the localStorage.'); + } + + if (modalShown) { + try { + localStorage.setItem('showUserRecruitmentModal', 'false'); + } catch (e) { + console.error('Unable to access the localStorage.'); + } + } + + return modalShown; +}; From 6199e0b40f1e381237871956fded70f3068bc9f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Luena=20Rodr=C3=ADguez?= Date: Tue, 21 Sep 2021 09:06:36 +0200 Subject: [PATCH 7/8] airtable modal removed (#39) --- .env.default | 2 - README.md | 5 +- components/explore/component.js | 10 -- components/explore/tour/component.js | 2 +- components/user-modal/component.js | 95 -------------- components/user-modal/content/constants.js | 22 ---- components/user-modal/content/step1.js | 140 --------------------- components/user-modal/content/step2.js | 78 ------------ components/user-modal/index.js | 1 - components/user-modal/style.scss | 66 ---------- run.sh | 2 - 11 files changed, 2 insertions(+), 421 deletions(-) delete mode 100644 components/user-modal/component.js delete mode 100644 components/user-modal/content/constants.js delete mode 100644 components/user-modal/content/step1.js delete mode 100644 components/user-modal/content/step2.js delete mode 100644 components/user-modal/index.js delete mode 100644 components/user-modal/style.scss diff --git a/.env.default b/.env.default index cbda645..eafb378 100644 --- a/.env.default +++ b/.env.default @@ -9,5 +9,3 @@ AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_MAX_Z_TILE_STORAGE= DEPLOYMENT_KEY= -AIRTABLE_API_KEY= -AIRTABLE_USER_ID= diff --git a/README.md b/README.md index 033cd36..a2db6e5 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,7 @@ Below is a description of each of the keys. | AWS_BUCKET_NAME | Name of the AWS S3 bucket storing the tiles of the soils layers | | AWS_ACCESS_KEY_ID | Access key ID of the AWS server storing the tiles of the soils layers | | AWS_SECRET_ACCESS_KEY | Secret access key of the AWS server storing the tiles of the soils layers | -| AWS_MAX_Z_TILE_STORAGE | Maximum zoom at which tiles generated on-the-fly will be saved in the AWS S3 bucket | -| AIRTABLE_API_KEY | Secret access key for [Airtable](https://airtable.com/) | -| AIRTABLE_USER_ID | Airtable User ID | - +| AWS_MAX_Z_TILE_STORAGE | Maximum zoom at which tiles generated on-the-fly will be saved in the AWS S3 bucket ## Deployment diff --git a/components/explore/component.js b/components/explore/component.js index a29f6ab..d0c3350 100644 --- a/components/explore/component.js +++ b/components/explore/component.js @@ -29,7 +29,6 @@ import InfoModal from './info-modal'; import InteractiveFeaturePopup from './interactive-feature-popup'; import DrawBoard from './draw-board'; import MapContainer from './map-container'; -import UserModal from 'components/user-modal'; import './style.scss'; @@ -78,14 +77,6 @@ const Explore = ({ const [interactiveFeatures, setInteractiveFeatures] = useState(null); const [showTour, setShowTour] = useState(false); - // User recruitment modal. This modal should appear just the first time the user - // visits the map section - const [userModalOpen, setUserModalOpen] = useState(isModalShown()); - - const handleModalClose = () => { - setUserModalOpen(false); - }; - // When the user clicks the popup's button that triggers its close, the map also receives the // event and it opens a new popup right after // This is a bug of react-map-gl's library @@ -232,7 +223,6 @@ const Explore = ({ className="c-explore" style={isDesktop ? { backgroundColor: BASEMAPS[basemap].backgroundColor } : undefined} > - {isDesktop && ( <> {showTour && } diff --git a/components/explore/tour/component.js b/components/explore/tour/component.js index a9bfe15..0de566e 100644 --- a/components/explore/tour/component.js +++ b/components/explore/tour/component.js @@ -154,7 +154,7 @@ const ExploreTour = props => { previousShowTour.current = showTour; } } - }, [previousShowTour.current, showTour, props, stepIndex, setOpened, updateShowTour]); + }, [showTour, props, stepIndex, setOpened, updateShowTour]); const onChange = useCallback( ({ action, type }) => { diff --git a/components/user-modal/component.js b/components/user-modal/component.js deleted file mode 100644 index 5ac1726..0000000 --- a/components/user-modal/component.js +++ /dev/null @@ -1,95 +0,0 @@ -import React, { useCallback, useState } from 'react'; -import PropTypes from 'prop-types'; - -import Modal from 'components/modal'; - -import { createUserEntry, updateUserEntry } from 'utils/airtable'; - -import Step1 from './content/step1'; -import Step2 from './content/step2'; - -import './style.scss'; - -const UserModal = ({ open, onClose }) => { - const [step, setStep] = useState('step1'); - const [userId, setUserId] = useState(null); - const [userData, setUserData] = useState({ - job_role: '', - job_role_description: '', - map_usage: '', - map_usage_description: '', - email: '', - }); - const [userEntryError, setCreateUserEntryError] = useState(null); - - const handleCreateUser = async e => { - e.preventDefault(); - try { - const user = await createUserEntry({ ...userData }); - setUserId(user[0].id); - setStep('step2'); - } catch (e) { - setCreateUserEntryError(e.message); - } - }; - - const handleUpdateUser = async e => { - e.preventDefault(); - try { - await updateUserEntry(userId, userData); - onClose(); - } catch (e) { - setCreateUserEntryError(e.message); - } - }; - - const userDataUpdate = useCallback( - (key, value) => { - if (key === 'job_role_description') { - setUserData({ - ...userData, - job_role: 'other', - [key]: value, - }); - } else if (key === 'map_usage_description') { - setUserData({ - ...userData, - map_usage: 'other', - [key]: value, - }); - } else setUserData({ ...userData, [key]: value }); - }, - [userData] - ); - - return ( - - {step === 'step1' && ( - - )} - {step === 'step2' && ( - - )} - - ); -}; - -UserModal.propTypes = { - open: PropTypes.bool.isRequired, - onClose: PropTypes.func.isRequired, -}; - -export default UserModal; diff --git a/components/user-modal/content/constants.js b/components/user-modal/content/constants.js deleted file mode 100644 index f9ea0ad..0000000 --- a/components/user-modal/content/constants.js +++ /dev/null @@ -1,22 +0,0 @@ -export const userTypeOptions = [ - { label: 'Academic research', slug: 'Academic research' }, - { label: 'Government (local, regional or traditional)', slug: 'Government' }, - { label: 'Private sector', slug: 'Private sector' }, - { label: 'NGO sector', slug: 'NGO sector' }, - { label: 'Other [please specify]', slug: 'other', value: '' }, -]; - -export const useTypeOptions = [ - { label: 'Curiosity', slug: 'Curiosity' }, - { label: 'Education', slug: 'Education' }, - { label: 'Research purposes', slug: 'Research purposes' }, - { label: 'Planning & Land management', slug: 'Planning and land management' }, - { label: 'Policy-making', slug: 'Policy-making' }, - { label: 'Impact & Evaluation', slug: 'Impact and evaluation' }, - { label: 'Other [please specify]', slug: 'other', value: '' }, -]; - -export default { - userTypeOptions, - useTypeOptions, -}; diff --git a/components/user-modal/content/step1.js b/components/user-modal/content/step1.js deleted file mode 100644 index bd242af..0000000 --- a/components/user-modal/content/step1.js +++ /dev/null @@ -1,140 +0,0 @@ -import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; - -import Radio from 'components/forms/radio'; - -import { userTypeOptions, useTypeOptions } from './constants'; - -const Step1 = ({ onClick, userData, handleUserData, error }) => ( -
    onClick(e)}> -

    Can you tell us a bit about yourself?

    -

    - In which sector do you work: * (pick the one that brought you to this map) -

    -
    - {userTypeOptions.map(option => ( - - handleUserData('job_role', option.slug)} - required - > - {option.label} - - {option.slug === 'other' && ( - <> - - handleUserData('job_role_description', currentTarget.value) - } - required={userData.job_role_description === 'other'} - /> - - )} - - ))} -
    - -

    - Please tell us what you are using Soils platform for: * -

    - -
    - {useTypeOptions.map(option => ( - - handleUserData('map_usage', option.slug)} - required - > - {option.label} - - {option.slug === 'other' && ( - <> - handleUserData('map_usage_description', e.currentTarget.value)} - required={userData.map_usage === 'other'} - /> - - )} - - ))} -
    -
    -
    -
    -

    *required fields

    -
    -
    -
    - - {error && ( -

    - Unable to create user entry, please try again. -

    - )} -
    -
    -
    - -
    -
    -
    -
    -); - -Step1.propTypes = { - onClick: PropTypes.func.isRequired, - handleUserData: PropTypes.func.isRequired, - userData: PropTypes.shape({ - job_role: PropTypes.string, - job_role_description: PropTypes.string, - map_usage: PropTypes.string, - map_usage_description: PropTypes.string, - email: PropTypes.string, - }), - user: PropTypes.string, - error: PropTypes.string, -}; - -Step1.defaultProps = { - user: null, - error: null, -}; - -export default Step1; diff --git a/components/user-modal/content/step2.js b/components/user-modal/content/step2.js deleted file mode 100644 index 19a7d56..0000000 --- a/components/user-modal/content/step2.js +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -const Step2 = ({ userData, handleUserData, onClick, error }) => ( -
    !error && onClick(e)}> -

    Thank you!

    -
    -

    Would you like to hep us to improve this map?

    -

    - Feedback is really important to us. If you’d like to provide your opinion, write down your - email and we might invite you for a one-to-one chat session. -

    -
    -
    -
    - handleUserData('email', currentTarget.value)} - /> -
    -
    -

    - Your email address will be used by Simbiotica SL DBA Vizzuality on behalf of The Nature - Conservancy. We will only be using your email address to contact you in order to conduct - user research on this website.Your email will not be shared with any thirsd parties. For - more information and instructions on exercisong your rights on this data please read our{' '} - - Privacy policy - -

    -
    - - {error && ( -

    - Unable to update user entry, please try again. -

    - )} - -
    -
    -
    - -
    -
    -
    -
    -
    -); - -Step2.propTypes = { - onClick: PropTypes.func.isRequired, - handleUserData: PropTypes.func.isRequired, - userData: PropTypes.shape({ - job_role: PropTypes.string, - job_role_description: PropTypes.string, - map_usage: PropTypes.string, - map_usage_description: PropTypes.string, - email: PropTypes.string, - }), - error: PropTypes.string, -}; - -Step2.defaultProps = { - error: null, -}; - -export default Step2; diff --git a/components/user-modal/index.js b/components/user-modal/index.js deleted file mode 100644 index b404d7f..0000000 --- a/components/user-modal/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './component'; diff --git a/components/user-modal/style.scss b/components/user-modal/style.scss deleted file mode 100644 index 1e9797a..0000000 --- a/components/user-modal/style.scss +++ /dev/null @@ -1,66 +0,0 @@ -@import 'css/settings'; - -.c-user-modal { - max-width: rem(850); - display: flex; - justify-content: center; - - @include media-breakpoint-up(lg, $grid-breakpoints) { - padding: rem(50) rem(100); - } - - h1, - h2 { - @include font-size($font-size-xxl); - text-align: left; - } - - .text-size-base { - @include font-size($font-size-base); - } - - .user-modal-radio-input-container { - max-width: 100%; - max-height: 100%; - flex-wrap: wrap; - flex-direction: column; - display: flex; - - @include media-breakpoint-up(sm, $grid-breakpoints) { - max-height: 125px; - } - - .custom-radio { - @include media-breakpoint-up(sm, $grid-breakpoints) { - width: 50%; - } - } - } - - .user-modal-text-input { - background: transparent; - border: none; - border-bottom: 1px solid $primary; - border-radius: 0; - padding-left: 0; - max-width: 180px; - } - - .user-modal-content-note { - font-family: $font-family-2; - @include font-size($font-size-xxs); - font-weight: initial; - opacity: 50%; - - p { - text-align: justify; - - a { - text-decoration: underline; - color: $text-color-1; - } - } - } -} - - diff --git a/run.sh b/run.sh index 7583fad..9278c82 100755 --- a/run.sh +++ b/run.sh @@ -14,8 +14,6 @@ AWS_BUCKET_NAME=$AWS_BUCKET_NAME\n\ AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID\n\ AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY\n\ AWS_MAX_Z_TILE_STORAGE=$AWS_MAX_Z_TILE_STORAGE\n\ -AIRTABLE_API_KEY=$AIRTABLE_API_KEY\n\ -AIRTABLE_USER_ID=$AIRTABLE_USER_ID" >> .env echo -e "Initiating yarn build" yarn build From 3c8f88dcdad6811e17eed454b28e3234ce6cd5bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Sep 2021 07:07:20 +0000 Subject: [PATCH 8/8] Bump color-string from 1.5.4 to 1.6.0 Bumps [color-string](https://github.com/Qix-/color-string) from 1.5.4 to 1.6.0. - [Release notes](https://github.com/Qix-/color-string/releases) - [Changelog](https://github.com/Qix-/color-string/blob/master/CHANGELOG.md) - [Commits](https://github.com/Qix-/color-string/compare/1.5.4...1.6.0) --- updated-dependencies: - dependency-name: color-string dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 111f049..9e1e418 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2902,9 +2902,9 @@ color-name@^1.0.0, color-name@~1.1.4: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== color-string@^1.5.4: - version "1.5.4" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" - integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== + version "1.6.0" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.6.0.tgz#c3915f61fe267672cb7e1e064c9d692219f6c312" + integrity sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2"